@vyla-entertainment/sdk 1.8.0 → 1.9.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.
@@ -0,0 +1,107 @@
1
+ import { fetchJson, getTmdbInfo, USER_AGENT } from '../utils/helpers.js';
2
+
3
+ const BASE = "https://kaa.lt";
4
+ const HLS_BASE = "https://hls.krussdomi.com/manifest";
5
+ const HEADERS = { "User-Agent": USER_AGENT, Accept: "application/json" };
6
+
7
+ async function kaaSearch(query) {
8
+ const res = await fetchJson(`${BASE}/api/fsearch`, {
9
+ method: "POST",
10
+ headers: { ...HEADERS, "Content-Type": "application/json" },
11
+ body: JSON.stringify({ page: 1, query }),
12
+ }).catch(() => null);
13
+ return Array.isArray(res?.result) ? res.result : [];
14
+ }
15
+
16
+ async function kaaShowInfo(showSlug) {
17
+ return fetchJson(`${BASE}/api/show/${showSlug}`, { headers: HEADERS }).catch(() => null);
18
+ }
19
+
20
+ async function kaaEpisodePage(showSlug, ep) {
21
+ return fetchJson(`${BASE}/api/show/${showSlug}/episodes?ep=${ep}&lang=ja-JP`, { headers: HEADERS }).catch(() => null);
22
+ }
23
+
24
+ async function kaaAllEpisodes(showSlug) {
25
+ const first = await kaaEpisodePage(showSlug, 1);
26
+ if (!first) return [];
27
+ const pages = Array.isArray(first.pages) ? first.pages : [];
28
+ const all = Array.isArray(first.result) ? [...first.result] : [];
29
+
30
+ if (pages.length > 1) {
31
+ const rest = await Promise.all(
32
+ pages.slice(1).map(async (pg) => {
33
+ const startEp = pg.eps?.[0];
34
+ if (!startEp) return [];
35
+ const d = await kaaEpisodePage(showSlug, startEp);
36
+ return Array.isArray(d?.result) ? d.result : [];
37
+ })
38
+ );
39
+ for (const batch of rest) all.push(...batch);
40
+ }
41
+
42
+ return all;
43
+ }
44
+
45
+ async function kaaEpisodeServers(showSlug, fullEpSlug) {
46
+ return fetchJson(`${BASE}/api/show/${showSlug}/episode/${fullEpSlug}`, { headers: HEADERS }).catch(() => null);
47
+ }
48
+
49
+ export async function getStream({ id, s, e, tmdbApiKey }) {
50
+ try {
51
+ const isTv = !!s;
52
+ const mediaType = isTv ? 'tv' : 'movie';
53
+ const epNum = e ? Number(e) : 1;
54
+ const seasonNum = s ? Number(s) : 1;
55
+
56
+ const tmdbInfo = await getTmdbInfo(tmdbApiKey, id, mediaType, seasonNum);
57
+ const titles = tmdbInfo?.titles || [];
58
+
59
+ let showSlug = null;
60
+ for (const title of titles) {
61
+ const results = await kaaSearch(title);
62
+ if (results.length) {
63
+ showSlug = results[0].slug;
64
+ break;
65
+ }
66
+ }
67
+
68
+ if (!showSlug) return null;
69
+
70
+ const showInfo = await kaaShowInfo(showSlug);
71
+ let fullEpSlug = null;
72
+
73
+ if (showInfo?.type === "movie") {
74
+ const m = (showInfo.watch_uri || "").match(/\/(ep-(\d+)-([a-f0-9]+))$/i);
75
+ if (m) fullEpSlug = m[1];
76
+ } else {
77
+ const episodes = await kaaAllEpisodes(showSlug);
78
+ const epItem = episodes.find((item) => item.episode_number === epNum);
79
+ if (epItem) fullEpSlug = `ep-${epItem.episode_number}-${epItem.slug}`;
80
+ }
81
+
82
+ if (!fullEpSlug) return null;
83
+
84
+ const episodeData = await kaaEpisodeServers(showSlug, fullEpSlug);
85
+ const servers = Array.isArray(episodeData?.servers) ? episodeData.servers : [];
86
+
87
+ const urls = [];
88
+ for (const srv of servers) {
89
+ if (!srv.src) continue;
90
+ const m = srv.src.match(/[?&]id=([^&]+)/);
91
+ if (m) {
92
+ urls.push({
93
+ url: `${HLS_BASE}/${m[1]}/master.m3u8`,
94
+ server: `KickAssAnime (${srv.name || 'Server'})`,
95
+ });
96
+ }
97
+ }
98
+
99
+ return urls.length ? { allUrls: urls } : null;
100
+ } catch (err) {
101
+ return null;
102
+ }
103
+ }
104
+
105
+ export async function getSources() {
106
+ return ['KickAssAnime'];
107
+ }
@@ -13,15 +13,8 @@ const HEADERS = {
13
13
  };
