better-ani-scraped 1.6.82 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DOCUMENTATION.md +63 -1
- package/examples/animesama/example_usage_getAllAnime.js +1 -1
- package/examples/animesama/example_usage_getAllTitleScans.js +10 -0
- package/examples/animesama/example_usage_getAnimeInfo.js +1 -1
- package/examples/animesama/example_usage_getAvailableLanguages.js +1 -1
- package/examples/animesama/example_usage_getEmbed.js +2 -2
- package/examples/animesama/example_usage_getEpisodeTitles.js +1 -1
- package/examples/animesama/example_usage_getImgScans.js +10 -0
- package/examples/animesama/example_usage_getLatestScans.js +10 -0
- package/examples/animesama/example_usage_getSeasons.js +2 -2
- package/examples/utility-functions/example_usage_getVideoUrlFromEmbed.js +9 -0
- package/package.json +2 -2
- package/scrapers/animesama.js +155 -41
- package/scrapers/scrapers.js +79 -16
- package/utils/dispatcher.js +3 -0
- package/utils/extractVideoUrl.js +25 -2
package/DOCUMENTATION.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
,,,,# Better-Ani-Scraped Documentation
|
|
2
2
|
|
|
3
|
-
A set of utility functions for scraping anime data from multiple sources (only [anime-sama](https://anime-sama.
|
|
3
|
+
A set of utility functions for scraping anime data from multiple sources (only [anime-sama](https://anime-sama.org) and [animepahe](https://animepahe.ru) available at the moment). This tool allows you to search for anime, retrieve information, get episodes, and more.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -35,7 +35,11 @@ const crunchyroll = new AnimeScraper('crunchyroll') //for Crunchyroll
|
|
|
35
35
|
- [getAvailableLanguages](#animesamagetavailablelanguagesseasonurl-wantedlanguages--vostfr-vf-va-vkr-vcn-vqc-vf1-vf2-numberepisodes--false)
|
|
36
36
|
- [getAllAnime](#animesamagetallanimewantedlanguages--vostfr-vf-vastfr-wantedtypes--anime-film-page--null-output--anime_listjson-get_seasons--false)
|
|
37
37
|
- [getLatestEpisodes](#animesamagetlatestepisodeslanguagefilter--null)
|
|
38
|
+
- [getLatestScans](#animesamagetlatestscanslanguagefilter--null)
|
|
38
39
|
- [getRandomAnime](#animesamagetrandomanimewantedlanguages--vostfr-vf-vastfr-wantedtypes--anime-film-maxattempts--null-attempt--0)
|
|
40
|
+
- [getAllTitleScans](#animesamagetalltitlescansmangaurl-numberimg--false)
|
|
41
|
+
- [getImgScans](#animesamagetimgscansmangaurl-wantedchapter)
|
|
42
|
+
|
|
39
43
|
|
|
40
44
|
### `animesama.searchAnime(query, limit = 10, wantedLanguages = ["vostfr", "vf", "vastfr"], wantedTypes = ["Anime", "Film"], page = null)`
|
|
41
45
|
Searches for anime titles that match the given query.
|
|
@@ -232,6 +236,30 @@ Scrapes the latest released episodes, optionally filtered by language.
|
|
|
232
236
|
|
|
233
237
|
---
|
|
234
238
|
|
|
239
|
+
### `animesama.getLatestScans(languageFilter = null)`
|
|
240
|
+
Scrapes the latest released episodes, optionally filtered by language.
|
|
241
|
+
|
|
242
|
+
- **Parameters:**
|
|
243
|
+
- `languageFilter` *(string[]|null)*: If set, filters episodes by language in the array. If null, returns all scans.
|
|
244
|
+
- **Returns:**
|
|
245
|
+
Array of scans objects:
|
|
246
|
+
```js
|
|
247
|
+
[
|
|
248
|
+
{
|
|
249
|
+
title: string,
|
|
250
|
+
url: string,
|
|
251
|
+
cover: string,
|
|
252
|
+
type: string,
|
|
253
|
+
language: string,
|
|
254
|
+
chapter: string
|
|
255
|
+
}
|
|
256
|
+
...
|
|
257
|
+
]
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
|
|
235
263
|
### `animesama.getRandomAnime(wantedLanguages = ["vostfr", "vf", "vastfr"], wantedTypes = ["Anime", "Film"], maxAttempts = null, attempt = 0)`
|
|
236
264
|
Fetches a random anime from the catalogue.
|
|
237
265
|
|
|
@@ -254,6 +282,40 @@ Fetches a random anime from the catalogue.
|
|
|
254
282
|
|
|
255
283
|
---
|
|
256
284
|
|
|
285
|
+
### `animesama.getAllTitleScans(mangaUrl, numberImg = false)`
|
|
286
|
+
Scrapes all the scans of a chapter.
|
|
287
|
+
|
|
288
|
+
- **Parameters:**
|
|
289
|
+
- `mangaUrl` *(string)*: The manga URL.
|
|
290
|
+
- `numberImg` *(boolean)*: If `true`, indicates the number of images in each chapter.
|
|
291
|
+
- **Returns:**
|
|
292
|
+
An array of chapter titles if *numberImg = false*
|
|
293
|
+
Else :
|
|
294
|
+
```js
|
|
295
|
+
{
|
|
296
|
+
scans :
|
|
297
|
+
[
|
|
298
|
+
{
|
|
299
|
+
title: string,
|
|
300
|
+
url: string,
|
|
301
|
+
},
|
|
302
|
+
...
|
|
303
|
+
]
|
|
304
|
+
mangaTitle: string,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
### `animesama.getImgScans(mangaUrl, wantedChapter)`
|
|
310
|
+
Scrapes all the scans of a chapter.
|
|
311
|
+
|
|
312
|
+
- **Parameters:**
|
|
313
|
+
- `mangaUrl` *(string)*: The manga URL.
|
|
314
|
+
- `wantedChapter` *(int)*: The number of the chapter you want.
|
|
315
|
+
- **Returns:**
|
|
316
|
+
An array of image URL.
|
|
317
|
+
|
|
318
|
+
---
|
|
257
319
|
|
|
258
320
|
## `AnimeScraper("animepahe")` methods
|
|
259
321
|
|
|
@@ -3,7 +3,7 @@ import { AnimeScraper } from "../../index.js"; // REPLACE BY "from 'better-ani-s
|
|
|
3
3
|
const main = async () => {
|
|
4
4
|
const animesama = new AnimeScraper('animesama');
|
|
5
5
|
|
|
6
|
-
const catalogue = await animesama.getAllAnime(["vostfr", "vf", "vastfr"], ["Anime", "Film"],
|
|
6
|
+
const catalogue = await animesama.getAllAnime(["vostfr", "vf", "vastfr"], ["Anime", "Film", "Scans"], 1);
|
|
7
7
|
console.log(catalogue)
|
|
8
8
|
};
|
|
9
9
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AnimeScraper } from "../../index.js"; // REPLACE BY "from 'better-ani-scraped';"
|
|
2
|
+
|
|
3
|
+
const main = async () => {
|
|
4
|
+
const animesama = new AnimeScraper('animesama');
|
|
5
|
+
const mangaUrl = "https://anime-sama.org/catalogue/one-piece/scan_noir-et-blanc/vf";
|
|
6
|
+
const chapterTitles = await animesama.getAllTitleScans(mangaUrl, true);
|
|
7
|
+
console.log("Titles:", chapterTitles);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
main().catch(console.error);
|
|
@@ -2,7 +2,7 @@ import { AnimeScraper } from "../../index.js"; // REPLACE BY "from 'better-ani-s
|
|
|
2
2
|
|
|
3
3
|
const main = async () => {
|
|
4
4
|
const animesama = new AnimeScraper('animesama');
|
|
5
|
-
const animeUrl = "https://anime-sama.
|
|
5
|
+
const animeUrl = "https://anime-sama.org/catalogue/86-eighty-six/";
|
|
6
6
|
|
|
7
7
|
const animeInfo = await animesama.getAnimeInfo(animeUrl);
|
|
8
8
|
console.log(animeInfo);
|
|
@@ -2,7 +2,7 @@ import { AnimeScraper } from "../../index.js"; // REPLACE BY "from 'better-ani-s
|
|
|
2
2
|
|
|
3
3
|
const main = async () => {
|
|
4
4
|
const animesama = new AnimeScraper('animesama');
|
|
5
|
-
const seasonUrl = "https://anime-sama.
|
|
5
|
+
const seasonUrl = "https://anime-sama.org/catalogue/86-eighty-six/saison1/vostfr/";
|
|
6
6
|
|
|
7
7
|
const animeLanguages = await animesama.getAvailableLanguages(seasonUrl, ["vostfr", "vf", "va", "vkr","vcn", "vqc", "vf1", "vf2"], false);
|
|
8
8
|
console.log(animeLanguages);
|
|
@@ -2,9 +2,9 @@ import { AnimeScraper } from "../../index.js"; // REPLACE BY "from 'better-ani-s
|
|
|
2
2
|
|
|
3
3
|
const main = async () => {
|
|
4
4
|
const animesama = new AnimeScraper('animesama');
|
|
5
|
-
const seasonUrl = "https://anime-sama.
|
|
5
|
+
const seasonUrl = "https://anime-sama.org/catalogue/one-piece/saison11/vostfr";
|
|
6
6
|
|
|
7
|
-
const embeds = await animesama.getEmbed(seasonUrl, ["sibnet", "vidmoly", "sendvid"], true, true);
|
|
7
|
+
const embeds = await animesama.getEmbed(seasonUrl, ["smoothpre", "movearnpre", "sibnet", "vidmoly", "sendvid"], true, true);
|
|
8
8
|
|
|
9
9
|
console.log("Embed Links:", JSON.stringify(embeds, null, 2));
|
|
10
10
|
|
|
@@ -2,7 +2,7 @@ import { AnimeScraper } from "../../index.js"; // REPLACE BY "from 'better-ani-s
|
|
|
2
2
|
|
|
3
3
|
const main = async () => {
|
|
4
4
|
const animesama = new AnimeScraper('animesama');
|
|
5
|
-
const seasonUrl = "https://anime-sama.
|
|
5
|
+
const seasonUrl = "https://anime-sama.org/catalogue/86-eighty-six/saison1/vostfr/";
|
|
6
6
|
|
|
7
7
|
const episodeTitles = await animesama.getEpisodeTitles(seasonUrl);
|
|
8
8
|
console.log("Episode Titles:", episodeTitles);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AnimeScraper } from "../../index.js"; // REPLACE BY "from 'better-ani-scraped';"
|
|
2
|
+
|
|
3
|
+
const main = async () => {
|
|
4
|
+
const animesama = new AnimeScraper('animesama');
|
|
5
|
+
const scansUrl = "https://anime-sama.org/catalogue/drcl-midnight-children/scan/vf";
|
|
6
|
+
const scansImgUrl = await animesama.getImgScans(scansUrl, 9);
|
|
7
|
+
console.log("Image scans:", scansImgUrl);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AnimeScraper } from "../../index.js"; // REPLACE BY "from 'better-ani-scraped';"
|
|
2
|
+
|
|
3
|
+
const main = async () => {
|
|
4
|
+
const animesama = new AnimeScraper("animesama");
|
|
5
|
+
|
|
6
|
+
const new_scans = await animesama.getLatestScans(["vostfr", "vf"]);
|
|
7
|
+
console.log(new_scans);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
main().catch(console.error);
|
|
@@ -2,9 +2,9 @@ import { AnimeScraper } from "../../index.js"; // REPLACE BY "from 'better-ani-s
|
|
|
2
2
|
|
|
3
3
|
const main = async () => {
|
|
4
4
|
const animesama = new AnimeScraper('animesama');
|
|
5
|
-
const animeUrl = "https://anime-sama.
|
|
5
|
+
const animeUrl = "https://anime-sama.org/catalogue/drcl-midnight-children";
|
|
6
6
|
|
|
7
|
-
const seasons = await animesama.getSeasons(animeUrl, ["vostfr", "vf"]);
|
|
7
|
+
const seasons = await animesama.getSeasons(animeUrl, ["vostfr", "vf"], ["Anime", "Scans"]);
|
|
8
8
|
console.log("Seasons:", seasons);
|
|
9
9
|
};
|
|
10
10
|
|
|
@@ -5,6 +5,8 @@ const main = async () => {
|
|
|
5
5
|
const embedUrlSendvid = "https://sendvid.com/embed/4vzpcb0q";
|
|
6
6
|
const embedUrlVidmoly = "https://vidmoly.to/embed-rvqrwg5zk37w.html";
|
|
7
7
|
const embedUrlOneupload = "https://oneupload.net/embed-axdrxh1y3p37.html";
|
|
8
|
+
const embedUrlSmoothpre = "https://smoothpre.com/embed/8294jcf1q8jf";
|
|
9
|
+
const embedUrlMovearnpre = "https://movearnpre.com/embed/e3xbkin87yt3";
|
|
8
10
|
|
|
9
11
|
const videoUrlSibnet = await getVideoUrlFromEmbed("sibnet", embedUrlSibnet)
|
|
10
12
|
console.log("Video URL Sibnet:", videoUrlSibnet);
|
|
@@ -17,6 +19,13 @@ const main = async () => {
|
|
|
17
19
|
|
|
18
20
|
const videoUrlOneupload = await getVideoUrlFromEmbed("oneupload", embedUrlOneupload)
|
|
19
21
|
console.log("Video URL Oneupload:", videoUrlOneupload);
|
|
22
|
+
|
|
23
|
+
const videoUrlSmoothpre = await getVideoUrlFromEmbed("smoothpre", embedUrlSmoothpre)
|
|
24
|
+
console.log("Video URL Smoothpre:", videoUrlSmoothpre);
|
|
25
|
+
|
|
26
|
+
const videoUrlMovearnpre = await getVideoUrlFromEmbed("movearnpre", embedUrlMovearnpre)
|
|
27
|
+
console.log("Video URL Movearnpre:", videoUrlMovearnpre);
|
|
28
|
+
|
|
20
29
|
};
|
|
21
30
|
|
|
22
31
|
main().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-ani-scraped",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Scrape anime data from different sources (only anime-sama.
|
|
3
|
+
"version": "1.7.2",
|
|
4
|
+
"description": "Scrape anime data from different sources (only anime-sama.org, animepahe and crunchyroll for the moment)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
package/scrapers/animesama.js
CHANGED
|
@@ -4,10 +4,11 @@ import fs from "fs";
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { exec as execCallback } from 'child_process';
|
|
6
6
|
import { promisify } from 'util';
|
|
7
|
+
import { title } from "process";
|
|
7
8
|
const execAsync = promisify(execCallback);
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
const BASE_URL = "https://anime-sama.
|
|
11
|
+
const BASE_URL = "https://anime-sama.org";
|
|
11
12
|
const CATALOGUE_URL = `${BASE_URL}/catalogue`;
|
|
12
13
|
|
|
13
14
|
async function ensureChromiumInstalled(customPath) {
|
|
@@ -124,15 +125,26 @@ export async function searchAnime(
|
|
|
124
125
|
return results;
|
|
125
126
|
}
|
|
126
127
|
|
|
127
|
-
export async function getSeasons(animeUrl, languagePriority = ["vostfr", "vf", "va", "vkr", "vcn", "vqc", "vf1", "vf2"]) {
|
|
128
|
+
export async function getSeasons(animeUrl, languagePriority = ["vostfr", "vf", "va", "vkr", "vcn", "vqc", "vf1", "vf2"], wantedTypes=["Anime", "Kai", "Scans"]) {
|
|
128
129
|
const res = await axios.get(animeUrl, { headers: getHeaders(CATALOGUE_URL) });
|
|
129
130
|
const html = res.data;
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
let mainAnimeOnly = html;
|
|
132
|
+
if (wantedTypes.length !== 0) {
|
|
133
|
+
if (!wantedTypes.includes("Anime")) {
|
|
134
|
+
mainAnimeOnly = mainAnimeOnly.replace(/<h2.*?>Anime<\/h2>[\s\S]*?(?=<h2|$)/g, '');
|
|
135
|
+
}
|
|
136
|
+
if (!wantedTypes.includes("Kai")) {
|
|
137
|
+
mainAnimeOnly = mainAnimeOnly.replace(/<h2.*?>Anime Version Kai<\/h2>[\s\S]*?(?=<h2|$)/g, '');
|
|
138
|
+
}
|
|
139
|
+
if (!wantedTypes.includes("Scans")) {
|
|
140
|
+
mainAnimeOnly = mainAnimeOnly.replace(/<h2.*?>Manga<\/h2>[\s\S]*?(?=<h2|$)/g, '');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
132
144
|
const $ = cheerio.load(mainAnimeOnly);
|
|
133
145
|
const scriptTags = $("script")
|
|
134
146
|
.toArray()
|
|
135
|
-
.filter(script => $(script).html().includes(
|
|
147
|
+
.filter(script => ["panneauAnime", "panneauScan"].some(str => $(script).html().includes(str)));
|
|
136
148
|
|
|
137
149
|
const animeName = animeUrl.split("/")[4];
|
|
138
150
|
let seasons = [];
|
|
@@ -140,7 +152,7 @@ export async function getSeasons(animeUrl, languagePriority = ["vostfr", "vf", "
|
|
|
140
152
|
for (const language of languagePriority) {
|
|
141
153
|
seasons = [];
|
|
142
154
|
let languageAvailable = false;
|
|
143
|
-
|
|
155
|
+
let scansLanguage = "";
|
|
144
156
|
for (let script of scriptTags) {
|
|
145
157
|
const content = $(script).html();
|
|
146
158
|
|
|
@@ -148,33 +160,49 @@ export async function getSeasons(animeUrl, languagePriority = ["vostfr", "vf", "
|
|
|
148
160
|
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
149
161
|
.replace(/\/\/.*$/gm, "");
|
|
150
162
|
|
|
151
|
-
const matches = [...uncommentedContent.matchAll(/panneauAnime\("([^"]+)", "([^"]+)"\);/g)];
|
|
163
|
+
const matches = [...uncommentedContent.matchAll(/(panneauAnime|panneauScan)\("([^"]+)", "([^"]+)"\);/g)];
|
|
152
164
|
|
|
153
165
|
for (let match of matches) {
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
const type = match[1];
|
|
167
|
+
const title = match[2];
|
|
168
|
+
const href = match[3].split("/")[0];
|
|
169
|
+
|
|
170
|
+
if (type === "panneauScan") {
|
|
171
|
+
let found = false;
|
|
172
|
+
for (const lang of languagePriority) {
|
|
173
|
+
const fullUrl = `${CATALOGUE_URL}/${animeName}/${href}/${lang}`;
|
|
174
|
+
try {
|
|
175
|
+
const check = await axios.head(fullUrl, { headers: getHeaders(animeUrl) });
|
|
176
|
+
if (check.status === 200) {
|
|
177
|
+
seasons.push({ title, url: fullUrl });
|
|
178
|
+
scansLanguage = lang
|
|
179
|
+
found = true;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
} catch {}
|
|
165
183
|
}
|
|
166
|
-
|
|
167
|
-
|
|
184
|
+
if (found) languageAvailable = true;
|
|
185
|
+
} else {
|
|
186
|
+
const fullUrl = `${CATALOGUE_URL}/${animeName}/${href}/${language}`;
|
|
187
|
+
try {
|
|
188
|
+
const check = await axios.head(fullUrl, { headers: getHeaders(animeUrl) });
|
|
189
|
+
if (check.status === 200) {
|
|
190
|
+
seasons.push({ title, url: fullUrl });
|
|
191
|
+
languageAvailable = true;
|
|
192
|
+
}
|
|
193
|
+
} catch {}
|
|
168
194
|
}
|
|
169
195
|
}
|
|
170
196
|
}
|
|
171
|
-
|
|
172
|
-
|
|
197
|
+
if (wantedTypes.includes("Scans") && wantedTypes.length==1 && scansLanguage) {
|
|
198
|
+
return { scansLanguage, seasons };
|
|
199
|
+
}
|
|
200
|
+
else if (languageAvailable) {
|
|
173
201
|
return { language, seasons };
|
|
174
202
|
}
|
|
175
203
|
}
|
|
176
204
|
|
|
177
|
-
return
|
|
205
|
+
return [];
|
|
178
206
|
}
|
|
179
207
|
|
|
180
208
|
|
|
@@ -256,7 +284,7 @@ export async function getEmbed(
|
|
|
256
284
|
.get(scriptUrl, { headers: getHeaders(seasonUrl) })
|
|
257
285
|
.then((r) => r.data);
|
|
258
286
|
|
|
259
|
-
const matches = [...episodesJs.matchAll(/var\s+(eps\d+)\s*=\s*(\[[^\]]+\])/g)];
|
|
287
|
+
const matches = [...episodesJs.toLowerCase().matchAll(/var\s+(eps\d+)\s*=\s*(\[[^\]]+\])/g)];
|
|
260
288
|
if (!matches.length) throw new Error("No episode arrays found");
|
|
261
289
|
|
|
262
290
|
let episodeMatrix = [];
|
|
@@ -279,18 +307,18 @@ export async function getEmbed(
|
|
|
279
307
|
|
|
280
308
|
for (const host of hostPriority) {
|
|
281
309
|
for (const arr of episodeMatrix) {
|
|
282
|
-
if (i < arr.length && arr[i].includes(host)) {
|
|
283
|
-
if (!hosts.includes(host)) {
|
|
310
|
+
if (i < arr.length && arr[i].includes(host.toLowerCase())) {
|
|
311
|
+
if (!hosts.includes(host.toLowerCase())) {
|
|
284
312
|
urls.push(arr[i]);
|
|
285
|
-
hosts.push(host);
|
|
313
|
+
hosts.push(host.toLowerCase());
|
|
286
314
|
}
|
|
287
|
-
break;
|
|
315
|
+
break;
|
|
288
316
|
}
|
|
289
317
|
}
|
|
290
318
|
}
|
|
291
319
|
|
|
292
320
|
finalEmbeds.push({
|
|
293
|
-
title: null,
|
|
321
|
+
title: null,
|
|
294
322
|
url: urls.length ? urls : null,
|
|
295
323
|
host: hosts.length ? hosts : null,
|
|
296
324
|
});
|
|
@@ -301,9 +329,9 @@ export async function getEmbed(
|
|
|
301
329
|
|
|
302
330
|
for (const host of hostPriority) {
|
|
303
331
|
for (const arr of episodeMatrix) {
|
|
304
|
-
if (i < arr.length && arr[i].includes(host)) {
|
|
332
|
+
if (i < arr.length && arr[i].includes(host.toLowerCase())) {
|
|
305
333
|
selectedUrl = arr[i];
|
|
306
|
-
selectedHost = host;
|
|
334
|
+
selectedHost = host.toLowerCase();
|
|
307
335
|
break;
|
|
308
336
|
}
|
|
309
337
|
}
|
|
@@ -311,7 +339,7 @@ export async function getEmbed(
|
|
|
311
339
|
}
|
|
312
340
|
|
|
313
341
|
finalEmbeds.push({
|
|
314
|
-
title: null,
|
|
342
|
+
title: null,
|
|
315
343
|
url: selectedUrl || null,
|
|
316
344
|
host: selectedHost || null,
|
|
317
345
|
});
|
|
@@ -417,28 +445,37 @@ export async function getAllAnime(
|
|
|
417
445
|
const url = pageNum === 1 ? CATALOGUE_URL : `${CATALOGUE_URL}?page=${pageNum}`;
|
|
418
446
|
const res = await axios.get(url, { headers: getHeaders(CATALOGUE_URL) });
|
|
419
447
|
const $ = cheerio.load(res.data);
|
|
420
|
-
|
|
448
|
+
|
|
421
449
|
const containers = $("div.shrink-0.m-3.rounded.border-2");
|
|
422
|
-
|
|
450
|
+
|
|
423
451
|
containers.each((_, el) => {
|
|
424
452
|
const anchor = $(el).find("a");
|
|
425
453
|
const title = anchor.find("h1").text().trim();
|
|
426
454
|
const link = anchor.attr("href");
|
|
427
455
|
const img = anchor.find("img").attr("src");
|
|
428
|
-
|
|
456
|
+
|
|
429
457
|
const paragraphs = anchor.find("p").toArray().map(p => $(p).text().trim());
|
|
430
|
-
|
|
458
|
+
|
|
431
459
|
const altTitles = paragraphs[0] ? paragraphs[0].split(',').map(name => name.trim()) : [];
|
|
432
460
|
const genres = paragraphs[1] ? paragraphs[1].split(',').map(genre => genre.trim()) : [];
|
|
433
461
|
const type = paragraphs[2] ? paragraphs[2].split(',').map(t => t.trim()) : [];
|
|
434
462
|
const language = paragraphs[3] ? paragraphs[3].split(',').map(lang => lang.trim()) : [];
|
|
435
|
-
|
|
436
|
-
const
|
|
463
|
+
|
|
464
|
+
const filteredTypes = wantedTypes.length === 0
|
|
465
|
+
? type
|
|
466
|
+
: type.filter(t => isWanted(t, wantedTypes));
|
|
467
|
+
|
|
468
|
+
const hasScans = filteredTypes.some(t => t.toLowerCase() === "scans".toLowerCase());
|
|
469
|
+
|
|
470
|
+
const filteredLanguages = (wantedLanguages.length === 0 || hasScans)
|
|
471
|
+
? language
|
|
472
|
+
: language.filter(lang => isWanted(lang, wantedLanguages));
|
|
473
|
+
|
|
437
474
|
if (
|
|
438
475
|
title &&
|
|
439
476
|
link &&
|
|
440
477
|
filteredTypes.length > 0 &&
|
|
441
|
-
filteredLanguages.length > 0
|
|
478
|
+
(filteredLanguages.length > 0 || hasScans)
|
|
442
479
|
) {
|
|
443
480
|
const fullUrl = link.startsWith("http") ? link : `${BASE_URL}${link}`;
|
|
444
481
|
animeLinks.push({
|
|
@@ -452,10 +489,10 @@ export async function getAllAnime(
|
|
|
452
489
|
});
|
|
453
490
|
}
|
|
454
491
|
});
|
|
455
|
-
|
|
492
|
+
|
|
456
493
|
return containers.length > 0;
|
|
457
494
|
};
|
|
458
|
-
|
|
495
|
+
|
|
459
496
|
const enrichWithSeasons = async (list) => {
|
|
460
497
|
for (const anime of list) {
|
|
461
498
|
try {
|
|
@@ -534,6 +571,54 @@ export async function getLatestEpisodes(languageFilter = null) {
|
|
|
534
571
|
}
|
|
535
572
|
}
|
|
536
573
|
|
|
574
|
+
export async function getLatestScans(languageFilter = null) {
|
|
575
|
+
try {
|
|
576
|
+
const res = await axios.get(BASE_URL, { headers: getHeaders() });
|
|
577
|
+
const $ = cheerio.load(res.data);
|
|
578
|
+
|
|
579
|
+
const container = $("#containerAjoutsScans");
|
|
580
|
+
const scans = [];
|
|
581
|
+
|
|
582
|
+
container.find("a").each((_, el) => {
|
|
583
|
+
const url = $(el).attr("href");
|
|
584
|
+
const title = $(el).find("h1").text().trim();
|
|
585
|
+
const cover = $(el).find("img").attr("src");
|
|
586
|
+
|
|
587
|
+
const buttons = $(el).find("button");
|
|
588
|
+
const type = $(buttons[0]).text().trim().toLowerCase();
|
|
589
|
+
const language = url.split("/").slice(6, 7)[0];
|
|
590
|
+
const chapter = $(buttons[2]).text().trim();
|
|
591
|
+
|
|
592
|
+
if (
|
|
593
|
+
title &&
|
|
594
|
+
url &&
|
|
595
|
+
cover &&
|
|
596
|
+
type &&
|
|
597
|
+
language &&
|
|
598
|
+
chapter &&
|
|
599
|
+
(languageFilter === null ||
|
|
600
|
+
languageFilter
|
|
601
|
+
.map((l) => l.toLowerCase())
|
|
602
|
+
.includes(language.toLowerCase()))
|
|
603
|
+
) {
|
|
604
|
+
scans.push({
|
|
605
|
+
title,
|
|
606
|
+
url,
|
|
607
|
+
cover,
|
|
608
|
+
type,
|
|
609
|
+
language,
|
|
610
|
+
chapter,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
return scans;
|
|
616
|
+
} catch (err) {
|
|
617
|
+
console.error("Failed to fetch today scans:", err.message);
|
|
618
|
+
return [];
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
537
622
|
export async function getRandomAnime(
|
|
538
623
|
wantedLanguages = ["vostfr", "vf", "vastfr"],
|
|
539
624
|
wantedTypes = ["Anime", "Film"],
|
|
@@ -609,3 +694,32 @@ export async function getRandomAnime(
|
|
|
609
694
|
return null;
|
|
610
695
|
}
|
|
611
696
|
}
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
export async function getAllTitleScans(mangaUrl, numberImg = false) {
|
|
700
|
+
const res = await axios.get(mangaUrl, { headers: getHeaders() });
|
|
701
|
+
const $ = cheerio.load(res.data);
|
|
702
|
+
|
|
703
|
+
const title = encodeURIComponent($("#titreOeuvre").text().trim());
|
|
704
|
+
const urlInfo = `https://anime-sama.org/s2/scans/get_nb_chap_et_img.php?oeuvre=${title}`
|
|
705
|
+
const infoPage = await axios.get(urlInfo, { headers: getHeaders() });
|
|
706
|
+
const titleChapter = Object.keys(infoPage.data)
|
|
707
|
+
|
|
708
|
+
if (numberImg) {
|
|
709
|
+
return {scans :infoPage.data, mangaTitle :title}
|
|
710
|
+
} else {
|
|
711
|
+
return titleChapter
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
export async function getImgScans(mangaUrl, wantedChapter) {
|
|
716
|
+
const infoScan = await getAllTitleScans(mangaUrl, true)
|
|
717
|
+
const numberImg = infoScan.scans[wantedChapter.toString()];
|
|
718
|
+
const mangaTitle = infoScan.mangaTitle
|
|
719
|
+
const imgUrls = [];
|
|
720
|
+
for (let i = 1; i <= numberImg; i++) {
|
|
721
|
+
imgUrls.push(`https://anime-sama.org/s2/scans/${mangaTitle}/${wantedChapter}/${i}.jpg`);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return imgUrls
|
|
725
|
+
}
|
package/scrapers/scrapers.js
CHANGED
|
@@ -4,14 +4,16 @@ import * as crunchyroll from "./crunchyroll.js";
|
|
|
4
4
|
|
|
5
5
|
export class AnimeScraper {
|
|
6
6
|
constructor(source) {
|
|
7
|
-
if (source ===
|
|
7
|
+
if (source === "animepahe") {
|
|
8
8
|
this.source = animepahe;
|
|
9
|
-
} else if (source ===
|
|
9
|
+
} else if (source === "animesama") {
|
|
10
10
|
this.source = animesama;
|
|
11
|
-
} else if (source ===
|
|
11
|
+
} else if (source === "crunchyroll") {
|
|
12
12
|
this.source = crunchyroll;
|
|
13
|
-
}
|
|
14
|
-
throw new Error(
|
|
13
|
+
} else {
|
|
14
|
+
throw new Error(
|
|
15
|
+
'Invalid source. Choose either "animepahe", "crunchyroll" or "animesama".'
|
|
16
|
+
);
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -19,7 +21,9 @@ export class AnimeScraper {
|
|
|
19
21
|
try {
|
|
20
22
|
return await this.source.searchAnime(query, ...rest);
|
|
21
23
|
} catch (error) {
|
|
22
|
-
console.error(
|
|
24
|
+
console.error(
|
|
25
|
+
`This scraper does not have the searchAnime function implemented or an error happened -> ${error}`
|
|
26
|
+
);
|
|
23
27
|
return null;
|
|
24
28
|
}
|
|
25
29
|
}
|
|
@@ -28,7 +32,9 @@ export class AnimeScraper {
|
|
|
28
32
|
try {
|
|
29
33
|
return await this.source.getSeasons(animeUrl, ...rest);
|
|
30
34
|
} catch (error) {
|
|
31
|
-
console.error(
|
|
35
|
+
console.error(
|
|
36
|
+
`This scraper does not have the getSeasons function implemented or an error happened -> ${error}`
|
|
37
|
+
);
|
|
32
38
|
return null;
|
|
33
39
|
}
|
|
34
40
|
}
|
|
@@ -37,16 +43,30 @@ export class AnimeScraper {
|
|
|
37
43
|
try {
|
|
38
44
|
return await this.source.getEpisodeTitles(seasonUrl, ...rest);
|
|
39
45
|
} catch (error) {
|
|
40
|
-
console.error(
|
|
46
|
+
console.error(
|
|
47
|
+
`This scraper does not have the getEpisodeTitles function implemented or an error happened -> ${error}`
|
|
48
|
+
);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getEmbed(seasonUrl, ...rest) {
|
|
54
|
+
try {
|
|
55
|
+
return await this.source.getEmbed(seasonUrl, ...rest);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(
|
|
58
|
+
`This scraper does not have the getEmbed function implemented or an error happened -> ${error}`
|
|
59
|
+
);
|
|
41
60
|
return null;
|
|
42
61
|
}
|
|
43
62
|
}
|
|
44
|
-
|
|
45
63
|
async getEmbed(seasonUrl, ...rest) {
|
|
46
64
|
try {
|
|
47
65
|
return await this.source.getEmbed(seasonUrl, ...rest);
|
|
48
66
|
} catch (error) {
|
|
49
|
-
console.error(
|
|
67
|
+
console.error(
|
|
68
|
+
`This scraper does not have the getEmbed function implemented or an error happened -> ${error}`
|
|
69
|
+
);
|
|
50
70
|
return null;
|
|
51
71
|
}
|
|
52
72
|
}
|
|
@@ -55,7 +75,9 @@ export class AnimeScraper {
|
|
|
55
75
|
try {
|
|
56
76
|
return await this.source.getAnimeInfo(animeUrl);
|
|
57
77
|
} catch (error) {
|
|
58
|
-
console.error(
|
|
78
|
+
console.error(
|
|
79
|
+
`This scraper does not have the getAnimeInfo function implemented or an error happened -> ${error}`
|
|
80
|
+
);
|
|
59
81
|
return null;
|
|
60
82
|
}
|
|
61
83
|
}
|
|
@@ -64,7 +86,9 @@ export class AnimeScraper {
|
|
|
64
86
|
try {
|
|
65
87
|
return await this.source.getAvailableLanguages(animeUrl, ...rest);
|
|
66
88
|
} catch (error) {
|
|
67
|
-
console.error(
|
|
89
|
+
console.error(
|
|
90
|
+
`This scraper does not have the getAvailableLanguages function implemented or an error happened -> ${error}`
|
|
91
|
+
);
|
|
68
92
|
return null;
|
|
69
93
|
}
|
|
70
94
|
}
|
|
@@ -73,7 +97,9 @@ export class AnimeScraper {
|
|
|
73
97
|
try {
|
|
74
98
|
return await this.source.getAllAnime(...rest);
|
|
75
99
|
} catch (error) {
|
|
76
|
-
console.error(
|
|
100
|
+
console.error(
|
|
101
|
+
`This scraper does not have the getAllAnime function implemented or an error happened -> ${error}`
|
|
102
|
+
);
|
|
77
103
|
return null;
|
|
78
104
|
}
|
|
79
105
|
}
|
|
@@ -82,16 +108,30 @@ export class AnimeScraper {
|
|
|
82
108
|
try {
|
|
83
109
|
return await this.source.getLatestEpisodes(...rest);
|
|
84
110
|
} catch (error) {
|
|
85
|
-
console.error(
|
|
111
|
+
console.error(
|
|
112
|
+
`This scraper does not have the getLatestEpisodes function implemented or an error happened -> ${error}`
|
|
113
|
+
);
|
|
86
114
|
return null;
|
|
87
115
|
}
|
|
88
116
|
}
|
|
89
117
|
|
|
118
|
+
async getLatestScans(...rest) {
|
|
119
|
+
try {
|
|
120
|
+
return await this.source.getLatestScans(...rest);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error(
|
|
123
|
+
`This scraper does not have the getLatestScans function implemented or an error happened -> ${error}`
|
|
124
|
+
);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
90
128
|
async getRandomAnime(...rest) {
|
|
91
129
|
try {
|
|
92
130
|
return await this.source.getRandomAnime(...rest);
|
|
93
131
|
} catch (error) {
|
|
94
|
-
console.error(
|
|
132
|
+
console.error(
|
|
133
|
+
`This scraper does not have the getRandomAnime function implemented or an error happened -> ${error}`
|
|
134
|
+
);
|
|
95
135
|
return null;
|
|
96
136
|
}
|
|
97
137
|
}
|
|
@@ -100,7 +140,30 @@ export class AnimeScraper {
|
|
|
100
140
|
try {
|
|
101
141
|
return await this.source.getEpisodeInfo(animeUrl, ...rest);
|
|
102
142
|
} catch (error) {
|
|
103
|
-
console.error(
|
|
143
|
+
console.error(
|
|
144
|
+
`This scraper does not have the getEpisodeInfo function implemented or an error happened -> ${error}`
|
|
145
|
+
);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async getAllTitleScans(mangaUrl, ...rest) {
|
|
151
|
+
try {
|
|
152
|
+
return await this.source.getAllTitleScans(mangaUrl, ...rest);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(
|
|
155
|
+
`This scraper does not have the getAllTitleScans function implemented or an error happened -> ${error}`
|
|
156
|
+
);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async getImgScans(mangaUrl, ...rest) {
|
|
161
|
+
try {
|
|
162
|
+
return await this.source.getImgScans(mangaUrl, ...rest);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(
|
|
165
|
+
`This scraper does not have the getImgScans function implemented or an error happened -> ${error}`
|
|
166
|
+
);
|
|
104
167
|
return null;
|
|
105
168
|
}
|
|
106
169
|
}
|
package/utils/dispatcher.js
CHANGED
|
@@ -10,6 +10,9 @@ export async function getVideoUrlFromEmbed(source, embedUrl) {
|
|
|
10
10
|
if (source === "vidmoly" || source === "oneupload" ) {
|
|
11
11
|
return await extractor.getVidmolyOrOneuploadVideo(embedUrl);
|
|
12
12
|
}
|
|
13
|
+
if (source === "movearnpre" || source === "smoothpre" ) {
|
|
14
|
+
return await extractor.getMovearnpreOrSmoothpreVideo(embedUrl);
|
|
15
|
+
}
|
|
13
16
|
|
|
14
17
|
throw new Error(`Unsupported embed source: ${source}`);
|
|
15
18
|
}
|
package/utils/extractVideoUrl.js
CHANGED
|
@@ -56,11 +56,9 @@ export async function getVidmolyOrOneuploadVideo(embedUrl) {
|
|
|
56
56
|
if (embedUrl.includes("vidmoly.to/")) {
|
|
57
57
|
embedUrl = embedUrl.replace("vidmoly.to/", "vidmoly.net/");
|
|
58
58
|
}
|
|
59
|
-
console.log(embedUrl)
|
|
60
59
|
const { data } = await axios.get(embedUrl, {
|
|
61
60
|
headers: getHeaders(embedUrl),
|
|
62
61
|
});
|
|
63
|
-
console.log(data)
|
|
64
62
|
const $ = cheerio.load(data);
|
|
65
63
|
const scripts = $("script");
|
|
66
64
|
|
|
@@ -77,3 +75,28 @@ export async function getVidmolyOrOneuploadVideo(embedUrl) {
|
|
|
77
75
|
return null;
|
|
78
76
|
}
|
|
79
77
|
}
|
|
78
|
+
|
|
79
|
+
import puppeteer from 'puppeteer';
|
|
80
|
+
|
|
81
|
+
export async function getMovearnpreOrSmoothpreVideo(embedUrl) {
|
|
82
|
+
const browser = await puppeteer.launch({ headless: true });
|
|
83
|
+
const page = await browser.newPage();
|
|
84
|
+
|
|
85
|
+
await page.setUserAgent(
|
|
86
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
await page.goto(embedUrl, { waitUntil: 'networkidle2' });
|
|
90
|
+
|
|
91
|
+
await page.waitForFunction('typeof jwplayer !== "undefined"');
|
|
92
|
+
|
|
93
|
+
const videoUrl = await page.evaluate(() => {
|
|
94
|
+
const player = jwplayer();
|
|
95
|
+
const sources = player?.getPlaylist()?.[0]?.sources;
|
|
96
|
+
return sources?.[0]?.file || null;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
await browser.close();
|
|
100
|
+
const finalUrl = embedUrl.split("/").slice(0, 3).join("/") + videoUrl
|
|
101
|
+
return finalUrl;
|
|
102
|
+
}
|