@vyla-entertainment/sdk 1.6.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/README.md CHANGED
@@ -13,8 +13,6 @@ pnpm install @vyla-entertainment/sdk
13
13
 
14
14
  View our full documentation at https://docs.vyla.cc/sdk
15
15
 
16
- ```
17
-
18
16
  # [DMCA](https://docs.vyla.cc/misc/dmca)
19
17
 
20
18
  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.
@@ -30,4 +28,4 @@ Original work and core authorship:
30
28
  [@GavinGoGaming](https://github.com/GavinGoGaming)
31
29
 
32
30
  Full credits and acknowledgements:
33
- 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.6.0",
3
+ "version": "1.8.0",
4
4
  "scripts": {
5
5
  "test": "node test/index.js"
6
6
  },
package/src/config.js CHANGED
@@ -1,4 +1,4 @@
1
- // Currently active sources reported on 7:47:50 PM PST 7/6/2026
1
+ // Currently active sources reported on 9:22:41 PM PST 7/30/2026
2
2
 
3
3
  // None Anime Tested: 936075
4
4
  // Anime Tested: 37854
@@ -135,7 +135,7 @@ export const SOURCES = [
135
135
  jitter: 500,
136
136
  retries: 2,
137
137
  multiUrl: true,
138
- disabled: true, // Temporarily disabled because their site is down, all streams are returning 403's
138
+ skipProxy: true,
139
139
  cdnHeaders: [{
140
140
  pattern: /flix2watch\.pro/i,
141
141
  headers: {
@@ -161,18 +161,6 @@ export const SOURCES = [
161
161
  },
162
162
  },
163
163
 
164
- {
165
- key: 'goated',
166
- label: 'Goated',
167
- sourceFile: 'goated',
168
- proxyParam: 'gt',
169
- timeout: 25000,
170
- jitter: 500,
171
- retries: 2,
172
- multiUrl: true,
173
- skipProxy: true,
174
- },
175
-
176
164
  {
177
165
  key: 'fsonic',
178
166
  label: 'Fsonic',
@@ -216,6 +204,17 @@ export const SOURCES = [
216
204
  retries: 1,
217
205
  },
218
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
+
219
218
  {
220
219
  key: 'lookmovie',
221
220
  label: 'LookMovie',
@@ -252,38 +251,6 @@ export const SOURCES = [
252
251
  skipProxy: true
253
252
  },
254
253
 
255
- {
256
- key: 'nebula',
257
- label: 'Nebula',
258
- sourceFile: 'nebula',
259
- proxyParam: 'nb',
260
- timeout: 25000,
261
- jitter: 500,
262
- retries: 1,
263
- multiUrl: true,
264
- skipProxy: true,
265
- },
266
-
267
- {
268
- key: 'nekowatch-sub',
269
- sourceFile: 'nekowatch',
270
- label: 'NekoWatch (Sub)',
271
- proxyParam: 'nwsub',
272
- timeout: 30000,
273
- jitter: 500,
274
- retries: 2
275
- },
276
-
277
- {
278
- key: 'nekowatch-dub',
279
- sourceFile: 'nekowatch',
280
- label: 'NekoWatch (Dub)',
281
- proxyParam: 'nwdub',
282
- timeout: 30000,
283
- jitter: 500,
284
- retries: 2
285
- },
286
-
287
254
  {
288
255
  key: 'opstream',
289
256
  label: 'OpStream',
@@ -332,6 +299,7 @@ export const SOURCES = [
332
299
  timeout: 20000,
333
300
  jitter: 500,
334
301
  retries: 2,
302
+ skipProxy: true
335
303
  },
336
304
 
337
305
  {
@@ -485,6 +453,17 @@ export const SOURCES = [
485
453
  multiUrl: true
486
454
  },
487
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
+
488
467
  {
489
468
  key: 'vidzee',
490
469
  label: 'VidZee',
@@ -494,13 +473,13 @@ export const SOURCES = [
494
473
  sourcesTimeout: 10000,
495
474
  jitter: 400,
496
475
  retries: 3,
497
- disabled: true, // Temporarily disabled because their site is down, all streams are returning 403's
498
476
  verifyHeaders: {
499
477
  Accept: '/',
500
478
  'Accept-Language': 'en-US,en;q=0.9',
501
479
  Referer: 'https://player.vidzee.wtf',
502
480
  Origin: 'https://player.vidzee.wtf',
503
481
  },
482
+ disabled: true // Disabled temporarily due to decryption issues
504
483
  },
505
484
 
506
485
  {
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 = [];
@@ -1,14 +1,26 @@
1
1
  import { fetchJson } from '../utils/helpers.js';
2
2
 
3
- const API_BASE = 'https://itjiocunahckqxcnzpoy.supabase.co/functions/v1';
4
- const APIKEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Iml0amlvY3VuYWhja3F4Y256cG95Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzczMzM1MjQsImV4cCI6MjA5MjkwOTUyNH0.9x9ykdHAzrv_GSvnawPeQaOxQeh3sZg0QAh4u9VOF4M';
5
- const HEADERS = { 'apikey': APIKEY, 'authorization': `Bearer ${APIKEY}`, 'content-type': 'application/json', 'origin': 'https://flaxmovies.xyz', 'referer': 'https://flaxmovies.xyz/' };
6
-
7
3
  export async function getStream({ id, s, e }) {
8
4
  try {
9
- const isMovie = !s;
10
- const data = await fetchJson(isMovie ? `${API_BASE}/get-movie` : `${API_BASE}/get-tv`, { method: 'POST', headers: HEADERS, body: JSON.stringify(isMovie ? { id: String(id) } : { id: String(id), season: Number(s), episode: Number(e) }), signal: AbortSignal.timeout(15000) });
11
- if (!data?.signed_url) return null;
12
- return { allUrls: [{ url: data.signed_url, headers: { 'Referer': 'https://flaxmovies.xyz/', 'Origin': 'https://flaxmovies.xyz' } }] };
13
- } catch { return null; }
5
+ const url = s && e
6
+ ? `https://sparkling-mode-a1ed.fillatame.workers.dev/?tmdb_id=${id}&season=${s}&episode=${e}`
7
+ : `https://sparkling-mode-a1ed.fillatame.workers.dev/?tmdb_id=${id}`;
8
+ const data = await fetchJson(url, { signal: AbortSignal.timeout(15000) });
9
+ if (!data || !Array.isArray(data.streams)) return null;
10
+ const allUrls = [];
11
+ for (const stream of data.streams) {
12
+ const streamUrl = stream.url || stream.link;
13
+ if (!streamUrl) continue;
14
+ const isHls = stream.format === 'HLS' || stream.isM3U8 === true || streamUrl.includes('m3u8');
15
+ allUrls.push({
16
+ url: streamUrl,
17
+ server: stream.provider || 'FlaxMovies',
18
+ quality: stream.resolution || 'Auto',
19
+ type: isHls ? 'hls' : 'mp4'
20
+ });
21
+ }
22
+ return allUrls.length ? { allUrls } : null;
23
+ } catch {
24
+ return null;
25
+ }
14
26
  }
