better-ani-scraped 1.6.9 → 1.6.11

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
@@ -36,6 +36,9 @@ const crunchyroll = new AnimeScraper('crunchyroll') //for Crunchyroll
36
36
  - [getAllAnime](#animesamagetallanimewantedlanguages--vostfr-vf-vastfr-wantedtypes--anime-film-page--null-output--anime_listjson-get_seasons--false)
37
37
  - [getLatestEpisodes](#animesamagetlatestepisodeslanguagefilter--null)
38
38
  - [getRandomAnime](#animesamagetrandomanimewantedlanguages--vostfr-vf-vastfr-wantedtypes--anime-film-maxattempts--null-attempt--0)
39
+ - [getAllTitleScans](#animesamagetalltitlescansmangaurl-numberimg--false)
40
+ - [getImgScans](#animesamagetimgscansmangaurl-wantedchapter)
41
+
39
42
 
40
43
  ### `animesama.searchAnime(query, limit = 10, wantedLanguages = ["vostfr", "vf", "vastfr"], wantedTypes = ["Anime", "Film"], page = null)`
41
44
  Searches for anime titles that match the given query.
@@ -254,6 +257,40 @@ Fetches a random anime from the catalogue.
254
257
 
255
258
  ---
256
259
 
260
+ ### `animesama.getAllTitleScans(mangaUrl, numberImg = false)`
261
+ Scrapes all the scans of a chapter.
262
+
263
+ - **Parameters:**
264
+ - `mangaUrl` *(string)*: The manga URL.
265
+ - `numberImg` *(boolean)*: If `true`, indicates the number of images in each chapter.
266
+ - **Returns:**
267
+ An array of chapter titles if *numberImg = false*
268
+ Else :
269
+ ```js
270
+ {
271
+ scans :
272
+ [
273
+ {
274
+ title: string,
275
+ url: string,
276
+ },
277
+ ...
278
+ ]
279
+ mangaTitle: string,
280
+ }
281
+
282
+ ---
283
+
284
+ ### `animesama.getImgScans(mangaUrl, wantedChapter)`
285
+ Scrapes all the scans of a chapter.
286
+
287
+ - **Parameters:**
288
+ - `mangaUrl` *(string)*: The manga URL.
289
+ - `wantedChapter` *(int)*: The number of the chapter you want.
290
+ - **Returns:**
291
+ An array of image URL.
292
+
293
+ ---
257
294
 
258
295
  ## `AnimeScraper("animepahe")` methods
259
296
 
@@ -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.fr/catalogue/drcl-midnight-children/scan/vf";
6
+ const chapterTitles = await animesama.getAllTitleScans(mangaUrl, true);
7
+ console.log("Titles:", chapterTitles);
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 seasonUrl = "https://anime-sama.fr/catalogue/solo-leveling/saison1/vostfr/";
5
+ const seasonUrl = "https://anime-sama.fr/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
 
@@ -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.fr/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);
@@ -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.fr/catalogue/one-piece";
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-ani-scraped",
3
- "version": "1.6.9",
3
+ "version": "1.6.11",
4
4
  "description": "Scrape anime data from different sources (only anime-sama.fr, animepahe and crunchyroll for the moment)",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -4,6 +4,7 @@ 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
 
@@ -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 {
@@ -609,3 +646,32 @@ export async function getRandomAnime(
609
646
  return null;
610
647
  }
611
648
  }
649
+
650
+
651
+ export async function getAllTitleScans(mangaUrl, numberImg = false) {
652
+ const res = await axios.get(mangaUrl, { headers: getHeaders() });
653
+ const $ = cheerio.load(res.data);
654
+
655
+ const title = encodeURIComponent($("#titreOeuvre").text().trim());
656
+ const urlInfo = `https://anime-sama.fr/s2/scans/get_nb_chap_et_img.php?oeuvre=${title}`
657
+ const infoPage = await axios.get(urlInfo, { headers: getHeaders() });
658
+ const titleChapter = Object.keys(infoPage.data)
659
+
660
+ if (numberImg) {
661
+ return {scans :infoPage.data, mangaTitle :title}
662
+ } else {
663
+ return titleChapter
664
+ }
665
+ }
666
+
667
+ export async function getImgScans(mangaUrl, wantedChapter) {
668
+ const infoScan = await getAllTitleScans(mangaUrl, true)
669
+ const numberImg = infoScan.scans[wantedChapter.toString()];
670
+ const mangaTitle = infoScan.mangaTitle
671
+ const imgUrls = [];
672
+ for (let i = 1; i <= numberImg; i++) {
673
+ imgUrls.push(`https://anime-sama.fr/s2/scans/${mangaTitle}/${wantedChapter}/${i}.jpg`);
674
+ }
675
+
676
+ return imgUrls
677
+ }
@@ -104,4 +104,21 @@ export class AnimeScraper {
104
104
  return null;
105
105
  }
106
106
  }
107
+
108
+ async getAllTitleScans(mangaUrl, ...rest) {
109
+ try {
110
+ return await this.source.getAllTitleScans(mangaUrl, ...rest);
111
+ } catch (error) {
112
+ console.error(`This scraper does not have the getAllTitleScans function implemented or an error happened -> ${error}`);
113
+ return null;
114
+ }
115
+ }
116
+ async getImgScans(mangaUrl, ...rest) {
117
+ try {
118
+ return await this.source.getImgScans(mangaUrl, ...rest);
119
+ } catch (error) {
120
+ console.error(`This scraper does not have the getImgScans function implemented or an error happened -> ${error}`);
121
+ return null;
122
+ }
123
+ }
107
124
  }