@vyla-entertainment/sdk 1.17.0 → 1.19.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.
@@ -1,282 +1,210 @@
1
- import { getUA } from '../utils/helpers.js';
1
+ import { fetchJson, fetchText, getTmdbInfo } from '../utils/helpers.js';
2
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' },
3
+ const K = [
4
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
5
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
6
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
7
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174
23
8
  ];
9
+ const MAGIC = [109, 118, 109, 49];
24
10
 
25
- const seedCache = new Map();
11
+ const isEven = e => ((e * (e + 1)) & 1) === 0;
26
12
 
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;
13
+ function scramble(e) {
14
+ e >>>= 0;
15
+ e ^= e >>> 16;
16
+ e = Math.imul(e, 0x85ebca6b) >>> 0;
17
+ e ^= e >>> 13;
18
+ e = Math.imul(e, 0xc2b2ae35) >>> 0;
19
+ return (e ^ (e >>> 16)) >>> 0;
34
20
  }
35
21
 
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;
22
+ function rotl(e, a) {
23
+ e >>>= 0;
24
+ a &= 31;
25
+ return a === 0 ? e >>> 0 : ((e << a) | (e >>> (32 - a))) >>> 0;
40
26
  }
41
27
 
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;
28
+ function initCipherState(keyStr, seedStr) {
29
+ let keyLen = keyStr.length;
30
+ if (((keyLen * (keyLen + 1)) & 1) === 1) {
31
+ const S = new Array(256);
32
+ for (let i = 0; i < 256; i++) S[i] = i;
33
+ let j = 0;
34
+ for (let i = 0; i < 256; i++) {
35
+ j = (j + S[i] + keyStr.charCodeAt(i % keyStr.length)) & 255;
36
+ const tmp = S[i];
37
+ S[i] = S[j];
38
+ S[j] = tmp;
39
+ }
40
+ let acc = 0x67452301;
41
+ for (let i = 0; i < keyStr.length; i++) {
42
+ acc = rotl((acc ^ Math.imul(keyStr.charCodeAt(i), K[15 & i])) >>> 0, 5);
43
+ }
44
+ return { S, acc: scramble(acc) };
47
45
  }
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
46
 
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);
47
+ const S = new Array(61);
48
+ let hash = 0x811c9dc5;
49
+ for (let i = 0; i < keyStr.length; i++) {
50
+ hash = Math.imul(hash ^ keyStr.charCodeAt(i), 0x1000193) >>> 0;
63
51
  }
64
-
65
- const acc = _l((0xa5a5a5a5 ^ r) >>> 0);
66
- return { s, isSet, acc };
52
+ let n = scramble(scramble(hash) ^ scramble((seedStr >>> 0) ^ 0x9e3779b9)) >>> 0;
53
+ for (let i = 0; i < 8; i++) {
54
+ if (isEven(i)) {
55
+ const idx = n % 61;
56
+ n = rotl((n + 0x9e3779b9) >>> 0, 7 + (7 & i));
57
+ S[idx] = (n ^ scramble(n)) >>> 0;
58
+ n = scramble((n + idx) >>> 0);
59
+ } else {
60
+ S[i] = K[15 & i];
61
+ }
62
+ }
63
+ return {
64
+ S,
65
+ acc: scramble(0xa5a5a5a5 ^ n) >>> 0
66
+ };
67
67
  }
68
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;
69
+ function nextKeystreamWord(state, step) {
70
+ const S = state.S;
71
+ let acc = state.acc;
72
+ const idx = acc % 61;
73
+ const hasIdx = 0 - Number(idx in S);
74
+ const sVal = S[idx] >>> 0;
75
+ const c = Math.imul(0x9e3779b9, step + 1) >>> 0;
76
+ let b = ((acc ^ ((sVal ^ c) >>> 0)) >>> 0 | (acc & (sVal ^ c) & hasIdx) >>> 0) >>> 0;
77
+ b = (rotl((b + acc) >>> 0, 31 & idx) ^ rotl(acc, 31 & Math.imul(idx, 7))) >>> 0;
78
+ acc = scramble((b + 0x9e3779b9) >>> 0);
79
+ S[idx] = acc >>> 0;
80
+ state.acc = acc;
81
+ return acc >>> 0;
85
82
  }
86
83
 
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;
84
+ function decodeBase64Url(str) {
85
+ const padded = str.replace(/-/g, "+").replace(/_/g, "/").padEnd(4 * Math.ceil(str.length / 4), "=");
86
+ const bin = atob(padded);
87
+ const bytes = new Uint8Array(bin.length);
88
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
89
+ return bytes;
101
90
  }