@@ -4,10 +4,10 @@ const BASE_URL = 'https://www.fsonic.net';
4
4
  const FSHARE_BASE = 'https://fsharetv.co';
5
5
  const HEADERS = { 'User-Agent': USER_AGENT, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.9' };
6
6
 
7
- export async function getStream({ id, s }) {
7
+ export async function getStream({ id, s, tmdbApiKey }) {
8
8
  if (s != null) return null;
9
9
  try {
10
- const info = await getTmdbInfo(id, 'movie');
10
+ const info = await getTmdbInfo(tmdbApiKey, id, 'movie');
11
11
  if (!info?.titles?.length) return null;
12
12
  let watchSlug = null;
13
13
  for (const title of info.titles) {
@@ -4,10 +4,10 @@ const ORIGIN = 'https://www3.fsonline.app';
4
4
  const AJAX_URL = `${ORIGIN}/wp-admin/admin-ajax.php`;
5
5
  const HEADERS = { 'User-Agent': USER_AGENT, 'Origin': ORIGIN, 'Referer': `${ORIGIN}/` };
6
6
 
7
- export async function getStream({ id, s, e }) {
7
+ export async function getStream({ id, s, e, tmdbApiKey }) {
8
8
  try {
9
9
  const isTv = s != null && e != null;
10
- const info = await getTmdbInfo(id, isTv ? 'tv' : 'movie');
10
+ const info = await getTmdbInfo(tmdbApiKey, id, isTv ? 'tv' : 'movie');
11
11
  if (!info?.titles?.length) return null;
12
12
  const searchHtml = await fetchText(`${ORIGIN}/?s=${encodeURIComponent(info.titles[0] + (info.year ? ' ' + info.year : ''))}`, { headers: HEADERS });
13
13
  const typeFolder = isTv ? 'seriale' : 'film';
@@ -0,0 +1,43 @@
1
+ import { getTmdbInfo } from '../utils/helpers.js';
2
+
3
+ export async function getStream(args) {
4
+ const { id, s, e, tmdbApiKey } = args;
5
+ const type = (s != null && e != null) ? 'tv' : 'movie';
6
+
7
+ try {
8
+ const tmdbInfo = await getTmdbInfo(tmdbApiKey, id, type, s);
9
+ const name = tmdbInfo.titles[0] || '';
10
+ if (!name) return null;
11
+
12
+ if (type === 'tv') {
13
+ return null;
14
+ } else {
15
+ const url = `https://lmscript.xyz/v1/movies?filters[q]=${encodeURIComponent(name)}&expand=streams`;
16
+ const response = await fetch(url);
17
+ const data = await response.json();
18
+
19
+ const movie = data.items?.find(item => `${item.tmdb_prefix}` === `${id}` || `${item.tmdb_id}` === `${id}`);
20
+ if (!movie) return null;
21
+
22
+ const streams = movie.streams || {};
23
+ const SOURCE_ORDER = ["1080p", "720p", "480p", "360p"];
24
+ const sortedStreams = Object.keys(streams).sort((a, b) => SOURCE_ORDER.indexOf(a) - SOURCE_ORDER.indexOf(b));
25
+
26
+ if (!sortedStreams.length) return null;
27
+
28
+ const allUrls = sortedStreams.map(quality => {
29
+ const streamUrl = streams[quality];
30
+ return {
31
+ url: streamUrl,
32
+ quality,
33
+ type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
34
+ headers: {}
35
+ };
36
+ });
37
+
38
+ return { allUrls };
39
+ }
40
+ } catch (err) {
41
+ return null;
42
+ }
43
+ }
@@ -1,28 +1,24 @@
1
1
  import { getTmdbInfo, fetchJson, fetchText, USER_AGENT } from '../utils/helpers.js';
2
2
 
3
- const WINGS_BASE = "https://api.wingsdatabase.com";
3
+ const SPEEDRACE_BASE = "https://api.speedracelight.com";
4
4
  const HEADERS = { "Accept": "*/*", "Origin": "https://player.videasy.to", "Referer": "https://player.videasy.to/", "User-Agent": USER_AGENT };
5
5
  const SERVERS = [
6
- { id: 'jett', name: 'Jett' },
7
6
  { id: 'cdn', name: 'Yoru' },
8
- { id: 'tejo', name: 'Tejo' },
9
- { id: 'neon2', name: 'Neon' },
10
- { id: 'ym', name: 'Sage' },
11
- { id: 'downloader2', name: 'Cypher' },
12
7
  { id: 'm4uhd', name: 'Breach' },
8
+ { id: 'vsrc', name: 'Neon' },
13
9
  { id: 'hdmovie', name: 'Vyse' },
14
10
  { id: 'meine', name: 'Killjoy' },
15
11
  { id: 'lamovie', name: 'Omen' },
16
12
  { id: 'superflix', name: 'Raze' }
17
13
  ];
18
14
 
19
- export async function getStream({ id, s, e, server }) {
15
+ export async function getStream({ id, s, e, server, tmdbApiKey }) {
20
16
  try {
21
17
  const isTv = s != null && e != null;
22
- const info = await getTmdbInfo(id, isTv ? 'tv' : 'movie');
18
+ const info = await getTmdbInfo(tmdbApiKey, id, isTv ? 'tv' : 'movie');
23
19
  if (!info?.titles?.length) return null;
24
20
 
25
- const seedData = await fetchJson(`${WINGS_BASE}/seed?mediaId=${id}`, { headers: HEADERS, signal: AbortSignal.timeout(5000) });
21
+ const seedData = await fetchJson(`${SPEEDRACE_BASE}/seed?mediaId=${id}`, { headers: HEADERS, signal: AbortSignal.timeout(5000) });
26
22
  const seed = seedData?.seed;
27
23
  if (!seed) return null;
28
24
 
@@ -36,9 +32,7 @@ export async function getStream({ id, s, e, server }) {
36
32
  }
37
33
 
38
34
  const settled = await Promise.allSettled(targets.map(async srv => {
39
- if (srv.id === 'cdn' && isTv) throw new Error();
40
-
41
- let url = `${WINGS_BASE}/${srv.id}/sources-with-title?title=${encTitle}&mediaType=${isTv ? 'tv' : 'movie'}&year=${info.year || ''}&tmdbId=${id}&imdbId=${info.imdbId || "tt0000000"}&enc=2&seed=${seed}`;
35
+ let url = `${SPEEDRACE_BASE}/${srv.id}/sources-with-title?title=${encTitle}&mediaType=${isTv ? 'tv' : 'movie'}&year=${info.year || ''}&tmdbId=${id}&imdbId=${info.imdbId || "tt0000000"}&enc=2&seed=${seed}`;
42
36
  if (isTv) url += `&episodeId=${e}&seasonId=${s}`;
43
37
 
44
38
  const encText = await fetchText(url, { headers: HEADERS, signal: AbortSignal.timeout(10000) });