@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 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
@@ -0,0 +1,2 @@
1
+ export { scrapeVidsrc, clearCache } from './scraper.js';
2
+ export type { ScrapeOptions, ScrapeResult, CacheEntry } from './types.js';
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;
@@ -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
+ }
@@ -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
+ }