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 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.fr) and [animepahe](https://animepahe.ru) available at the moment). This tool allows you to search for anime, retrieve information, get episodes, and more.
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"], 2);
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.fr/catalogue/86-eighty-six/";
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.fr/catalogue/86-eighty-six/saison1/vostfr/";
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.fr/catalogue/solo-leveling/saison1/vostfr/";
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.fr/catalogue/86-eighty-six/saison1/vostfr/";
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.fr/catalogue/sword-art-online";
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.6.82",
4
- "description": "Scrape anime data from different sources (only anime-sama.fr, animepahe and crunchyroll for the moment)",
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"
@@ -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.fr";
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
- const mainAnimeOnly = html.split("Anime Version Kai")[0];
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("panneauAnime"));
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 title = match[1];
155
- const href = match[2].split("/")[0];
156
- const fullUrl = `${CATALOGUE_URL}/${animeName}/${href}/${language}`;
157
-
158
- try {
159
- const check = await axios.head(fullUrl, {
160
- headers: getHeaders(animeUrl),
161
- });
162
- if (check.status === 200) {
163
- languageAvailable = true;
164
- seasons.push({ title, url: fullUrl });
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
- } catch (err) {
167
- // Ignore invalid URLs
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
- if (languageAvailable) {
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 { error: "No language available in : " + languagePriority.join(", ") };
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; // une seule URL par host
315
+ break;
288
316
  }
289
317
  }
290
318
  }
291
319
 
292
320
  finalEmbeds.push({
293
- title: null, // à remplir plus tard
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, // à remplir plus tard
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
- const filteredTypes = type.filter(t => isWanted(t, wantedTypes));
436
- const filteredLanguages = language.filter(lang => isWanted(lang, wantedLanguages));
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
+ }
@@ -4,14 +4,16 @@ import * as crunchyroll from "./crunchyroll.js";
4
4
 
5
5
  export class AnimeScraper {
6
6
  constructor(source) {
7
- if (source === 'animepahe') {
7
+ if (source === "animepahe") {
8
8
  this.source = animepahe;
9
- } else if (source === 'animesama') {
9
+ } else if (source === "animesama") {
10
10
  this.source = animesama;
11
- } else if (source === 'crunchyroll') {
11
+ } else if (source === "crunchyroll") {
12
12
  this.source = crunchyroll;
13
- } else {
14
- throw new Error('Invalid source. Choose either "animepahe", "crunchyroll" or "animesama".');
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(`This scraper does not have the searchAnime function implemented or an error happened -> ${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(`This scraper does not have the getSeasons function implemented or an error happened -> ${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(`This scraper does not have the getEpisodeTitles function implemented or an error happened -> ${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(`This scraper does not have the getEmbed function implemented or an error happened -> ${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(`This scraper does not have the getAnimeInfo function implemented or an error happened -> ${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(`This scraper does not have the getAvailableLanguages function implemented or an error happened -> ${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(`This scraper does not have the getAllAnime function implemented or an error happened -> ${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(`This scraper does not have the getLatestEpisodes function implemented or an error happened -> ${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(`This scraper does not have the getRandomAnime function implemented or an error happened -> ${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(`This scraper does not have the getEpisodeInfo function implemented or an error happened -> ${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
  }
@@ -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
  }
@@ -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
+ }