@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 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: 30000,
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 | 60000 | Page load timeout (ms) |
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
- - Playwright Firefox
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: 60000,
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}&autoplay=1&ds_lang=en`;
25
+ return `${base}/tv?tmdb=${tmdbId}&season=${season}&episode=${episode}`;
26
26
  }
27
- return `${base}/movie?tmdb=${tmdbId}&autoplay=1&ds_lang=en`;
27
+ return `${base}/movie?tmdb=${tmdbId}`;
28
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);
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
- 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 { }
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
- 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);
74
+ let hlsUrl = null;
75
+ let subtitles = [];
65
76
  try {
66
77
  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
- }
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
- await page.waitForTimeout(3000);
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: responseData.hlsUrl,
104
- subtitles: responseData.subtitles,
105
- success: !!responseData.hlsUrl,
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
@@ -1,6 +1,5 @@
1
1
  export interface ScrapeOptions {
2
2
  timeout?: number;
3
- headless?: boolean;
4
3
  cacheTtl?: number;
5
4
  }
6
5
  export interface ScrapeResult {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@definisi/vidsrc-scraper",
3
- "version": "1.0.0",
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
- "peerDependencies": {
30
- "playwright": "^1.40.0"
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^22.0.0",