@vyla-entertainment/sdk 1.10.0 → 1.12.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.
@@ -4,7 +4,7 @@ const API_BASE = 'https://api.meowtv.ru';
4
4
  const REFERER = 'https://meowtv.ru/';
5
5
  const ENC_DEC_API = 'https://enc-dec.app/api/dec-meowtv';
6
6
 
7
- const HEADERS = {
7
+ const BASE_HEADERS = {
8
8
  'User-Agent': USER_AGENT,
9
9
  'Accept': 'application/json',
10
10
  'Referer': REFERER,
@@ -13,13 +13,46 @@ const HEADERS = {
13
13
  };
14
14
 
15
15
  const SERVERS = [
16
- 'hindiv3'
16
+ 'dcloud', 'acme', 'tcloud', 'ipcloud', 'turkce', 'hindiv3'
17
17
  ];
18
18
 
19
19
  const TIMEOUT_MS = 15000;
20
20
 
21
21
  export const VERIFY_HEADERS = { 'User-Agent': USER_AGENT, 'Referer': REFERER, 'Origin': 'https://meowtv.ru' };
22
22
 
23
+ let cachedTicket = null;
24
+ let ticketExpiry = 0;
25
+
26
+ /**
27
+ * Fetches a stream ticket or returns a cached valid ticket.
28
+ */
29
+ async function getTicket(forceRefresh = false) {
30
+ const now = Date.now();
31
+ if (!forceRefresh && cachedTicket && now < ticketExpiry - 10000) {
32
+ return cachedTicket;
33
+ }
34
+
35
+ try {
36
+ const res = await fetchJson(`${API_BASE}/streams/ticket`, {
37
+ method: 'POST',
38
+ headers: {
39
+ ...BASE_HEADERS,
40
+ 'Content-Type': 'application/json'
41
+ },
42
+ body: JSON.stringify({})
43
+ });
44
+
45
+ if (res?.ticket) {
46
+ cachedTicket = res.ticket;
47
+ ticketExpiry = typeof res.exp === 'number' ? res.exp : now + 60000;
48
+ return cachedTicket;
49
+ }
50
+ } catch {
51
+ }
52
+
53
+ return null;
54
+ }
55
+
23
56
  async function decryptPayload(payload) {
24
57
  try {
25
58
  const response = await fetchJson(ENC_DEC_API, {
@@ -47,6 +80,12 @@ export async function getStream({ id, s, e, server }) {
47
80
  targets = SERVERS.includes(cleanName) ? [cleanName] : [targets[0]];
48
81
  }
49
82
 
83
+ const ticket = await getTicket();
84
+ const requestHeaders = {
85
+ ...BASE_HEADERS,
86
+ ...(ticket ? { 'x-stream-ticket': ticket } : {})
87
+ };
88
+
50
89
  const results = await Promise.all(targets.map(async srv => {
51
90
  try {
52
91
  return await Promise.race([
@@ -56,7 +95,7 @@ export async function getStream({ id, s, e, server }) {
56
95
  : `/streams/movie/${id}?s=${encodeURIComponent(srv)}`;
57
96
 
58
97
  const payload = await fetchJson(`${API_BASE}${path}`, {
59
- headers: HEADERS
98
+ headers: requestHeaders
60
99
  });
61
100
 
62
101
  if (!payload) return null;
@@ -0,0 +1,117 @@
1
+ import { getUA } from '../utils/helpers.js';
2
+
3
+ const BASE_URL = 'https://movienig.ht';
4
+
5
+ const PRIORITY_SERVERS = [
6
+ { id: 'dallas', label: 'Dallas 4K' },
7
+ { id: 'austin', label: 'Austin' },
8
+ { id: 'helena', label: 'Helena' },
9
+ { id: 'seattle', label: 'Seattle 4K' },
10
+ { id: 'vixsrc-1', label: 'Newport Beach' },
11
+ { id: 'tucson', label: 'Tucson' },
12
+ { id: 'salem', label: 'Salem' },
13
+ ];
14
+
15
+ async function resolveDetails(sdk, id, isTv) {
16
+ const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
17
+ if (!apiKey) return null;
18
+ const type = isTv ? 'tv' : 'movie';
19
+ try {
20
+ const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, {
21
+ signal: AbortSignal.timeout(6000),
22
+ });
23
+ if (!res.ok) return null;
24
+ const data = await res.json();
25
+ const title = isTv ? data.name : data.title;
26
+ const releaseDate = isTv ? data.first_air_date : data.release_date;
27
+ const year = releaseDate ? parseInt(releaseDate.split('-')[0], 10) : null;
28
+ const imdbId = data.imdb_id || data.external_ids?.imdb_id || null;
29
+ return { title, year, imdbId };
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ export async function getStream({ id, s, e, sdk }) {
36
+ const isTv = Boolean(s && e);
37
+ const ua = getUA();
38
+
39
+ const details = await resolveDetails(sdk, id, isTv);
40
+ const title = details?.title || String(id);
41
+ const year = details?.year;
42
+ const imdbId = details?.imdbId;
43
+
44
+ const encTitle = encodeURIComponent(title);
45
+ const yearQuery = year ? `&year=${year}` : '';
46
+ const imdbQuery = imdbId ? `&imdbId=${imdbId}` : '';
47
+
48
+ const seenUrls = new Set();
49
+ const allUrls = [];
50
+
51
+ const defaultHeaders = {
52
+ 'User-Agent': ua,
53
+ Referer: `${BASE_URL}/`,
54
+ Origin: BASE_URL,
55
+ Accept: 'text/event-stream',
56
+ };
57
+
58
+ const tasks = PRIORITY_SERVERS.map(async (server) => {
59
+ try {
60
+ const url = isTv
61
+ ? `${BASE_URL}/api/stream/v1/tv/${id}/${s}/${e}?title=${encTitle}${yearQuery}${imdbQuery}&server=${server.id}&only=1`
62
+ : `${BASE_URL}/api/stream/v1/movie/${id}?title=${encTitle}${yearQuery}${imdbQuery}&server=${server.id}&only=1`;
63
+
64
+ const res = await fetch(url, {
65
+ headers: defaultHeaders,
66
+ signal: AbortSignal.timeout(6000),
67
+ });
68
+
69
+ if (!res.ok) return;
70
+ const bodyStr = await res.text();
71
+
72
+ if (bodyStr.includes('event: done')) {
73
+ const doneIdx = bodyStr.indexOf('event: done');
74
+ const dataIdx = bodyStr.indexOf('data: ', doneIdx);
75
+
76
+ if (dataIdx !== -1) {
77
+ const jsonStart = dataIdx + 6;
78
+ const jsonEnd = bodyStr.indexOf('\n', jsonStart);
79
+ const jsonText = (jsonEnd !== -1 ? bodyStr.substring(jsonStart, jsonEnd) : bodyStr.substring(jsonStart)).trim();
80
+
81
+ const data = JSON.parse(jsonText);
82
+ if (Array.isArray(data?.sources)) {
83
+ for (const src of data.sources) {
84
+ if (!src || typeof src !== 'object') continue;
85
+ const rawUrl = String(src.url || '').trim();
86
+ if (!rawUrl || seenUrls.has(rawUrl)) continue;
87
+
88
+ seenUrls.add(rawUrl);
89
+
90
+ const quality = src.quality ? String(src.quality) : 'Auto';
91
+ const titleQuality = quality !== 'Auto' ? ` (${quality})` : '';
92
+
93
+ allUrls.push({
94
+ url: rawUrl,
95
+ label: `MovieNight ${server.label}${titleQuality}`,
96
+ headers: {
97
+ 'User-Agent': ua,
98
+ Referer: `${BASE_URL}/`,
99
+ },
100
+ });
101
+ }
102
+ }
103
+ }
104
+ }
105
+ } catch { }
106
+ });
107
+
108
+ await Promise.all(tasks);
109
+
110
+ if (allUrls.length === 0) return null;
111
+
112
+ return {
113
+ url: allUrls[0].url,
114
+ allUrls,
115
+ headers: allUrls[0].headers,
116
+ };
117
+ }
@@ -0,0 +1,282 @@
1
+ import { getUA } from '../utils/helpers.js';
2
+
3
+ const API_BASE = 'https://api.wecollege.net';
4
+ const REFERER = 'https://www.movy.bz/';
5
+ const ORIGIN = 'https://www.movy.bz';
6
+ const MAGIC = [109, 118, 109, 49];
7
+
8
+ const SERVERS = [
9
+ { endpoint: 'miami', name: 'Miami', note: 'Original audio (Up to 4K)' },
10
+ { endpoint: 'seattle', name: 'Seattle', note: 'Original audio' },
11
+ { endpoint: 'denver', name: 'Denver', note: 'Original audio' },
12
+ { endpoint: 'chicago', name: 'Chicago', note: 'Original audio' },
13
+ { endpoint: 'dallas', name: 'Dallas', note: 'Original audio' },
14
+ { endpoint: 'atlanta', name: 'Atlanta', note: 'Original audio' },
15
+ { endpoint: 'houston', name: 'Houston', note: 'Original audio' },
16
+ { endpoint: 'austin', name: 'Austin', note: 'Original audio' },
17
+ { endpoint: 'boston', name: 'Boston', note: 'Original audio' },
18
+ { endpoint: 'munich', name: 'Munich', note: 'German audio', extra: 'language=german' },
19
+ { endpoint: 'berlin', name: 'Berlin', note: 'German audio' },
20
+ { endpoint: 'paris', name: 'Paris', note: 'French audio' },
21
+ { endpoint: 'delhi', name: 'Delhi', note: 'Hindi audio' },
22
+ { endpoint: 'cancun', name: 'Cancun', note: 'Spanish audio' },
23
+ ];
24
+
25
+ const seedCache = new Map();
26
+
27
+ function _l(e) {
28
+ let v = e >>> 0;
29
+ v = (v ^ (v >>> 16)) >>> 0;
30
+ v = Math.imul(v, 0x85ebca6b) >>> 0;
31
+ v = (v ^ (v >>> 13)) >>> 0;
32
+ v = Math.imul(v, 0xc2b2ae35) >>> 0;
33
+ return (v ^ (v >>> 16)) >>> 0;
34
+ }
35
+
36
+ function _u(e, t) {
37
+ const shift = t & 31;
38
+ if (shift === 0) return e >>> 0;
39
+ return (((e << shift) >>> 0) | (e >>> (32 - shift))) >>> 0;
40
+ }
41
+
42
+ function _fnv1a(str) {
43
+ let t = 0x811c9dc5 >>> 0;
44
+ for (let i = 0; i < str.length; i++) {
45
+ const code = str.charCodeAt(i);
46
+ t = Math.imul((t ^ code) >>> 0, 0x1000193) >>> 0;
47
+ }
48
+ return _l(t);
49
+ }
50
+
51
+ function _initKeyState(seed, tmdbId) {
52
+ const s = new Array(61).fill(0);
53
+ const isSet = new Array(61).fill(false);
54
+ const idVal = ((tmdbId >>> 0) ^ 0x9e3779b9) >>> 0;
55
+ let r = _l((_fnv1a(seed) ^ _l(idVal)) >>> 0);
56
+
57
+ for (let e = 0; e < 8; e++) {
58
+ const t = r % 61;
59
+ r = _u((r + 0x9e3779b9) >>> 0, 7 + (7 & e));
60
+ s[t] = (r ^ _l(r)) >>> 0;
61
+ isSet[t] = true;
62
+ r = _l((r + t) >>> 0);
63
+ }
64
+
65
+ const acc = _l((0xa5a5a5a5 ^ r) >>> 0);
66
+ return { s, isSet, acc };
67
+ }
68
+
69
+ function _nextKeystreamWord(state, t) {
70
+ const r = state.s;
71
+ let nState = state.acc;
72
+ const i = nState % 61;
73
+ const d = state.isSet[i] ? r[i] : 0;
74
+ const c = Math.imul((t + 1) >>> 0, 0x9e3779b9) >>> 0;
75
+ const a = nState;
76
+ const sVal = (d ^ c) >>> 0;
77
+ const h = state.isSet[i] ? ((a | sVal) >>> 0) : ((a ^ sVal) >>> 0);
78
+ const term1 = _u((h + nState) >>> 0, 31 & i);
79
+ const term2 = _u(nState, 31 & (i * 7));
80
+ nState = _l((((term1 ^ term2) >>> 0) + 0x9e3779b9) >>> 0);
81
+ r[i] = nState;
82
+ state.isSet[i] = true;
83
+ state.acc = nState;
84
+ return nState >>> 0;
85
+ }
86
+
87
+ function _generateKeyStream(seed, tmdbId, len) {
88
+ const state = _initKeyState(seed, tmdbId);
89
+ const out = new Uint8Array(len);
90
+ let wordIdx = 0;
91
+ let byteIdx = 0;
92
+
93
+ while (byteIdx < len) {
94
+ const word = _nextKeystreamWord(state, wordIdx++);
95
+ out[byteIdx++] = word & 0xFF;
96
+ if (byteIdx < len) out[byteIdx++] = (word >>> 8) & 0xFF;
97
+ if (byteIdx < len) out[byteIdx++] = (word >>> 16) & 0xFF;
98
+ if (byteIdx < len) out[byteIdx++] = (word >>> 24) & 0xFF;
99
+ }
100
+ return out;
101
+ }
102
+
103
+ function decrypt(cipherB64, seed, tmdbId) {
104
+ try {
105
+ let normalized = cipherB64.replace(/-/g, '+').replace(/_/g, '/');
106
+ while (normalized.length % 4 !== 0) normalized += '=';
107
+ const cipherBytes = Buffer.from(normalized, 'base64');
108
+ if (cipherBytes.length <= MAGIC.length) return null;
109
+
110
+ const ks = _generateKeyStream(seed, tmdbId, cipherBytes.length);
111
+ const decrypted = new Uint8Array(cipherBytes.length);
112
+ for (let i = 0; i < cipherBytes.length; i++) {
113
+ decrypted[i] = cipherBytes[i] ^ ks[i];
114
+ }
115
+
116
+ for (let k = 0; k < MAGIC.length; k++) {
117
+ if (decrypted[k] !== MAGIC[k]) return null;
118
+ }
119
+
120
+ return Buffer.from(decrypted.subarray(MAGIC.length)).toString('utf8');
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
125
+
126
+ async function getSeed(tmdbId, ua) {
127
+ const now = Date.now();
128
+ const cached = seedCache.get(tmdbId);
129
+ if (cached && cached.expiresAt > now + 5000) {
130
+ return cached.seed;
131
+ }
132
+
133
+ try {
134
+ const res = await fetch(`${API_BASE}/seed?mediaId=${tmdbId}`, {
135
+ headers: {
136
+ 'User-Agent': ua,
137
+ Referer: REFERER,
138
+ Origin: ORIGIN,
139
+ },
140
+ signal: AbortSignal.timeout(8000),
141
+ });
142
+
143
+ if (res.ok) {
144
+ const data = await res.json();
145
+ const seed = data?.seed ? String(data.seed) : '';
146
+ const ttlMs = typeof data?.ttlMs === 'number' ? data.ttlMs : 30000;
147
+ if (seed) {
148
+ seedCache.set(tmdbId, { seed, expiresAt: now + ttlMs });
149
+ return seed;
150
+ }
151
+ }
152
+ } catch { }
153
+
154
+ return null;
155
+ }
156
+
157
+ async function resolveDetails(sdk, id, isTv) {
158
+ const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
159
+ if (!apiKey) return null;
160
+ const type = isTv ? 'tv' : 'movie';
161
+ try {
162
+ const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, {
163
+ signal: AbortSignal.timeout(6000),
164
+ });
165
+ if (!res.ok) return null;
166
+ const data = await res.json();
167
+ const title = isTv ? data.name : data.title;
168
+ const releaseDate = isTv ? data.first_air_date : data.release_date;
169
+ const year = releaseDate ? parseInt(releaseDate.split('-')[0], 10) : null;
170
+ const imdbId = data.imdb_id || data.external_ids?.imdb_id || null;
171
+ return { title, year, imdbId };
172
+ } catch {
173
+ return null;
174
+ }
175
+ }
176
+
177
+ function formatQuality(raw) {
178
+ const lower = String(raw).toLowerCase();
179
+ if (lower.includes('2160') || lower.includes('4k')) return '4K';
180
+ if (lower.includes('1080')) return '1080p';
181
+ if (lower.includes('720')) return '720p';
182
+ if (lower.includes('480')) return '480p';
183
+ if (lower.includes('360')) return '360p';
184
+ if (raw) return String(raw);
185
+ return 'Auto';
186
+ }
187
+
188
+ export async function getStream({ id, s, e, sdk }) {
189
+ const isTv = Boolean(s && e);
190
+ const mediaType = isTv ? 'tv' : 'movie';
191
+ const tmdbId = parseInt(id, 10);
192
+ const ua = getUA();
193
+
194
+ const details = await resolveDetails(sdk, id, isTv);
195
+ const title = details?.title || String(id);
196
+ const year = details?.year;
197
+ const imdbId = details?.imdbId;
198
+
199
+ const seed = await getSeed(tmdbId, ua);
200
+ if (!seed) return null;
201
+
202
+ const params = new URLSearchParams({
203
+ title,
204
+ mediaType,
205
+ tmdbId: String(tmdbId),
206
+ enc: '2',
207
+ seed,
208
+ });
209
+
210
+ if (year) params.set('year', String(year));
211
+ if (isTv) {
212
+ if (s) params.set('seasonId', String(s));
213
+ if (e) params.set('episodeId', String(e));
214
+ }
215
+ if (imdbId) params.set('imdbId', imdbId);
216
+
217
+ const baseQuery = params.toString();
218
+ const seenUrls = new Set();
219
+ const allUrls = [];
220
+
221
+ const defaultHeaders = {
222
+ 'User-Agent': ua,
223
+ Referer: REFERER,
224
+ Origin: ORIGIN,
225
+ };
226
+
227
+ const tasks = SERVERS.map(async (server) => {
228
+ let fullUrl = `${API_BASE}/${server.endpoint}/sources?${baseQuery}`;
229
+ if (server.extra) fullUrl += `&${server.extra}`;
230
+
231
+ try {
232
+ const res = await fetch(fullUrl, {
233
+ headers: defaultHeaders,
234
+ signal: AbortSignal.timeout(8000),
235
+ });
236
+
237
+ if (!res.ok) return;
238
+ const encText = (await res.text()).trim();
239
+ if (!encText || encText.startsWith('<')) return;
240
+
241
+ const decJsonStr = decrypt(encText, seed, tmdbId);
242
+ if (!decJsonStr) return;
243
+
244
+ const parsed = JSON.parse(decJsonStr);
245
+ if (!Array.isArray(parsed?.sources)) return;
246
+
247
+ for (const src of parsed.sources) {
248
+ if (!src || typeof src !== 'object') continue;
249
+ const streamUrl = String(src.url || '').trim();
250
+ if (!streamUrl || seenUrls.has(streamUrl)) continue;
251
+
252
+ seenUrls.add(streamUrl);
253
+
254
+ const cleanQuality = formatQuality(src.quality || 'Auto');
255
+ const isSpecificAudio = server.note.toLowerCase().includes('audio') && !server.note.toLowerCase().includes('original');
256
+ const langLabel = isSpecificAudio ? server.note.split(' ')[0] : '';
257
+ const label = isSpecificAudio
258
+ ? `${server.name} (${langLabel}) · ${cleanQuality}`
259
+ : `${server.name} · ${cleanQuality}`;
260
+
261
+ allUrls.push({
262
+ url: streamUrl,
263
+ label,
264
+ headers: {
265
+ 'User-Agent': ua,
266
+ Referer: REFERER,
267
+ },
268
+ });
269
+ }
270
+ } catch { }
271
+ });
272
+
273
+ await Promise.all(tasks);
274
+
275
+ if (allUrls.length === 0) return null;
276
+
277
+ return {
278
+ url: allUrls[0].url,
279
+ allUrls,
280
+ headers: allUrls[0].headers,
281
+ };
282
+ }
@@ -0,0 +1,68 @@
1
+ import { USER_AGENT } from '../utils/helpers.js';
2
+
3
+ async function safeImport(moduleName) {
4
+ try {
5
+ return await import(`./${moduleName}.js`);
6
+ } catch {
7
+ return null;
8
+ }
9
+ }
10
+
11
+ export async function getStream(args) {
12
+ const { id, s, e, clientIP, sdk } = args;
13
+ const isTv = s != null && e != null;
14
+ const type = isTv ? 'tv' : 'movie';
15
+ const allUrls = [];
16
+
17
+ const providers = [
18
+ { name: 'CineSu', key: 'cinesu', fnName: 'fetchCineSuStreams' },
19
+ { name: 'ZxcStream', key: 'zxcstream', fnName: 'fetchZxcStreams' },
20
+ { name: 'VidFast', key: 'vidfast', fnName: 'fetchVidFastStreams' },
21
+ { name: '1Embed', key: '1embed', fnName: 'fetchOneEmbedStreams' },
22
+ ];
23
+
24
+ await Promise.all(
25
+ providers.map(async ({ name, key, fnName }) => {
26
+ try {
27
+ let streamResult = null;
28
+
29
+ if (sdk) {
30
+ try {
31
+ streamResult = await sdk.getStream(key, id, s, e, clientIP);
32
+ } catch { }
33
+ }
34
+
35
+ if (!streamResult) {
36
+ const mod = await safeImport(key);
37
+ if (mod) {
38
+ if (typeof mod.getStream === 'function') {
39
+ streamResult = await mod.getStream(args);
40
+ } else if (typeof mod[fnName] === 'function') {
41
+ const raw = await mod[fnName]({ type, id, season: s, episode: e });
42
+ if (Array.isArray(raw)) {
43
+ streamResult = { allUrls: raw };
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ if (!streamResult) return;
50
+
51
+ const streams = streamResult.allUrls || (streamResult.url ? [streamResult] : []);
52
+ for (const item of streams) {
53
+ allUrls.push({
54
+ url: item.url,
55
+ server: `FlyStream (${item.source || item.server || name})`,
56
+ quality: item.quality || 'Auto',
57
+ type: item.type || item.format || (item.url?.includes('.m3u8') ? 'hls' : 'mp4'),
58
+ headers: item.headers || {
59
+ 'User-Agent': USER_AGENT,
60
+ },
61
+ });
62
+ }
63
+ } catch { }
64
+ })
65
+ );
66
+
67
+ return allUrls.length ? { allUrls } : null;
68
+ }
@@ -1,7 +1,7 @@
1
1
  import { fetchJson, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
2
2
 
3
- const DOMAIN = 'https://purstream.club';
4
- const API_BASE = 'https://api.purstream.club/api/v1';
3
+ const DOMAIN = 'https://purstream.ad';
4
+ const API_BASE = 'https://api.purstream.ad/api/v1';
5
5
 
6
6
  const HEADERS = {
7
7
  'User-Agent': USER_AGENT,
@@ -0,0 +1,127 @@
1
+ import { getUA } from '../utils/helpers.js';
2
+
3
+ const BASE_URL = 'https://streamaggregator.in';
4
+
5
+ async function resolveDetails(sdk, id, isTv) {
6
+ const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
7
+ if (!apiKey) return null;
8
+ const type = isTv ? 'tv' : 'movie';
9
+ try {
10
+ const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}`, {
11
+ signal: AbortSignal.timeout(6000),
12
+ });
13
+ if (!res.ok) return null;
14
+ const data = await res.json();
15
+ const title = isTv ? data.name : data.title;
16
+ const releaseDate = isTv ? data.first_air_date : data.release_date;
17
+ const year = releaseDate ? releaseDate.split('-')[0] : null;
18
+ return { title, year };
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ export async function getStream({ id, s, e, sdk }) {
25
+ const isTv = Boolean(s && e);
26
+ const mediaType = isTv ? 'tv' : 'movie';
27
+ const ua = getUA();
28
+
29
+ const details = await resolveDetails(sdk, id, isTv);
30
+ const title = details?.title || String(id);
31
+ const year = details?.year || '';
32
+
33
+ const formattedTitle = encodeURIComponent(title).replaceAll('%20', '+');
34
+ const params = new URLSearchParams({
35
+ type: mediaType,
36
+ stream: 'true',
37
+ });
38
+
39
+ if (year) params.set('year', String(year));
40
+ if (isTv) {
41
+ params.set('season', String(s));
42
+ params.set('episode', String(e));
43
+ }
44
+
45
+ const scrapeUrl = `${BASE_URL}/api/scrape?title=${formattedTitle}&${params.toString()}`;
46
+
47
+ const res = await fetch(scrapeUrl, {
48
+ headers: {
49
+ 'User-Agent': ua,
50
+ Referer: `${BASE_URL}/`,
51
+ Origin: BASE_URL,
52
+ Accept: 'application/x-ndjson, text/plain, */*',
53
+ },
54
+ signal: AbortSignal.timeout(18000),
55
+ });
56
+
57
+ if (!res.ok) return null;
58
+
59
+ const lines = [];
60
+ if (res.body?.getReader) {
61
+ const reader = res.body.getReader();
62
+ const decoder = new TextDecoder('utf-8');
63
+ let buffer = '';
64
+
65
+ try {
66
+ while (true) {
67
+ const { done, value } = await reader.read();
68
+ if (done) break;
69
+ buffer += decoder.decode(value, { stream: true });
70
+ const parts = buffer.split('\n');
71
+ buffer = parts.pop() || '';
72
+ for (const part of parts) {
73
+ const trimmed = part.trim();
74
+ if (trimmed) lines.push(trimmed);
75
+ }
76
+ }
77
+ } catch { }
78
+ if (buffer.trim()) lines.push(buffer.trim());
79
+ } else {
80
+ const text = await res.text();
81
+ for (const line of text.split('\n')) {
82
+ const trimmed = line.trim();
83
+ if (trimmed) lines.push(trimmed);
84
+ }
85
+ }
86
+
87
+ const seenUrls = new Set();
88
+ const allUrls = [];
89
+
90
+ for (const line of lines) {
91
+ try {
92
+ const item = JSON.parse(line);
93
+ const streamUrl = String(item?.url || '').trim();
94
+ if (!streamUrl || !streamUrl.startsWith('http') || seenUrls.has(streamUrl)) continue;
95
+
96
+ seenUrls.add(streamUrl);
97
+
98
+ const serverName = item.server || item.providerKey || 'Stream';
99
+ const quality = item.quality ? ` · ${item.quality}` : '';
100
+ const size = item.size ? ` (${item.size})` : '';
101
+ const label = `${serverName}${quality}${size}`;
102
+
103
+ const headers = {
104
+ 'User-Agent': ua,
105
+ ...(item.headers && typeof item.headers === 'object' ? item.headers : {}),
106
+ };
107
+
108
+ if (item.referer) {
109
+ headers.Referer = item.referer;
110
+ }
111
+
112
+ allUrls.push({
113
+ url: streamUrl,
114
+ label,
115
+ headers,
116
+ });
117
+ } catch { }
118
+ }
119
+
120
+ if (allUrls.length === 0) return null;
121
+
122
+ return {
123
+ url: allUrls[0].url,
124
+ allUrls,
125
+ headers: allUrls[0].headers,
126
+ };
127
+ }