@definisi/vidsrc-scraper 1.0.0 → 1.1.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/README.md +14 -8
- package/dist/scraper.js +66 -62
- package/dist/types.d.ts +0 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# @definisi/vidsrc-scraper
|
|
2
2
|
|
|
3
|
-
Extract HLS streaming URLs from VidSrc using TMDB ID.
|
|
3
|
+
Extract HLS streaming URLs from VidSrc using TMDB ID. Zero dependencies, works on any platform.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install @definisi/vidsrc-scraper
|
|
9
|
-
npx playwright install firefox
|
|
10
9
|
```
|
|
11
10
|
|
|
12
11
|
## Usage
|
|
@@ -22,8 +21,7 @@ const tv = await scrapeVidsrc('1396', 'tv', '1', '1');
|
|
|
22
21
|
|
|
23
22
|
// With options
|
|
24
23
|
const result = await scrapeVidsrc('27205', 'movie', null, null, {
|
|
25
|
-
timeout:
|
|
26
|
-
headless: true,
|
|
24
|
+
timeout: 15000,
|
|
27
25
|
cacheTtl: 600,
|
|
28
26
|
});
|
|
29
27
|
```
|
|
@@ -44,8 +42,7 @@ const result = await scrapeVidsrc('27205', 'movie', null, null, {
|
|
|
44
42
|
|
|
45
43
|
| Option | Type | Default | Description |
|
|
46
44
|
|--------|------|---------|-------------|
|
|
47
|
-
| timeout | number |
|
|
48
|
-
| headless | boolean | true | Run browser headless |
|
|
45
|
+
| timeout | number | 30000 | Request timeout (ms) |
|
|
49
46
|
| cacheTtl | number | 900 | Cache TTL (seconds) |
|
|
50
47
|
|
|
51
48
|
### ScrapeResult
|
|
@@ -69,5 +66,14 @@ Clear the in-memory cache.
|
|
|
69
66
|
|
|
70
67
|
## Requirements
|
|
71
68
|
|
|
72
|
-
- Node.js 18+
|
|
73
|
-
|
|
69
|
+
- Node.js 18+ (uses native fetch)
|
|
70
|
+
|
|
71
|
+
## Playback
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# VLC
|
|
75
|
+
vlc "HLS_URL" --http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0"
|
|
76
|
+
|
|
77
|
+
# ffplay
|
|
78
|
+
ffplay -user_agent "Mozilla/5.0" "HLS_URL"
|
|
79
|
+
```
|
package/dist/scraper.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { firefox } from 'playwright';
|
|
2
1
|
const DEFAULT_OPTIONS = {
|
|
3
|
-
timeout:
|
|
4
|
-
headless: true,
|
|
2
|
+
timeout: 30000,
|
|
5
3
|
cacheTtl: 900,
|
|
6
4
|
};
|
|
5
|
+
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0';
|
|
6
|
+
const CDN_DOMAINS = ['cloudnestra.com', 'quibblezoomfable.com'];
|
|
7
7
|
const cache = new Map();
|
|
8
8
|
function getCacheKey(tmdbId, type, season, episode) {
|
|
9
9
|
return `${type}-${tmdbId}-${season || ''}-${episode || ''}`;
|
|
@@ -22,87 +22,91 @@ function setCache(key, data) {
|
|
|
22
22
|
function buildEmbedUrl(tmdbId, type, season, episode) {
|
|
23
23
|
const base = 'https://vidsrcme.vidsrc.icu/embed';
|
|
24
24
|
if (type === 'tv') {
|
|
25
|
-
return `${base}/tv?tmdb=${tmdbId}&season=${season}&episode=${episode}
|
|
25
|
+
return `${base}/tv?tmdb=${tmdbId}&season=${season}&episode=${episode}`;
|
|
26
26
|
}
|
|
27
|
-
return `${base}/movie?tmdb=${tmdbId}
|
|
27
|
+
return `${base}/movie?tmdb=${tmdbId}`;
|
|
28
28
|
}
|
|
29
|
-
async function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
async function fetchWithHeaders(url, referer) {
|
|
30
|
+
const headers = {
|
|
31
|
+
'User-Agent': UA,
|
|
32
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
33
|
+
'Accept-Language': 'en-US,en;q=0.5',
|
|
34
|
+
};
|
|
35
|
+
if (referer) {
|
|
36
|
+
headers['Referer'] = referer;
|
|
37
|
+
}
|
|
38
|
+
const res = await fetch(url, { headers });
|
|
39
|
+
if (!res.ok)
|
|
40
|
+
throw new Error(`HTTP ${res.status}`);
|
|
41
|
+
return res.text();
|
|
42
|
+
}
|
|
43
|
+
async function resolveM3u8Domain(templateUrl) {
|
|
44
|
+
for (const domain of CDN_DOMAINS) {
|
|
45
|
+
const testUrl = templateUrl.replace(/\{v\d+\}/, domain);
|
|
46
|
+
try {
|
|
47
|
+
const res = await fetch(testUrl, {
|
|
48
|
+
method: 'HEAD',
|
|
49
|
+
headers: { 'User-Agent': UA },
|
|
50
|
+
});
|
|
51
|
+
if (res.ok)
|
|
52
|
+
return testUrl;
|
|
37
53
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
54
|
+
catch { }
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
function extractSubtitles(html) {
|
|
59
|
+
const subtitles = [];
|
|
60
|
+
const subMatches = html.matchAll(/https?:\/\/[^"'\s]+\.(?:vtt|srt)[^"'\s]*/g);
|
|
61
|
+
for (const match of subMatches) {
|
|
62
|
+
if (!subtitles.includes(match[0])) {
|
|
63
|
+
subtitles.push(match[0]);
|
|
47
64
|
}
|
|
48
|
-
}
|
|
65
|
+
}
|
|
66
|
+
return subtitles;
|
|
49
67
|
}
|
|
50
68
|
export async function scrapeVidsrc(tmdbId, type = 'movie', season = null, episode = null, options = {}) {
|
|
51
69
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
52
70
|
const cacheKey = getCacheKey(tmdbId, type, season, episode);
|
|
53
71
|
const cached = getFromCache(cacheKey, opts.cacheTtl);
|
|
54
|
-
if (cached)
|
|
72
|
+
if (cached)
|
|
55
73
|
return cached;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const context = await browser.newContext({
|
|
59
|
-
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
|
|
60
|
-
viewport: { width: 1920, height: 1080 },
|
|
61
|
-
});
|
|
62
|
-
const page = await context.newPage();
|
|
63
|
-
const responseData = { hlsUrl: null, subtitles: [] };
|
|
64
|
-
await setupResponseHandler(context, responseData);
|
|
74
|
+
let hlsUrl = null;
|
|
75
|
+
let subtitles = [];
|
|
65
76
|
try {
|
|
66
77
|
const embedUrl = buildEmbedUrl(tmdbId, type, season, episode);
|
|
67
|
-
await
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
catch { }
|
|
86
|
-
}
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
78
|
+
const embedHtml = await fetchWithHeaders(embedUrl);
|
|
79
|
+
const rcpMatch = embedHtml.match(/src="([^"]*\/rcp\/[^"]*)"/);
|
|
80
|
+
if (!rcpMatch)
|
|
81
|
+
throw new Error('RCP iframe not found');
|
|
82
|
+
let rcpUrl = rcpMatch[1];
|
|
83
|
+
if (rcpUrl.startsWith('//'))
|
|
84
|
+
rcpUrl = 'https:' + rcpUrl;
|
|
85
|
+
const rcpHtml = await fetchWithHeaders(rcpUrl, embedUrl);
|
|
86
|
+
const prorcpMatch = rcpHtml.match(/\/prorcp\/([a-zA-Z0-9=]+)/);
|
|
87
|
+
if (!prorcpMatch)
|
|
88
|
+
throw new Error('Prorcp hash not found');
|
|
89
|
+
const prorcpUrl = `https://cloudnestra.com/prorcp/${prorcpMatch[1]}`;
|
|
90
|
+
const prorcpHtml = await fetchWithHeaders(prorcpUrl, rcpUrl);
|
|
91
|
+
const fileMatch = prorcpHtml.match(/file:\s*["']([^"']+)["']/);
|
|
92
|
+
if (fileMatch) {
|
|
93
|
+
const fileUrls = fileMatch[1].split(' or ');
|
|
94
|
+
const templateUrl = fileUrls[0].trim();
|
|
95
|
+
hlsUrl = await resolveM3u8Domain(templateUrl);
|
|
89
96
|
}
|
|
90
|
-
|
|
97
|
+
subtitles = extractSubtitles(prorcpHtml);
|
|
91
98
|
}
|
|
92
99
|
catch (error) {
|
|
93
100
|
console.error(`[ERROR] ${error.message}`);
|
|
94
101
|
}
|
|
95
|
-
finally {
|
|
96
|
-
await browser.close();
|
|
97
|
-
}
|
|
98
102
|
const result = {
|
|
99
103
|
tmdbId,
|
|
100
104
|
type,
|
|
101
105
|
season,
|
|
102
106
|
episode,
|
|
103
|
-
hlsUrl
|
|
104
|
-
subtitles
|
|
105
|
-
success: !!
|
|
107
|
+
hlsUrl,
|
|
108
|
+
subtitles,
|
|
109
|
+
success: !!hlsUrl,
|
|
106
110
|
timestamp: new Date().toISOString(),
|
|
107
111
|
};
|
|
108
112
|
if (result.success) {
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@definisi/vidsrc-scraper",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Extract HLS streaming URLs from VidSrc",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Extract HLS streaming URLs from VidSrc (no browser required)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"tmdb"
|
|
27
27
|
],
|
|
28
28
|
"license": "MIT",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^22.0.0",
|