better-ani-scraped 1.5.1 → 1.6.0
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 +9 -4
- package/examples/animesama/example_usage_searchAnime.js +1 -1
- package/examples/utility-functions/example_usage_getVideoUrlFromEmbed.js +15 -4
- package/package.json +2 -2
- package/scrapers/animesama.js +55 -49
- package/utils/dispatcher.js +6 -0
- package/utils/extractVideoUrl.js +65 -58
package/DOCUMENTATION.md
CHANGED
|
@@ -27,7 +27,7 @@ const crunchyroll = new AnimeScraper('crunchyroll') //for Crunchyroll
|
|
|
27
27
|
|
|
28
28
|
## `AnimeScraper("animesama")` methods
|
|
29
29
|
|
|
30
|
-
- [searchAnime](#animesamasearchanimequery-limit--10-wantedlanguages--vostfr-vf-vastfr-wantedtypes--anime-film)
|
|
30
|
+
- [searchAnime](#animesamasearchanimequery-limit--10-wantedlanguages--vostfr-vf-vastfr-wantedtypes--anime-film-page--null)
|
|
31
31
|
- [getSeasons](#animesamagetseasonsanimeurl-language--vostfr)
|
|
32
32
|
- [getEpisodeTitles](#animesamagetepisodetitlesseasonurl-customchromiumpath)
|
|
33
33
|
- [getEmbed](#animesamagetembedseasonurl-hostpriority--sibnet-vidmoly)
|
|
@@ -37,7 +37,7 @@ const crunchyroll = new AnimeScraper('crunchyroll') //for Crunchyroll
|
|
|
37
37
|
- [getLatestEpisodes](#animesamagetlatestepisodeslanguagefilter--null)
|
|
38
38
|
- [getRandomAnime](#animesamagetrandomanimewantedlanguages--vostfr-vf-vastfr-wantedtypes--anime-film-maxattempts--null-attempt--0)
|
|
39
39
|
|
|
40
|
-
### `animesama.searchAnime(query, limit = 10, wantedLanguages = ["vostfr", "vf", "vastfr"], wantedTypes = ["Anime", "Film"])`
|
|
40
|
+
### `animesama.searchAnime(query, limit = 10, wantedLanguages = ["vostfr", "vf", "vastfr"], wantedTypes = ["Anime", "Film"], page = null)`
|
|
41
41
|
Searches for anime titles that match the given query.
|
|
42
42
|
|
|
43
43
|
- **Parameters:**
|
|
@@ -45,6 +45,7 @@ Searches for anime titles that match the given query.
|
|
|
45
45
|
- `limit` *(number)*: Maximum number of results to return (default: 10).
|
|
46
46
|
- `wantedLanguages` *(string[])*: Array of wanted languages.
|
|
47
47
|
- `wantedTypes` *(string[])*: Array of wanted types.
|
|
48
|
+
- `page` *(number)*: The catalog page number.
|
|
48
49
|
- **Returns:**
|
|
49
50
|
An array of anime objects:
|
|
50
51
|
```js
|
|
@@ -315,10 +316,14 @@ Extracts information from all episodes of a season of an anime.
|
|
|
315
316
|
Retrieves the video URL of the source's embed.
|
|
316
317
|
|
|
317
318
|
- **Parameters:**
|
|
318
|
-
- `source` *(string)*: The embed source (only "sibnet" available at the moment)
|
|
319
|
+
- `source` *(string)*: The embed source (only "sibnet", "sendvid", "vidmoly" and "oneupload" available at the moment)
|
|
319
320
|
- `embedUrl` *(string)*: The embed url of the given source.
|
|
320
321
|
- **Returns:**
|
|
321
|
-
A video URL as a string
|
|
322
|
+
A video URL as a string :
|
|
323
|
+
- `sibnet`: mp4
|
|
324
|
+
- `sendvid`: mp4
|
|
325
|
+
- `vidmoly`: m3u8
|
|
326
|
+
- `oneupload`: m3u8
|
|
322
327
|
|
|
323
328
|
---
|
|
324
329
|
---
|
|
@@ -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 search = await animesama.searchAnime("
|
|
6
|
+
const search = await animesama.searchAnime("a", 100, ["vostfr", "vf", "vastfr"], ["Anime", "Film"], 2);
|
|
7
7
|
console.log("Search Results:", search);
|
|
8
8
|
};
|
|
9
9
|
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { getVideoUrlFromEmbed } from "../../index.js"; // REPLACE BY "from 'better-ani-scraped';"
|
|
2
2
|
|
|
3
3
|
const main = async () => {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
4
|
+
const embedUrlSibnet = "https://video.sibnet.ru/shell.php?videoid=4291083";
|
|
5
|
+
const embedUrlSendvid = "https://sendvid.com/embed/4vzpcb0q";
|
|
6
|
+
const embedUrlVidmoly = "https://vidmoly.to/embed-vt374ef2joph.html";
|
|
7
|
+
const embedUrlOneupload = "https://oneupload.net/embed-axdrxh1y3p37.html";
|
|
8
|
+
|
|
9
|
+
const videoUrlSibnet = await getVideoUrlFromEmbed("sibnet", embedUrlSibnet)
|
|
10
|
+
console.log("Video URL Sibnet:", videoUrlSibnet);
|
|
8
11
|
|
|
12
|
+
const videoUrlSendvid = await getVideoUrlFromEmbed("sendvid", embedUrlSendvid)
|
|
13
|
+
console.log("Video URL Sendvid:", videoUrlSendvid);
|
|
14
|
+
|
|
15
|
+
const videoUrlVidmoly = await getVideoUrlFromEmbed("vidmoly", embedUrlVidmoly)
|
|
16
|
+
console.log("Video URL Vidmoly:", videoUrlVidmoly);
|
|
17
|
+
|
|
18
|
+
const videoUrlOneupload = await getVideoUrlFromEmbed("oneupload", embedUrlOneupload)
|
|
19
|
+
console.log("Video URL Oneupload:", videoUrlOneupload);
|
|
9
20
|
};
|
|
10
21
|
|
|
11
22
|
main().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-ani-scraped",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Scrape anime data from different sources (only anime-sama.fr for the moment)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"axios": "^1.8.4",
|
|
22
22
|
"cheerio": "^1.0.0",
|
|
23
23
|
"playwright": "^1.52.0",
|
|
24
|
-
"puppeteer": "^24.
|
|
24
|
+
"puppeteer": "^24.8.1",
|
|
25
25
|
"puppeteer-extra": "^3.3.6",
|
|
26
26
|
"puppeteer-extra-plugin-stealth": "^2.11.2"
|
|
27
27
|
},
|
package/scrapers/animesama.js
CHANGED
|
@@ -47,67 +47,73 @@ export async function searchAnime(
|
|
|
47
47
|
query,
|
|
48
48
|
limit = 10,
|
|
49
49
|
wantedLanguages = ["vostfr", "vf", "vastfr"],
|
|
50
|
-
wantedTypes = ["Anime", "Film"]
|
|
50
|
+
wantedTypes = ["Anime", "Film"],
|
|
51
|
+
page = null
|
|
51
52
|
) {
|
|
52
|
-
const url = `${CATALOGUE_URL}/?search=${encodeURIComponent(
|
|
53
|
-
query
|
|
54
|
-
)}`;
|
|
55
53
|
const isWanted = (text, list) =>
|
|
56
54
|
list.some(item => text.toLowerCase().includes(item.toLowerCase()));
|
|
57
|
-
|
|
58
|
-
const $ = cheerio.load(res.data);
|
|
55
|
+
|
|
59
56
|
const results = [];
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
const fetchPage = async (pageNum) => {
|
|
59
|
+
const url =
|
|
60
|
+
pageNum === 1
|
|
61
|
+
? `${CATALOGUE_URL}/?search=${encodeURIComponent(query)}`
|
|
62
|
+
: `${CATALOGUE_URL}/?search=${encodeURIComponent(query)}&page=${pageNum}`;
|
|
63
63
|
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const title = anchor.find("h1").first().text().trim();
|
|
67
|
-
const altRaw = anchor
|
|
68
|
-
.find("p.text-xs.opacity-40.italic")
|
|
69
|
-
.first()
|
|
70
|
-
.text()
|
|
71
|
-
.trim();
|
|
72
|
-
const cover = anchor.find("img").first().attr("src");
|
|
64
|
+
const res = await axios.get(url, { headers: getHeaders(CATALOGUE_URL) });
|
|
65
|
+
const $ = cheerio.load(res.data);
|
|
73
66
|
|
|
74
|
-
const
|
|
75
|
-
isWanted($(p).text(), wantedTypes)
|
|
76
|
-
).first().text();
|
|
67
|
+
const containers = $("a.flex.divide-x");
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
).first().text();
|
|
69
|
+
containers.each((_, el) => {
|
|
70
|
+
if (results.length >= limit) return false;
|
|
81
71
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
: [];
|
|
72
|
+
const anchor = $(el);
|
|
73
|
+
const link = anchor.attr("href");
|
|
74
|
+
const title = anchor.find("h1").first().text().trim();
|
|
75
|
+
const altRaw = anchor.find("p.text-xs.opacity-40.italic").first().text().trim();
|
|
76
|
+
const cover = anchor.find("img").first().attr("src");
|
|
88
77
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
.first()
|
|
92
|
-
.text()
|
|
93
|
-
.trim();
|
|
94
|
-
const genres = genreRaw
|
|
95
|
-
? genreRaw
|
|
96
|
-
.split(",")
|
|
97
|
-
.map((g) => g.trim())
|
|
98
|
-
.filter(Boolean)
|
|
99
|
-
: [];
|
|
78
|
+
const tagText = anchor.find("p").filter((_, p) =>
|
|
79
|
+
isWanted($(p).text(), wantedTypes)
|
|
80
|
+
).first().text();
|
|
100
81
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
82
|
+
const languageText = anchor.find("p").filter((_, p) =>
|
|
83
|
+
isWanted($(p).text(), wantedLanguages)
|
|
84
|
+
).first().text();
|
|
85
|
+
|
|
86
|
+
const altTitles = altRaw
|
|
87
|
+
? altRaw.split(",").map((t) => t.trim()).filter(Boolean)
|
|
88
|
+
: [];
|
|
89
|
+
|
|
90
|
+
const genreRaw = anchor.find("p.text-xs.font-medium.text-gray-300").first().text().trim();
|
|
91
|
+
const genres = genreRaw
|
|
92
|
+
? genreRaw.split(",").map((g) => g.trim()).filter(Boolean)
|
|
93
|
+
: [];
|
|
94
|
+
|
|
95
|
+
if (title && link && tagText && languageText) {
|
|
96
|
+
results.push({
|
|
97
|
+
title,
|
|
98
|
+
altTitles,
|
|
99
|
+
genres,
|
|
100
|
+
url: link.startsWith("http") ? link : `${CATALOGUE_URL}${link}`,
|
|
101
|
+
cover,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return containers.length > 0;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (page) {
|
|
110
|
+
await fetchPage(page);
|
|
111
|
+
} else {
|
|
112
|
+
let currentPage = 1;
|
|
113
|
+
while (await fetchPage(currentPage++) && results.length < limit) {
|
|
114
|
+
await new Promise((res) => setTimeout(res, 300));
|
|
109
115
|
}
|
|
110
|
-
}
|
|
116
|
+
}
|
|
111
117
|
|
|
112
118
|
return results;
|
|
113
119
|
}
|
package/utils/dispatcher.js
CHANGED
|
@@ -4,6 +4,12 @@ export async function getVideoUrlFromEmbed(source, embedUrl) {
|
|
|
4
4
|
if (source === "sibnet") {
|
|
5
5
|
return await extractor.getSibnetVideo(embedUrl);
|
|
6
6
|
}
|
|
7
|
+
if (source === "sendvid") {
|
|
8
|
+
return await extractor.getSendvidVideo(embedUrl);
|
|
9
|
+
}
|
|
10
|
+
if (source === "vidmoly" || source === "oneupload" ) {
|
|
11
|
+
return await extractor.getVidmolyOrOneuploadVideo(embedUrl);
|
|
12
|
+
}
|
|
7
13
|
|
|
8
14
|
throw new Error(`Unsupported embed source: ${source}`);
|
|
9
15
|
}
|
package/utils/extractVideoUrl.js
CHANGED
|
@@ -1,67 +1,74 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import * as cheerio from "cheerio";
|
|
3
3
|
|
|
4
|
-
export async function getSibnetVideo(embedUrl) {
|
|
5
|
-
let intermediaries = [];
|
|
6
|
-
let realUrl = "";
|
|
7
|
-
|
|
8
|
-
const getIntermediary = async () => {
|
|
9
|
-
try {
|
|
10
|
-
const { data } = await axios.get(embedUrl, { headers: getHeaders(embedUrl) });
|
|
11
|
-
const $ = cheerio.load(data);
|
|
12
|
-
const script = $("script")
|
|
13
|
-
.toArray()
|
|
14
|
-
.map((s) => $(s).html())
|
|
15
|
-
.find((s) => s.includes("player.src"));
|
|
16
|
-
const match = script?.match(/player\.src\(\[{src:\s*["']([^"']+)["']/);
|
|
17
|
-
if (match) intermediaries.push(`https://video.sibnet.ru${match[1]}`);
|
|
18
|
-
return !!match;
|
|
19
|
-
} catch {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
4
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
5
|
+
const getHeaders = (referer) => ({
|
|
6
|
+
Accept: "*/*",
|
|
7
|
+
Referer: referer,
|
|
8
|
+
"User-Agent":
|
|
9
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
10
|
+
});
|
|
11
|
+
export async function getSibnetVideo(embedUrl) {
|
|
12
|
+
try {
|
|
13
|
+
const { data } = await axios.get(embedUrl, {
|
|
14
|
+
headers: getHeaders(embedUrl),
|
|
15
|
+
});
|
|
16
|
+
const $ = cheerio.load(data);
|
|
17
|
+
const script = $("script")
|
|
18
|
+
.toArray()
|
|
19
|
+
.map((s) => $(s).html())
|
|
20
|
+
.find((s) => s.includes("player.src"));
|
|
21
|
+
const match = script?.match(/player\.src\(\[{src:\s*["']([^"']+)["']/);
|
|
22
|
+
if (!match || !match[1]) return null;
|
|
23
|
+
const intermediateUrl = `https://video.sibnet.ru${match[1]}`;
|
|
24
|
+
const res1 = await axios.get(intermediateUrl, {
|
|
25
|
+
headers: getHeaders(embedUrl),
|
|
26
|
+
maxRedirects: 0,
|
|
27
|
+
validateStatus: (s) => s >= 200 && s < 400,
|
|
28
|
+
});
|
|
29
|
+
const redirectUrl = res1.headers.location;
|
|
30
|
+
if (!redirectUrl) return null;
|
|
31
|
+
const finalUrl = redirectUrl.startsWith("http")
|
|
32
|
+
? redirectUrl
|
|
33
|
+
: `https:${redirectUrl}`;
|
|
34
|
+
return finalUrl;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error("Erreur getSibnetVideo:", err.message);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
export async function getSendvidVideo(embedUrl) {
|
|
42
|
+
try {
|
|
43
|
+
const { data } = await axios.get(embedUrl, {
|
|
44
|
+
headers: getHeaders(embedUrl),
|
|
45
|
+
});
|
|
46
|
+
const $ = cheerio.load(data);
|
|
47
|
+
const sourceTag = $("video source[type='video/mp4']").attr("src");
|
|
48
|
+
return sourceTag || null;
|
|
49
|
+
} catch (err) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
35
53
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
export async function getVidmolyOrOneuploadVideo(embedUrl) {
|
|
55
|
+
try {
|
|
56
|
+
const { data } = await axios.get(embedUrl, {
|
|
57
|
+
headers: getHeaders(embedUrl),
|
|
58
|
+
});
|
|
59
|
+
const $ = cheerio.load(data);
|
|
60
|
+
const scripts = $("script");
|
|
41
61
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return !!realUrl;
|
|
49
|
-
} catch {
|
|
50
|
-
return false;
|
|
62
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
63
|
+
const content = $(scripts[i]).html();
|
|
64
|
+
const match = content && content.match(/file\s*:\s*"(https[^"]+\.m3u8[^"]*)"/);
|
|
65
|
+
if (match && match[1]) {
|
|
66
|
+
return match[1];
|
|
67
|
+
}
|
|
51
68
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Accept: "*/*",
|
|
58
|
-
Referer: referer,
|
|
59
|
-
Range: "bytes=0-",
|
|
60
|
-
"User-Agent":
|
|
61
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
return (await getIntermediary()) && (await followRedirection())
|
|
65
|
-
? realUrl
|
|
66
|
-
: null;
|
|
69
|
+
return null;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error("Erreur getVidmolyVideo:", err.message);
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
67
74
|
}
|