102
91
 
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
- }
92
+ function decryptPayload(ciphertext, seed, mediaId) {
93
+ const rawBytes = decodeBase64Url(ciphertext);
94
+ const totalLen = rawBytes.length;
95
+ const state = initCipherState(seed, mediaId);
96
+ const keystream = new Uint8Array(totalLen);
119
97
 
120
- return Buffer.from(decrypted.subarray(MAGIC.length)).toString('utf8');
121
- } catch {
122
- return null;
98
+ let byteIdx = 0;
99
+ let step = 0;
100
+ while (byteIdx < totalLen) {
101
+ const word = nextKeystreamWord(state, step++);
102
+ keystream[byteIdx++] = 255 & word;
103
+ if (byteIdx < totalLen) keystream[byteIdx++] = (word >>> 8) & 255;
104
+ if (byteIdx < totalLen) keystream[byteIdx++] = (word >>> 16) & 255;
105
+ if (byteIdx < totalLen) keystream[byteIdx++] = (word >>> 24) & 255;
123
106
  }
124
- }
125
107
 
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;
108
+ for (let i = 0; i < totalLen; i++) {
109
+ rawBytes[i] ^= keystream[i];
131
110
  }
132
111
 
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
- }
112
+ for (let i = 0; i < MAGIC.length; i++) {
113
+ if (rawBytes[i] !== MAGIC[i]) {
114
+ throw new Error("decrypt failed: bad seed or tampered payload");
151
115
  }
152
- } catch { }
116
+ }
153
117
 
154
- return null;
118
+ const payload = rawBytes.subarray(MAGIC.length);
119
+ return new TextDecoder("utf-8").decode(payload);
155
120
  }
156
121
 
157
- async function resolveDetails(sdk, id, isTv) {
158
- const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
159
- if (!apiKey) return null;
122
+ export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
123
+ const isTv = s != null && e != null;
160
124
  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
- }
125
+ const apiKey = tmdbApiKey || sdk?.tmdbApiKey || process.env.TMDB_API_KEY || '45dbdd5e714da212424bda0336167447';
176
126
 
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
- }
127
+ const headers = {
128
+ 'Accept': '*/*',
129
+ 'Origin': 'https://www.movy.sx',
130
+ 'Referer': 'https://www.movy.sx/',
131
+ 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1'
132
+ };
187
133
 
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();
134
+ let title = '';
135
+ let year = '';
136
+ let imdbId = '';
193
137
 
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;
138
+ try {
139
+ const info = await getTmdbInfo(apiKey, id, type, s);
140
+ title = info?.titles?.[0] || '';
141
+ year = info?.year || '';
142
+ imdbId = info?.imdbId || '';
143
+ } catch { }
198
144
 
199
- const seed = await getSeed(tmdbId, ua);
200
- if (!seed) return null;
145
+ if (!title) {
146
+ try {
147
+ const tmdbRes = await fetchJson(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`);
148
+ title = tmdbRes?.title || tmdbRes?.name || '';
149
+ year = (tmdbRes?.release_date || tmdbRes?.first_air_date || '').split('-')[0] || '';
150
+ imdbId = tmdbRes?.imdb_id || tmdbRes?.external_ids?.imdb_id || '';
151
+ } catch { }
152
+ }
201
153
 
202
- const params = new URLSearchParams({
203
- title,
204
- mediaType,
205
- tmdbId: String(tmdbId),
206
- enc: '2',
207
- seed,
208
- });
154
+ if (!title) return null;
209
155
 
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);
156
+ const seedRes = await fetchJson(`https://api.wecollege.net/seed?mediaId=${id}`, { headers }).catch(() => null);
157
+ const seed = seedRes?.seed;
158
+ if (!seed) return null;
216
159
 
217
- const baseQuery = params.toString();
218
- const seenUrls = new Set();
160
+ const servers = ['miami', 'boise', 'vegas', 'chicago', 'reno', 'seattle'];
219
161
  const allUrls = [];
220
162
 
221
- const defaultHeaders = {
222
- 'User-Agent': ua,
223
- Referer: REFERER,
224
- Origin: ORIGIN,
225
- };
163
+ for (const srv of servers) {
164
+ const params = new URLSearchParams({
165
+ title,
166
+ mediaType: type,
167
+ year: String(year),
168
+ episodeId: String(e || 1),
169
+ seasonId: String(s || 1),
170
+ tmdbId: String(id),
171
+ imdbId: String(imdbId || ''),
172
+ enc: '2',
173
+ seed
174
+ });
226
175
 
227
- const tasks = SERVERS.map(async (server) => {
228
- let fullUrl = `${API_BASE}/${server.endpoint}/sources?${baseQuery}`;
229
- if (server.extra) fullUrl += `&${server.extra}`;
176
+ const url = `https://api.wecollege.net/${srv}/sources?${params.toString()}`;
177
+ const rawEnc = await fetchText(url, { headers }).catch(() => null);
178
+ if (!rawEnc || rawEnc.length < 20 || rawEnc.startsWith('{') || rawEnc.startsWith('<')) continue;
230
179
 
231
180
  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
- });
181
+ const dec = decryptPayload(rawEnc.trim(), seed, id);
182
+ const parsed = JSON.parse(dec);
183
+
184
+ if (Array.isArray(parsed?.sources)) {
185
+ for (const src of parsed.sources) {
186
+ if (src?.url) {
187
+ allUrls.push({
188
+ url: src.url,
189
+ server: `Movy - ${srv.toUpperCase()} (${src.quality || 'Auto'})`,
190
+ headers: {
191
+ Referer: 'https://www.movy.sx/',
192
+ Origin: 'https://www.movy.sx'
193
+ }
194
+ });
195
+ }
196
+ }
269
197
  }
270
198
  } catch { }
271
- });
272
199
 
273
- await Promise.all(tasks);
200
+ if (allUrls.length) break;
201
+ }
274
202
 
