@vyla-entertainment/sdk 1.21.0 → 1.23.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 +2 -1
- package/package.json +1 -1
- package/src/config.js +145 -0
- package/src/health.js +60 -20
- package/src/sources/4movies.js +77 -0
- package/src/sources/aether.js +99 -0
- package/src/sources/braflix.js +80 -0
- package/src/sources/castle.js +415 -0
- package/src/sources/framextv.js +126 -0
- package/src/sources/meowtv.js +140 -0
- package/src/sources/vidora.js +94 -0
- package/src/sources/zenox.js +108 -0
package/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# vyla-entertainment/sdk
|
|
2
|
+
|
|
2
3
|
The Vyla SDK (@vyla-entertainment/sdk) is a server-side JavaScript/TypeScript package that provides direct access to Vyla's stream resolvers, health probes, download resolvers, and subtitle providers.
|
|
3
4
|
|
|
4
5
|
## Installing
|
|
@@ -11,7 +12,7 @@ npm install @vyla-entertainment/sdk
|
|
|
11
12
|
pnpm install @vyla-entertainment/sdk
|
|
12
13
|
```
|
|
13
14
|
|
|
14
|
-
View our full documentation at https://
|
|
15
|
+
View our full documentation at https://vyla.cc/sdk
|
|
15
16
|
|
|
16
17
|
# [DMCA](https://vyla.cc/misc/project/DMCA)
|
|
17
18
|
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -18,6 +18,25 @@ export const SOURCES = [
|
|
|
18
18
|
}],
|
|
19
19
|
},
|
|
20
20
|
|
|
21
|
+
{
|
|
22
|
+
key: '4movies',
|
|
23
|
+
label: '4Movies',
|
|
24
|
+
sourceFile: '4movies',
|
|
25
|
+
proxyParam: '4m',
|
|
26
|
+
timeout: 25000,
|
|
27
|
+
jitter: 500,
|
|
28
|
+
retries: 2,
|
|
29
|
+
multiUrl: true,
|
|
30
|
+
skipProxy: true,
|
|
31
|
+
cdnHeaders: [{
|
|
32
|
+
pattern: /4movie\.in|aapanel\.devcorp\.me/i,
|
|
33
|
+
headers: {
|
|
34
|
+
Referer: 'https://4movie.in/',
|
|
35
|
+
Origin: 'https://4movie.in',
|
|
36
|
+
},
|
|
37
|
+
}],
|
|
38
|
+
},
|
|
39
|
+
|
|
21
40
|
{
|
|
22
41
|
key: '123anime',
|
|
23
42
|
label: '123Anime',
|
|
@@ -56,6 +75,56 @@ export const SOURCES = [
|
|
|
56
75
|
}],
|
|
57
76
|
},
|
|
58
77
|
|
|
78
|
+
{
|
|
79
|
+
key: 'aether',
|
|
80
|
+
label: 'Aether',
|
|
81
|
+
sourceFile: 'aether',
|
|
82
|
+
proxyParam: 'aet',
|
|
83
|
+
timeout: 25000,
|
|
84
|
+
jitter: 500,
|
|
85
|
+
retries: 2,
|
|
86
|
+
multiUrl: true,
|
|
87
|
+
skipProxy: true,
|
|
88
|
+
cdnHeaders: [{
|
|
89
|
+
pattern: /raccoonunbutteredbagel\.zip|1x2\.space|kaxate\.fun|mechanicalserenity\.xyz|reelvault\.click|visionaryfounderslab\.site|goldenkite\.top|aether\.ist/i,
|
|
90
|
+
headers: {
|
|
91
|
+
Referer: 'https://aether.st/',
|
|
92
|
+
Origin: 'https://aether.st',
|
|
93
|
+
},
|
|
94
|
+
}],
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
{
|
|
98
|
+
key: 'braflix',
|
|
99
|
+
label: 'Braflix',
|
|
100
|
+
sourceFile: 'braflix',
|
|
101
|
+
proxyParam: 'bf',
|
|
102
|
+
timeout: 25000,
|
|
103
|
+
jitter: 500,
|
|
104
|
+
retries: 2,
|
|
105
|
+
multiUrl: true,
|
|
106
|
+
skipProxy: true,
|
|
107
|
+
cdnHeaders: [{
|
|
108
|
+
pattern: /braflix\.dev/i,
|
|
109
|
+
headers: {
|
|
110
|
+
Referer: 'https://braflix.dev/',
|
|
111
|
+
Origin: 'https://braflix.dev',
|
|
112
|
+
},
|
|
113
|
+
}],
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
{
|
|
117
|
+
key: 'castle',
|
|
118
|
+
label: 'Castle',
|
|
119
|
+
sourceFile: 'castle',
|
|
120
|
+
proxyParam: 'cst',
|
|
121
|
+
timeout: 25000,
|
|
122
|
+
jitter: 500,
|
|
123
|
+
retries: 2,
|
|
124
|
+
multiUrl: true,
|
|
125
|
+
skipProxy: true,
|
|
126
|
+
},
|
|
127
|
+
|
|
59
128
|
{
|
|
60
129
|
key: 'cinemaarmy',
|
|
61
130
|
label: 'CinemaArmy',
|
|
@@ -151,6 +220,25 @@ export const SOURCES = [
|
|
|
151
220
|
}],
|
|
152
221
|
},
|
|
153
222
|
|
|
223
|
+
{
|
|
224
|
+
key: 'framextv',
|
|
225
|
+
label: 'FrameXTV',
|
|
226
|
+
sourceFile: 'framextv',
|
|
227
|
+
proxyParam: 'fxt',
|
|
228
|
+
timeout: 25000,
|
|
229
|
+
jitter: 500,
|
|
230
|
+
retries: 2,
|
|
231
|
+
multiUrl: true,
|
|
232
|
+
skipProxy: true,
|
|
233
|
+
cdnHeaders: [{
|
|
234
|
+
pattern: /framextv\.cc|streamos\.top|workers\.dev|films365\.org/i,
|
|
235
|
+
headers: {
|
|
236
|
+
Referer: 'https://framextv.cc/',
|
|
237
|
+
Origin: 'https://framextv.cc',
|
|
238
|
+
},
|
|
239
|
+
}],
|
|
240
|
+
},
|
|
241
|
+
|
|
154
242
|
{
|
|
155
243
|
key: 'frost',
|
|
156
244
|
label: 'FrostStream',
|
|
@@ -278,6 +366,25 @@ export const SOURCES = [
|
|
|
278
366
|
},
|
|
279
367
|
},
|
|
280
368
|
|
|
369
|
+
{
|
|
370
|
+
key: 'meowtv',
|
|
371
|
+
label: 'MeowTV',
|
|
372
|
+
sourceFile: 'meowtv',
|
|
373
|
+
proxyParam: 'mtv',
|
|
374
|
+
timeout: 25000,
|
|
375
|
+
jitter: 500,
|
|
376
|
+
retries: 2,
|
|
377
|
+
multiUrl: true,
|
|
378
|
+
skipProxy: true,
|
|
379
|
+
cdnHeaders: [{
|
|
380
|
+
pattern: /meowtv\.ru/i,
|
|
381
|
+
headers: {
|
|
382
|
+
Referer: 'https://meowtv.ru/',
|
|
383
|
+
Origin: 'https://meowtv.ru',
|
|
384
|
+
},
|
|
385
|
+
}],
|
|
386
|
+
},
|
|
387
|
+
|
|
281
388
|
{
|
|
282
389
|
key: 'movenerds',
|
|
283
390
|
label: 'MoverNerds',
|
|
@@ -481,6 +588,25 @@ export const SOURCES = [
|
|
|
481
588
|
multiUrl: true,
|
|
482
589
|
},
|
|
483
590
|
|
|
591
|
+
{
|
|
592
|
+
key: 'vidora',
|
|
593
|
+
label: 'Vidora',
|
|
594
|
+
sourceFile: 'vidora',
|
|
595
|
+
proxyParam: 'vd',
|
|
596
|
+
timeout: 25000,
|
|
597
|
+
jitter: 500,
|
|
598
|
+
retries: 2,
|
|
599
|
+
multiUrl: true,
|
|
600
|
+
skipProxy: true,
|
|
601
|
+
cdnHeaders: [{
|
|
602
|
+
pattern: /vidoraplayer\.duckdns\.org/i,
|
|
603
|
+
headers: {
|
|
604
|
+
Referer: 'https://vidoraplayer.duckdns.org/',
|
|
605
|
+
Origin: 'https://vidoraplayer.duckdns.org',
|
|
606
|
+
},
|
|
607
|
+
}],
|
|
608
|
+
},
|
|
609
|
+
|
|
484
610
|
{
|
|
485
611
|
key: 'vidup',
|
|
486
612
|
label: 'VidUp',
|
|
@@ -618,6 +744,25 @@ export const SOURCES = [
|
|
|
618
744
|
multiUrl: true,
|
|
619
745
|
},
|
|
620
746
|
|
|
747
|
+
{
|
|
748
|
+
key: 'zenox',
|
|
749
|
+
label: 'Zenox',
|
|
750
|
+
sourceFile: 'zenox',
|
|
751
|
+
proxyParam: 'zx',
|
|
752
|
+
timeout: 25000,
|
|
753
|
+
jitter: 500,
|
|
754
|
+
retries: 2,
|
|
755
|
+
multiUrl: true,
|
|
756
|
+
skipProxy: true,
|
|
757
|
+
cdnHeaders: [{
|
|
758
|
+
pattern: /zenox\.lol|hls-m3u8\.lol/i,
|
|
759
|
+
headers: {
|
|
760
|
+
Referer: 'https://zenox.lol/',
|
|
761
|
+
Origin: 'https://zenox.lol',
|
|
762
|
+
},
|
|
763
|
+
}],
|
|
764
|
+
},
|
|
765
|
+
|
|
621
766
|
];
|
|
622
767
|
|
|
623
768
|
export const HEALTH_PROBE_ID = '155';
|
package/src/health.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import { HEALTH_PROBE_ID } from "./config.js";
|
|
2
1
|
|
|
3
|
-
const
|
|
2
|
+
const MOVIE_PROBE_ID = "936075";
|
|
3
|
+
const TV_PROBE_ID = "37854";
|
|
4
|
+
const TV_PROBE_SEASON = "1";
|
|
5
|
+
const TV_PROBE_EPISODE = "1";
|
|
6
|
+
|
|
7
|
+
const withTimeout = (promise, ms) =>
|
|
8
|
+
Promise.race([
|
|
9
|
+
promise,
|
|
10
|
+
new Promise(resolve => setTimeout(() => resolve(null), ms))
|
|
11
|
+
]);
|
|
12
|
+
|
|
4
13
|
async function withRetry(fn, attempts = 2, delay = 300) {
|
|
5
14
|
for (let i = 0; i < attempts; i++) {
|
|
6
15
|
try {
|
|
@@ -8,31 +17,62 @@ async function withRetry(fn, attempts = 2, delay = 300) {
|
|
|
8
17
|
if (result != null) return result;
|
|
9
18
|
} catch (err) {
|
|
10
19
|
if (i === attempts - 1) throw err;
|
|
11
|
-
await new Promise(
|
|
20
|
+
await new Promise(resolve =>
|
|
21
|
+
setTimeout(resolve, delay + Math.random() * delay * 0.5)
|
|
22
|
+
);
|
|
12
23
|
}
|
|
13
24
|
}
|
|
14
25
|
return null;
|
|
15
26
|
}
|
|
16
27
|
|
|
28
|
+
function hasStream(result) {
|
|
29
|
+
const urls = [
|
|
30
|
+
result?.url,
|
|
31
|
+
...(result?.allUrls || []).map(item => item?.url)
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
return urls.some(url =>
|
|
35
|
+
typeof url === "string" && url.startsWith("http")
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
17
39
|
export async function probeSource(sdk, cfg, mod) {
|
|
18
|
-
const audio = /dub$/.test(cfg.key) ?
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
const audio = /dub$/.test(cfg.key) ? "dub" : "sub";
|
|
41
|
+
const start = Date.now();
|
|
42
|
+
|
|
43
|
+
const test = async (id, s = null, e = null) => {
|
|
44
|
+
const streamArgs = {
|
|
45
|
+
id,
|
|
46
|
+
s,
|
|
47
|
+
e,
|
|
48
|
+
clientIP: null,
|
|
49
|
+
absoluteBase: null,
|
|
50
|
+
audio,
|
|
51
|
+
sdk,
|
|
52
|
+
config: cfg,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const result = await withTimeout(
|
|
56
|
+
withRetry(
|
|
57
|
+
() => mod.getStream(streamArgs),
|
|
58
|
+
cfg.retries,
|
|
59
|
+
300
|
|
60
|
+
),
|
|
61
|
+
cfg.timeout
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return hasStream(result);
|
|
28
65
|
};
|
|
29
66
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
);
|
|
67
|
+
const [movie, tv] = await Promise.all([
|
|
68
|
+
test(MOVIE_PROBE_ID),
|
|
69
|
+
test(TV_PROBE_ID, TV_PROBE_SEASON, TV_PROBE_EPISODE)
|
|
70
|
+
]);
|
|
35
71
|
|
|
36
|
-
|
|
37
|
-
|
|
72
|
+
return {
|
|
73
|
+
ok: movie || tv,
|
|
74
|
+
movie,
|
|
75
|
+
tv,
|
|
76
|
+
ms: Date.now() - start
|
|
77
|
+
};
|
|
38
78
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const BASE_URL = 'https://4movie.in';
|
|
2
|
+
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
|
|
3
|
+
|
|
4
|
+
const HEADERS = {
|
|
5
|
+
'User-Agent': UA,
|
|
6
|
+
Referer: `${BASE_URL}/`,
|
|
7
|
+
Origin: BASE_URL,
|
|
8
|
+
Accept: 'application/json, text/plain, */*',
|
|
9
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const PLAYBACK_HEADERS = {
|
|
13
|
+
'User-Agent': UA,
|
|
14
|
+
Referer: `${BASE_URL}/`,
|
|
15
|
+
Origin: BASE_URL,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export async function getStream(args) {
|
|
19
|
+
const { id, s, e } = args || {};
|
|
20
|
+
const isTv = s != null && e != null;
|
|
21
|
+
|
|
22
|
+
let query = isTv
|
|
23
|
+
? `tmdb=${id}&type=tv&season=${s}&episode=${e}`
|
|
24
|
+
: `tmdb=${id}&type=movie`;
|
|
25
|
+
|
|
26
|
+
if (args?.title) query += `&title=${encodeURIComponent(args.title)}`;
|
|
27
|
+
if (args?.year) query += `&year=${encodeURIComponent(args.year)}`;
|
|
28
|
+
|
|
29
|
+
const targetUrl = `${BASE_URL}/api/hubcloud?${query}`;
|
|
30
|
+
|
|
31
|
+
const res = await fetch(targetUrl, { headers: HEADERS }).catch(() => null);
|
|
32
|
+
if (!res || !res.ok) return null;
|
|
33
|
+
|
|
34
|
+
const data = await res.json().catch(() => null);
|
|
35
|
+
if (!data?.ok || !Array.isArray(data.streams) || !data.streams.length) return null;
|
|
36
|
+
|
|
37
|
+
const allUrls = [];
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
|
|
40
|
+
for (const stream of data.streams) {
|
|
41
|
+
if (stream.rawUrl && !seen.has(stream.rawUrl)) {
|
|
42
|
+
seen.add(stream.rawUrl);
|
|
43
|
+
allUrls.push({
|
|
44
|
+
url: stream.rawUrl,
|
|
45
|
+
server: `4Movies - ${stream.name || 'Direct'}`,
|
|
46
|
+
quality: stream.quality || 'Auto',
|
|
47
|
+
type: stream.type || (stream.rawUrl.includes('.m3u8') ? 'hls' : 'mp4'),
|
|
48
|
+
headers: PLAYBACK_HEADERS,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (stream.url) {
|
|
53
|
+
const proxyUrl = stream.url.startsWith('http')
|
|
54
|
+
? stream.url
|
|
55
|
+
: `${BASE_URL}${stream.url.startsWith('/') ? '' : '/'}${stream.url}`;
|
|
56
|
+
|
|
57
|
+
if (!seen.has(proxyUrl)) {
|
|
58
|
+
seen.add(proxyUrl);
|
|
59
|
+
allUrls.push({
|
|
60
|
+
url: proxyUrl,
|
|
61
|
+
server: `4Movies - ${stream.name ? `${stream.name} (Proxy)` : 'Proxy'}`,
|
|
62
|
+
quality: stream.quality || 'Auto',
|
|
63
|
+
type: stream.type || 'hls',
|
|
64
|
+
headers: PLAYBACK_HEADERS,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!allUrls.length) return null;
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
url: allUrls[0].url,
|
|
74
|
+
headers: allUrls[0].headers,
|
|
75
|
+
allUrls,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
|
|
2
|
+
|
|
3
|
+
const HEADERS = {
|
|
4
|
+
'User-Agent': UA,
|
|
5
|
+
Origin: 'https://aether.st',
|
|
6
|
+
Referer: 'https://aether.st/',
|
|
7
|
+
Accept: 'application/json, text/plain, */*',
|
|
8
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const PLAYBACK_HEADERS = {
|
|
12
|
+
'User-Agent': UA,
|
|
13
|
+
Referer: 'https://aether.st/',
|
|
14
|
+
Origin: 'https://aether.st',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function extractStreamUrls(data, target) {
|
|
18
|
+
const urls = [];
|
|
19
|
+
if (!data) return urls;
|
|
20
|
+
|
|
21
|
+
if (Array.isArray(data.streams)) {
|
|
22
|
+
for (const s of data.streams) {
|
|
23
|
+
if (s?.url) {
|
|
24
|
+
urls.push({
|
|
25
|
+
url: s.url,
|
|
26
|
+
name: `${target.name} (${s.name || s.language || 'Default'})`,
|
|
27
|
+
quality: s.quality || 'Auto',
|
|
28
|
+
type: s.type || (s.url.includes('.m3u8') ? 'hls' : 'mp4'),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const singleUrl = data.directUrl || data.url || data.stream;
|
|
35
|
+
if (typeof singleUrl === 'string' && /^https?:\/\//.test(singleUrl)) {
|
|
36
|
+
urls.push({
|
|
37
|
+
url: singleUrl,
|
|
38
|
+
name: `${target.name}${data.server ? ` - ${data.server}` : ''}`,
|
|
39
|
+
quality: data.quality || 'Auto',
|
|
40
|
+
type: data.streamType || data.container || (singleUrl.includes('.m3u8') ? 'hls' : 'mp4'),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return urls;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function getStream(args) {
|
|
48
|
+
const { id, s, e } = args || {};
|
|
49
|
+
const isTv = s != null && e != null;
|
|
50
|
+
|
|
51
|
+
const endpoints = [
|
|
52
|
+
{ name: 'Meridian', url: isTv ? `https://meridian.raccoonunbutteredbagel.zip/show/${id}/${s}/${e}` : `https://meridian.raccoonunbutteredbagel.zip/movie/${id}` },
|
|
53
|
+
{ name: 'Rock', url: isTv ? `https://rock.raccoonunbutteredbagel.zip/tv/${id}/${s}/${e}` : `https://rock.raccoonunbutteredbagel.zip/movie/${id}` },
|
|
54
|
+
{ name: 'Shore', url: isTv ? `https://shore.raccoonunbutteredbagel.zip/show/${id}?s=${s}&e=${e}` : `https://shore.raccoonunbutteredbagel.zip/movie/${id}` },
|
|
55
|
+
{ name: 'Filler', url: isTv ? `https://filler.raccoonunbutteredbagel.zip/tv/${id}/${s}/${e}` : `https://filler.raccoonunbutteredbagel.zip/movie/${id}` },
|
|
56
|
+
{ name: 'Tiki', url: isTv ? `https://tiki.raccoonunbutteredbagel.zip/tv/${id}/${s}/${e}` : `https://tiki.raccoonunbutteredbagel.zip/movie/${id}` },
|
|
57
|
+
{ name: 'Link', url: isTv ? `https://link.raccoonunbutteredbagel.zip/tv/${id}/${s}/${e}` : `https://link.raccoonunbutteredbagel.zip/movie/${id}` },
|
|
58
|
+
{ name: 'VFast Rapid', url: isTv ? `https://vfast.raccoonunbutteredbagel.zip/tv/${id}/${s}/${e}?server=vRapid` : `https://vfast.raccoonunbutteredbagel.zip/movie/${id}?server=vRapid` },
|
|
59
|
+
{ name: 'VFast Blaze', url: isTv ? `https://vfast.raccoonunbutteredbagel.zip/tv/${id}/${s}/${e}?server=vBlaze` : `https://vfast.raccoonunbutteredbagel.zip/movie/${id}?server=vBlaze` },
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const responses = await Promise.allSettled(
|
|
63
|
+
endpoints.map(async target => {
|
|
64
|
+
const res = await fetch(target.url, { headers: HEADERS });
|
|
65
|
+
if (!res.ok) return null;
|
|
66
|
+
const data = await res.json();
|
|
67
|
+
return { target, data };
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const allUrls = [];
|
|
72
|
+
const seen = new Set();
|
|
73
|
+
|
|
74
|
+
for (const res of responses) {
|
|
75
|
+
if (res.status !== 'fulfilled' || !res.value?.data) continue;
|
|
76
|
+
const { target, data } = res.value;
|
|
77
|
+
const found = extractStreamUrls(data, target);
|
|
78
|
+
|
|
79
|
+
for (const item of found) {
|
|
80
|
+
if (!item?.url || seen.has(item.url)) continue;
|
|
81
|
+
seen.add(item.url);
|
|
82
|
+
allUrls.push({
|
|
83
|
+
url: item.url,
|
|
84
|
+
server: `Aether - ${item.name}`,
|
|
85
|
+
quality: item.quality || 'Auto',
|
|
86
|
+
type: item.type || (item.url.includes('.m3u8') ? 'hls' : 'mp4'),
|
|
87
|
+
headers: PLAYBACK_HEADERS,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!allUrls.length) return null;
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
url: allUrls[0].url,
|
|
96
|
+
headers: allUrls[0].headers,
|
|
97
|
+
allUrls,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const BASE_URL = 'https://braflix.dev';
|
|
2
|
+
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
|
|
3
|
+
|
|
4
|
+
const HEADERS = {
|
|
5
|
+
Origin: BASE_URL,
|
|
6
|
+
Referer: `${BASE_URL}/`,
|
|
7
|
+
'User-Agent': UA,
|
|
8
|
+
Accept: 'application/json, text/plain, */*',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const PLAYBACK_HEADERS = {
|
|
12
|
+
Origin: BASE_URL,
|
|
13
|
+
Referer: `${BASE_URL}/`,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function getStream(args) {
|
|
17
|
+
const { id, s, e } = args || {};
|
|
18
|
+
const idNum = parseInt(id, 10);
|
|
19
|
+
if (!idNum) return null;
|
|
20
|
+
|
|
21
|
+
const isTv = s != null && e != null;
|
|
22
|
+
const targetSeason = isTv ? (s || 1) : 1;
|
|
23
|
+
const targetEpisode = isTv ? (e || 1) : 1;
|
|
24
|
+
|
|
25
|
+
const path = isTv
|
|
26
|
+
? `/api/play/tv/${idNum}/${targetSeason}/${targetEpisode}`
|
|
27
|
+
: `/api/play/movie/${idNum}`;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
31
|
+
headers: HEADERS,
|
|
32
|
+
signal: AbortSignal.timeout(10000),
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok) return null;
|
|
35
|
+
const data = await res.json();
|
|
36
|
+
|
|
37
|
+
if (data?.status !== 'ready' || !data.result?.file?.stream) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const rawStreamPath = data.result.file.stream;
|
|
42
|
+
const fullStreamUrl = rawStreamPath.startsWith('http')
|
|
43
|
+
? rawStreamPath
|
|
44
|
+
: `${BASE_URL}${rawStreamPath}`;
|
|
45
|
+
|
|
46
|
+
const qualityRaw = String(data.result.source?.quality || '1080,720,480');
|
|
47
|
+
const qualityParts = qualityRaw.split(',').map(q => q.trim()).filter(Boolean);
|
|
48
|
+
const maxQ = qualityParts.reduce((acc, curr) => {
|
|
49
|
+
const num = parseInt(curr, 10);
|
|
50
|
+
return num > acc ? num : acc;
|
|
51
|
+
}, 0);
|
|
52
|
+
|
|
53
|
+
const quality = maxQ > 0 ? `${maxQ}p` : 'Auto';
|
|
54
|
+
const qualitySummary = qualityParts.length > 0
|
|
55
|
+
? qualityParts.map(q => `${q}p`).join('/')
|
|
56
|
+
: 'DASH';
|
|
57
|
+
|
|
58
|
+
const streamType = fullStreamUrl.includes('.mpd')
|
|
59
|
+
? 'dash'
|
|
60
|
+
: fullStreamUrl.includes('.m3u8')
|
|
61
|
+
? 'hls'
|
|
62
|
+
: 'dash';
|
|
63
|
+
|
|
64
|
+
const allUrls = [{
|
|
65
|
+
url: fullStreamUrl,
|
|
66
|
+
server: `Braflix - DASH (${qualitySummary})`,
|
|
67
|
+
quality,
|
|
68
|
+
type: streamType,
|
|
69
|
+
headers: PLAYBACK_HEADERS,
|
|
70
|
+
}];
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
url: fullStreamUrl,
|
|
74
|
+
headers: PLAYBACK_HEADERS,
|
|
75
|
+
allUrls,
|
|
76
|
+
};
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|