@vyla-entertainment/sdk 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.
Files changed (44) hide show
  1. package/LICENSE +407 -0
  2. package/README.md +44 -0
  3. package/package.json +31 -0
  4. package/src/config.js +519 -0
  5. package/src/downloads/downloads.js +8 -0
  6. package/src/downloads/sources/trendimovies.js +63 -0
  7. package/src/health.js +38 -0
  8. package/src/sdk.js +65 -0
  9. package/src/sources/123anime.js +57 -0
  10. package/src/sources/1embed.js +92 -0
  11. package/src/sources/anihq.js +112 -0
  12. package/src/sources/anineko.js +81 -0
  13. package/src/sources/anipm.js +70 -0
  14. package/src/sources/flaxmovies.js +14 -0
  15. package/src/sources/fsharetv.js +44 -0
  16. package/src/sources/fsonic.js +47 -0
  17. package/src/sources/fsonline.js +46 -0
  18. package/src/sources/goated.js +262 -0
  19. package/src/sources/hexa.js +82 -0
  20. package/src/sources/kisskh.js +60 -0
  21. package/src/sources/lookmovie.js +78 -0
  22. package/src/sources/mapple.js +115 -0
  23. package/src/sources/meowtv.js +111 -0
  24. package/src/sources/nebula.js +62 -0
  25. package/src/sources/nekowatch.js +57 -0
  26. package/src/sources/opstream.js +123 -0
  27. package/src/sources/peachify.js +78 -0
  28. package/src/sources/pengu.js +45 -0
  29. package/src/sources/purstream.js +104 -0
  30. package/src/sources/vidapi.js +115 -0
  31. package/src/sources/vidcore.js +43 -0
  32. package/src/sources/videasy.js +98 -0
  33. package/src/sources/vidfast.js +53 -0
  34. package/src/sources/vidlink.js +28 -0
  35. package/src/sources/vidnest.js +77 -0
  36. package/src/sources/vidrift.js +95 -0
  37. package/src/sources/vidrock.js +145 -0
  38. package/src/sources/vidsync.js +38 -0
  39. package/src/sources/vidup.js +79 -0
  40. package/src/sources/vidzee.js +120 -0
  41. package/src/sources/vixsrc.js +98 -0
  42. package/src/sources/xpass.js +40 -0
  43. package/src/subtitles.js +127 -0
  44. package/src/utils/helpers.js +194 -0