275
- if (allUrls.length === 0) return null;
203
+ if (!allUrls.length) return null;
276
204
 
277
205
  return {
278
206
  url: allUrls[0].url,
279
- allUrls,
280
207
  headers: allUrls[0].headers,
208
+ allUrls
281
209
  };
282
210
  }
@@ -0,0 +1,56 @@
1
+ import { fetchJson } from '../utils/helpers.js';
2
+
3
+ export async function getStream({ id, s, e }) {
4
+ const isTv = s != null && e != null;
5
+ const url = isTv
6
+ ? `https://net79.cc/api/embed-tmdb/${id}?type=tv&se=${s}&ep=${e}`
7
+ : `https://net79.cc/api/embed-tmdb/${id}?type=movie`;
8
+
9
+ const headers = {
10
+ 'Accept': 'application/json',
11
+ 'Referer': 'https://net79.cc/',
12
+ 'Origin': 'https://net79.cc',
13
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
14
+ };
15
+
16
+ const res = await fetchJson(url, { headers }).catch(() => null);
17
+ if (!res || !res.ok || res.noSource) return null;
18
+
19
+ const allUrls = [];
20
+
21
+ if (Array.isArray(res.streams) && res.streams.length > 0) {
22
+ for (const stream of res.streams) {
23
+ if (stream?.url) {
24
+ allUrls.push({
25
+ url: stream.url,
26
+ quality: stream.resolution ? `${stream.resolution}p` : 'Auto',
27
+ server: `Net79 - ${stream.resolution ? `${stream.resolution}p` : 'MP4'}`,
28
+ type: stream.url.includes('.m3u8') ? 'hls' : 'mp4',
29
+ headers: {
30
+ Referer: 'https://net79.cc/',
31
+ Origin: 'https://net79.cc'
32
+ }
33
+ });
34
+ }
35
+ }
36
+ } else if (res.mp4) {
37
+ allUrls.push({
38
+ url: res.mp4,
39
+ quality: res.resolution ? `${res.resolution}p` : 'Auto',
40
+ server: `Net79 - ${res.resolution ? `${res.resolution}p` : 'MP4'}`,
41
+ type: res.mp4.includes('.m3u8') ? 'hls' : 'mp4',
42
+ headers: {
43
+ Referer: 'https://net79.cc/',
44
+ Origin: 'https://net79.cc'
45
+ }
46
+ });
47
+ }
48
+
49
+ if (!allUrls.length) return null;
50
+
51
+ return {
52
+ url: allUrls[0].url,
53
+ headers: allUrls[0].headers,
54
+ allUrls
55
+ };
56
+ }
@@ -0,0 +1,54 @@
1
+ import { fetchJson } from '../utils/helpers.js';
2
+
3
+ export async function getStream({ id, s, e }) {
4
+ const isTv = s != null && e != null;
5
+ const separator = isTv ? '&' : '?';
6
+ const basePath = isTv
7
+ ? `https://f.rivo.st/api/source/tv/${id}?s=${s}&e=${e}`
8
+ : `https://f.rivo.st/api/source/movie/${id}`;
9
+
10
+ const headers = {
11
+ 'Accept': '*/*',
12
+ 'Referer': 'https://f.rivo.st/',
13
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
14
+ };
15
+
16
+ const serverIndices = Array.from({ length: 26 }, (_, i) => i + 1);
17
+
18
+ const responses = await Promise.allSettled(
19
+ serverIndices.map(serverNum =>
20
+ fetchJson(`${basePath}&server=${serverNum}`, { headers })
21
+ .catch(() => null)
22
+ )
23
+ );
24
+
25
+ const allUrls = [];
26
+
27
+ for (const res of responses) {
28
+ if (res.status !== 'fulfilled' || !res.value) continue;
29
+ const data = res.value;
30
+
31
+ if (Array.isArray(data.sources)) {
32
+ for (const src of data.sources) {
33
+ if (src?.url) {
34
+ allUrls.push({
35
+ url: src.url,
36
+ server: `Rivo - ${data.server || src.subProvider || 'Server'}`,
37
+ headers: {
38
+ Referer: 'https://f.rivo.st/',
39
+ Origin: 'https://f.rivo.st'
40
+ }
41
+ });
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ if (!allUrls.length) return null;
48
+
49
+ return {
50
+ url: allUrls[0].url,
51
+ headers: allUrls[0].headers,
52
+ allUrls
53
+ };
54
+ }