@definisi/vidsrc-scraper 1.0.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 +73 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/scraper.d.ts +3 -0
- package/dist/scraper.js +115 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @definisi/vidsrc-scraper
|
|
2
|
+
|
|
3
|
+
Extract HLS streaming URLs from VidSrc using TMDB ID.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @definisi/vidsrc-scraper
|
|
9
|
+
npx playwright install firefox
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { scrapeVidsrc } from '@definisi/vidsrc-scraper';
|
|
16
|
+
|
|
17
|
+
// Movie
|
|
18
|
+
const movie = await scrapeVidsrc('27205');
|
|
19
|
+
|
|
20
|
+
// TV Show
|
|
21
|
+
const tv = await scrapeVidsrc('1396', 'tv', '1', '1');
|
|
22
|
+
|
|
23
|
+
// With options
|
|
24
|
+
const result = await scrapeVidsrc('27205', 'movie', null, null, {
|
|
25
|
+
timeout: 30000,
|
|
26
|
+
headless: true,
|
|
27
|
+
cacheTtl: 600,
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API
|
|
32
|
+
|
|
33
|
+
### scrapeVidsrc(tmdbId, type?, season?, episode?, options?)
|
|
34
|
+
|
|
35
|
+
| Parameter | Type | Default | Description |
|
|
36
|
+
|-----------|------|---------|-------------|
|
|
37
|
+
| tmdbId | string | - | TMDB ID |
|
|
38
|
+
| type | 'movie' \| 'tv' | 'movie' | Content type |
|
|
39
|
+
| season | string \| null | null | Season number (TV only) |
|
|
40
|
+
| episode | string \| null | null | Episode number (TV only) |
|
|
41
|
+
| options | ScrapeOptions | {} | Optional config |
|
|
42
|
+
|
|
43
|
+
### ScrapeOptions
|
|
44
|
+
|
|
45
|
+
| Option | Type | Default | Description |
|
|
46
|
+
|--------|------|---------|-------------|
|
|
47
|
+
| timeout | number | 60000 | Page load timeout (ms) |
|
|
48
|
+
| headless | boolean | true | Run browser headless |
|
|
49
|
+
| cacheTtl | number | 900 | Cache TTL (seconds) |
|
|
50
|
+
|
|
51
|
+
### ScrapeResult
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
{
|
|
55
|
+
tmdbId: string;
|
|
56
|
+
type: 'movie' | 'tv';
|
|
57
|
+
season: string | null;
|
|
58
|
+
episode: string | null;
|
|
59
|
+
hlsUrl: string | null;
|
|
60
|
+
subtitles: string[];
|
|
61
|
+
success: boolean;
|
|
62
|
+
timestamp: string;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### clearCache()
|
|
67
|
+
|
|
68
|
+
Clear the in-memory cache.
|
|
69
|
+
|
|
70
|
+
## Requirements
|
|
71
|
+
|
|
72
|
+
- Node.js 18+
|
|
73
|
+
- Playwright Firefox
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { scrapeVidsrc, clearCache } from './scraper.js';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ScrapeOptions, ScrapeResult } from './types.js';
|
|
2
|
+
export declare function scrapeVidsrc(tmdbId: string, type?: 'movie' | 'tv', season?: string | null, episode?: string | null, options?: ScrapeOptions): Promise<ScrapeResult>;
|
|
3
|
+
export declare function clearCache(): void;
|
package/dist/scraper.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { firefox } from 'playwright';
|
|
2
|
+
const DEFAULT_OPTIONS = {
|
|
3
|
+
timeout: 60000,
|
|
4
|
+
headless: true,
|
|
5
|
+
cacheTtl: 900,
|
|
6
|
+
};
|
|
7
|
+
const cache = new Map();
|
|
8
|
+
function getCacheKey(tmdbId, type, season, episode) {
|
|
9
|
+
return `${type}-${tmdbId}-${season || ''}-${episode || ''}`;
|
|
10
|
+
}
|
|
11
|
+
function getFromCache(key, ttl) {
|
|
12
|
+
const cached = cache.get(key);
|
|
13
|
+
if (cached && Date.now() - cached.timestamp < ttl * 1000) {
|
|
14
|
+
return cached.data;
|
|
15
|
+
}
|
|
16
|
+
cache.delete(key);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function setCache(key, data) {
|
|
20
|
+
cache.set(key, { data, timestamp: Date.now() });
|
|
21
|
+
}
|
|
22
|
+
function buildEmbedUrl(tmdbId, type, season, episode) {
|
|
23
|
+
const base = 'https://vidsrcme.vidsrc.icu/embed';
|
|
24
|
+
if (type === 'tv') {
|
|
25
|
+
return `${base}/tv?tmdb=${tmdbId}&season=${season}&episode=${episode}&autoplay=1&ds_lang=en`;
|
|
26
|
+
}
|
|
27
|
+
return `${base}/movie?tmdb=${tmdbId}&autoplay=1&ds_lang=en`;
|
|
28
|
+
}
|
|
29
|
+
async function setupResponseHandler(context, result) {
|
|
30
|
+
context.on('response', async (response) => {
|
|
31
|
+
const url = response.url();
|
|
32
|
+
if (url.includes('.m3u8') && !result.hlsUrl) {
|
|
33
|
+
result.hlsUrl = url;
|
|
34
|
+
}
|
|
35
|
+
if (url.includes('.vtt') || url.includes('.srt')) {
|
|
36
|
+
result.subtitles.push(url);
|
|
37
|
+
}
|
|
38
|
+
if (url.includes('/prorcp/')) {
|
|
39
|
+
try {
|
|
40
|
+
const body = await response.text();
|
|
41
|
+
const m3u8Match = body.match(/https?:\/\/[^"'\s\\]+\.m3u8[^"'\s\\]*/);
|
|
42
|
+
if (m3u8Match && !result.hlsUrl) {
|
|
43
|
+
result.hlsUrl = m3u8Match[0].replace(/\\/g, '');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export async function scrapeVidsrc(tmdbId, type = 'movie', season = null, episode = null, options = {}) {
|
|
51
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
52
|
+
const cacheKey = getCacheKey(tmdbId, type, season, episode);
|
|
53
|
+
const cached = getFromCache(cacheKey, opts.cacheTtl);
|
|
54
|
+
if (cached) {
|
|
55
|
+
return cached;
|
|
56
|
+
}
|
|
57
|
+
const browser = await firefox.launch({ headless: opts.headless });
|
|
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);
|
|
65
|
+
try {
|
|
66
|
+
const embedUrl = buildEmbedUrl(tmdbId, type, season, episode);
|
|
67
|
+
await page.goto(embedUrl, { waitUntil: 'networkidle', timeout: opts.timeout });
|
|
68
|
+
await page.waitForTimeout(3000);
|
|
69
|
+
for (const frame of page.frames()) {
|
|
70
|
+
const frameUrl = frame.url();
|
|
71
|
+
if (frameUrl.includes('cloudnestra.com/rcp/') || frameUrl.includes('/rcp/')) {
|
|
72
|
+
try {
|
|
73
|
+
await frame.click('#pl_but, #pl_but_background, .play-btn', { timeout: 5000 });
|
|
74
|
+
await page.waitForTimeout(5000);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
try {
|
|
78
|
+
await frame.evaluate(() => {
|
|
79
|
+
if (typeof window.loadIframe === 'function') {
|
|
80
|
+
window.loadIframe(1);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
await page.waitForTimeout(5000);
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
await page.waitForTimeout(3000);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error(`[ERROR] ${error.message}`);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await browser.close();
|
|
97
|
+
}
|
|
98
|
+
const result = {
|
|
99
|
+
tmdbId,
|
|
100
|
+
type,
|
|
101
|
+
season,
|
|
102
|
+
episode,
|
|
103
|
+
hlsUrl: responseData.hlsUrl,
|
|
104
|
+
subtitles: responseData.subtitles,
|
|
105
|
+
success: !!responseData.hlsUrl,
|
|
106
|
+
timestamp: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
if (result.success) {
|
|
109
|
+
setCache(cacheKey, result);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
export function clearCache() {
|
|
114
|
+
cache.clear();
|
|
115
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ScrapeOptions {
|
|
2
|
+
timeout?: number;
|
|
3
|
+
headless?: boolean;
|
|
4
|
+
cacheTtl?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface ScrapeResult {
|
|
7
|
+
tmdbId: string;
|
|
8
|
+
type: 'movie' | 'tv';
|
|
9
|
+
season: string | null;
|
|
10
|
+
episode: string | null;
|
|
11
|
+
hlsUrl: string | null;
|
|
12
|
+
subtitles: string[];
|
|
13
|
+
success: boolean;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
}
|
|
16
|
+
export interface CacheEntry {
|
|
17
|
+
data: ScrapeResult;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@definisi/vidsrc-scraper",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Extract HLS streaming URLs from VidSrc",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"vidsrc",
|
|
23
|
+
"scraper",
|
|
24
|
+
"hls",
|
|
25
|
+
"streaming",
|
|
26
|
+
"tmdb"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"playwright": "^1.40.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.0.0",
|
|
34
|
+
"typescript": "^5.5.0"
|
|
35
|
+
}
|
|
36
|
+
}
|