better-ani-scraped 1.6.81 → 1.7.1

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
@@ -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.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);
@@ -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.fr/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,6 +1,6 @@
1
1
  {
2
2
  "name": "better-ani-scraped",
3
- "version": "1.6.81",
3
+ "version": "1.7.1",
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
 
@@ -36,19 +37,13 @@ async function ensureChromiumInstalled(customPath) {
36
37
  }
37
38
 
38
39
  function getHeaders(referer = BASE_URL) {
39
- const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' + '(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36';
40
40
  return {
41
- headers: {
42
- "User-Agent": userAgent,
43
- "Accept-Language": "fr-FR,fr;q=0.9,en;q=0.8",
44
- "Referer": referer,
45
- },
46
- userAgent
41
+ "User-Agent": "Mozilla/5.0",
42
+ "Accept-Language": "fr-FR,fr;q=0.9,en;q=0.8",
43
+ Referer: referer,
47
44
  };
48
45
  }
49
46
 
50
-
51
-
52
47
  export async function searchAnime(
53
48
  query,
54
49
  limit = 10,
@@ -59,7 +54,6 @@ export async function searchAnime(
59
54
  const languages = Array.isArray(wantedLanguages) ? wantedLanguages : ["vostfr", "vf", "vastfr"];
60
55
  const types = Array.isArray(wantedTypes) ? wantedTypes : ["Anime", "Film"];
61
56
 
62
- const { headers } = getHeaders(CATALOGUE_URL);
63
57
  const isWanted = (text, list) =>
64
58
  list.length === 0 || list.some(item => text.toLowerCase().includes(item.toLowerCase()));
65
59
 
@@ -71,7 +65,7 @@ export async function searchAnime(
71
65
  ? `${CATALOGUE_URL}/?search=${encodeURIComponent(query)}`
72
66
  : `${CATALOGUE_URL}/?search=${encodeURIComponent(query)}&page=${pageNum}`;
73
67
 
74
- const res = await axios.get(url, { headers: headers });
68
+ const res = await axios.get(url, { headers: getHeaders(CATALOGUE_URL) });
75
69
  const $ = cheerio.load(res.data);
76
70
 
77
71
  const containers = $("a.flex.divide-x");
@@ -131,17 +125,26 @@ export async function searchAnime(
131
125
  return results;
132
126
  }
133
127
 
134
- export async function getSeasons(animeUrl, languagePriority = ["vostfr", "vf", "va", "vkr", "vcn", "vqc", "vf1", "vf2"]) {
135
- const { headersCatalog } = getHeaders(CATALOGUE_URL);
136
- const { headersAnime } = getHeaders(animeUrl);
137
- const res = await axios.get(animeUrl, { headers: headersCatalog });
128
+ export async function getSeasons(animeUrl, languagePriority = ["vostfr", "vf", "va", "vkr", "vcn", "vqc", "vf1", "vf2"], wantedTypes=["Anime", "Kai", "Scans"]) {
129
+ const res = await axios.get(animeUrl, { headers: getHeaders(CATALOGUE_URL) });
138
130
  const html = res.data;
139
-
140
- 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
+
141
144
  const $ = cheerio.load(mainAnimeOnly);
142
145
  const scriptTags = $("script")
143
146
  .toArray()
144
- .filter(script => $(script).html().includes("panneauAnime"));
147
+ .filter(script => ["panneauAnime", "panneauScan"].some(str => $(script).html().includes(str)));
145
148
 
146
149
  const animeName = animeUrl.split("/")[4];
147
150
  let seasons = [];
@@ -149,7 +152,7 @@ export async function getSeasons(animeUrl, languagePriority = ["vostfr", "vf", "
149
152
  for (const language of languagePriority) {
150
153
  seasons = [];
151
154
  let languageAvailable = false;
152
-
155
+ let scansLanguage = "";
153
156
  for (let script of scriptTags) {
154
157
  const content = $(script).html();
155
158
 
@@ -157,38 +160,53 @@ export async function getSeasons(animeUrl, languagePriority = ["vostfr", "vf", "
157
160
  .replace(/\/\*[\s\S]*?\*\//g, "")
158
161
  .replace(/\/\/.*$/gm, "");
159
162
 
160
- const matches = [...uncommentedContent.matchAll(/panneauAnime\("([^"]+)", "([^"]+)"\);/g)];
163
+ const matches = [...uncommentedContent.matchAll(/(panneauAnime|panneauScan)\("([^"]+)", "([^"]+)"\);/g)];
161
164
 
162
165
  for (let match of matches) {
163
- const title = match[1];
164
- const href = match[2].split("/")[0];
165
- const fullUrl = `${CATALOGUE_URL}/${animeName}/${href}/${language}`;
166
-
167
- try {
168
- const check = await axios.head(fullUrl, {
169
- headers: headersAnime,
170
- });
171
- if (check.status === 200) {
172
- languageAvailable = true;
173
- 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 {}
183
+ }
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 {}
174
194
  }
175
- } catch (err) {
176
- // Ignore missing URLs
177
195
  }
178
196
  }
179
- }
180
-
181
- if (languageAvailable) {
197
+ if (wantedTypes.includes("Scans") && wantedTypes.length==1 && scansLanguage) {
198
+ return { scansLanguage, seasons };
199
+ }
200
+ else if (languageAvailable) {
182
201
  return { language, seasons };
183
202
  }
184
203
  }
185
204
 
186
- return { error: "No language available in : " + languagePriority.join(", ") };
205
+ return [];
187
206
  }
188
207
 
189
208
 
190
209
  export async function getEpisodeTitles(seasonUrl, customChromiumPath) {
191
- const { headers, userAgent } = getHeaders(seasonUrl);
192
210
  let browser;
193
211
  try {
194
212
  const puppeteer = await import('puppeteer');
@@ -205,7 +223,7 @@ export async function getEpisodeTitles(seasonUrl, customChromiumPath) {
205
223
  });
206
224
 
207
225
  const page = await browser.newPage();
208
- await page.setExtraHTTPHeaders(headers);
226
+ await page.setExtraHTTPHeaders(getHeaders(seasonUrl));
209
227
  await page.setRequestInterception(true);
210
228
  page.on('request', (req) => {
211
229
  const blocked = ['image', 'stylesheet', 'font', 'media'];
@@ -215,7 +233,7 @@ export async function getEpisodeTitles(seasonUrl, customChromiumPath) {
215
233
  req.continue();
216
234
  }
217
235
  });
218
- await page.setUserAgent(userAgent);
236
+ await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' + '(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36');
219
237
  await page.evaluateOnNewDocument(() => {
220
238
  Object.defineProperty(navigator, 'webdriver', { get: () => false });
221
239
  });
@@ -244,10 +262,8 @@ export async function getEmbed(
244
262
  includeInfo = false,
245
263
  customChromiumPath
246
264
  ) {
247
- const { headersSplit } = getHeaders(seasonUrl.split("/").slice(0, 5).join("/"));
248
- const { headers } = getHeaders(seasonUrl);
249
265
  const res = await axios.get(seasonUrl, {
250
- headers: headersSplit,
266
+ headers: getHeaders(seasonUrl.split("/").slice(0, 5).join("/")),
251
267
  });
252
268
 
253
269
  const $ = cheerio.load(res.data);
@@ -265,10 +281,10 @@ export async function getEmbed(
265
281
  : seasonUrl + "/" + scriptTag;
266
282
 
267
283
  const episodesJs = await axios
268
- .get(scriptUrl, { headers: headers })
284
+ .get(scriptUrl, { headers: getHeaders(seasonUrl) })
269
285
  .then((r) => r.data);
270
286
 
271
- const matches = [...episodesJs.matchAll(/var\s+(eps\d+)\s*=\s*(\[[^\]]+\])/g)];
287
+ const matches = [...episodesJs.toLowerCase().matchAll(/var\s+(eps\d+)\s*=\s*(\[[^\]]+\])/g)];
272
288
  if (!matches.length) throw new Error("No episode arrays found");
273
289
 
274
290
  let episodeMatrix = [];
@@ -291,18 +307,18 @@ export async function getEmbed(
291
307
 
292
308
  for (const host of hostPriority) {
293
309
  for (const arr of episodeMatrix) {
294
- if (i < arr.length && arr[i].includes(host)) {
295
- if (!hosts.includes(host)) {
310
+ if (i < arr.length && arr[i].includes(host.toLowerCase())) {
311
+ if (!hosts.includes(host.toLowerCase())) {
296
312
  urls.push(arr[i]);
297
- hosts.push(host);
313
+ hosts.push(host.toLowerCase());
298
314
  }
299
- break; // une seule URL par host
315
+ break;
300
316
  }
301
317
  }
302
318
  }
303
319
 
304
320
  finalEmbeds.push({
305
- title: null, // à remplir plus tard
321
+ title: null,
306
322
  url: urls.length ? urls : null,
307
323
  host: hosts.length ? hosts : null,
308
324
  });
@@ -313,22 +329,22 @@ export async function getEmbed(
313
329
 
314
330
  for (const host of hostPriority) {
315
331
  for (const arr of episodeMatrix) {
316
- if (i < arr.length && arr[i].includes(host)) {
332
+ if (i < arr.length && arr[i].includes(host.toLowerCase())) {
317
333
  selectedUrl = arr[i];
318
- selectedHost = host;
334
+ selectedHost = host.toLowerCase();
319
335
  break;
320
336
  }
321
337
  }
322
338
  if (selectedUrl) break;
323
339
  }
324
340
 
325
- finalEmbeds.push({
326
- title: null,
327
- url: selectedUrl || null,
328
- host: selectedHost || null
329
- });
341
+ finalEmbeds.push({
342
+ title: null,
343
+ url: selectedUrl || null,
344
+ host: selectedHost || null,
345
+ });
346
+ }
330
347
  }
331
- }
332
348
 
333
349
  const titles = await getEpisodeTitles(seasonUrl, customChromiumPath);
334
350
  finalEmbeds.forEach((embed, i) => {
@@ -351,8 +367,7 @@ export async function getEmbed(
351
367
 
352
368
 
353
369
  export async function getAnimeInfo(animeUrl) {
354
- const { headers } = getHeaders(CATALOGUE_URL);
355
- const res = await axios.get(animeUrl, { headers: headers });
370
+ const res = await axios.get(animeUrl, { headers: getHeaders(CATALOGUE_URL) });
356
371
  const $ = cheerio.load(res.data);
357
372
 
358
373
  const cover = $("#coverOeuvre").attr("src");
@@ -388,7 +403,6 @@ export async function getAvailableLanguages(
388
403
  wantedLanguages = ["vostfr", "vf", "va", "vkr", "vcn", "vqc", "vf1", "vf2"],
389
404
  numberEpisodes = false
390
405
  ) {
391
- const { headers } = getHeaders(CATALOGUE_URL);
392
406
  const languageLinks = [];
393
407
 
394
408
  // Iterate over each possible language and check if the page exists
@@ -396,7 +410,7 @@ export async function getAvailableLanguages(
396
410
  const languageUrl = seasonUrl.split('/').map((s, i) => i === 6 ? language : s).join('/');
397
411
  try {
398
412
  const res = await axios.get(languageUrl, {
399
- headers: headers,
413
+ headers: getHeaders(CATALOGUE_URL),
400
414
  });
401
415
  if (res.status === 200) {
402
416
  if (numberEpisodes){
@@ -422,7 +436,6 @@ export async function getAllAnime(
422
436
  output = "anime_list.json",
423
437
  get_seasons = false
424
438
  ) {
425
- const { headers } = getHeaders(CATALOGUE_URL);
426
439
  let animeLinks = [];
427
440
 
428
441
  const isWanted = (text, list) =>
@@ -430,30 +443,39 @@ export async function getAllAnime(
430
443
 
431
444
  const fetchPage = async (pageNum) => {
432
445
  const url = pageNum === 1 ? CATALOGUE_URL : `${CATALOGUE_URL}?page=${pageNum}`;
433
- const res = await axios.get(url, { headers: headers });
446
+ const res = await axios.get(url, { headers: getHeaders(CATALOGUE_URL) });
434
447
  const $ = cheerio.load(res.data);
435
-
448
+
436
449
  const containers = $("div.shrink-0.m-3.rounded.border-2");
437
-
450
+
438
451
  containers.each((_, el) => {
439
452
  const anchor = $(el).find("a");
440
453
  const title = anchor.find("h1").text().trim();
441
454
  const link = anchor.attr("href");
442
455
  const img = anchor.find("img").attr("src");
443
-
456
+
444
457
  const paragraphs = anchor.find("p").toArray().map(p => $(p).text().trim());
445
-
458
+
446
459
  const altTitles = paragraphs[0] ? paragraphs[0].split(',').map(name => name.trim()) : [];
447
460
  const genres = paragraphs[1] ? paragraphs[1].split(',').map(genre => genre.trim()) : [];
448
461
  const type = paragraphs[2] ? paragraphs[2].split(',').map(t => t.trim()) : [];
449
462
  const language = paragraphs[3] ? paragraphs[3].split(',').map(lang => lang.trim()) : [];
450
- const filteredTypes = type.filter(t => isWanted(t, wantedTypes));
451
- 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
+
452
474
  if (
453
475
  title &&
454
476
  link &&
455
477
  filteredTypes.length > 0 &&
456
- filteredLanguages.length > 0
478
+ (filteredLanguages.length > 0 || hasScans)
457
479
  ) {
458
480
  const fullUrl = link.startsWith("http") ? link : `${BASE_URL}${link}`;
459
481
  animeLinks.push({
@@ -467,10 +489,10 @@ export async function getAllAnime(
467
489
  });
468
490
  }
469
491
  });
470
-
492
+
471
493
  return containers.length > 0;
472
494
  };
473
-
495
+
474
496
  const enrichWithSeasons = async (list) => {
475
497
  for (const anime of list) {
476
498
  try {
@@ -509,8 +531,7 @@ export async function getAllAnime(
509
531
 
510
532
  export async function getLatestEpisodes(languageFilter = null) {
511
533
  try {
512
- const { headers } = getHeaders(BASE_URL);
513
- const res = await axios.get(BASE_URL, { headers: headers });
534
+ const res = await axios.get(BASE_URL, { headers: getHeaders() });
514
535
  const $ = cheerio.load(res.data);
515
536
 
516
537
  const container = $("#containerAjoutsAnimes");
@@ -522,7 +543,7 @@ export async function getLatestEpisodes(languageFilter = null) {
522
543
  const cover = $(el).find("img").attr("src");
523
544
 
524
545
  const buttons = $(el).find("button");
525
- const language = $(buttons[0]).text().trim().toLowerCase();
546
+ const language = $(buttons[0]).text().trim().toLowerCase(); // Normalisation
526
547
  const episode = $(buttons[1]).text().trim();
527
548
 
528
549
  if (
@@ -550,17 +571,64 @@ export async function getLatestEpisodes(languageFilter = null) {
550
571
  }
551
572
  }
552
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
+
553
622
  export async function getRandomAnime(
554
623
  wantedLanguages = ["vostfr", "vf", "vastfr"],
555
624
  wantedTypes = ["Anime", "Film"],
556
625
  maxAttempts = null,
557
626
  attempt = 0
558
627
  ) {
559
- const { headers } = getHeaders(CATALOGUE_URL);
560
628
  try {
561
629
  const res = await axios.get(
562
630
  `${CATALOGUE_URL}/?search=&random=1`,
563
- { headers:headers }
631
+ { headers: getHeaders(CATALOGUE_URL) }
564
632
  );
565
633
 
566
634
  const $ = cheerio.load(res.data);
@@ -627,3 +695,31 @@ export async function getRandomAnime(
627
695
  }
628
696
  }
629
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.fr/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.fr/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
  }
@@ -60,7 +60,6 @@ export async function getVidmolyOrOneuploadVideo(embedUrl) {
60
60
  const { data } = await axios.get(embedUrl, {
61
61
  headers: getHeaders(embedUrl),
62
62
  });
63
- console.log(data)
64
63
  const $ = cheerio.load(data);
65
64
  const scripts = $("script");
66
65
 
@@ -77,3 +76,28 @@ export async function getVidmolyOrOneuploadVideo(embedUrl) {
77
76
  return null;
78
77
  }
79
78
  }
79
+
80
+ import puppeteer from 'puppeteer';
81
+
82
+ export async function getMovearnpreOrSmoothpreVideo(embedUrl) {
83
+ const browser = await puppeteer.launch({ headless: true });
84
+ const page = await browser.newPage();
85
+
86
+ await page.setUserAgent(
87
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
88
+ );
89
+
90
+ await page.goto(embedUrl, { waitUntil: 'networkidle2' });
91
+
92
+ await page.waitForFunction('typeof jwplayer !== "undefined"');
93
+
94
+ const videoUrl = await page.evaluate(() => {
95
+ const player = jwplayer();
96
+ const sources = player?.getPlaylist()?.[0]?.sources;
97
+ return sources?.[0]?.file || null;
98
+ });
99
+
100
+ await browser.close();
101
+ const finalUrl = embedUrl.split("/").slice(0, 3).join("/") + videoUrl
102
+ return finalUrl;
103
+ }