14
14
 
15
15
  const SERVERS = [
16
- 'pseudo',
17
- 'lynx',
18
16
  'tik',
19
- 'ipcloud',
20
- 'v4:English',
21
- 'turkce',
22
- 'v5:Hindi',
23
- 'v4:Hindi',
24
- 'v6:Hindi'
17
+ 'hindiv3'
25
18
  ];
26
19
 
27
20
  const TIMEOUT_MS = 15000;
@@ -0,0 +1,324 @@
1
+ import crypto from 'node:crypto';
2
+ import { fetchJson, getTmdbInfo, tmdbToAnilist, USER_AGENT } from '../utils/helpers.js';
3
+
4
+ const BASE = "https://reanime.to";
5
+ const FLIX = "https://flixcloud.cc";
6
+ const HEADERS = { "User-Agent": USER_AGENT, Accept: "application/json, */*" };
7
+
8
+ async function sha256hex(s) {
9
+ const buf = await crypto.subtle.digest("SHA-256", typeof s === "string" ? new TextEncoder().encode(s) : s);
10
+ return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
11
+ }
12
+
13
+ function b64toU8(b64) {
14
+ const bin = Buffer.from(b64, 'base64');
15
+ return new Uint8Array(bin);
16
+ }
17
+
18
+ async function deriveFields(seed) {
19
+ let e = seed;
20
+ for (let i = 0; i < 3; i++) e = await sha256hex(e + i);
21
+ let l = e;
22
+ for (let i = 0; i < 3; i++) l = await sha256hex(l + i);
23
+ return {
24
+ keyField: "kf_" + e.substring(8, 16),
25
+ ivField: "ivf_" + e.substring(16, 24),
26
+ containerName: "cd_" + e.substring(24, 32),
27
+ arrayName: "ad_" + e.substring(32, 40),
28
+ objectName: "od_" + e.substring(40, 48),
29
+ tokenField: e.substring(48, 64) + "_" + e.substring(56, 64),
30
+ keyFrag2Field: l.substring(0, 16) + "_" + l.substring(16, 24)
31
+ };
32
+ }
33
+
34
+ function extractSsrObj(html) {
35
+ const m = html.match(/\{type:"data",data:(\{)/);
36
+ if (!m) return null;
37
+ let depth = 0;
38
+ const start = html.indexOf("{", m.index + m[0].length - 1);
39
+ for (let i = start; i < html.length; i++) {
40
+ if (html[i] === "{") depth++;
41
+ else if (html[i] === "}") {
42
+ if (--depth === 0) return html.slice(start, i + 1);
43
+ }
44
+ }
45
+ return null;
46
+ }
47
+
48
+ function parseJsLiteral(src) {
49
+ let i = 0;
50
+ function ws() { while (i < src.length && /\s/.test(src[i])) i++; }
51
+ function parseValue() {
52
+ ws();
53
+ if (src[i] === "{") return parseObject();
54
+ if (src[i] === "[") return parseArray();
55
+ if (src[i] === '"') return parseDStr();
56
+ if (src[i] === "'") return parseSStr();
57
+ if (src.startsWith("true", i)) { i += 4; return true; }
58
+ if (src.startsWith("false", i)) { i += 5; return false; }
59
+ if (src.startsWith("null", i)) { i += 4; return null; }
60
+ if (src.startsWith("undefined", i)) { i += 9; return null; }
61
+ if (src.startsWith("!0", i)) { i += 2; return true; }
62
+ if (src.startsWith("!1", i)) { i += 2; return false; }
63
+ const m = src.slice(i).match(/^-?[\d.]+([eE][+-]?\d+)?/);
64
+ if (m) { i += m[0].length; return parseFloat(m[0]); }
65
+ throw new Error(`Parse error`);
66
+ }
67
+ function parseDStr() {
68
+ let r = ""; i++;
69
+ while (i < src.length && src[i] !== '"') {
70
+ if (src[i] === "\\") { i++; r += src[i++]; }
71
+ else r += src[i++];
72
+ }
73
+ i++; return r;
74
+ }
75
+ function parseSStr() {
76
+ let r = ""; i++;
77
+ while (i < src.length && src[i] !== "'") {
78
+ if (src[i] === "\\") { i++; r += src[i++]; }
79
+ else r += src[i++];
80
+ }
81
+ i++; return r;
82
+ }
83
+ function parseKey() {
84
+ ws();
85
+ if (src[i] === '"') return parseDStr();
86
+ if (src[i] === "'") return parseSStr();
87
+ const m = src.slice(i).match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/);
88
+ if (m) { i += m[0].length; return m[0]; }
89
+ throw new Error(`Bad key`);
90
+ }
91
+ function parseObject() {
92
+ const obj = {}; i++; ws();
93
+ while (i < src.length && src[i] !== "}") {
94
+ if (src[i] === ",") { i++; ws(); continue; }
95
+ const k = parseKey(); ws(); i++;
96
+ obj[k] = parseValue(); ws();
97
+ }
98
+ i++; return obj;
99
+ }
100
+ function parseArray() {
101
+ const arr = []; i++; ws();
102
+ while (i < src.length && src[i] !== "]") {
103
+ if (src[i] === ",") { i++; ws(); continue; }
104
+ arr.push(parseValue()); ws();
105
+ }
106
+ i++; return arr;
107
+ }
108
+ return parseValue();
109
+ }
110
+
111
+ function parseWasmDecrypt(wasmBytes) {
112
+ const b = wasmBytes;
113
+ let pos = 8;
114
+ while (pos < b.length) {
115
+ const secId = b[pos++];
116
+ let sz = 0, sh = 0, by;
117
+ do { by = b[pos++]; sz |= (by & 127) << sh; sh += 7; } while (by & 128);
118
+ if (secId === 10) {
119
+ pos++;
120
+ let sbs = 0, sh2 = 0, by2;
121
+ do { by2 = b[pos++]; sbs |= (by2 & 127) << sh2; sh2 += 7; } while (by2 & 128);
122
+ pos += sbs; break;
123
+ }
124
+ pos += sz;
125
+ }
126
+ let rbs = 0, sh3 = 0, by3;
127
+ do { by3 = b[pos++]; rbs |= (by3 & 127) << sh3; sh3 += 7; } while (by3 & 128);
128
+ const r = b.slice(pos, pos + rbs);
129
+ function leb(arr, i) {
130
+ let v = 0, s = 0, b2;
131
+ do { b2 = arr[i++]; v |= (b2 & 127) << s; s += 7; } while (b2 & 128);
132
+ return [v, i];
133
+ }
134
+ const XOR_END = [32, 2, 32, 5, 106, 45, 0, 0, 115, 33, 6];
135
+ let txStart = -1;
136
+ outer: for (let i = 0; i < r.length - XOR_END.length; i++) {
137
+ for (let j = 0; j < XOR_END.length; j++) if (r[i + j] !== XOR_END[j]) continue outer;
138
+ txStart = i + XOR_END.length; break;
139
+ }
140
+ if (txStart < 0) return null;
141
+ let txEnd = -1, step = 36;
142
+ for (let i = txStart; i < r.length - 4; i++) {
143
+ if (r[i] === 32 && r[i + 1] === 5 && r[i + 2] === 65) {
144
+ const [val, ni] = leb(r, i + 3);
145
+ if (r[ni] === 108) { txEnd = i; step = val; break; }
146
+ }
147
+ }
148
+ if (txEnd < 0) return null;
149
+ const code = r.slice(txStart, txEnd);
150
+ function transform(inputByte) {
151
+ let local6 = inputByte & 255;
152
+ const stk = []; let i = 0;
153
+ while (i < code.length) {
154
+ const op = code[i++];
155
+ if (op === 32) { const [idx, ni] = leb(code, i); i = ni; stk.push(idx === 6 ? local6 : 0); }
156
+ else if (op === 33) { const [idx, ni] = leb(code, i); i = ni; const v = stk.pop(); if (idx === 6) local6 = v & 255; }
157
+ else if (op === 65) { const [v, ni] = leb(code, i); i = ni; stk.push(v); }
158
+ else if (op === 106) { const b2 = stk.pop(), a = stk.pop(); stk.push(a + b2 & 255); }
159
+ else if (op === 107) { const b2 = stk.pop(), a = stk.pop(); stk.push(a - b2 + 256 & 255); }
160
+ else if (op === 113) { const b2 = stk.pop(), a = stk.pop(); stk.push(a & b2 & 255); }
161
+ else if (op === 114) { const b2 = stk.pop(), a = stk.pop(); stk.push((a | b2) & 255); }
162
+ else if (op === 115) { const b2 = stk.pop(), a = stk.pop(); stk.push((a ^ b2) & 255); }
163
+ else if (op === 116) { const b2 = stk.pop(), a = stk.pop(); stk.push(a << (b2 & 7) & 255); }
164
+ else if (op === 118) { const b2 = stk.pop(), a = stk.pop(); stk.push(a >>> (b2 & 7) & 255); }
165
+ }
166
+ return local6;
167
+ }
168
+ return { step, transform };
169
+ }
170
+
171
+ function runDecrypt(wasmBytes, frag1, kf2, T, seedInt) {
172
+ const parsed = parseWasmDecrypt(wasmBytes);
173
+ if (!parsed) return null;
174
+ const { step, transform } = parsed;
175
+ const out = new Uint8Array(frag1.length);
176
+ for (let i = 0; i < frag1.length; i++) {
177
+ const c = (frag1[i] ^ kf2[i] ^ T[i]) & 255;
178
+ out[i] = transform(c) ^ i * step + seedInt & 255;
179
+ }
180
+ return out;
181
+ }
182
+
183
+ async function decryptEmbed(html) {
184
+ const raw = extractSsrObj(html);
185
+ if (!raw) return null;
186
+ const data = parseJsLiteral(raw);
187
+ const seed = data.obfuscation_seed;
188
+ if (!seed) return null;
189
+
190
+ const fields = await deriveFields(seed);
191
+ const ocd = data.obfuscated_crypto_data;
192
+ if (!ocd) return null;
193
+
194
+ const container = ocd[fields.containerName];
195
+ if (!container) return null;
196
+
197
+ const arr = container[fields.arrayName];
198
+ if (!arr || !arr[0]) return null;
199
+
200
+ const obj = arr[0][fields.objectName];
201
+ if (!obj) return null;
202
+
203
+ const frag1 = b64toU8(obj[fields.keyField]);
204
+ const iv = b64toU8(obj[fields.ivField]);
205
+ const kf2raw = data[fields.keyFrag2Field];
206
+ if (!kf2raw) return null;
207
+
208
+ const kf2 = b64toU8(kf2raw);
209
+ const token = data[fields.tokenField];
210
+ if (!token) return null;
211
+
212
+ const tokData = await fetchJson(`${FLIX}/api/m3u8/${token}`, { headers: { ...HEADERS, Referer: `${BASE}/` } }).catch(() => null);
213
+ if (!tokData) return null;
214
+
215
+ const vidKey = (await sha256hex(token + "vid")).substring(0, 10);
216
+ const keyKey = (await sha256hex(token + "key")).substring(0, 10);
217
+ const v_bytes = b64toU8(tokData[vidKey] || "");
218
+ const T_bytes = b64toU8(tokData[keyKey] || "");
219
+
220
+ if (!v_bytes.length || !T_bytes.length) return null;
221
+
222
+ const seedInt = parseInt(seed.substring(0, 8), 16);
223
+ const wPayload = b64toU8(data.w_payload ?? "");
224
+ if (!wPayload.length) return null;
225
+
226
+ const wasmOut = runDecrypt(wPayload, frag1, kf2, T_bytes, seedInt);
227
+ if (!wasmOut) return null;
228
+
229
+ const keyMat = await crypto.subtle.importKey("raw", wasmOut, { name: "PBKDF2" }, false, ["deriveBits"]);
230
+ const derived = new Uint8Array(await crypto.subtle.deriveBits(
231
+ { name: "PBKDF2", salt: new TextEncoder().encode(seed), iterations: 1000, hash: "SHA-256" },
232
+ keyMat,
233
+ 256
234
+ ));
235
+
236
+ for (let i = 0; i < 32; i++) derived[i] ^= seed.charCodeAt(i % seed.length);
237
+
238
+ const aesKeyBytes = new Uint8Array(await crypto.subtle.digest("SHA-256", derived));
239
+ const aesKey = await crypto.subtle.importKey("raw", aesKeyBytes, { name: "AES-CBC" }, false, ["decrypt"]);
240
+
241
+ const plain = await crypto.subtle.decrypt({ name: "AES-CBC", iv }, aesKey, v_bytes).catch(() => null);
242
+ if (!plain) return null;
243
+
244
+ const url = new TextDecoder().decode(plain).trim().replace(/\0+$/, "");
245
+ if (!url.startsWith("http")) return null;
246
+
247
+ return { url };
248
+ }
249
+
250
+ async function searchReanime(query) {
251
+ const data = await fetchJson(`${BASE}/api/v1/search?${new URLSearchParams({ q: query, limit: 10 })}`, { headers: HEADERS }).catch(() => null);
252
+ return Array.isArray(data?.results) ? data.results : [];
253
+ }
254
+
255
+ export async function getStream({ id, s, e, audio, tmdbApiKey }) {
256
+ try {
257
+ const isTv = !!s;
258
+ const mediaType = isTv ? 'tv' : 'movie';
259
+ const epNum = e ? Number(e) : 1;
260
+ const seasonNum = s ? Number(s) : 1;
261
+
262
+ let anilistId = null;
263
+ let titles = [];
264
+ const tmdbInfo = await getTmdbInfo(tmdbApiKey, id, mediaType, seasonNum);
265
+
266
+ if (tmdbInfo) {
267
+ titles = tmdbInfo.titles || [];
268
+ anilistId = await tmdbToAnilist(id, mediaType, seasonNum, titles, tmdbInfo.year);
269
+ }
270
+ if (!anilistId) {
271
+ anilistId = Number(id);
272
+ }
273
+
274
+ let animeSlug = null;
275
+ for (const title of titles) {
276
+ const results = await searchReanime(title);
277
+ if (results.length) {
278
+ animeSlug = results[0].anime_id;
279
+ break;
280
+ }
281
+ }
282
+
283
+ if (!animeSlug) return null;
284
+
285
+ const audioPref = audio === 'dub' ? 'dub' : 'sub';
286
+ const watchRes = await fetchJson(`${BASE}/api/watch/${animeSlug}/${epNum}`, { headers: HEADERS }).catch(() => null);
287
+ const links = [...(watchRes?.episode_links || [])];
288
+
289
+ const flixRes = await fetchJson(`${BASE}/api/flix/${anilistId}/${epNum}`, { headers: HEADERS }).catch(() => null);
290
+ if (flixRes?.success && Array.isArray(flixRes.servers)) {
291
+ const seen = new Set(links.map((s) => s["$id"]));
292
+ for (const srv of flixRes.servers) {
293
+ if (!seen.has(srv["$id"])) links.push(srv);
294
+ }
295
+ }
296
+
297
+ const audioTypes = audioPref === "sub" ? ["sub", "s-sub"] : ["dub", "s-dub"];
298
+ const servers = links.filter((srv) => audioTypes.includes(srv.dataType));
299
+ if (!servers.length) return null;
300
+
301
+ const urls = [];
302
+ for (const srv of servers.slice(0, 3)) {
303
+ if (!srv.dataLink) continue;
304
+ const res = await fetch(srv.dataLink, { headers: { ...HEADERS, Referer: `${BASE}/` } }).catch(() => null);
305
+ if (!res || !res.ok) continue;
306
+ const html = await res.text();
307
+ const stream = await decryptEmbed(html);
308
+ if (stream?.url) {
309
+ urls.push({
310
+ url: stream.url,
311
+ server: `Reanime - ${srv.serverName || 'Server'}`,
312
+ });
313
+ }
314
+ }
315
+
316
+ return urls.length ? { allUrls: urls } : null;
317
+ } catch (err) {
318
+ return null;
319
+ }
320
+ }
321
+
322
+ export async function getSources() {
323
+ return ['Reanime'];
324
+ }