-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathdatacenter_scraper.js
More file actions
122 lines (105 loc) · 5.67 KB
/
Copy pathdatacenter_scraper.js
File metadata and controls
122 lines (105 loc) · 5.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
'use strict';
// Fontes que funcionam a partir de IPs de datacenter (Vercel) — só axios,
// sem browser/Puppeteer. Ao contrário do streamimdb.me/Cloudflare Turnstile,
// estas não bloqueiam pedidos server-side.
//
// 1. VixSrc (vixsrc.to) — extrai master m3u8 via token da página embed
// 2. Vidlink (vidlink.pro) — API que devolve playlist directa
//
// Ambas usam TMDB id, por isso convertemos IMDb → TMDB primeiro.
const axios = require('axios');
const { convertImdbToTmdb } = require('./providers');
const TIMEOUT = 10000;
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36';
// ── VixSrc ─────────────────────────────────────────────────────────────────
const VIX_BASE = 'https://vixsrc.to';
const VIX_HEADERS = {
'User-Agent': UA,
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'en-US,en;q=0.9',
'Referer': VIX_BASE,
'Origin': VIX_BASE,
};
async function tryVixsrc(tmdbId, type, season, episode) {
const apiUrl = type === 'series'
? `${VIX_BASE}/api/tv/${tmdbId}/${season}/${episode}`
: `${VIX_BASE}/api/movie/${tmdbId}`;
try {
// Passo 1: API → { src: "/embed/..." }
const api = await axios.get(apiUrl, { headers: VIX_HEADERS, timeout: TIMEOUT, validateStatus: s => s < 500 });
if (api.status !== 200 || !api.data || !api.data.src) {
console.log('[dc:vixsrc] sem src na API');
return null;
}
// Passo 2: página embed (HTML)
const embed = await axios.get(VIX_BASE + api.data.src, {
headers: { ...VIX_HEADERS, Accept: 'text/html,application/xhtml+xml,*/*' },
timeout: TIMEOUT, responseType: 'text', validateStatus: s => s < 500,
});
if (embed.status !== 200) { console.log('[dc:vixsrc] embed HTTP', embed.status); return null; }
const html = typeof embed.data === 'string' ? embed.data : '';
// Passo 3: extrair token, expires, playlist
const token = html.match(/token["']\s*:\s*["']([^"']+)/)?.[1];
const expires = html.match(/expires["']\s*:\s*["']([^"']+)/)?.[1];
const playlist = html.match(/url\s*:\s*["']([^"']+)/)?.[1];
if (!token || !expires || !playlist) { console.log('[dc:vixsrc] token/playlist não encontrados'); return null; }
if (parseInt(expires, 10) * 1000 - 60000 < Date.now()) { console.log('[dc:vixsrc] token expirado'); return null; }
// Passo 4: master URL
const sep = playlist.includes('?') ? '&' : '?';
const masterUrl = `${playlist}${sep}token=${token}&expires=${expires}&h=1`;
console.log(`[dc:vixsrc] ✓ master: ${masterUrl.substring(0, 70)}...`);
// proxyable:false — entregamos o URL directo da CDN. O nosso proxy corre
// num IP de datacenter do Vercel, que a CDN bloqueia com 403 (mesmo anti-bot
// que bloqueia a API). O cliente Stremio corre no IP residencial do
// utilizador, que tem mais probabilidade de passar.
return [{ url: masterUrl, quality: 'Auto', proxyable: false, referer: apiUrl }];
} catch (e) {
console.log(`[dc:vixsrc] erro: ${e.message}`);
return null;
}
}
// ── Vidlink ────────────────────────────────────────────────────────────────
const VIDLINK_REF = 'https://vidlink.pro';
async function tryVidlink(tmdbId, type, season, episode) {
try {
// Passo 1: encriptar o TMDB id
const enc = await axios.get(
`https://enc-dec.app/api/enc-vidlink?text=${encodeURIComponent(String(tmdbId))}`,
{ timeout: 8000, validateStatus: s => s < 500 },
);
const encoded = enc.data && enc.data.result;
if (!encoded) { console.log('[dc:vidlink] encriptação sem resultado'); return null; }
// Passo 2: API de stream
const apiUrl = type === 'series'
? `${VIDLINK_REF}/api/b/tv/${encoded}/${season}/${episode}?multiLang=0`
: `${VIDLINK_REF}/api/b/movie/${encoded}?multiLang=0`;
const res = await axios.get(apiUrl, {
headers: { 'User-Agent': UA, Referer: VIDLINK_REF },
timeout: 8000, validateStatus: s => s < 500,
});
const playlist = res.data && res.data.stream && res.data.stream.playlist;
if (!playlist) { console.log('[dc:vidlink] sem playlist'); return null; }
console.log(`[dc:vidlink] ✓ playlist: ${playlist.substring(0, 70)}...`);
// proxyable:false — ver nota em tryVixsrc: a CDN bloqueia o IP de
// datacenter do nosso proxy; o cliente (IP residencial) tenta directo.
return [{ url: playlist, quality: 'Auto', proxyable: false, referer: VIDLINK_REF + '/' }];
} catch (e) {
console.log(`[dc:vidlink] erro: ${e.message}`);
return null;
}
}
// ── Orquestração ─────────────────────────────────────────────────────────────
async function fetchFromDatacenterSources(imdbId, type, season, episode) {
const tmdb = await convertImdbToTmdb(imdbId);
if (!tmdb) { console.log('[dc] falha ao converter IMDb → TMDB (TMDB_API_KEY?)'); return null; }
const tmdbId = tmdb.id;
console.log(`[dc] IMDb ${imdbId} → TMDB ${tmdbId}`);
// VixSrc devolve 403 logo na API a partir de IPs de datacenter (confirmado
// via /diag/sources) — tenta primeiro o Vidlink, que continua a funcionar.
const vid = await tryVidlink(tmdbId, type, season, episode);
if (vid && vid.length) return vid;
const vix = await tryVixsrc(tmdbId, type, season, episode);
if (vix && vix.length) return vix;
return null;
}
module.exports = { fetchFromDatacenterSources };