@vyla-entertainment/sdk 1.20.0 → 1.22.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.
Files changed (58) hide show
  1. package/package.json +3 -2
  2. package/src/config.js +99 -67
  3. package/src/health.js +60 -20
  4. package/src/sdk.d.ts +94 -121
  5. package/src/sdk.js +82 -78
  6. package/src/sources/111477.js +88 -100
  7. package/src/sources/123anime.js +120 -56
  8. package/src/sources/3eyedraven.js +82 -135
  9. package/src/sources/4movies.js +77 -0
  10. package/src/sources/aether.js +99 -0
  11. package/src/sources/castle.js +415 -0
  12. package/src/sources/cinecat.js +37 -18
  13. package/src/sources/cinemaarmy.js +179 -180
  14. package/src/sources/cinesrc.js +401 -397
  15. package/src/sources/cno.js +101 -94
  16. package/src/sources/dulo.js +134 -149
  17. package/src/sources/flaxmovies.js +114 -105
  18. package/src/sources/framextv.js +126 -0
  19. package/src/sources/frost.js +38 -29
  20. package/src/sources/fsonline.js +95 -47
  21. package/src/sources/hdhub.js +43 -36
  22. package/src/sources/hexa.js +60 -53
  23. package/src/sources/hyper.js +42 -35
  24. package/src/sources/kickassanime.js +117 -124
  25. package/src/sources/kisskh.js +114 -59
  26. package/src/sources/lmscript.js +72 -70
  27. package/src/sources/lookmovie.js +80 -79
  28. package/src/sources/meowtv.js +140 -0
  29. package/src/sources/movenerds.js +74 -124
  30. package/src/sources/movy.js +197 -209
  31. package/src/sources/net79.js +59 -55
  32. package/src/sources/peestream.js +126 -138
  33. package/src/sources/pengu.js +46 -49
  34. package/src/sources/pstream.js +73 -67
  35. package/src/sources/purstream.js +96 -104
  36. package/src/sources/rivestream.js +182 -176
  37. package/src/sources/rivo.js +58 -53
  38. package/src/sources/streamaggregator.js +114 -128
  39. package/src/sources/thugtv.js +70 -62
  40. package/src/sources/vidapi.js +122 -114
  41. package/src/sources/vidcore.js +14 -122
  42. package/src/sources/vidfast.js +14 -122
  43. package/src/sources/vidlink.js +78 -27
  44. package/src/sources/vidora.js +74 -42
  45. package/src/sources/vidup.js +14 -122
  46. package/src/sources/vidvault.js +133 -187
  47. package/src/sources/vidy.js +192 -187
  48. package/src/sources/vidzee.js +189 -183
  49. package/src/sources/vixsrc.js +123 -113
  50. package/src/sources/watchout.js +60 -46
  51. package/src/sources/xdownloader.js +127 -128
  52. package/src/sources/yapgrid.js +68 -62
  53. package/src/sources/yastream.js +35 -29
  54. package/src/subtitles.js +105 -122
  55. package/src/utils/helpers.js +586 -247
  56. package/src/sources/bcine.js +0 -202
  57. package/src/sources/cinezo.js +0 -55
  58. package/src/sources/vidrock.js +0 -145
