@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.
package/README.md CHANGED
@@ -1,8 +1,11 @@
1
- # vyla/sdk
2
- VylaSDK is a javascript base version of [Vyla](https://vyla.mintlify.app/)'s stream & scrape api, allowing developers to access content providers directly without using a REST api. This gives you the power & updates of Vyla without the headaches of api keys & server reliability.
1
+ # Vyla SDK
2
+
3
+ VylaSDK is a JavaScript/TypeScript SDK for accessing Vyla's stream providers, subtitles, and download sources directly from Node.js. Instead of using the Vyla REST API, developers can run the scraping layer directly in their own environment with full control over requests, execution, and provider management.
4
+
5
+ ### Installing
3
6
 
4
- ## Installing
5
7
  To start, install VylaSDK through npm or pnpm:
8
+
6
9
  ```bash
7
10
  # npm
8
11
  npm install @vyla-entertainment/sdk
@@ -11,21 +14,22 @@ npm install @vyla-entertainment/sdk
11
14
  pnpm install @vyla-entertainment/sdk
12
15
  ```
13
16
 
14
- View our full documentation at https://docs.vyla.cc/sdk
17
+ Please note we do not provide proxying, you have to do that on your end.
15
18
 
16
- # [DMCA](https://docs.vyla.cc/misc/dmca)
19
+ **View our full documentation at:** https://docs.vyla.cc/sdk
20
+
21
+ ### [DMCA](https://docs.vyla.cc/misc/dmca)
17
22
 
18
23
  If your source is listed here, I sincerely apologize. I understand that its inclusion may be costing you a lot of money, and that was never my intention. Please contact me as soon as possible, and I'll remove it ASAP.
19
24
 
20
25
  This is a small hobby project that I built for learning and personal interest, not for profit. I genuinely want to respect the wishes of source owners and will address any removal requests as quickly as I can.
21
26
 
22
- This project is developed and maintained by [Vyla Entertainment](https://github.com/vyla-entertainment) (@vyla-entertainment).
27
+ This project is developed and maintained by [Vyla Entertainment](https://github.com/vyla-entertainment).
23
28
 
24
29
  Original work and core authorship:
25
30
 
26
31
  [@endoverdosing](https://github.com/EndOverdosing)
27
-
28
32
  [@GavinGoGaming](https://github.com/GavinGoGaming)
29
33
 
30
- Full credits and acknowledgements:
34
+ **Full credits and acknowledgements:**
31
35
  https://docs.vyla.cc/misc/credit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vyla-entertainment/sdk",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "scripts": {
5
5
  "test": "node test/index.js"
6
6
  },
package/src/config.js CHANGED
@@ -60,6 +60,30 @@ export const SOURCES = [
60
60
  skipProxy: true
61
61
  },
62
62
 
63
+ {
64
+ key: 'anikoto-sub',
65
+ label: 'AniKoto (Sub)',
66
+ sourceFile: 'anikoto',
67
+ proxyParam: 'aksub',
68
+ timeout: 25000,
69
+ jitter: 500,
70
+ retries: 2,
71
+ multiUrl: true,
72
+ skipProxy: true
73
+ },
74
+
75
+ {
76
+ key: 'anikoto-dub',
77
+ label: 'AniKoto (Dub)',
78
+ sourceFile: 'anikoto',
79
+ proxyParam: 'akdub',
80
+ timeout: 25000,
81
+ jitter: 500,
82
+ retries: 2,
83
+ multiUrl: true,
84
+ skipProxy: true
85
+ },
86
+
63
87
  {
64
88
  key: 'anineko-sub',
65
89
  sourceFile: 'anineko',
@@ -114,6 +138,18 @@ export const SOURCES = [
114
138
  },
115
139
  },
116
140
 
141
+ {
142
+ key: 'anizone',
143
+ label: 'AniZone',
144
+ sourceFile: 'anizone',
145
+ proxyParam: 'az',
146
+ timeout: 25000,
147
+ jitter: 500,
148
+ retries: 2,
149
+ multiUrl: true,
150
+ skipProxy: true
151
+ },
152
+
117
153
  {
118
154
  key: 'cinejoy',
119
155
  label: 'Cinejoy',
@@ -126,6 +162,25 @@ export const SOURCES = [
126
162
  skipProxy: true,
127
163
  },
128
164
 
165
+ {
166
+ key: 'cinesu',
167
+ label: 'CineSu',
168
+ sourceFile: 'cinesu',
169
+ proxyParam: 'cs',
170
+ timeout: 20000,
171
+ jitter: 500,
172
+ retries: 2,
173
+ multiUrl: false,
174
+ skipProxy: true,
175
+ cdnHeaders: [{
176
+ pattern: /glendale-plumbing\.com/i,
177
+ headers: {
178
+ Referer: 'https://cine.su/',
179
+ Origin: 'https://cine.su',
180
+ },
181
+ }],
182
+ },
183
+
129
184
  {
130
185
  key: 'flaxmovies',
131
186
  label: 'FlaxMovies',
@@ -194,6 +249,18 @@ export const SOURCES = [
194
249
  retries: 2,
195
250
  },
196
251
 
252
+ {
253
+ key: 'kickassanime',
254
+ label: 'KickAssAnime',
255
+ sourceFile: 'kickassanime',
256
+ proxyParam: 'kaa',
257
+ timeout: 25000,
258
+ jitter: 500,
259
+ retries: 2,
260
+ multiUrl: true,
261
+ skipProxy: true
262
+ },
263
+
197
264
  {
198
265
  key: 'kisskh',
199
266
  label: 'KissKH',
@@ -248,7 +315,6 @@ export const SOURCES = [
248
315
  jitter: 500,
249
316
  retries: 2,
250
317
  multiUrl: true,
251
- skipProxy: true
252
318
  },
253
319
 
254
320
  {
@@ -302,6 +368,30 @@ export const SOURCES = [
302
368
  skipProxy: true
303
369
  },
304
370
 
371
+ {
372
+ key: 'reanime-sub',
373
+ label: 'Reanime (Sub)',
374
+ sourceFile: 'reanime',
375
+ proxyParam: 'resub',
376
+ timeout: 25000,
377
+ jitter: 500,
378
+ retries: 2,
379
+ multiUrl: true,
380
+ skipProxy: true
381
+ },
382
+
383
+ {
384
+ key: 'reanime-dub',
385
+ label: 'Reanime (Dub)',
386
+ sourceFile: 'reanime',
387
+ proxyParam: 'redub',
388
+ timeout: 25000,
389
+ jitter: 500,
390
+ retries: 2,
391
+ multiUrl: true,
392
+ skipProxy: true
393
+ },
394
+
305
395
  {
306
396
  key: 'vidapi',
307
397
  label: 'VidAPI',
@@ -479,7 +569,6 @@ export const SOURCES = [
479
569
  Referer: 'https://player.vidzee.wtf',
480
570
  Origin: 'https://player.vidzee.wtf',
481
571
  },
482
- disabled: true // Disabled temporarily due to decryption issues
483
572
  },
484
573
 
485
574
  {
@@ -0,0 +1,198 @@
1
+ import { fetchJson, fetchText, getTmdbInfo, tmdbToAnilist, USER_AGENT } from '../utils/helpers.js';
2
+
3
+ const ANIKOTO = "https://anikototv.to";
4
+ const MEGAPLAY = "https://megaplay.buzz";
5
+ const VIDWISH = "https://vidwish.live";
6
+ const SPOOF_REF = "https://hianimes.re/";
7
+ const HEADERS = { "User-Agent": USER_AGENT, Accept: "text/html,*/*" };
8
+
9
+ function extractEpisodes(html) {
10
+ const episodes = [];
11
+ const re = /<a\s[^>]*data-id="[^"]*"[^>]*>/g;
12
+ let m;
13
+ while ((m = re.exec(html)) !== null) {
14
+ const tag = m[0];
15
+ const getAttr = (a) => {
16
+ const x = tag.match(new RegExp(`data-${a}="([^"]*)"`));
17
+ return x ? x[1] : "";
18
+ };
19
+ const id = getAttr("id"), num = getAttr("num");
20
+ if (!id || !num) continue;
21
+ episodes.push({ id, num: parseInt(num), slug: getAttr("slug"), hasSub: getAttr("sub") === "1", hasDub: getAttr("dub") === "1", ids: getAttr("ids") });
22
+ }
23
+ return episodes;
24
+ }
25
+
26
+ function extractSearchCandidates(html) {
27
+ const results = [];
28
+ const re = /<a class="item" href="https:\/\/anikototv\.to\/watch\/([^"]+)"([\s\S]*?)<\/a>/g;
29
+ let m;
30
+ while ((m = re.exec(html)) !== null) {
31
+ const block = m[2];
32
+ const enM = block.match(/class="name d-title"[^>]*>([^<]*)</);
33
+ const jpM = block.match(/data-jp="([^"]*)"/);
34
+ results.push({ slug: m[1], titleEn: enM ? enM[1].trim() : "", titleJp: jpM ? jpM[1].trim() : "" });
35
+ }
36
+ return results;
37
+ }
38
+
39
+ async function searchAnikoto(keyword) {
40
+ const data = await fetchJson(`${ANIKOTO}/ajax/anime/search?keyword=${encodeURIComponent(keyword)}`, {
41
+ headers: { ...HEADERS, "X-Requested-With": "XMLHttpRequest", Referer: `${ANIKOTO}/` }
42
+ }).catch(() => null);
43
+
44
+ const results = extractSearchCandidates(data?.result?.html ?? "");
45
+ const html = await fetchText(`${ANIKOTO}/filter?keyword=${encodeURIComponent(keyword)}`, { headers: { ...HEADERS, Referer: `${ANIKOTO}/` } }).catch(() => "");
46
+
47
+ for (const m of html.matchAll(/<a class="name d-title" href="https:\/\/anikototv\.to\/watch\/([^"/]+)(?:\/ep-\d+)?" data-jp="([^"]*)">([\s\S]*?)<\/a>/g)) {
48
+ results.push({
49
+ slug: m[1],
50
+ titleEn: m[3].replace(/<[^>]*>/g, "").trim(),
51
+ titleJp: m[2].trim()
52
+ });
53
+ }
54
+
55
+ const seen = new Set();
56
+ return results.filter((r) => {
57
+ if (seen.has(r.slug)) return false;
58
+ seen.add(r.slug);
59
+ return true;
60
+ });
61
+ }
62
+
63
+ async function findAnikotoShow(title) {
64
+ const candidates = await searchAnikoto(title);
65
+ if (!candidates.length) return null;
66
+ const chosenSlug = candidates[0].slug;
67
+
68
+ const pageHtml = await fetchText(`${ANIKOTO}/watch/${chosenSlug}`, { headers: { ...HEADERS, Referer: `${ANIKOTO}/` } });
69
+ const idM = pageHtml.match(/data-id="(\d+)"/);
70
+ if (!idM) return null;
71
+ return { slug: chosenSlug, showId: idM[1] };
72
+ }
73
+
74
+ async function extractVidWish(realId, audio) {
75
+ try {
76
+ const page = await fetchText(`${VIDWISH}/stream/s-2/${realId}/${audio}`, { headers: { ...HEADERS, Referer: SPOOF_REF } });
77
+ const m = page.match(/data-id="([^"]*)"/);
78
+ if (!m?.[1]) return null;
79
+ const fileId = m[1];
80
+ const data = await fetchJson(`${VIDWISH}/stream/getSources?id=${fileId}&id=${fileId}`, { headers: { ...HEADERS, Referer: `${VIDWISH}/`, "X-Requested-With": "XMLHttpRequest" } });
81
+ return { fileId, data };
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
87
+ export async function getStream({ id, s, e, audio, tmdbApiKey }) {
88
+ try {
89
+ const isTv = !!s;
90
+ const mediaType = isTv ? 'tv' : 'movie';
91
+ const epNum = e ? Number(e) : 1;
92
+ const seasonNum = s ? Number(s) : 1;
93
+
94
+ let anilistId = null;
95
+ let titles = [];
96
+ const tmdbInfo = await getTmdbInfo(tmdbApiKey, id, mediaType, seasonNum);
97
+
98
+ if (tmdbInfo) {
99
+ titles = tmdbInfo.titles || [];
100
+ anilistId = await tmdbToAnilist(id, mediaType, seasonNum, titles, tmdbInfo.year);
101
+ }
102
+ if (!anilistId) {
103
+ anilistId = Number(id);
104
+ }
105
+
106
+ const audioPref = audio === 'dub' ? 'dub' : 'sub';
107
+ let embedUrl = `${MEGAPLAY}/stream/ani/${anilistId}/${epNum}/${audioPref}`;
108
+ let megaHtml = await fetchText(embedUrl, { headers: { ...HEADERS, Referer: SPOOF_REF } }).catch(() => '');
109
+
110
+ const frameSrc = megaHtml.match(/<iframe\b[^>]*src="([^"]+)"/i)?.[1];
111
+ if (!megaHtml.match(/data-id="([^"]*)"/) && frameSrc) {
112
+ embedUrl = frameSrc.startsWith("http") ? frameSrc : `${MEGAPLAY}${frameSrc}`;
113
+ megaHtml = await fetchText(embedUrl, { headers: { ...HEADERS, Referer: SPOOF_REF } }).catch(() => '');
114
+ }
115
+
116
+ const attrMatch = (name) => {
117
+ const m = megaHtml.match(new RegExp(`data-${name}="([^"]*)"`));
118
+ return m ? m[1] : null;
119
+ };
120
+
121
+ const fileId = attrMatch("id");
122
+ const realId = attrMatch("realid");
123
+
124
+ const urls = [];
125
+
126
+ if (fileId) {
127
+ const megaSources = await fetchJson(`${MEGAPLAY}/stream/getSources?id=${fileId}&id=${fileId}`, {
128
+ headers: { ...HEADERS, Referer: `${MEGAPLAY}/`, "X-Requested-With": "XMLHttpRequest" }
129
+ }).catch(() => null);
130
+
131
+ if (megaSources?.sources?.file) {
132
+ urls.push({
133
+ url: megaSources.sources.file,
134
+ server: 'AniKoto (Megaplay)',
135
+ });
136
+ }
137
+ }
138
+
139
+ if (realId) {
140
+ const vidwish = await extractVidWish(realId, audioPref);
141
+ if (vidwish?.data?.sources?.file) {
142
+ urls.push({
143
+ url: vidwish.data.sources.file,
144
+ server: 'AniKoto (VidWish)',
145
+ });
146
+ }
147
+ }
148
+
149
+ if (!urls.length && titles.length) {
150
+ const show = await findAnikotoShow(titles[0]);
151
+ if (show) {
152
+ const listData = await fetchJson(`${ANIKOTO}/ajax/episode/list/${show.showId}`, {
153
+ headers: { ...HEADERS, "X-Requested-With": "XMLHttpRequest", Referer: `${ANIKOTO}/watch/${show.slug}` }
154
+ }).catch(() => null);
155
+
156
+ const episodes = extractEpisodes(listData?.result ?? "");
157
+ const ep = episodes.find((item) => item.num === epNum);
158
+ if (ep && ep.ids) {
159
+ const serverData = await fetchJson(`${ANIKOTO}/ajax/server/list?servers=${encodeURIComponent(ep.ids)}`, {
160
+ headers: { ...HEADERS, "X-Requested-With": "XMLHttpRequest", Referer: `${ANIKOTO}/` }
161
+ }).catch(() => null);
162
+
163
+ const items = [];
164
+ const typeRe = /<div class="type" data-type="(sub|dub)">([\s\S]*?)<\/ul>\s*<\/div>/g;
165
+ let typeM;
166
+ while ((typeM = typeRe.exec(serverData?.result ?? "")) !== null) {
167
+ if (typeM[1] !== audioPref) continue;
168
+ for (const li of typeM[2].matchAll(/<li\s+([^>]*data-link-id[^>]*)>([\s\S]*?)<\/li>/g)) {
169
+ const linkId = li[1].match(/data-link-id="([^"]+)"/)?.[1];
170
+ const name = li[2].replace(/<[^>]+>/g, "").trim();
171
+ if (linkId) items.push({ linkId, name });
172
+ }
173
+ }
174
+
175
+ for (const item of items) {
176
+ const resolved = await fetchJson(`${ANIKOTO}/ajax/server?get=${encodeURIComponent(item.linkId)}`, {
177
+ headers: { ...HEADERS, "X-Requested-With": "XMLHttpRequest", Referer: `${ANIKOTO}/` }
178
+ }).catch(() => null);
179
+ if (resolved?.result?.url) {
180
+ urls.push({
181
+ url: resolved.result.url,
182
+ server: `AniKoto (${item.name})`,
183
+ });
184
+ }
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ return urls.length ? { allUrls: urls } : null;
191
+ } catch (err) {
192
+ return null;
193
+ }
194
+ }
195
+
196
+ export async function getSources() {
197
+ return ['AniKoto'];
198
+ }
@@ -0,0 +1,109 @@
1
+ import { fetchText, getTmdbInfo, USER_AGENT } from '../utils/helpers.js';
2
+
3
+ const BASE = "https://anizone.to";
4
+ const HEADERS = { "User-Agent": USER_AGENT, Referer: `${BASE}/` };
5
+
6
+ function decodeEntities(encodedString) {
7
+ var translate_re = /&(nbsp|amp|quot|lt|gt);/g;
8
+ var translate = { "nbsp": " ", "amp": "&", "quot": "\"", "lt": "<", "gt": ">" };
9
+ return (encodedString || "").replace(translate_re, function (match, entity) {
10
+ return translate[entity];
11
+ }).replace(/&#(\d+);/g, function (match, dec) {
12
+ return String.fromCharCode(dec);
13
+ });
14
+ }
15
+
16
+ function processJsonArg(raw) {
17
+ const PH = "\x01U\x01";
18
+ let s = raw.replace(/\\\\u([0-9a-fA-F]{4})/g, `${PH}$1`);
19
+ s = s.replace(/\\u([0-9a-fA-F]{4})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));
20
+ s = s.replace(/\x01U\x01([0-9a-fA-F]{4})/g, "\\u$1");
21
+ try { return JSON.parse(s); } catch { return {}; }
22
+ }
23
+
24
+ function pickTitle(titles) {
25
+ return titles["1"] || titles["5"] || titles["8"] || Object.values(titles)[0] || "";
26
+ }
27
+
28
+ function extractSlug(ctx) {
29
+ const m = ctx.match(/href="(?:https:\/\/anizone\.to)?\/anime\/([a-z0-9-]+)"/);
30
+ return m ? m[1] : null;
31
+ }
32
+
33
+ function extractJsonArg(xdata, key) {
34
+ const re = new RegExp(`${key}:\\s*JSON\\.parse\\('((?:[^'\\\\]|\\\\.)*)'\\)`);
35
+ const m = xdata.match(re);
36
+ return m ? m[1] : null;
37
+ }
38
+
39
+ async function search(query) {
40
+ const html = await fetchText(`${BASE}/anime?search=${encodeURIComponent(query)}`, { headers: HEADERS }).catch(() => "");
41
+ const results = [];
42
+ const xdataRe = /x-data="(\{[^"]*anmTitles[^"]*\})"/g;
43
+ let m;
44
+ while ((m = xdataRe.exec(html)) !== null) {
45
+ const ctxStart = Math.max(0, m.index - 300);
46
+ const ctxEnd = Math.min(html.length, m.index + m[0].length + 800);
47
+ const ctx = html.slice(ctxStart, ctxEnd);
48
+ const slug = extractSlug(ctx);
49
+ if (!slug) continue;
50
+ const xdata = decodeEntities(m[1]);
51
+ const raw = extractJsonArg(xdata, "anmTitles");
52
+ if (!raw) continue;
53
+ const titles = processJsonArg(raw);
54
+ const title = pickTitle(titles);
55
+ if (title) results.push({ slug, text: title });
56
+ }
57
+ return results;
58
+ }
59
+
60
+ async function scrapeWatch(slug, episodeNum) {
61
+ const html = await fetchText(`${BASE}/anime/${slug}/${episodeNum}`, { headers: HEADERS }).catch(() => "");
62
+ if (!html) return null;
63
+
64
+ const hlsMatch = html.match(/<media-player[^>]+src="([^"]+\.m3u8[^"]*)"/i);
65
+ const hls = hlsMatch ? decodeEntities(hlsMatch[1]) : null;
66
+
67
+ return hls;
68
+ }
69
+
70
+ export async function getStream({ id, s, e, tmdbApiKey }) {
71
+ try {
72
+ const isTv = !!s;
73
+ const mediaType = isTv ? 'tv' : 'movie';
74
+ const epNum = e ? Number(e) : 1;
75
+ const seasonNum = s ? Number(s) : 1;
76
+
77
+ const tmdbInfo = await getTmdbInfo(tmdbApiKey, id, mediaType, seasonNum);
78
+ const titles = tmdbInfo?.titles || [];
79
+
80
+ let slug = null;
81
+ for (const title of titles) {
82
+ const results = await search(title);
83
+ if (results.length) {
84
+ slug = results[0].slug;
85
+ break;
86
+ }
87
+ }
88
+
89
+ if (!slug) return null;
90
+
91
+ const hls = await scrapeWatch(slug, epNum);
92
+ if (!hls) return null;
93
+
94
+ return {
95
+ allUrls: [
96
+ {
97
+ url: hls,
98
+ server: 'AniZone',
99
+ }
100
+ ]
101
+ };
102
+ } catch (err) {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ export async function getSources() {
108
+ return ['AniZone'];
109
+ }
@@ -0,0 +1,94 @@
1
+ import { USER_AGENT } from '../utils/helpers.js';
2
+
3
+ const lS = [17, 91, 203, 44, 8, 177, 62, 239, 119, 3, 154, 81, 28, 210, 101, 7];
4
+
5
+ function lA(e) {
6
+ let t = e >>> 0;
7
+ t ^= t >>> 16;
8
+ t = Math.imul(t, 0x7feb352d) >>> 0;
9
+ t ^= t >>> 15;
10
+ t = Math.imul(t, 0x846ca68b) >>> 0;
11
+ return (t ^ (t >>> 16)) >>> 0;
12
+ }
13
+
14
+ function lL(keyStr, targetLen) {
15
+ const encoder = new TextEncoder();
16
+ const i = encoder.encode(keyStr || "dev");
17
+ const rLen = Math.max(32, Math.min(128, targetLen + 17));
18
+ const r = new Uint8Array(rLen);
19
+ let s = 0x811c9dc5;
20
+ for (let e = 0; e < rLen; e += 1) {
21
+ const a = i[e % i.length] ?? e;
22
+ s ^= a;
23
+ s = lA((s + lS[e % lS.length] + ((0x9e3779b1 * e) >>> 0)) >>> 0);
24
+ r[e] = s & 255;
25
+ }
26
+ return r;
27
+ }
28
+
29
+ function base64UrlEncode(bytes) {
30
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
31
+ let i = "";
32
+ for (let r = 0; r < bytes.length; r += 3) {
33
+ const s = bytes[r];
34
+ const a = bytes[r + 1];
35
+ const n = bytes[r + 2];
36
+ i += chars[s >>> 2];
37
+ i += chars[((3 & s) << 4) | ((a ?? 0) >>> 4)];
38
+ if (a === undefined) break;
39
+ i += chars[((15 & a) << 2) | ((n ?? 0) >>> 6)];
40
+ if (n === undefined) break;
41
+ i += chars[63 & n];
42
+ }
43
+ return i;
44
+ }
45
+
46
+ export function generateCineSuUrl(type, tmdbId, season = 1, episode = 1) {
47
+ const kind = type === "show" || type === "tv" ? "show" : "movie";
48
+ const s = kind === "show" ? Math.max(1, Math.floor(season)) : 0;
49
+ const ep = kind === "show" ? Math.max(1, Math.floor(episode)) : 0;
50
+ const char = kind[0];
51
+
52
+ const payloadStr = `4860ac8bfddb:${char}:${Math.floor(Number(tmdbId))}:${s}:${ep}`;
53
+ const encoder = new TextEncoder();
54
+ const d = encoder.encode(payloadStr);
55
+
56
+ const key = "224eff10e662e9635c9f671cf46351dcd69af42b1edd56f5e5fa21751f44b9c8";
57
+ const u = lL(key, d.length);
58
+
59
+ const h = new Uint8Array(d.length + 2);
60
+ h[0] = 255 & d.length;
61
+ h[1] = (d.length >>> 8) & 255;
62
+
63
+ let c = (0x9e3779b9 ^ d.length) >>> 0;
64
+ for (let e = 0; e < d.length; e += 1) {
65
+ c = lA((c + u[e % u.length] + lS[e % lS.length] + e) >>> 0);
66
+ h[e + 2] = d[e] ^ (255 & c) ^ u[(7 * e + 3) % u.length];
67
+ }
68
+
69
+ const token = base64UrlEncode(h);
70
+ return `https://glendale-plumbing.com/c/v1/${token}/master.m3u8`;
71
+ }
72
+
73
+ export async function getStream({ id, s, e }) {
74
+ try {
75
+ const isTv = !!s;
76
+ const mediaType = isTv ? 'tv' : 'movie';
77
+ const m3u8Url = generateCineSuUrl(mediaType, id, s || 1, e || 1);
78
+
79
+ return {
80
+ allUrls: [
81
+ {
82
+ url: m3u8Url,
83
+ server: 'CineSu',
84
+ }
85
+ ]
86
+ };
87
+ } catch (err) {
88
+ return null;
89
+ }
90
+ }
91
+
92
+ export async function getSources() {
93
+ return ['CineSu'];
94
+ }