@vyla-entertainment/sdk 1.5.0 → 1.8.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/.env.example ADDED
@@ -0,0 +1 @@
1
+ TMDB_API_KEY=your_tmdb_api_key_here
package/README.md CHANGED
@@ -11,37 +11,7 @@ npm install @vyla-entertainment/sdk
11
11
  pnpm install @vyla-entertainment/sdk
12
12
  ```
13
13
 
14
- ## Usage
15
- Start by creating a VylaSDK element with your TMDB api key.
16
- ```js
17
- import VylaSDK from "@vyla-entertainment/sdk";
18
-
19
- const sdk = new VylaSDK({
20
- tmdbApiKey: ...
21
- });
22
- ```
23
-
24
- You can now use this to access Vyla's content.
25
- ```js
26
- // Get sources
27
- // Args: excludeDisabled
28
- sdk.getSources(false); -> {key, timeout, label, ...}[]
29
-
30
- // Get subtitles
31
- // Args: tmdb id, season?, episode?
32
- await sdk.getSubtitles('0000000', null, null); -> {label, file, type, source}[]
33
-
34
- // Probe & test a source
35
- // Args: source key
36
- await sdk.probeSource('nebula'); -> {ok, ms}
37
-
38
- // Probe & test all sources
39
- await sdk.probeAllSources(); -> {'key': {ok, ms}, ...}
40
-
41
- // Get HLS streams
42
- // Args: source key, tmdb id, season?, episode?, clientIP?
43
- await sdk.getStream('nebula', '0000000', null, null, null) -> {allUrls: {url, quality, label?, server?, type?, skipProxy?, skipVerify?, headers? ...}[]}
44
- ```
14
+ View our full documentation at https://docs.vyla.cc/sdk
45
15
 
46
16
  # [DMCA](https://docs.vyla.cc/misc/dmca)
47
17
 
@@ -58,4 +28,4 @@ Original work and core authorship:
58
28
  [@GavinGoGaming](https://github.com/GavinGoGaming)
59
29
 
60
30
  Full credits and acknowledgements:
61
- https://docs.vyla.cc/misc/credit
31
+ 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.5.0",
3
+ "version": "1.8.0",
4
4
  "scripts": {
5
5
  "test": "node test/index.js"
6
6
  },
package/src/config.js CHANGED
@@ -1,3 +1,10 @@
1
+ // Currently active sources reported on 9:22:41 PM PST 7/30/2026
2
+
3
+ // None Anime Tested: 936075
4
+ // Anime Tested: 37854
5
+
6
+ // Fshare/Fsonic Tested: 155 ( they don't have Michael )
7
+
1
8
  export const SOURCES = [
2
9
  {
3
10
  key: '1embed',
@@ -107,6 +114,18 @@ export const SOURCES = [
107
114
  },
108
115
  },
109
116
 
117
+ {
118
+ key: 'cinejoy',
119
+ label: 'Cinejoy',
120
+ sourceFile: 'cinejoy',
121
+ proxyParam: 'cj',
122
+ timeout: 30000,
123
+ jitter: 500,
124
+ retries: 1,
125
+ multiUrl: true,
126
+ skipProxy: true,
127
+ },
128
+
110
129
  {
111
130
  key: 'flaxmovies',
112
131
  label: 'FlaxMovies',
@@ -116,7 +135,7 @@ export const SOURCES = [
116
135
  jitter: 500,
117
136
  retries: 2,
118
137
  multiUrl: true,
119
- disabled: true, // Temporarily disabled because their site is down, all streams are returning 403's
138
+ skipProxy: true,
120
139
  cdnHeaders: [{
121
140
  pattern: /flix2watch\.pro/i,
122
141
  headers: {
@@ -142,18 +161,6 @@ export const SOURCES = [
142
161
  },
143
162
  },
144
163
 
145
- {
146
- key: 'goated',
147
- label: 'Goated',
148
- sourceFile: 'goated',
149
- proxyParam: 'gt',
150
- timeout: 25000,
151
- jitter: 500,
152
- retries: 2,
153
- multiUrl: true,
154
- skipProxy: true,
155
- },
156
-
157
164
  {
158
165
  key: 'fsonic',
159
166
  label: 'Fsonic',
@@ -197,6 +204,17 @@ export const SOURCES = [
197
204
  retries: 1,
198
205
  },
199
206
 
207
+ {
208
+ key: 'lmscript',
209
+ label: 'LMScript',
210
+ sourceFile: 'lmscript',
211
+ proxyParam: 'lmsc',
212
+ timeout: 20000,
213
+ jitter: 500,
214
+ retries: 2,
215
+ multiUrl: true,
216
+ },
217
+
200
218
  {
201
219
  key: 'lookmovie',
202
220
  label: 'LookMovie',
@@ -233,38 +251,6 @@ export const SOURCES = [
233
251
  skipProxy: true
234
252
  },
235
253
 
236
- {
237
- key: 'nebula',
238
- label: 'Nebula',
239
- sourceFile: 'nebula',
240
- proxyParam: 'nb',
241
- timeout: 25000,
242
- jitter: 500,
243
- retries: 1,
244
- multiUrl: true,
245
- skipProxy: true,
246
- },
247
-
248
- {
249
- key: 'nekowatch-sub',
250
- sourceFile: 'nekowatch',
251
- label: 'NekoWatch (Sub)',
252
- proxyParam: 'nwsub',
253
- timeout: 30000,
254
- jitter: 500,
255
- retries: 2
256
- },
257
-
258
- {
259
- key: 'nekowatch-dub',
260
- sourceFile: 'nekowatch',
261
- label: 'NekoWatch (Dub)',
262
- proxyParam: 'nwdub',
263
- timeout: 30000,
264
- jitter: 500,
265
- retries: 2
266
- },
267
-
268
254
  {
269
255
  key: 'opstream',
270
256
  label: 'OpStream',
@@ -313,6 +299,7 @@ export const SOURCES = [
313
299
  timeout: 20000,
314
300
  jitter: 500,
315
301
  retries: 2,
302
+ skipProxy: true
316
303
  },
317
304
 
318
305
  {
@@ -466,6 +453,17 @@ export const SOURCES = [
466
453
  multiUrl: true
467
454
  },
468
455
 
456
+ {
457
+ key: 'vidvault',
458
+ label: 'VidVault',
459
+ sourceFile: 'vidvault',
460
+ proxyParam: 'vv',
461
+ timeout: 25000,
462
+ jitter: 500,
463
+ retries: 2,
464
+ multiUrl: true,
465
+ },
466
+
469
467
  {
470
468
  key: 'vidzee',
471
469
  label: 'VidZee',
@@ -475,13 +473,13 @@ export const SOURCES = [
475
473
  sourcesTimeout: 10000,
476
474
  jitter: 400,
477
475
  retries: 3,
478
- disabled: true, // Temporarily disabled because their site is down, all streams are returning 403's
479
476
  verifyHeaders: {
480
477
  Accept: '/',
481
478
  'Accept-Language': 'en-US,en;q=0.9',
482
479
  Referer: 'https://player.vidzee.wtf',
483
480
  Origin: 'https://player.vidzee.wtf',
484
481
  },
482
+ disabled: true // Disabled temporarily due to decryption issues
485
483
  },
486
484
 
487
485
  {
@@ -512,6 +510,7 @@ export const SOURCES = [
512
510
  retries: 2,
513
511
  skipProxy: true,
514
512
  },
513
+
515
514
  ];
516
515
 
517
516
  export const HEALTH_PROBE_ID = '155';
@@ -1,8 +1,12 @@
1
1
  import { getDownloads, getDownloadsTv } from "./sources/trendimovies.js";
2
2
 
3
3
  export async function fetchDownloads(id, s = null, e = null) {
4
- const res = s != null && e != null
5
- ? await getDownloadsTv(id, s, e)
6
- : await getDownloads(id);
7
- return res;
4
+ try {
5
+ const res = s != null && e != null
6
+ ? await getDownloadsTv(id, s, e)
7
+ : await getDownloads(id);
8
+ return res || [];
9
+ } catch {
10
+ return [];
11
+ }
8
12
  }
package/src/sdk.d.ts CHANGED
@@ -95,7 +95,7 @@ export default class VylaSDK {
95
95
  s?: number | null,
96
96
  e?: number | null,
97
97
  clientIP?: string | null
98
- ): Promise<{allUrls: StreamResult[]}>;
98
+ ): Promise<StreamResult | { allUrls: StreamResult[] }>;
99
99
 
100
100
  /**
101
101
  * Get subtitles for a movie or TV episode.
@@ -3,13 +3,11 @@ import { USER_AGENT } from '../utils/helpers.js';
3
3
  const BASE_URL = "https://1embed.cc";
4
4
 
5
5
  const PROVIDERS = [
6
- { id: "sdev", name: "1Embed - SDev" },
7
- { id: "xpa", name: "1Embed - XPass" },
8
- { id: "vnes", name: "1Embed - VidNest" },
9
- { id: "pro", name: "1Embed - Pro" },
10
- { id: "pur", name: "1Embed - Purstream" },
11
- { id: "czo", name: "1Embed - Cinezo" },
12
- { id: "fas", name: "1Embed - VidFast" }
6
+ { id: "BUKE", name: "1Embed - BUKE" },
7
+ { id: "YOTA", name: "1Embed - YOTA" },
8
+ { id: "NEPT", name: "1Embed - NEPT" },
9
+ { id: "AZURE", name: "1Embed - AZURE" },
10
+ { id: "YORU", name: "1Embed - YORU" }
13
11
  ];
14
12
 
15
13
  let cachedToken = null;
@@ -28,6 +26,30 @@ async function getToken() {
28
26
  } catch { return null; }
29
27
  }
30
28
 
29
+ function decodeBsUrl(url) {
30
+ if (!url) return url;
31
+ let target = url;
32
+ if (url.includes('d=')) {
33
+ try {
34
+ const parts = url.split(/[?&]/);
35
+ for (const part of parts) {
36
+ if (part.startsWith('d=')) {
37
+ target = decodeURIComponent(part.substring(2));
38
+ break;
39
+ }
40
+ }
41
+ } catch { }
42
+ }
43
+ if (target.startsWith('bs_')) {
44
+ try {
45
+ const decoded = Buffer.from(target.slice(3), 'base64').toString('utf-8');
46
+ const reversed = decoded.split('').reverse().join('');
47
+ if (reversed.startsWith('http')) return reversed;
48
+ } catch { }
49
+ }
50
+ return url;
51
+ }
52
+
31
53
  async function fetchProviderStream(provider, type, id, s, e, token) {
32
54
  try {
33
55
  const referer = type === "movie" ? `${BASE_URL}/embed/movie/${id}` : `${BASE_URL}/embed/tv/${id}/${s}/${e}`;
@@ -51,11 +73,12 @@ async function fetchProviderStream(provider, type, id, s, e, token) {
51
73
  const results = [];
52
74
  for (const src of rawSources) {
53
75
  if (typeof src !== 'object' || !src) continue;
54
- let streamUrl = src.url || src.file || src.link;
55
- if (typeof streamUrl !== 'string') continue;
76
+ let rawUrl = src.url || src.file || src.link;
77
+ if (typeof rawUrl !== 'string') continue;
78
+ let streamUrl = decodeBsUrl(rawUrl);
56
79
  if (streamUrl.startsWith('/')) streamUrl = `${BASE_URL}${streamUrl}`;
57
80
  const isWorker = streamUrl.includes('omena-puu') || streamUrl.includes('nocach') || streamUrl.includes('?p=');
58
- const isHls = src.type === 'hls' || streamUrl.includes('m3u8') || isWorker;
81
+ const isHls = src.type === 'hls' || src.type === 'm3u8' || streamUrl.includes('m3u8') || isWorker;
59
82
  if (isHls && !streamUrl.includes('m3u8')) streamUrl += streamUrl.includes('?') ? '&format=.m3u8' : '?format=.m3u8';
60
83
  const srcHeaders = (src.headers && typeof src.headers === 'object') ? src.headers : {};
61
84
  results.push({
@@ -80,7 +103,7 @@ export async function getStream(args) {
80
103
  let targets = PROVIDERS;
81
104
  if (server && server !== 'all') {
82
105
  const cleanName = server.replace('1Embed - ', '').toLowerCase();
83
- targets = PROVIDERS.filter(p => p.id === cleanName || p.name.toLowerCase().includes(cleanName));
106
+ targets = PROVIDERS.filter(p => p.id.toLowerCase() === cleanName || p.name.toLowerCase().includes(cleanName));
84
107
  if (!targets.length) targets = PROVIDERS;
85
108
  }
86
109
  const settled = await Promise.allSettled(targets.map(p => fetchProviderStream(p, type, id, s, e, token)));
@@ -2,15 +2,27 @@ import { getTmdbInfo, USER_AGENT } from '../utils/helpers.js';
2
2
 
3
3
  async function extractVoe(voeUrl) {
4
4
  try {
5
- const res = await fetch(voeUrl, {
5
+ let res = await fetch(voeUrl, {
6
6
  headers: {
7
7
  'User-Agent': USER_AGENT,
8
8
  'Referer': 'https://anihq.cc/'
9
9
  }
10
10
  });
11
11
 
12
+ let html = await res.text();
13
+
14
+ const redirectMatch = html.match(/window\.location\.href\s*=\s*['"](https?:\/\/[^'"]+)['"]/);
15
+ if (redirectMatch) {
16
+ res = await fetch(redirectMatch[1], {
17
+ headers: {
18
+ 'User-Agent': USER_AGENT,
19
+ 'Referer': 'https://anihq.cc/'
20
+ }
21
+ });
22
+ html = await res.text();
23
+ }
24
+
12
25
  const mirrorUrl = res.url;
13
- const html = await res.text();
14
26
 
15
27
  const scriptMatch = html.match(/<script type="application\/json">\["(.*?)"\]<\/script>/);
16
28
  if (!scriptMatch) return null;
@@ -1,4 +1,4 @@
1
- import { getTmdbInfo } from '../utils/helpers.js';
1
+ import { getTmdbInfo, USER_AGENT, fetchText } from '../utils/helpers.js';
2
2
 
3
3
  const BASE_URL = "https://anineko.to";
4
4
 
@@ -10,7 +10,10 @@ function decodeEntities(str) {
10
10
 
11
11
  async function search(query) {
12
12
  try {
13
- const html = await fetchText(`${BASE_URL}/browser?keyword=${encodeURIComponent(query)}`, { signal: AbortSignal.timeout(6000) });
13
+ const html = await fetchText(`${BASE_URL}/browser?keyword=${encodeURIComponent(query)}`, {
14
+ headers: { 'User-Agent': USER_AGENT },
15
+ signal: AbortSignal.timeout(6000)
16
+ });
14
17
  const results = [];
15
18
  let idx = 0;
16
19
  while ((idx = html.indexOf('nv-anime-thumb', idx)) !== -1) {
@@ -27,40 +30,144 @@ async function search(query) {
27
30
  }
28
31
  idx += 14;
29
32
  }
33
+ if (results.length === 0) {
34
+ const linkMatches = [...html.matchAll(/href=["']\/watch\/([^"']+)["']/gi)];
35
+ for (const match of linkMatches) {
36
+ if (!results.find(r => r.slug === match[1])) {
37
+ const linkIdx = html.indexOf(match[0]);
38
+ const titleStart = html.lastIndexOf('>', linkIdx);
39
+ const titleEnd = html.indexOf('<', linkIdx);
40
+ if (titleStart !== -1 && titleEnd !== -1 && titleEnd > linkIdx) {
41
+ const title = html.slice(titleStart + 1, Math.min(titleEnd, linkIdx + 200)).trim();
42
+ if (title.length > 2 && title.length < 100) {
43
+ results.push({ slug: match[1], text: title });
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
30
49
  return results;
31
50
  } catch { return []; }
32
51
  }
33
52
 
34
53
  async function extractHls(embedUrl) {
35
54
  try {
36
- const html = await fetchText(embedUrl, { headers: { Referer: `${BASE_URL}/` }, signal: AbortSignal.timeout(6000) });
37
- const m = html.match(/const\s+src\s*=\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) || html.match(/file\s*:\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) || html.match(/["'](https?:\/\/[^"']+\/master\.m3u8[^"']*)["']/i) || html.match(/["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i);
55
+ const html = await fetchText(embedUrl, {
56
+ headers: {
57
+ 'User-Agent': USER_AGENT,
58
+ Referer: `${BASE_URL}/`
59
+ },
60
+ signal: AbortSignal.timeout(6000)
61
+ });
62
+ const m = html.match(/const\s+src\s*=\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) ||
63
+ html.match(/file\s*:\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) ||
64
+ html.match(/["'](https?:\/\/[^"']+\/master\.m3u8[^"']*)["']/i) ||
65
+ html.match(/["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) ||
66
+ html.match(/src:\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i);
38
67
  if (m) return decodeEntities(m[1]);
39
68
  } catch { }
40
69
  return null;
41
70
  }
42
71
 
43
- export async function getStream({ id, s, e, audio }) {
72
+ export async function getStream({ id, s, e, audio, tmdbApiKey }) {
44
73
  try {
45
- const info = await getTmdbInfo(id, s ? 'tv' : 'movie', s);
74
+ const info = await getTmdbInfo(tmdbApiKey, id, s ? 'tv' : 'movie', s);
46
75
  if (!info || !info.isAnime) return null;
47
76
  let seriesSlug = null;
48
77
  for (const title of info.titles) {
49
- const results = await search(title);
50
- const targetClean = cleanTitle(title);
51
- for (const r of results) if (cleanTitle(r.text) === targetClean || r.text.toLowerCase().includes(title.toLowerCase())) { seriesSlug = r.slug; break; }
52
- if (seriesSlug) break;
78
+ const potentialSlug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
79
+ if (!potentialSlug) continue;
80
+ try {
81
+ const checkHtml = await fetchText(`${BASE_URL}/watch/${potentialSlug}/ep-${e || 1}`, {
82
+ headers: { 'User-Agent': USER_AGENT, Referer: `${BASE_URL}/watch/${potentialSlug}` },
83
+ signal: AbortSignal.timeout(4000)
84
+ });
85
+ if (checkHtml && checkHtml.includes('nv-watch-page')) {
86
+ seriesSlug = potentialSlug;
87
+ break;
88
+ }
89
+ } catch {}
90
+ }
91
+
92
+ if (!seriesSlug) {
93
+ for (const title of info.titles) {
94
+ const results = await search(title);
95
+ const targetClean = cleanTitle(title);
96
+
97
+ const exactSlugMatch = results.find(r => {
98
+ const slugClean = cleanTitle(r.slug);
99
+ return slugClean === targetClean || r.slug === targetClean;
100
+ });
101
+
102
+ if (exactSlugMatch) {
103
+ seriesSlug = exactSlugMatch.slug;
104
+ break;
105
+ }
106
+
107
+ const sortedResults = [...results].sort((a, b) => a.slug.length - b.slug.length);
108
+
109
+ for (const r of sortedResults) {
110
+ const resultClean = cleanTitle(r.text);
111
+ const isExactMatch = resultClean === targetClean;
112
+ const isCloseMatch = r.text.toLowerCase().includes(title.toLowerCase());
113
+ if (isExactMatch || isCloseMatch) {
114
+ seriesSlug = r.slug;
115
+ break;
116
+ }
117
+ }
118
+ if (seriesSlug) break;
119
+ }
53
120
  }
54
121
  if (!seriesSlug && info.titles.length) {
55
122
  const fSearch = await search(info.titles[0]);
56
- if (fSearch.length) seriesSlug = fSearch[0].slug;
123
+ const sortedResults = [...fSearch].sort((a, b) => a.slug.length - b.slug.length);
124
+ if (sortedResults.length) seriesSlug = sortedResults[0].slug;
57
125
  }
58
126
  if (!seriesSlug) return null;
59
- const watchHtml = await fetchText(`${BASE_URL}/watch/${seriesSlug}/ep-${e || 1}`, { headers: { Referer: `${BASE_URL}/watch/${seriesSlug}` }, signal: AbortSignal.timeout(6000) });
127
+ const watchHtml = await fetchText(`${BASE_URL}/watch/${seriesSlug}/ep-${e || 1}`, {
128
+ headers: {
129
+ 'User-Agent': USER_AGENT,
130
+ Referer: `${BASE_URL}/watch/${seriesSlug}`
131
+ },
132
+ signal: AbortSignal.timeout(6000)
133
+ });
60
134
  const byAudio = { sub: [], dub: [] };
61
- for (const panel of watchHtml.matchAll(/<div\b[^>]*class=["'][^"']*nv-server-grid[^"']*["'][^>]*data-id=["']([^"']+)["'][^>]*>([\s\S]*?)(?=<div\b[^>]*class=["'][^"']*nv-server-grid|$)/gi)) {
62
- const panelAudio = panel[1].toLowerCase().includes("dub") ? "dub" : "sub";
63
- for (const btn of panel[2].matchAll(/data-video=["']([^"']+)["']/gi)) byAudio[panelAudio].push(decodeEntities(btn[1]));
135
+
136
+ const iframeMatch = watchHtml.match(/<iframe[^>]*src=["']([^"']+)["'][^>]*>/i);
137
+ if (iframeMatch) {
138
+ const iframeSrc = decodeEntities(iframeMatch[1]);
139
+ const hls = await extractHls(iframeSrc);
140
+ if (hls) {
141
+ const audioType = audio === 'dub' ? 'dub' : 'sub';
142
+ byAudio[audioType].push(iframeSrc);
143
+ }
144
+ }
145
+
146
+ if (byAudio.sub.length === 0 && byAudio.dub.length === 0) {
147
+ const tabMatches = [...watchHtml.matchAll(/<button[^>]*class=["'][^"']*nv-server-tab[^"']*["'][^>]*data-id=["']([^"']+)["'][^>]*>/gi)];
148
+ const tabMap = {};
149
+ for (const match of tabMatches) {
150
+ const tabId = match[1].toLowerCase();
151
+ const tabClassMatch = match[0].match(/class=["'][^"']*(tab_\d+)[^"']/i);
152
+ if (tabClassMatch) {
153
+ const tabClass = tabClassMatch[1];
154
+ tabMap[tabClass] = tabId.includes('dub') ? 'dub' : 'sub';
155
+ }
156
+ }
157
+
158
+ const btnRegex = /<button\s+[^>]*class=["'][^"']*server-video[^"']*["'][^>]*>/gi;
159
+ const btnMatches = [...watchHtml.matchAll(btnRegex)];
160
+ for (const match of btnMatches) {
161
+ const btnHtml = match[0];
162
+ const videoMatch = btnHtml.match(/data-video=["']([^"']+)["']/i);
163
+ const tabMatch = btnHtml.match(/data-tab=["']([^"']+)["']/i);
164
+ if (videoMatch && tabMatch) {
165
+ const videoUrl = decodeEntities(videoMatch[1]);
166
+ const tabClass = tabMatch[1];
167
+ const audioType = tabMap[tabClass] || 'sub';
168
+ byAudio[audioType].push(videoUrl);
169
+ }
170
+ }
64
171
  }
65
172
  const audiosToTry = audio === "all" ? ["sub", "dub"] : (audio === "dub" ? ["dub", "sub"] : ["sub", "dub"]);
66
173
  const allUrls = [];