@@ -1,125 +1,75 @@
1
- import { getUA } from "../utils/helpers.js";
2
-
3
- const API_BASE = "https://api.movenerds.space/api";
4
-
5
- function getHeaders() {
6
- return {
7
- "User-Agent": getUA(),
8
- Accept: "application/json",
9
- Referer: "https://movenerds.space/",
10
- Origin: "https://movenerds.space",
11
- };
12
- }
13
-
14
- function resolveApiKey(args) {
15
- return (
16
- args?.tmdbApiKey ||
17
- args?.sdk?.tmdbApiKey ||
18
- process.env.TMDB_API_KEY ||
19
- null
20
- );
21
- }
22
-
23
- async function getMeta(id, isTv, apiKey) {
24
- if (!apiKey) return null;
25
- const type = isTv ? "tv" : "movie";
26
- try {
27
- const res = await fetch(
28
- `https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}`,
29
- { signal: AbortSignal.timeout(5000) }
30
- );
31
- if (!res.ok) return null;
32
- const data = await res.json();
33
- const title = isTv ? data.name : data.title;
34
- const date = isTv ? data.first_air_date : data.release_date;
35
- const year = date ? parseInt(date.slice(0, 4), 10) : null;
36
- return { title, year };
37
- } catch {
38
- return null;
39
- }
40
- }
41
-
42
- function mapSource(source) {
43
- if (!source || !source.url) return null;
44
- if (source.type !== "hls" && source.type !== "mp4") return null;
45
- if (!/^https?:\/\//.test(source.url)) return null;
46
-
47
- return {
48
- url: source.url,
49
- headers: {
50
- "User-Agent": source.headers?.["User-Agent"] || getUA(),
51
- Referer: source.headers?.Referer || "https://movenerds.space/",
52
- Origin: source.headers?.Origin || "https://movenerds.space",
53
- },
54
- server: source.name || source.providerName || "MoverNerds",
55
- quality: source.quality || "1080p",
56
- type: source.type,
57
- skipProxy: true,
58
- };
59
- }
60
-
61
- export async function getStream(args) {
62
- const { id, s = null, e = null } = args;
63
- const isTv = s != null && e != null;
64
-
65
- const apiKey = resolveApiKey(args);
66
- const meta = await getMeta(id, isTv, apiKey);
67
-
68
- if (!meta?.title) {
69
- if (process.env.DEBUG_CAUSE) {
70
- console.error(
71
- `[movenerds] Could not resolve title for ${isTv ? "tv" : "movie"} ${id} ` +
72
- `(tmdbApiKey ${apiKey ? "present" : "MISSING"})`
73
- );
74
- }
75
- return null;
76
- }
77
-
78
- const params = new URLSearchParams({
79
- tmdbId: String(id),
80
- type: isTv ? "tv" : "movie",
81
- title: meta.title,
82
- });
83
- if (meta.year) params.set("year", String(meta.year));
84
- if (isTv) {
85
- params.set("season", String(s));
86
- params.set("episode", String(e));
87
- params.set("language", "en");
88
- }
89
-
90
- const url = `${API_BASE}/stream?${params.toString()}`;
91
-
92
- try {
93
- const res = await fetch(url, {
94
- headers: getHeaders(),
95
- signal: AbortSignal.timeout(20000),
96
- });
97
-
98
- if (!res.ok) {
99
- if (process.env.DEBUG_CAUSE) {
100
- console.error(`[movenerds] HTTP ${res.status} for ${url}`);
101
- }
102
- return null;
103
- }
104
-
105
- const data = await res.json();
106
- const primary = Array.isArray(data?.primarySources) ? data.primarySources : [];
107
- if (!primary.length) {
108
- if (process.env.DEBUG_CAUSE) {
109
- console.error(`[movenerds] No primarySources in response for ${url}`);
110
- }
111
- return null;
112
- }
113
-
114
- const allUrls = primary.map(mapSource).filter(Boolean);
115
- if (!allUrls.length) return null;
116
-
117
- return { allUrls };
118
- } catch (err) {
119
- if (process.env.DEBUG_CAUSE) {
120
- console.error(`[movenerds] fetch error for ${url}:`, err.message);
121
- if (err.cause) console.error(" cause:", err.cause.code, err.cause.message);
122
- }
123
- return null;
124
- }
1
+ import { getTmdbMetadata, getUA, resolveTmdbKey } from '../utils/helpers.js';
2
+
3
+ const BASE_URL = 'https://movenerds.space';
4
+ const API_BASE = 'https://api.movenerds.space/api';
5
+
6
+ function mapSource(source) {
7
+ if (!source || !source.url) return null;
8
+ if (source.type !== 'hls' && source.type !== 'mp4') return null;
9
+ if (!/^https?:\/\//.test(source.url)) return null;
10
+
11
+ return {
12
+ url: source.url,
13
+ headers: {
14
+ 'User-Agent': source.headers?.['User-Agent'] || getUA(),
15
+ Referer: source.headers?.Referer || `${BASE_URL}/`,
16
+ Origin: source.headers?.Origin || BASE_URL,
17
+ },
18
+ server: source.name || source.providerName || 'MoverNerds',
19
+ quality: source.quality || '1080p',
20
+ type: source.type,
21
+ skipProxy: true,
22
+ };
23
+ }
24
+
25
+ export async function getStream(args) {
26
+ const { id, s, e, sdk } = args || {};
27
+ const isTv = s != null && e != null;
28
+ const apiKey = resolveTmdbKey(args, sdk);
29
+
30
+ const meta = await getTmdbMetadata(id, isTv, apiKey);
31
+ if (!meta?.title) return null;
32
+
33
+ const params = new URLSearchParams({
34
+ tmdbId: String(id),
35
+ type: isTv ? 'tv' : 'movie',
36
+ title: meta.title,
37
+ });
38
+ if (meta.year) params.set('year', String(meta.year));
39
+ if (isTv) {
40
+ params.set('season', String(s));
41
+ params.set('episode', String(e));
42
+ params.set('language', 'en');
43
+ }
44
+
45
+ const url = `${API_BASE}/stream?${params.toString()}`;
46
+ const headers = {
47
+ 'User-Agent': getUA(),
48
+ Accept: 'application/json',
49
+ Referer: `${BASE_URL}/`,
50
+ Origin: BASE_URL,
51
+ };
52
+
53
+ try {
54
+ const res = await fetch(url, {
55
+ headers,
56
+ signal: AbortSignal.timeout(20000),
57
+ });
58
+ if (!res.ok) return null;
59
+
60
+ const data = await res.json();
61
+ const primary = Array.isArray(data?.primarySources) ? data.primarySources : [];
62
+ if (!primary.length) return null;
63
+
64
+ const allUrls = primary.map(mapSource).filter(Boolean);
65
+ if (!allUrls.length) return null;
66
+
67
+ return {
68
+ url: allUrls[0].url,
69
+ headers: allUrls[0].headers,
70
+ allUrls,
71
+ };
72
+ } catch {
73
+ return null;
74
+ }
125
75
  }
@@ -1,210 +1,198 @@
1
- import { fetchJson, fetchText, getTmdbInfo } from '../utils/helpers.js';
2
-
3
- const K = [
4
- 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
5
- 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
6
- 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
7
- 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174
8
- ];
9
- const MAGIC = [109, 118, 109, 49];
10
-
11
- const isEven = e => ((e * (e + 1)) & 1) === 0;
12
-
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;
20
- }
21
-
22
- function rotl(e, a) {
23
- e >>>= 0;
24
- a &= 31;
25
- return a === 0 ? e >>> 0 : ((e << a) | (e >>> (32 - a))) >>> 0;
26
- }
27
-
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) };
45
- }
46
-
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;
51
- }
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
- }
68
-
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;
82
- }
83
-
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;
90
- }
91
-
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);
97
-
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;
106
- }
107
-
108
- for (let i = 0; i < totalLen; i++) {
109
- rawBytes[i] ^= keystream[i];
110
- }
111
-
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");
115
- }
116
- }
117
-
118
- const payload = rawBytes.subarray(MAGIC.length);
119
- return new TextDecoder("utf-8").decode(payload);
120
- }
121
-
122
- export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
123
- const isTv = s != null && e != null;
124
- const type = isTv ? 'tv' : 'movie';
125
- const apiKey = tmdbApiKey || sdk?.tmdbApiKey || process.env.TMDB_API_KEY || '45dbdd5e714da212424bda0336167447';
126
-
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
- };
133
-
134
- let title = '';
135
- let year = '';
136
- let imdbId = '';
137
-
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 { }
144
-
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
- }
153
-
154
- if (!title) return null;
155
-
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;
159
-
160
- const servers = ['miami', 'boise', 'vegas', 'chicago', 'reno', 'seattle'];
161
- const allUrls = [];
162
-
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
- });
175
-
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;
179
-
180
- try {
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
- }
197
- }
198
- } catch { }
199
-
200
- if (allUrls.length) break;
201
- }
202
-
203
- if (!allUrls.length) return null;
204
-
205
- return {
206
- url: allUrls[0].url,
207
- headers: allUrls[0].headers,
208
- allUrls
209
- };
1
+ import { fetchJson, fetchText, getTmdbMetadata, resolveTmdbKey } from '../utils/helpers.js';
2
+
3
+ const K = [
4
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
5
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
6
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
7
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
8
+ ];
9
+ const MAGIC = [109, 118, 109, 49];
10
+
11
+ const isEven = e => ((e * (e + 1)) & 1) === 0;
12
+
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;
20
+ }
21
+
22
+ function rotl(e, a) {
23
+ e >>>= 0;
24
+ a &= 31;
25
+ return a === 0 ? e >>> 0 : ((e << a) | (e >>> (32 - a))) >>> 0;
26
+ }
27
+
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) };
45
+ }
46
+
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;
51
+ }
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
+ }
68
+
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;
82
+ }
83
+
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;
90
+ }
91
+
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);
97
+
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;
106
+ }
107
+
108
+ for (let i = 0; i < totalLen; i++) {
109
+ rawBytes[i] ^= keystream[i];
110
+ }
111
+
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');
115
+ }
116
+ }
117
+
118
+ const payload = rawBytes.subarray(MAGIC.length);
119
+ return new TextDecoder('utf-8').decode(payload);
120
+ }
121
+
122
+ export async function getStream(args) {
123
+ const { id, s, e, sdk } = args || {};
124
+ const isTv = s != null && e != null;
125
+ const type = isTv ? 'tv' : 'movie';
126
+ const apiKey = resolveTmdbKey(args, sdk);
127
+
128
+ const headers = {
129
+ Accept: '*/*',
130
+ Origin: 'https://www.movy.sx',
131
+ Referer: 'https://www.movy.sx/',
132
+ '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',
133
+ };
134
+
135
+ const meta = await getTmdbMetadata(id, isTv, apiKey);
136
+ const title = meta?.title || '';
137
+ const year = meta?.year ? String(meta.year) : '';
138
+ const imdbId = meta?.imdbId || '';
139
+
140
+ if (!title) return null;
141
+
142
+ const seedRes = await fetchJson(`https://api.wecollege.net/seed?mediaId=${id}`, { headers }).catch(() => null);
143
+ const seed = seedRes?.seed;
144
+ if (!seed) return null;
145
+
146
+ const servers = ['miami', 'boise', 'vegas', 'chicago', 'reno', 'seattle'];
147
+ const allUrls = [];
148
+
149
+ for (const srv of servers) {
150
+ const params = new URLSearchParams({
151
+ title,
152
+ mediaType: type,
153
+ year: String(year),
154
+ episodeId: String(e || 1),
155
+ seasonId: String(s || 1),
156
+ tmdbId: String(id),
157
+ imdbId: String(imdbId || ''),
158
+ enc: '2',
159
+ seed,
160
+ });
161
+
162
+ const url = `https://api.wecollege.net/${srv}/sources?${params.toString()}`;
163
+ const rawEnc = await fetchText(url, { headers }).catch(() => null);
164
+ if (!rawEnc || rawEnc.length < 20 || rawEnc.startsWith('{') || rawEnc.startsWith('<')) continue;
165
+
166
+ try {
167
+ const dec = decryptPayload(rawEnc.trim(), seed, id);
168
+ const parsed = JSON.parse(dec);
169
+
170
+ if (Array.isArray(parsed?.sources)) {
171
+ for (const src of parsed.sources) {
172
+ if (src?.url) {
173
+ allUrls.push({
174
+ url: src.url,
175
+ server: `Movy - ${srv.toUpperCase()} (${src.quality || 'Auto'})`,
176
+ quality: src.quality || 'Auto',
177
+ type: src.url.includes('.m3u8') ? 'hls' : 'mp4',
178
+ headers: {
179
+ Referer: 'https://www.movy.sx/',
180
+ Origin: 'https://www.movy.sx',
181
+ },
182
+ });
183
+ }
184
+ }
185
+ }
186
+ } catch { }
187
+
188
+ if (allUrls.length) break;
189
+ }
190
+
191
+ if (!allUrls.length) return null;
192
+
193
+ return {
194
+ url: allUrls[0].url,
195
+ headers: allUrls[0].headers,
196
+ allUrls,
197
+ };
210
198
  }