@vyla-entertainment/sdk 1.20.0 → 1.22.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/package.json +3 -2
- package/src/config.js +99 -67
- package/src/health.js +60 -20
- package/src/sdk.d.ts +94 -121
- package/src/sdk.js +82 -78
- package/src/sources/111477.js +88 -100
- package/src/sources/123anime.js +120 -56
- package/src/sources/3eyedraven.js +82 -135
- package/src/sources/4movies.js +77 -0
- package/src/sources/aether.js +99 -0
- package/src/sources/castle.js +415 -0
- package/src/sources/cinecat.js +37 -18
- package/src/sources/cinemaarmy.js +179 -180
- package/src/sources/cinesrc.js +401 -397
- package/src/sources/cno.js +101 -94
- package/src/sources/dulo.js +134 -149
- package/src/sources/flaxmovies.js +114 -105
- package/src/sources/framextv.js +126 -0
- package/src/sources/frost.js +38 -29
- package/src/sources/fsonline.js +95 -47
- package/src/sources/hdhub.js +43 -36
- package/src/sources/hexa.js +60 -53
- package/src/sources/hyper.js +42 -35
- package/src/sources/kickassanime.js +117 -124
- package/src/sources/kisskh.js +114 -59
- package/src/sources/lmscript.js +72 -70
- package/src/sources/lookmovie.js +80 -79
- package/src/sources/meowtv.js +140 -0
- package/src/sources/movenerds.js +74 -124
- package/src/sources/movy.js +197 -209
- package/src/sources/net79.js +59 -55
- package/src/sources/peestream.js +126 -138
- package/src/sources/pengu.js +46 -49
- package/src/sources/pstream.js +73 -67
- package/src/sources/purstream.js +96 -104
- package/src/sources/rivestream.js +182 -176
- package/src/sources/rivo.js +58 -53
- package/src/sources/streamaggregator.js +114 -128
- package/src/sources/thugtv.js +70 -62
- package/src/sources/vidapi.js +122 -114
- package/src/sources/vidcore.js +14 -122
- package/src/sources/vidfast.js +14 -122
- package/src/sources/vidlink.js +78 -27
- package/src/sources/vidora.js +74 -42
- package/src/sources/vidup.js +14 -122
- package/src/sources/vidvault.js +133 -187
- package/src/sources/vidy.js +192 -187
- package/src/sources/vidzee.js +189 -183
- package/src/sources/vixsrc.js +123 -113
- package/src/sources/watchout.js +60 -46
- package/src/sources/xdownloader.js +127 -128
- package/src/sources/yapgrid.js +68 -62
- package/src/sources/yastream.js +35 -29
- package/src/subtitles.js +105 -122
- package/src/utils/helpers.js +586 -247
- package/src/sources/bcine.js +0 -202
- package/src/sources/cinezo.js +0 -55
- package/src/sources/vidrock.js +0 -145
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const TMDB_BASE_URL = 'https://api.themoviedb.org/3';
|
|
4
|
+
const CASTLE_BASE = 'https://api.hlowb.com';
|
|
5
|
+
const PKG = 'com.external.castle';
|
|
6
|
+
const CHANNEL = 'IndiaA';
|
|
7
|
+
const CLIENT = '1';
|
|
8
|
+
const LANG = 'en-US';
|
|
9
|
+
const SUFFIX = 'T!BgJB';
|
|
10
|
+
const APK_SIGN_KEY = 'ED0955EB04E67A1D9F3305B95454FED485261475';
|
|
11
|
+
|
|
12
|
+
const WORKING_HEADERS = {
|
|
13
|
+
'User-Agent': 'okhttp/4.9.3',
|
|
14
|
+
Accept: 'application/json',
|
|
15
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
16
|
+
Connection: 'Keep-Alive',
|
|
17
|
+
Referer: CASTLE_BASE,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const CASTLE_PLAYBACK_HEADERS = {
|
|
21
|
+
'User-Agent':
|
|
22
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
|
|
23
|
+
Accept:
|
|
24
|
+
'video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5',
|
|
25
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
26
|
+
'Accept-Encoding': 'identity',
|
|
27
|
+
Connection: 'keep-alive',
|
|
28
|
+
'Sec-Fetch-Dest': 'video',
|
|
29
|
+
'Sec-Fetch-Mode': 'no-cors',
|
|
30
|
+
'Sec-Fetch-Site': 'cross-site',
|
|
31
|
+
DNT: '1',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const QUALITY_MAP = { 1: '480p', 2: '720p', 3: '1080p' };
|
|
35
|
+
|
|
36
|
+
function safeJsonParse(text) {
|
|
37
|
+
const safe = text.replace(/:(\s*)(-?\d{16,})(\s*[,}\]])/g, ':$1"$2"$3');
|
|
38
|
+
return JSON.parse(safe);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class CastleScraper {
|
|
42
|
+
constructor(apiKey) {
|
|
43
|
+
this.tmdbKey =
|
|
44
|
+
apiKey ||
|
|
45
|
+
process.env.CASTLE_TMDB_API_KEY ||
|
|
46
|
+
process.env.TMDB_API_KEY ||
|
|
47
|
+
'439c478a771f35c05022f9feabcca01c';
|
|
48
|
+
this.securityKey = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
deriveKey(apiKeyB64) {
|
|
52
|
+
const apiKeyBytes = Buffer.from(apiKeyB64, 'base64');
|
|
53
|
+
let keyMaterial = Buffer.concat([apiKeyBytes, Buffer.from(SUFFIX, 'ascii')]);
|
|
54
|
+
if (keyMaterial.length < 16) {
|
|
55
|
+
keyMaterial = Buffer.concat([
|
|
56
|
+
keyMaterial,
|
|
57
|
+
Buffer.alloc(16 - keyMaterial.length),
|
|
58
|
+
]);
|
|
59
|
+
} else if (keyMaterial.length > 16) {
|
|
60
|
+
keyMaterial = keyMaterial.subarray(0, 16);
|
|
61
|
+
}
|
|
62
|
+
return keyMaterial;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
decryptCastle(encryptedB64, securityKeyB64) {
|
|
66
|
+
const aesKey = this.deriveKey(securityKeyB64);
|
|
67
|
+
const iv = aesKey;
|
|
68
|
+
const encrypted = Buffer.from(encryptedB64, 'base64');
|
|
69
|
+
const decipher = crypto.createDecipheriv('aes-128-cbc', aesKey, iv);
|
|
70
|
+
decipher.setAutoPadding(true);
|
|
71
|
+
return Buffer.concat([
|
|
72
|
+
decipher.update(encrypted),
|
|
73
|
+
decipher.final(),
|
|
74
|
+
]).toString('utf-8');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async request(url, init) {
|
|
78
|
+
const r = await fetch(url, {
|
|
79
|
+
...init,
|
|
80
|
+
headers: { ...WORKING_HEADERS, ...(init?.headers || {}) },
|
|
81
|
+
});
|
|
82
|
+
if (!r.ok) throw new Error(`Castle request failed ${r.status} for ${url}`);
|
|
83
|
+
return r;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async extractCipher(response) {
|
|
87
|
+
const text = (await response.text()).trim();
|
|
88
|
+
if (!text) throw new Error('Empty response');
|
|
89
|
+
try {
|
|
90
|
+
const j = JSON.parse(text);
|
|
91
|
+
if (j && typeof j.data === 'string') return j.data.trim();
|
|
92
|
+
} catch { }
|
|
93
|
+
return text;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async decryptedJson(response, securityKey) {
|
|
97
|
+
const cipher = await this.extractCipher(response);
|
|
98
|
+
return safeJsonParse(this.decryptCastle(cipher, securityKey));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async getSecurityKey() {
|
|
102
|
+
if (this.securityKey) return this.securityKey;
|
|
103
|
+
const url = `${CASTLE_BASE}/v0.1/system/getSecurityKey/1?channel=${CHANNEL}&clientType=${CLIENT}&lang=${LANG}`;
|
|
104
|
+
const r = await this.request(url);
|
|
105
|
+
const data = await r.json();
|
|
106
|
+
if (data.code !== 200 || !data.data) {
|
|
107
|
+
throw new Error(`Security key API error: ${JSON.stringify(data)}`);
|
|
108
|
+
}
|
|
109
|
+
this.securityKey = data.data;
|
|
110
|
+
return this.securityKey;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async searchCastle(securityKey, keyword, page = 1, size = 30) {
|
|
114
|
+
const params = new URLSearchParams({
|
|
115
|
+
channel: CHANNEL,
|
|
116
|
+
clientType: CLIENT,
|
|
117
|
+
keyword,
|
|
118
|
+
lang: LANG,
|
|
119
|
+
mode: '1',
|
|
120
|
+
packageName: PKG,
|
|
121
|
+
page: String(page),
|
|
122
|
+
size: String(size),
|
|
123
|
+
});
|
|
124
|
+
const url = `${CASTLE_BASE}/film-api/v1.1.0/movie/searchByKeyword?${params}`;
|
|
125
|
+
return this.decryptedJson(await this.request(url), securityKey);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async getDetails(securityKey, movieId) {
|
|
129
|
+
const url = `${CASTLE_BASE}/film-api/v1.1/movie?channel=${CHANNEL}&clientType=${CLIENT}&lang=${LANG}&movieId=${movieId}&packageName=${PKG}`;
|
|
130
|
+
return this.decryptedJson(await this.request(url), securityKey);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async getVideo2(securityKey, movieId, episodeId, resolution = 2) {
|
|
134
|
+
const url = `${CASTLE_BASE}/film-api/v2.0.1/movie/getVideo2?clientType=${CLIENT}&packageName=${PKG}&channel=${CHANNEL}&lang=${LANG}`;
|
|
135
|
+
const body = {
|
|
136
|
+
mode: '1',
|
|
137
|
+
appMarket: 'GuanWang',
|
|
138
|
+
clientType: '1',
|
|
139
|
+
woolUser: 'false',
|
|
140
|
+
apkSignKey: APK_SIGN_KEY,
|
|
141
|
+
androidVersion: '13',
|
|
142
|
+
movieId: String(movieId),
|
|
143
|
+
episodeId: String(episodeId),
|
|
144
|
+
isNewUser: 'true',
|
|
145
|
+
resolution: String(resolution),
|
|
146
|
+
packageName: PKG,
|
|
147
|
+
};
|
|
148
|
+
const r = await this.request(url, {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: { 'Content-Type': 'application/json' },
|
|
151
|
+
body: JSON.stringify(body),
|
|
152
|
+
});
|
|
153
|
+
return this.decryptedJson(r, securityKey);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async getVideoV1(securityKey, movieId, episodeId, languageId, resolution = 2) {
|
|
157
|
+
const params = new URLSearchParams({
|
|
158
|
+
apkSignKey: APK_SIGN_KEY,
|
|
159
|
+
channel: CHANNEL,
|
|
160
|
+
clientType: CLIENT,
|
|
161
|
+
episodeId: String(episodeId),
|
|
162
|
+
lang: LANG,
|
|
163
|
+
languageId: String(languageId),
|
|
164
|
+
mode: '1',
|
|
165
|
+
movieId: String(movieId),
|
|
166
|
+
packageName: PKG,
|
|
167
|
+
resolution: String(resolution),
|
|
168
|
+
});
|
|
169
|
+
const url = `${CASTLE_BASE}/film-api/v1.9.1/movie/getVideo?${params}`;
|
|
170
|
+
return this.decryptedJson(await this.request(url), securityKey);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
extractDataBlock(obj) {
|
|
174
|
+
if (obj && typeof obj.data === 'object' && obj.data) return obj.data;
|
|
175
|
+
return obj && typeof obj === 'object' ? obj : {};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
qualityValue(quality) {
|
|
179
|
+
if (!quality) return 0;
|
|
180
|
+
let q = String(quality).toLowerCase();
|
|
181
|
+
q = q.replace(/^(sd|hd|fhd|uhd|4k)\s*/, '').replace(/p$/, '').trim();
|
|
182
|
+
if (q === '4k' || q === '2160') return 2160;
|
|
183
|
+
const known = [1440, 1080, 720, 480, 360, 240];
|
|
184
|
+
const n = parseInt(q, 10);
|
|
185
|
+
if (known.includes(n)) return n;
|
|
186
|
+
return n > 0 ? n : 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
formatSize(size) {
|
|
190
|
+
if (typeof size === 'number' && size > 0) {
|
|
191
|
+
return size > 1_000_000_000
|
|
192
|
+
? `${(size / 1_000_000_000).toFixed(2)} GB`
|
|
193
|
+
: `${Math.round(size / 1_000_000)} MB`;
|
|
194
|
+
}
|
|
195
|
+
return 'Unknown';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async getTmdbDetails(tmdbId, mediaType) {
|
|
199
|
+
const endpoint = mediaType === 'tv' ? 'tv' : 'movie';
|
|
200
|
+
const url = `${TMDB_BASE_URL}/${endpoint}/${tmdbId}?api_key=${this.tmdbKey}&append_to_response=external_ids`;
|
|
201
|
+
const r = await this.request(url);
|
|
202
|
+
const data = await r.json();
|
|
203
|
+
const title = mediaType === 'tv' ? data.name : data.title;
|
|
204
|
+
const releaseDate =
|
|
205
|
+
mediaType === 'tv' ? data.first_air_date : data.release_date;
|
|
206
|
+
const year = releaseDate ? parseInt(String(releaseDate).split('-')[0], 10) : null;
|
|
207
|
+
return { title, year, tmdbId };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
findMovieId(searchResult, tmdbInfo) {
|
|
211
|
+
const data = this.extractDataBlock(searchResult);
|
|
212
|
+
const rows = data.rows || [];
|
|
213
|
+
if (!rows.length) throw new Error('No search results found');
|
|
214
|
+
const searchTitle = (tmdbInfo.title || '').toLowerCase();
|
|
215
|
+
|
|
216
|
+
for (const item of rows) {
|
|
217
|
+
const itemTitle = String(item.title || item.name || '').toLowerCase();
|
|
218
|
+
if (
|
|
219
|
+
itemTitle &&
|
|
220
|
+
(itemTitle.includes(searchTitle) || searchTitle.includes(itemTitle))
|
|
221
|
+
) {
|
|
222
|
+
const id = item.id || item.redirectId || item.redirectIdStr;
|
|
223
|
+
if (id) return String(id);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const first = rows[0];
|
|
227
|
+
const id = first.id || first.redirectId || first.redirectIdStr;
|
|
228
|
+
if (id) return String(id);
|
|
229
|
+
throw new Error('Could not extract movie ID from search results');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
processVideoResponse(videoData, media, season, episode, resolution, languageInfo) {
|
|
233
|
+
const streams = [];
|
|
234
|
+
const data = this.extractDataBlock(videoData);
|
|
235
|
+
const videoUrl = data.videoUrl;
|
|
236
|
+
if (!videoUrl) return streams;
|
|
237
|
+
|
|
238
|
+
let mediaTitle = media.title || 'Unknown';
|
|
239
|
+
if (media.year) mediaTitle += ` (${media.year})`;
|
|
240
|
+
if (season && episode) {
|
|
241
|
+
mediaTitle = `${media.title} S${String(season).padStart(2, '0')}E${String(
|
|
242
|
+
episode
|
|
243
|
+
).padStart(2, '0')}`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const quality = QUALITY_MAP[resolution] || `${resolution}p`;
|
|
247
|
+
|
|
248
|
+
if (Array.isArray(data.videos)) {
|
|
249
|
+
for (const video of data.videos) {
|
|
250
|
+
let q = String(
|
|
251
|
+
video.resolutionDescription || video.resolution || quality
|
|
252
|
+
).replace(/^(SD|HD|FHD)\s+/i, '');
|
|
253
|
+
const name = languageInfo
|
|
254
|
+
? `Castle ${languageInfo} - ${q}`
|
|
255
|
+
: `Castle - ${q}`;
|
|
256
|
+
streams.push({
|
|
257
|
+
name,
|
|
258
|
+
title: mediaTitle,
|
|
259
|
+
url: video.url || videoUrl,
|
|
260
|
+
quality: q,
|
|
261
|
+
size: this.formatSize(video.size),
|
|
262
|
+
provider: 'castle',
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
const name = languageInfo
|
|
267
|
+
? `Castle ${languageInfo} - ${quality}`
|
|
268
|
+
: `Castle - ${quality}`;
|
|
269
|
+
streams.push({
|
|
270
|
+
name,
|
|
271
|
+
title: mediaTitle,
|
|
272
|
+
url: videoUrl,
|
|
273
|
+
quality,
|
|
274
|
+
size: this.formatSize(data.size),
|
|
275
|
+
provider: 'castle',
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return streams;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async fetchSources(tmdbId, season = 0, episode = 0) {
|
|
282
|
+
const mediaType = season && episode ? 'tv' : 'movie';
|
|
283
|
+
try {
|
|
284
|
+
const tmdbInfo = await this.getTmdbDetails(tmdbId, mediaType);
|
|
285
|
+
const securityKey = await this.getSecurityKey();
|
|
286
|
+
|
|
287
|
+
const searchTerm = tmdbInfo.year
|
|
288
|
+
? `${tmdbInfo.title} ${tmdbInfo.year}`
|
|
289
|
+
: tmdbInfo.title;
|
|
290
|
+
const searchResult = await this.searchCastle(securityKey, searchTerm);
|
|
291
|
+
let movieId = this.findMovieId(searchResult, tmdbInfo);
|
|
292
|
+
|
|
293
|
+
let details = await this.getDetails(securityKey, movieId);
|
|
294
|
+
|
|
295
|
+
if (mediaType === 'tv') {
|
|
296
|
+
const data = this.extractDataBlock(details);
|
|
297
|
+
const seasons = data.seasons || [];
|
|
298
|
+
const s = seasons.find((x) => x.number === season);
|
|
299
|
+
if (s && s.movieId && String(s.movieId) !== movieId) {
|
|
300
|
+
movieId = String(s.movieId);
|
|
301
|
+
details = await this.getDetails(securityKey, movieId);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const data = this.extractDataBlock(details);
|
|
306
|
+
const episodes = data.episodes || [];
|
|
307
|
+
|
|
308
|
+
let episodeId = null;
|
|
309
|
+
if (mediaType === 'tv') {
|
|
310
|
+
const ep = episodes.find((e) => e.number === episode);
|
|
311
|
+
if (ep && ep.id) episodeId = String(ep.id);
|
|
312
|
+
} else if (episodes.length) {
|
|
313
|
+
episodeId = String(episodes[0].id);
|
|
314
|
+
}
|
|
315
|
+
if (!episodeId) throw new Error('Could not find episode ID');
|
|
316
|
+
|
|
317
|
+
const epObj = episodes.find((e) => String(e.id) === episodeId);
|
|
318
|
+
const tracks = epObj?.tracks || [];
|
|
319
|
+
|
|
320
|
+
const resolution = 2;
|
|
321
|
+
let allStreams = [];
|
|
322
|
+
|
|
323
|
+
for (let i = 0; i < tracks.length; i++) {
|
|
324
|
+
const track = tracks[i];
|
|
325
|
+
const langName =
|
|
326
|
+
track.languageName || track.abbreviate || `Lang${i + 1}`;
|
|
327
|
+
if (track.existIndividualVideo && track.languageId) {
|
|
328
|
+
try {
|
|
329
|
+
const videoData = await this.getVideoV1(
|
|
330
|
+
securityKey,
|
|
331
|
+
movieId,
|
|
332
|
+
episodeId,
|
|
333
|
+
String(track.languageId),
|
|
334
|
+
resolution
|
|
335
|
+
);
|
|
336
|
+
const langStreams = this.processVideoResponse(
|
|
337
|
+
videoData,
|
|
338
|
+
tmdbInfo,
|
|
339
|
+
season,
|
|
340
|
+
episode,
|
|
341
|
+
resolution,
|
|
342
|
+
`[${langName}]`
|
|
343
|
+
);
|
|
344
|
+
allStreams.push(...langStreams);
|
|
345
|
+
} catch (e) {
|
|
346
|
+
console.error(`[Castle] ${langName}: v1 failed - ${e?.message || e}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!allStreams.length) {
|
|
352
|
+
const videoData = await this.getVideo2(
|
|
353
|
+
securityKey,
|
|
354
|
+
movieId,
|
|
355
|
+
episodeId,
|
|
356
|
+
resolution
|
|
357
|
+
);
|
|
358
|
+
allStreams = this.processVideoResponse(
|
|
359
|
+
videoData,
|
|
360
|
+
tmdbInfo,
|
|
361
|
+
season,
|
|
362
|
+
episode,
|
|
363
|
+
resolution,
|
|
364
|
+
'[Shared]'
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
allStreams.sort(
|
|
369
|
+
(a, b) => this.qualityValue(b.quality) - this.qualityValue(a.quality)
|
|
370
|
+
);
|
|
371
|
+
return allStreams;
|
|
372
|
+
} catch (e) {
|
|
373
|
+
console.error(`[Castle] Error: ${e?.message || e}`);
|
|
374
|
+
return [];
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export async function getStream(args) {
|
|
380
|
+
const { id, s, e, sdk } = args || {};
|
|
381
|
+
const scraper = new CastleScraper(sdk?.tmdbApiKey);
|
|
382
|
+
const tmdbId = parseInt(String(id), 10);
|
|
383
|
+
const season = s != null ? parseInt(String(s), 10) : 0;
|
|
384
|
+
const episode = e != null ? parseInt(String(e), 10) : 0;
|
|
385
|
+
|
|
386
|
+
const streams = await scraper.fetchSources(tmdbId, season, episode).catch(() => []);
|
|
387
|
+
if (!streams || !streams.length) return null;
|
|
388
|
+
|
|
389
|
+
const seen = new Set();
|
|
390
|
+
const allUrls = [];
|
|
391
|
+
|
|
392
|
+
for (const item of streams) {
|
|
393
|
+
if (!item?.url || seen.has(item.url)) continue;
|
|
394
|
+
seen.add(item.url);
|
|
395
|
+
allUrls.push({
|
|
396
|
+
url: item.url,
|
|
397
|
+
server: item.name || 'Castle',
|
|
398
|
+
quality: item.quality || 'Auto',
|
|
399
|
+
type: item.url.includes('.m3u8')
|
|
400
|
+
? 'hls'
|
|
401
|
+
: item.url.includes('.mpd')
|
|
402
|
+
? 'dash'
|
|
403
|
+
: 'mp4',
|
|
404
|
+
headers: CASTLE_PLAYBACK_HEADERS,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (!allUrls.length) return null;
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
url: allUrls[0].url,
|
|
412
|
+
headers: allUrls[0].headers,
|
|
413
|
+
allUrls,
|
|
414
|
+
};
|
|
415
|
+
}
|
package/src/sources/cinecat.js
CHANGED
|
@@ -1,19 +1,38 @@
|
|
|
1
|
-
import { fetchJson } from '../utils/helpers.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
import { fetchJson } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = 'https://zcdn.bluevelvet.space';
|
|
4
|
+
|
|
5
|
+
export async function getStream(args) {
|
|
6
|
+
const { id, s, e } = args || {};
|
|
7
|
+
const isTv = s != null && e != null;
|
|
8
|
+
|
|
9
|
+
const url = isTv
|
|
10
|
+
? `${BASE_URL}/cdn?tmdb=${id}&type=tv&season=${s}&episode=${e}`
|
|
11
|
+
: `${BASE_URL}/cdn?tmdb=${id}&type=movie`;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetchJson(url);
|
|
15
|
+
if (!res || !res.ready || !res.url) return null;
|
|
16
|
+
|
|
17
|
+
const headers = {
|
|
18
|
+
Referer: `${BASE_URL}/`,
|
|
19
|
+
Origin: BASE_URL,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const allUrls = [{
|
|
23
|
+
url: res.url,
|
|
24
|
+
server: 'CineCat',
|
|
25
|
+
quality: 'Auto',
|
|
26
|
+
type: res.url.includes('.m3u8') ? 'hls' : 'mp4',
|
|
27
|
+
headers,
|
|
28
|
+
}];
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
url: res.url,
|
|
32
|
+
headers,
|
|
33
|
+
allUrls,
|
|
34
|
+
};
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
19
38
|
}
|