@@ -0,0 +1,262 @@
1
+ import crypto from "crypto";
2
+
3
+ export const ID = "goated";
4
+ export const DOMAIN = "https://goated.cx";
5
+ export const RETURN_ALL_URLS = true;
6
+
7
+ const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
8
+
9
+ const enc = new TextEncoder();
10
+ const info = enc.encode("goated-api-v1");
11
+ const salt = enc.encode("goated-stream-salt");
12
+
13
+ const generateRandomIP = () => {
14
+ const p = () => Math.floor(Math.random() * 255);
15
+ let first = p();
16
+ while ([0, 10, 127, 169, 172, 192].includes(first)) first = p();
17
+ return `${first}.${p()}.${p()}.${p()}`;
18
+ };
19
+
20
+ let cachedSession = null;
21
+ let sessionExpiresAt = 0;
22
+ let cachedProviders = null;
23
+ let providersExpiresAt = 0;
24
+
25
+ async function getValidSession(sessionIp) {
26
+ const now = Date.now();
27
+ if (cachedSession && now < sessionExpiresAt) {
28
+ return cachedSession;
29
+ }
30
+ try {
31
+ const chalRes = await fetch(`${DOMAIN}/api/auth/challenge`, {
32
+ headers: {
33
+ "User-Agent": UA,
34
+ "X-Forwarded-For": sessionIp,
35
+ "X-Real-IP": sessionIp,
36
+ Origin: DOMAIN,
37
+ Referer: DOMAIN + "/",
38
+ },
39
+ signal: AbortSignal.timeout(7000),
40
+ });
41
+
42
+ if (!chalRes.ok) throw new Error();
43
+ const chalData = await chalRes.json();
44
+ const { challenge, difficulty } = chalData;
45
+ const targetPrefix = "0".repeat(Math.ceil((difficulty || 16) / 4));
46
+ let nonceStr = "";
47
+
48
+ for (let i = 0; i < 5000000; i++) {
49
+ const nonce = Math.random().toString(36).substring(2, 10);
50
+ const hash = crypto.createHash("sha256").update(`${challenge}:${nonce}`).digest("hex");
51
+ if (hash.startsWith(targetPrefix)) {
52
+ nonceStr = nonce;
53
+ break;
54
+ }
55
+ }
56
+
57
+ if (!nonceStr) throw new Error();
58
+
59
+ const res = await fetch(`${DOMAIN}/api/auth/session`, {
60
+ method: "POST",
61
+ headers: {
62
+ "Content-Type": "application/json",
63
+ "User-Agent": UA,
64
+ "X-Forwarded-For": sessionIp,
65
+ "X-Real-IP": sessionIp,
66
+ Origin: DOMAIN,
67
+ Referer: DOMAIN + "/",
68
+ },
69
+ body: JSON.stringify({ challenge, nonce: nonceStr }),
70
+ signal: AbortSignal.timeout(7000),
71
+ });
72
+
73
+ if (!res.ok) throw new Error();
74
+ const data = await res.json();
75
+ if (!data.token) throw new Error();
76
+
77
+ cachedSession = data.token;
78
+ sessionExpiresAt = now + (data.expiresIn || 600) * 1000 - 30000;
79
+ return cachedSession;
80
+ } catch (e) {
81
+ throw new Error();
82
+ }
83
+ }
84
+
85
+ async function getProviders() {
86
+ const now = Date.now();
87
+ if (cachedProviders && now < providersExpiresAt) return cachedProviders;
88
+ try {
89
+ const res = await fetch(`${DOMAIN}/api/providers`, {
90
+ headers: { "User-Agent": UA },
91
+ signal: AbortSignal.timeout(5000)
92
+ });
93
+ if (!res.ok) return [];
94
+ const data = await res.json();
95
+ cachedProviders = data.providers || [];
96
+ providersExpiresAt = now + 3600 * 1000;
97
+ return cachedProviders;
98
+ } catch (e) {
99
+ return [];
100
+ }
101
+ }
102
+
103
+ async function deriveKey(tokenString) {
104
+ const rawKey = await crypto.subtle.importKey("raw", enc.encode(tokenString), "HKDF", false, ["deriveKey"]);
105
+ return crypto.subtle.deriveKey(
106
+ { name: "HKDF", hash: "SHA-256", salt, info },
107
+ rawKey,
108
+ { name: "AES-GCM", length: 256 },
109
+ false,
110
+ ["encrypt", "decrypt"],
111
+ );
112
+ }
113
+
114
+ async function encryptParams(payloadObj, key) {
115
+ const iv = crypto.getRandomValues(new Uint8Array(12));
116
+ const data = enc.encode(JSON.stringify(payloadObj));
117
+ const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, data);
118
+ const combined = new Uint8Array(iv.length + ct.byteLength);
119
+ combined.set(iv);
120
+ combined.set(new Uint8Array(ct), iv.length);
121
+ return Buffer.from(combined).toString("base64");
122
+ }
123
+
124
+ async function decryptUrl(b64Url, key) {
125
+ const raw = Buffer.from(b64Url, "base64");
126
+ const iv = raw.slice(0, 12);
127
+ const ct = raw.slice(12);
128
+ try {
129
+ const plain = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ct);
130
+ return Buffer.from(plain).toString("utf8");
131
+ } catch {
132
+ return null;
133
+ }
134
+ }
135
+
136
+ async function decryptDeep(obj, key) {
137
+ if (!obj || typeof obj !== "object") return obj;
138
+ if (Array.isArray(obj)) return Promise.all(obj.map(x => decryptDeep(x, key)));
139
+
140
+ const out = {};
141
+ for (const [k, v] of Object.entries(obj)) {
142
+ if ((k === "url" || k === "file") && typeof v === "string" && v.length > 40) {
143
+ try {
144
+ const dec = await decryptUrl(v, key);
145
+ out[k] = dec ? dec : v;
146
+ } catch {
147
+ out[k] = v;
148
+ }
149
+ } else if (k === "links" && Array.isArray(v)) {
150
+ out[k] = await Promise.all(v.map(async x => {
151
+ if (typeof x === "string" && x.length > 40) {
152
+ try {
153
+ const dec = await decryptUrl(x, key);
154
+ return dec ? dec : x;
155
+ } catch {
156
+ return x;
157
+ }
158
+ }
159
+ return x;
160
+ }));
161
+ } else {
162
+ out[k] = await decryptDeep(v, key);
163
+ }
164
+ }
165
+ return out;
166
+ }
167
+
168
+ export async function getStream({ id, s, e, clientIP }) {
169
+ try {
170
+ const sessionIp = generateRandomIP();
171
+ const sessionJwt = await getValidSession(sessionIp);
172
+ const actualKey = await deriveKey(sessionJwt);
173
+
174
+ const isTv = s && e && s !== "null" && e !== "null";
175
+ const contentType = isTv ? "tv" : "movies";
176
+ const refererPath = isTv ? `tv/${id}-${s}-${e}/play` : `movie/${id}/play`;
177
+
178
+ let providers = await getProviders();
179
+ let activeProviders = providers.filter(p => p.active && (p.endpoints || []).includes(contentType));
180
+ if (activeProviders.length === 0) activeProviders = [{ name: null }];
181
+
182
+ const fetchProviderStream = async (provider) => {
183
+ try {
184
+ const payload = isTv
185
+ ? { type: "tv", id: id.toString(), season: s.toString(), episode: e.toString() }
186
+ : { type: "movie", id: id.toString() };
187
+
188
+ if (provider.name) payload.provider = provider.name;
189
+
190
+ const dParam = await encryptParams(payload, actualKey);
191
+
192
+ const streamRes = await fetch(`${DOMAIN}/api/stream?d=${encodeURIComponent(dParam)}`, {
193
+ headers: {
194
+ Authorization: `Bearer ${sessionJwt}`,
195
+ "User-Agent": UA,
196
+ "X-Forwarded-For": sessionIp,
197
+ "X-Real-IP": sessionIp,
198
+ Origin: DOMAIN,
199
+ Referer: `${DOMAIN}/${refererPath}`,
200
+ },
201
+ signal: AbortSignal.timeout(10000)
202
+ });
203
+
204
+ if (!streamRes.ok) return [];
205
+ let rawData = await streamRes.json();
206
+
207
+ if (rawData.enc) {
208
+ const raw = Buffer.from(rawData.enc, "base64");
209
+ const decIv = raw.slice(0, 12);
210
+ const decCt = raw.slice(12);
211
+ const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv: decIv }, actualKey, decCt);
212
+ rawData = JSON.parse(Buffer.from(decrypted).toString("utf-8"));
213
+ }
214
+
215
+ const streamData = await decryptDeep(rawData, actualKey);
216
+
217
+ if (!streamData.ok || !streamData.streams || streamData.streams.length === 0) return [];
218
+
219
+ const validStreams = [];
220
+ for (const stream of streamData.streams) {
221
+ if (!stream.url) continue;
222
+ if (stream.quality && stream.quality.toUpperCase() !== "AUTO" && streamData.streams.some(st => st.quality && st.quality.toUpperCase() === "AUTO")) continue;
223
+
224
+ let finalUrl = stream.url;
225
+ if (finalUrl.startsWith("/api/")) finalUrl = `${DOMAIN}${finalUrl}`;
226
+
227
+ validStreams.push({
228
+ url: finalUrl,
229
+ headers: {
230
+ "User-Agent": UA,
231
+ Referer: DOMAIN + "/",
232
+ Origin: DOMAIN,
233
+ },
234
+ server: stream.source || stream.server || provider.nickname || provider.name || "Goated",
235
+ quality: stream.quality || "Auto",
236
+ type: finalUrl.includes(".mp4") ? "mp4" : "hls",
237
+ skipVerify: true,
238
+ skipProxy: true,
239
+ });
240
+ }
241
+ return validStreams;
242
+ } catch (err) {
243
+ return [];
244
+ }
245
+ };
246
+
247
+ const promises = activeProviders.map(p => fetchProviderStream(p));
248
+ const results = await Promise.allSettled(promises);
249
+
250
+ const allUrls = [];
251
+ for (const res of results) {
252
+ if (res.status === "fulfilled" && res.value.length > 0) {
253
+ allUrls.push(...res.value);
254
+ }
255
+ }
256
+
257
+ if (allUrls.length > 0) {
258
+ return { ...allUrls[0], allUrls };
259
+ }
260
+ } catch (err) { }
261
+ return null;
262
+ }
@@ -0,0 +1,82 @@
1
+ import crypto from 'node:crypto';
2
+ import { fetchJson, fetchText, USER_AGENT } from '../utils/helpers.js';
3
+
4
+ const API_BASE = 'https://enc-dec.app/api';
5
+ const DOMAINS = ['hexa.su', 'flixer.su'];
6
+
7
+ async function getChallengeToken() {
8
+ try {
9
+ const data = await fetchJson(`${API_BASE}/enc-hexa`, { signal: AbortSignal.timeout(6000) });
10
+ return data?.status === 200 ? data.result.token : null;
11
+ } catch { return null; }
12
+ }
13
+
14
+ export async function getStream({ id, s, e, server }) {
15
+ try {
16
+ const apiKey = crypto.randomBytes(32).toString('hex');
17
+ const capToken = await getChallengeToken();
18
+ if (!capToken) return null;
19
+
20
+ const isTv = s != null && e != null;
21
+ let decrypted = null;
22
+
23
+ for (const domain of DOMAINS) {
24
+ try {
25
+ const url = isTv
26
+ ? `https://theemoviedb.${domain}/api/tmdb/tv/${id}/season/${s}/episode/${e}/images`
27
+ : `https://theemoviedb.${domain}/api/tmdb/movie/${id}/images`;
28
+
29
+ const encrypted = await fetchText(url, {
30
+ headers: {
31
+ 'User-Agent': USER_AGENT,
32
+ 'Referer': `https://${domain}/`,
33
+ 'Accept': 'text/plain',
34
+ 'X-Fingerprint-Lite': 'e9136c41504646444',
35
+ 'X-Api-Key': apiKey,
36
+ 'X-Cap-Token': capToken
37
+ },
38
+ signal: AbortSignal.timeout(10000)
39
+ });
40
+
41
+ if (!encrypted) continue;
42
+
43
+ const decData = await fetchJson(`${API_BASE}/dec-hexa`, {
44
+ method: 'POST',
45
+ headers: { 'Content-Type': 'application/json' },
46
+ body: JSON.stringify({ text: encrypted, key: apiKey }),
47
+ signal: AbortSignal.timeout(8000)
48
+ });
49
+
50
+ if (decData?.status === 200 && decData.result?.sources?.length) {
51
+ decrypted = decData.result;
52
+ break;
53
+ }
54
+ } catch { continue; }
55
+ }
56
+
57
+ if (!decrypted?.sources?.length) return null;
58
+
59
+ let sources = decrypted.sources.map(src => ({
60
+ url: src.url,
61
+ server: `Hexa - ${src.name || src.server || 'Server'}`,
62
+ type: src.url.includes('.m3u8') ? 'hls' : 'mp4',
63
+ quality: src.quality || 'Auto',
64
+ subtitles: (decrypted.subtitles || []).map(sub => ({
65
+ url: sub.url,
66
+ lang: sub.label || sub.lang || 'Unknown'
67
+ })),
68
+ skipProxy: false
69
+ }));
70
+
71
+ if (server && server !== 'all') {
72
+ const cleanName = server.replace('Hexa - ', '');
73
+ sources = sources.filter(u => u.server.includes(cleanName));
74
+ }
75
+
76
+ return sources.length ? { allUrls: sources } : null;
77
+ } catch { return null; }
78
+ }
79
+
80
+ export async function getSources() {
81
+ return ['Hexa'];
82
+ }
@@ -0,0 +1,60 @@
1
+ import { fetchJson, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
2
+
3
+ export const SKIP_VERIFY = true;
4
+ export const MULTI_URL = false;
5
+
6
+ const ENC_API = "https://enc-dec.app/api";
7
+ const BASE = "https://kisskh.do";
8
+ const HEADERS = { 'User-Agent': USER_AGENT, Accept: 'application/json' };
9
+
10
+ function cleanTitle(t) { return t ? t.toLowerCase().replace(/\(\d{4}\)/g, '').replace(/[^a-z0-9]/g, '') : ''; }
11
+
12
+ async function encKey(episodeId, type) {
13
+ try {
14
+ const data = await fetchJson(`${ENC_API}/enc-kisskh?text=${episodeId}&type=${type}`, { signal: AbortSignal.timeout(6000) });
15
+ return data?.status === 200 ? data.result : null;
16
+ } catch { return null; }
17
+ }
18
+
19
+ export async function getStream({ id, s, e }) {
20
+ try {
21
+ const info = await getTmdbInfo(id, s ? 'tv' : 'movie', s);
22
+ if (!info) return null;
23
+ let drama = null, bestScore = -1;
24
+ for (const title of (info.titles.length > 1 ? [...info.titles].reverse() : info.titles)) {
25
+ const results = await fetchJson(`${BASE}/api/DramaList/Search?q=${encodeURIComponent(title)}`, { headers: HEADERS, signal: AbortSignal.timeout(6000) }).catch(() => []);
26
+ const targetClean = cleanTitle(title);
27
+ for (const item of results) {
28
+ const parts = (item.title || '').split(/\s*-\s*/).map(cleanTitle);
29
+ let score = 0;
30
+ if (parts.some(p => p === targetClean)) score += 5;
31
+ else if (parts.some(p => p.includes(targetClean) || targetClean.includes(p))) score += 3;
32
+ else continue;
33
+ if (score > bestScore) { bestScore = score; drama = item; }
34
+ }
35
+ if (drama && bestScore >= 5) break;
36
+ }
37
+ if (!drama) return null;
38
+ const detail = await fetchJson(`${BASE}/api/DramaList/Drama/${drama.id}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
39
+ const episodeId = detail?.episodes?.find(ep => Math.floor(ep.number) === Number(e || 1))?.id;
40
+ if (!episodeId) return null;
41
+ const vidKey = await encKey(episodeId, 'vid');
42
+ if (!vidKey) return null;
43
+ const videoData = await fetchJson(`${BASE}/api/DramaList/Episode/${episodeId}.png?err=false&ts=&time=&kkey=${vidKey}`, { headers: HEADERS, signal: AbortSignal.timeout(10000) });
44
+ if (!videoData?.Video) return null;
45
+ let subtitles = [];
46
+ const subKey = await encKey(episodeId, 'sub');
47
+ if (subKey) {
48
+ try {
49
+ const subList = await fetchJson(`${BASE}/api/Sub/${episodeId}?kkey=${subKey}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
50
+ if (Array.isArray(subList)) subtitles = subList.map(sub => ({ url: sub.src, label: sub.label, lang: sub.land, default: !!sub.default }));
51
+ } catch { }
52
+ }
53
+ return { allUrls: [{ url: videoData.Video, type: videoData.Video.includes('.m3u8') ? 'hls' : 'mp4', audio: 'sub', server: 'KissKH', skipProxy: false, subtitles }] };
54
+ } catch { return null; }
55
+ }
56
+
57
+ export async function getSources(args) {
58
+ const stream = await getStream(args);
59
+ return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : [];
60
+ }
@@ -0,0 +1,78 @@
1
+ import { USER_AGENT, fetchText, fetchJson } from '../utils/helpers.js';
2
+
3
+ const TMDB_BASE = 'https://api.themoviedb.org/3';
4
+ const LM_DOMAINS = ['https://www.lookmovie2.to', 'https://lookmovie2.to', 'https://lookmovie.foundation'];
5
+ const HEADERS_BASE = { 'User-Agent': USER_AGENT, 'Accept-Language': 'en-US,en;q=0.9' };
6
+
7
+ async function searchLookMovie(type, title, year, clientIP) {
8
+ for (const base of LM_DOMAINS) {
9
+ try {
10
+ const headers = { ...HEADERS_BASE, 'Accept': 'application/json', 'Referer': `${base}/`, 'X-Requested-With': 'XMLHttpRequest', ...(clientIP && { 'X-Forwarded-For': clientIP }) };
11
+ const data = await fetchJson(`${base}/api/v1/${type}/do-search/?q=${encodeURIComponent(title)}`, { headers, signal: AbortSignal.timeout(4000) });
12
+ const results = data?.result;
13
+ if (results?.length) {
14
+ const match = results.find(r => String(r.year) === String(year)) ?? results.find(r => r.title?.toLowerCase() === title.toLowerCase()) ?? results[0];
15
+ if (match) return { match, base };
16
+ }
17
+ } catch { }
18
+ }
19
+ return null;
20
+ }
21
+
22
+ const STORAGE_RE = /window\[['"](?:movie|show)_storage['"]\]\s*=\s*\{([^}]+)\}/s;
23
+ const ID_MOVIE_RE = /['"]?(?:id_movie|movieId)['"]?\s*[:=]\s*['"]?(\d+)['"]?/i;
24
+ const SEASONS_RE = /seasons\s*:\s*(\[[\s\S]+?\])\s*[,}]/;
25
+
26
+ function getEpisodeIdFast(html, s, e) {
27
+ const sMatch = html.match(STORAGE_RE);
28
+ if (sMatch) {
29
+ const block = sMatch[1], sm = block.match(SEASONS_RE);
30
+ if (sm) {
31
+ try {
32
+ const seasons = JSON.parse(sm[1]);
33
+ const season = seasons.find(x => String(x?.season ?? x?.meta?.season) === String(s));
34
+ if (season) {
35
+ const ep = Array.isArray(season.episodes) ? season.episodes.find(x => String(x.episode) === String(e)) : (season.episodes?.[String(e)] || Object.values(season.episodes || {}).find(x => String(x.episode) === String(e)));
36
+ if (ep) return String(ep.id_episode ?? ep.id);
37
+ }
38
+ } catch { }
39
+ }
40
+ }
41
+ const parts = html.split(/id_episode['"]?\s*[:=]\s*['"]?(\d+)['"]?/i);
42
+ for (let i = 1; i < parts.length; i += 2) {
43
+ const context = parts[i - 1].slice(-300) + parts[i + 1].slice(0, 300);
44
+ if (context.match(new RegExp(`['"]?episode['"]?\\s*[:=]\\s*['"]?${e}['"]?`, 'i')) && context.match(new RegExp(`['"]?season['"]?\\s*[:=]\\s*['"]?${s}['"]?`, 'i'))) return parts[i];
45
+ }
46
+ const am = html.match(new RegExp(`data-season=["']${s}["'][^>]*?data-episode=["']${e}["'][^>]*?data-id=["'](\\d+)["']`, 'i')) || html.match(new RegExp(`data-episode=["']${e}["'][^>]*?data-season=["']${s}["'][^>]*?data-id=["'](\\d+)["']`, 'i'));
47
+ return am ? am[1] : null;
48
+ }
49
+
50
+ export async function getStream({ id, s, e, tmdbApiKey, clientIP }) {
51
+ try {
52
+ const tmdbKey = tmdbApiKey;
53
+ if (!tmdbKey) return null;
54
+ const isTV = s != null && e != null;
55
+ const typeStr = isTV ? 'shows' : 'movies';
56
+ const tmdbData = await fetchJson(`${TMDB_BASE}/${isTV ? 'tv' : 'movie'}/${id}?api_key=${tmdbKey}`, { signal: AbortSignal.timeout(3000) });
57
+ const title = tmdbData?.title || tmdbData?.name;
58
+ const year = (tmdbData?.first_air_date || tmdbData?.release_date || '').slice(0, 4);
59
+ if (!title) return null;
60
+ const searchRes = await searchLookMovie(typeStr, title, year, clientIP);
61
+ if (!searchRes?.match?.slug) return null;
62
+ const { match, base } = searchRes;
63
+ const headers = { ...HEADERS_BASE, 'Accept': 'text/html', 'Referer': `${base}/`, ...(clientIP && { 'X-Forwarded-For': clientIP }) };
64
+ const html = await fetchText(`${base}/${typeStr}/play/${match.slug}`, { headers, signal: AbortSignal.timeout(8000) });
65
+ const storageMatch = html.match(STORAGE_RE);
66
+ if (!storageMatch) return null;
67
+ const hashMatch = storageMatch[1].match(/hash\s*:\s*['"]([^'"]+)['"]/);
68
+ const expiresMatch = storageMatch[1].match(/expires\s*:\s*(\d+)/);
69
+ if (!hashMatch || !expiresMatch) return null;
70
+ const streamId = isTV ? getEpisodeIdFast(html, s, e) : (match.id_movie || match.id || html.match(ID_MOVIE_RE)?.[1]);
71
+ if (!streamId) return null;
72
+ const accessHeaders = { ...HEADERS_BASE, 'Accept': 'application/json', 'Referer': `${base}/`, 'X-Requested-With': 'XMLHttpRequest', ...(clientIP && { 'X-Forwarded-For': clientIP }) };
73
+ const data = await fetchJson(`${base}/api/v1/security/${isTV ? 'episode' : 'movie'}-access?id_${isTV ? 'episode' : 'movie'}=${streamId}&hash=${hashMatch[1]}&expires=${expiresMatch[1]}`, { headers: accessHeaders, signal: AbortSignal.timeout(8000) });
74
+ const streams = data?.streams ?? data?.result?.streams ?? data?.data?.streams ?? data;
75
+ const allUrls = Object.entries(streams || {}).filter(([, v]) => typeof v === 'string' && v.includes('.m3u8')).map(([quality, url]) => ({ url, quality }));
76
+ return allUrls.length ? { allUrls } : null;
77
+ } catch { return null; }
78
+ }
@@ -0,0 +1,115 @@
1
+ import { USER_AGENT } from '../utils/helpers.js';
2
+
3
+ const BASE_URL = "https://mapple.club";
4
+ const PROXY_HEADERS = { Referer: `${BASE_URL}/`, Origin: BASE_URL, "User-Agent": USER_AGENT };
5
+
6
+ const K = new Uint32Array([0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]);
7
+ const W_BUF = new Uint32Array(64);
8
+
9
+ function sha256Into(buf, total, out) {
10
+ let h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a, h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19;
11
+ const dv = new DataView(buf.buffer, buf.byteOffset, total);
12
+ for (let off = 0; off < total; off += 64) {
13
+ for (let i = 0; i < 16; i++) W_BUF[i] = dv.getUint32(off + i * 4, false);
14
+ for (let i = 16; i < 64; i++) {
15
+ const a = W_BUF[i - 15], b = W_BUF[i - 2];
16
+ const s0 = ((a >>> 7) | (a << 25)) ^ ((a >>> 18) | (a << 14)) ^ (a >>> 3);
17
+ const s1 = ((b >>> 17) | (b << 15)) ^ ((b >>> 19) | (b << 13)) ^ (b >>> 10);
18
+ W_BUF[i] = (W_BUF[i - 16] + s0 + W_BUF[i - 7] + s1) | 0;
19
+ }
20
+ let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7;
21
+ for (let i = 0; i < 64; i++) {
22
+ const S1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
23
+ const ch = (e & f) ^ (~e & g);
24
+ const t1 = (h + S1 + ch + K[i] + W_BUF[i]) | 0;
25
+ const S0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
26
+ const maj = (a & b) ^ (a & c) ^ (b & c);
27
+ const t2 = (S0 + maj) | 0;
28
+ h = g; g = f; f = e; e = (d + t1) | 0; d = c; c = b; b = a; a = (t1 + t2) | 0;
29
+ }
30
+ h0 = (h0 + a) | 0; h1 = (h1 + b) | 0; h2 = (h2 + c) | 0; h3 = (h3 + d) | 0; h4 = (h4 + e) | 0; h5 = (h5 + f) | 0; h6 = (h6 + g) | 0; h7 = (h7 + h) | 0;
31
+ }
32
+ const odv = new DataView(out.buffer, out.byteOffset, 32);
33
+ odv.setUint32(0, h0 >>> 0, false); odv.setUint32(4, h1 >>> 0, false); odv.setUint32(8, h2 >>> 0, false); odv.setUint32(12, h3 >>> 0, false); odv.setUint32(16, h4 >>> 0, false); odv.setUint32(20, h5 >>> 0, false); odv.setUint32(24, h6 >>> 0, false); odv.setUint32(28, h7 >>> 0, false);
34
+ }
35
+
36
+ function solvePoW(challenge, difficulty) {
37
+ const encoder = new TextEncoder();
38
+ const cBuf = encoder.encode(challenge);
39
+ const maxLen = cBuf.length + 16;
40
+ const total = (maxLen + 1 + 8 + 63) & ~63;
41
+ const buf = new Uint8Array(total);
42
+ buf.set(cBuf);
43
+ const hash = new Uint8Array(32);
44
+ const maskBytes = Math.floor(difficulty / 8);
45
+ const maskBits = difficulty % 8;
46
+ const finalMask = maskBits > 0 ? (0xff << (8 - maskBits)) & 0xff : 0;
47
+ const dv = new DataView(buf.buffer);
48
+ for (let nonce = 0; nonce < 10000000; nonce++) {
49
+ const str = nonce.toString();
50
+ const len = cBuf.length + str.length;
51
+ for (let i = 0; i < str.length; i++) buf[cBuf.length + i] = str.charCodeAt(i);
52
+ buf[len] = 0x80;
53
+ for (let i = len + 1; i < total - 8; i++) buf[i] = 0;
54
+ dv.setUint32(total - 4, len * 8, false);
55
+ dv.setUint32(total - 8, 0, false);
56
+ sha256Into(buf, total, hash);
57
+ let ok = true;
58
+ for (let i = 0; i < maskBytes; i++) if (hash[i] !== 0) { ok = false; break; }
59
+ if (ok && (finalMask === 0 || (hash[maskBytes] & finalMask) === 0)) return { found: true, nonce: str };
60
+ }
61
+ return { found: false };
62
+ }
63
+
64
+ const SERVER_LIST = [{ id: "mapple", name: "Mapple" }, { id: "s1", name: "Nexus" }, { id: "s2", name: "Cipher" }, { id: "s3", name: "Pulse" }, { id: "s4", name: "Vertex" }, { id: "s10", name: "Chimp" }];
65
+
66
+ export async function getStream(args) {
67
+ const { id, s, e, server } = args;
68
+ const isTv = s != null && e != null;
69
+ const mediaType = isTv ? "tv" : "movie";
70
+ const tvSlug = isTv ? `${s}-${e}` : "";
71
+ const pageUrl = isTv ? `${BASE_URL}/watch/tv/${id}/${s}/${e}` : `${BASE_URL}/watch/movie/${id}`;
72
+ try {
73
+ const pageRes = await fetch(pageUrl, { headers: PROXY_HEADERS, signal: AbortSignal.timeout(10000) });
74
+ if (!pageRes.ok) return null;
75
+ const html = await pageRes.text();
76
+ const reqMatch = html.match(/window\.__REQUEST_TOKEN__\s*=\s*"([^"]+)"/);
77
+ if (!reqMatch) return null;
78
+ const requestToken = reqMatch[1];
79
+ const initRes = await fetch(`${BASE_URL}/api/playback-init`, { method: "POST", headers: { "Content-Type": "application/json", ...PROXY_HEADERS, Referer: pageUrl }, body: JSON.stringify({ mediaId: Number(id), mediaType, requestToken }), signal: AbortSignal.timeout(10000) });
80
+ const initData = await initRes.json();
81
+ let streamToken = initData.token;
82
+ if (initData.requiresPow && initData.pow) {
83
+ const powRes = solvePoW(initData.pow.challenge, initData.pow.difficulty);
84
+ if (powRes.found) {
85
+ const solveRes = await fetch(`${BASE_URL}/api/playback-init`, { method: "POST", headers: { "Content-Type": "application/json", ...PROXY_HEADERS, Referer: pageUrl }, body: JSON.stringify({ mediaId: Number(id), mediaType, requestToken, pow: { challengeId: initData.pow.challengeId, nonce: powRes.nonce } }), signal: AbortSignal.timeout(10000) });
86
+ streamToken = (await solveRes.json()).token;
87
+ }
88
+ }
89
+ if (!streamToken) return null;
90
+ let targets = SERVER_LIST;
91
+ if (server && server !== "all") {
92
+ const clean = server.replace("Mapple - ", "");
93
+ targets = SERVER_LIST.filter(sv => sv.name === clean);
94
+ if (!targets.length) targets = SERVER_LIST;
95
+ }
96
+ const allUrls = [];
97
+ for (const srv of targets) {
98
+ try {
99
+ const streamUrl = `${BASE_URL}/api/stream?mediaId=${id}&mediaType=${mediaType}&tv_slug=${tvSlug}&source=${srv.id}&apikey=mptv_sk_a8f29c4e7b3d1f&requestToken=${requestToken}&token=${streamToken}`;
100
+ const streamRes = await fetch(streamUrl, { headers: { ...PROXY_HEADERS, Referer: pageUrl }, signal: AbortSignal.timeout(8000) });
101
+ const streamData = await streamRes.json();
102
+ if (streamData.success && streamData.data?.stream_url) {
103
+ let fileUrl = streamData.data.stream_url;
104
+ if (fileUrl.includes('omena-puu') || fileUrl.includes('nocach')) fileUrl += fileUrl.includes('?') ? '&format=.m3u8' : '?format=.m3u8';
105
+ allUrls.push({ url: fileUrl, server: `Mapple - ${srv.name}`, headers: PROXY_HEADERS, skipProxy: false, skipVerify: true, skipHlsCheck: true });
106
+ }
107
+ } catch { }
108
+ }
109
+ return allUrls.length ? { allUrls } : null;
110
+ } catch { return null; }
111
+ }
112
+
113
+ export async function getSources() { return SERVER_LIST.map(s => `Mapple - ${s.name}`); }
114
+ export const SKIP_VERIFY = true;
115
+ export const MULTI_URL = true;