@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,194 @@
1
+ import crypto from 'node:crypto';
2
+
3
+ export const UA_LIST = [
4
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
5
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
6
+ 'Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0',
7
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',
8
+ ];
9
+
10
+ export const getUA = () => UA_LIST[Math.floor(Math.random() * UA_LIST.length)];
11
+ export const USER_AGENT = UA_LIST[0];
12
+
13
+ let sharedDcKey = null;
14
+
15
+ export function decryptPartner(responseText, keyEnv) {
16
+ try {
17
+ const sep = responseText.indexOf(':');
18
+ if (sep === -1) return null;
19
+ const iv = Buffer.from(responseText.slice(0, sep), 'base64');
20
+ const encrypted = responseText.slice(sep + 1);
21
+ if (!sharedDcKey) sharedDcKey = crypto.createHash('sha256').update(keyEnv || '').digest();
22
+ const decipher = crypto.createDecipheriv('aes-256-cbc', sharedDcKey, iv);
23
+ return JSON.parse(decipher.update(encrypted, 'base64', 'utf8') + decipher.final('utf8'));
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ export async function fetchJson(url, options) {
30
+ const res = await fetch(url, options);
31
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
32
+ return res.json();
33
+ }
34
+
35
+ export async function fetchText(url, options) {
36
+ const res = await fetch(url, options);
37
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
38
+ return res.text();
39
+ }
40
+
41
+ const tmdbInfoCache = new Map();
42
+ const anilistCache = new Map();
43
+ const tmdbValidationCache = new Map();
44
+
45
+ function pruneHelperCache(cache) {
46
+ const now = Date.now();
47
+ for (const [k, v] of cache) if (now - v.ts >= v.ttl) cache.delete(k);
48
+ if (cache.size > 3000) {
49
+ const overflow = cache.size - 3000;
50
+ const it = cache.keys();
51
+ for (let i = 0; i < overflow; i++) cache.delete(it.next().value);
52
+ }
53
+ }
54
+
55
+ setInterval(() => {
56
+ pruneHelperCache(tmdbValidationCache);
57
+ pruneHelperCache(tmdbInfoCache);
58
+ pruneHelperCache(anilistCache);
59
+ }, 60000).unref();
60
+
61
+ function cacheGet(cache, key) {
62
+ const entry = cache.get(key);
63
+ if (!entry) return undefined;
64
+ if (Date.now() - entry.ts >= entry.ttl) { cache.delete(key); return undefined; }
65
+ return entry.val;
66
+ }
67
+
68
+ function cacheSet(cache, key, val, ttl) { cache.set(key, { val, ts: Date.now(), ttl }); }
69
+
70
+ export async function createStreamArgs(source, tmdbApiKey, id, s, e, clientIP=null) {
71
+ const getAbsoluteBase = host => (host.startsWith('localhost') || host.startsWith('127.0.0.1')) ? `http://${host}` : `https://${host}`;
72
+ const audio = /dub$/.test(source.key) ? 'dub' : 'sub';
73
+ return {
74
+ id: id,
75
+ s: s,
76
+ e: e,
77
+ clientIP: clientIP,
78
+ absoluteBase: '/',
79
+ audio,
80
+ tmdbApiKey,
81
+ config: source,
82
+ }
83
+ }
84
+
85
+ export async function validateTmdbId(tmdbApiKey, tmdbId, mediaType = 'movie') {
86
+ const key = `${tmdbId}-${mediaType}`;
87
+ const cached = cacheGet(tmdbValidationCache, key);
88
+ if (cached !== undefined) return cached;
89
+ const k = tmdbApiKey;
90
+ if (!k) return { valid: false, error: 'TMDB API key not configured' };
91
+ try {
92
+ const res = await fetch(`https://api.themoviedb.org/3/${mediaType}/${tmdbId}?api_key=${k}`, { signal: AbortSignal.timeout(5000) });
93
+ if (res.ok) {
94
+ const data = await res.json();
95
+ const isValid = !!(data?.id && (data?.title || data?.name));
96
+ const result = { valid: isValid, error: isValid ? null : 'Invalid TMDB ID' };
97
+ cacheSet(tmdbValidationCache, key, result, 300000);
98
+ return result;
99
+ }
100
+ const result = { valid: false, error: `TMDB API error: ${res.status}` };
101
+ cacheSet(tmdbValidationCache, key, result, 60000);
102
+ return result;
103
+ } catch (error) {
104
+ const result = { valid: false, error: `Failed to validate TMDB ID: ${error.message}` };
105
+ cacheSet(tmdbValidationCache, key, result, 60000);
106
+ return result;
107
+ }
108
+ }
109
+
110
+ export async function getTmdbInfo(tmdbApiKey, tmdbId, mediaType, season) {
111
+ const key = `${tmdbId}-${mediaType}-${season || ''}`;
112
+ const cached = cacheGet(tmdbInfoCache, key);
113
+ if (cached !== undefined) return cached;
114
+ const k = tmdbApiKey;
115
+ if (!k) return { isAnime: false, titles: [], year: null, imdbId: null };
116
+ try {
117
+ const [mainRes, seasonRes] = await Promise.all([
118
+ fetch(`https://api.themoviedb.org/3/${mediaType}/${tmdbId}?api_key=${k}&append_to_response=external_ids`, { signal: AbortSignal.timeout(5000) }),
119
+ season && mediaType === 'tv' ? fetch(`https://api.themoviedb.org/3/tv/${tmdbId}/season/${season}?api_key=${k}`, { signal: AbortSignal.timeout(5000) }) : Promise.resolve(null),
120
+ ]);
121
+ let mainData = null, seasonData = null;
122
+ if (mainRes.ok) mainData = await mainRes.json(); else mainRes.body?.cancel();
123
+ if (seasonRes) { if (seasonRes.ok) seasonData = await seasonRes.json(); else seasonRes.body?.cancel(); }
124
+ const genres = mainData?.genres || [];
125
+ const isAnime = genres.some(g => g.id === 16) && ((mainData?.origin_country || []).includes('JP') || mainData?.original_language === 'ja');
126
+ const titles = [];
127
+ if (seasonData?.name) titles.push(seasonData.name);
128
+ const t = mainData?.title || mainData?.name || '';
129
+ const ot = mainData?.original_title || mainData?.original_name || '';
130
+ if (t) titles.push(t);
131
+ if (ot && ot !== t) titles.push(ot);
132
+ const dateStr = seasonData?.air_date || mainData?.release_date || mainData?.first_air_date || '';
133
+ const result = { isAnime, titles: [...new Set(titles.filter(Boolean))], year: dateStr ? parseInt(dateStr.slice(0, 4), 10) : null, imdbId: mainData?.imdb_id || mainData?.external_ids?.imdb_id || null };
134
+ cacheSet(tmdbInfoCache, key, result, 600000);
135
+ return result;
136
+ } catch { return { isAnime: false, titles: [], year: null, imdbId: null }; }
137
+ }
138
+
139
+ export async function tmdbToAnilist(tmdbId, mediaType, season, titles = [], year = null) {
140
+ const key = `${tmdbId}-${mediaType}-${season || ''}`;
141
+ const cached = cacheGet(anilistCache, key);
142
+ if (cached !== undefined) return cached;
143
+ try {
144
+ const res = await fetch(`https://api.ani.zip/mappings?tmdb_id=${tmdbId}&type=${mediaType}&season=${season || 1}`, { signal: AbortSignal.timeout(6000) });
145
+ if (res.ok) {
146
+ const id = (await res.json())?.mappings?.[0]?.anilist_id;
147
+ if (id) { cacheSet(anilistCache, key, id, 600000); return id; }
148
+ } else res.body?.cancel();
149
+ } catch { }
150
+ if (!titles.length) return null;
151
+ const query = `query ($s: String) { Page(page:1,perPage:10) { media(search:$s,type:ANIME) { id title { romaji english native } startDate { year } format } } }`;
152
+ let bestId = null, bestScore = -1;
153
+ for (const searchTitle of titles) {
154
+ try {
155
+ const res = await fetch('https://graphql.anilist.co', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ query, variables: { s: searchTitle } }), signal: AbortSignal.timeout(6000) });
156
+ if (!res.ok) { res.body?.cancel(); continue; }
157
+ for (const entry of (await res.json())?.data?.Page?.media || []) {
158
+ const entryTitles = [entry.title?.romaji, entry.title?.english, entry.title?.native].filter(Boolean).map(t => t.toLowerCase());
159
+ const sl = searchTitle.toLowerCase();
160
+ let score = 0;
161
+ if (entryTitles.some(t => t === sl)) score += 5;
162
+ else if (entryTitles.some(t => t.includes(sl) || sl.includes(t))) score += 3;
163
+ else if (entryTitles.some(t => { const w = sl.split(/\s+/).filter(x => x.length > 3); return w.length > 0 && w.every(x => t.includes(x)); })) score += 2;
164
+ else continue;
165
+ if (year && entry.startDate?.year) {
166
+ const diff = Math.abs(entry.startDate.year - year);
167
+ if (diff === 0) score += 3; else if (diff === 1) score += 1; else if (diff > 2) score -= 3;
168
+ }
169
+ if (mediaType === 'tv' && ['TV', 'TV_SHORT', 'ONA', 'OVA'].includes(entry.format)) score += 1;
170
+ if (mediaType === 'movie' && entry.format === 'MOVIE') score += 1;
171
+ if (score > bestScore) { bestScore = score; bestId = entry.id; }
172
+ }
173
+ if (bestScore >= 8) break;
174
+ } catch { }
175
+ }
176
+ if (bestId) cacheSet(anilistCache, key, bestId, 600000);
177
+ return bestId;
178
+ }
179
+
180
+ export async function imdbToTmdb(tmdbApiKey, imdbId, mediaType = 'movie') {
181
+ const key = `imdb-${imdbId}`;
182
+ const cached = cacheGet(tmdbInfoCache, key);
183
+ if (cached !== undefined) return cached;
184
+ const k = tmdbApiKey;
185
+ if (!k) return null;
186
+ try {
187
+ const data = await fetchJson(`https://api.themoviedb.org/3/find/${imdbId}?api_key=${k}&external_source=imdb_id`, { signal: AbortSignal.timeout(5000) });
188
+ let result = null;
189
+ if (mediaType === 'movie' && data.movie_results?.length) result = data.movie_results[0].id;
190
+ else if (mediaType === 'tv' && data.tv_results?.length) result = data.tv_results[0].id;
191
+ if (result) cacheSet(tmdbInfoCache, key, result, 600000);
192
+ return result;
193
+ } catch { return null; }
194
+ }