@vyla-entertainment/sdk 1.11.0 → 1.12.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 +2 -2
- package/src/config.js +152 -94
- package/src/downloads/downloads.js +51 -10
- package/src/sdk.d.ts +0 -1
- package/src/sdk.js +1 -1
- package/src/sources/111477.js +101 -0
- package/src/sources/4khdhub.js +324 -0
- package/src/sources/downloadeverything.js +338 -0
- package/src/sources/meowtv.js +42 -3
- package/src/sources/movienight.js +117 -0
- package/src/sources/movy.js +282 -0
- package/src/sources/purstream.js +2 -2
- package/src/sources/streamaggregator.js +127 -0
- package/src/sources/vadapav.js +85 -0
- package/src/sources/vidvault.js +131 -57
- package/src/sources/vuflix.js +249 -0
- package/src/sources/xdownloader.js +129 -0
- package/src/subtitles.js +35 -40
- package/src/downloads/sources/trendimovies.js +0 -63
- package/src/sources/anipm.js +0 -70
- package/src/sources/mapple.js +0 -151
- package/src/sources/megasource.js +0 -47
- package/src/sources/nova.js +0 -46
- package/src/sources/videasy.js +0 -94
- package/src/sources/xpass.js +0 -40
- package/src/sources/zxcstream.js +0 -141
package/src/sources/vidvault.js
CHANGED
|
@@ -1,113 +1,187 @@
|
|
|
1
1
|
import { USER_AGENT } from '../utils/helpers.js';
|
|
2
2
|
|
|
3
|
-
const BASE_URL =
|
|
3
|
+
const BASE_URL = 'https://vidvault.to';
|
|
4
|
+
const API_BASE = `${BASE_URL}/api`;
|
|
5
|
+
const DL_PROXY_BASE = 'https://dl.gemlelispe.workers.dev';
|
|
6
|
+
const SUB_PROXY_BASE = 'https://sub.k5s7sjozpn.workers.dev';
|
|
7
|
+
|
|
4
8
|
const HEADERS = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
'Referer': `${BASE_URL}/`,
|
|
10
|
+
'Origin': BASE_URL,
|
|
11
|
+
'User-Agent': USER_AGENT,
|
|
12
|
+
'Accept': 'application/json, text/plain, */*',
|
|
13
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
8
14
|
};
|
|
9
15
|
|
|
16
|
+
function formatBytes(bytes, decimals = 2) {
|
|
17
|
+
if (!bytes || bytes <= 0) return '0 B';
|
|
18
|
+
const k = 1024;
|
|
19
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
20
|
+
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
|
|
21
|
+
return `${(bytes / Math.pow(k, i)).toFixed(decimals)} ${sizes[i]}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
10
24
|
async function getToken() {
|
|
11
|
-
const res = await fetch(`${
|
|
12
|
-
method:
|
|
13
|
-
headers: HEADERS
|
|
25
|
+
const res = await fetch(`${API_BASE}/get-token`, {
|
|
26
|
+
method: 'GET',
|
|
27
|
+
headers: HEADERS,
|
|
28
|
+
signal: AbortSignal.timeout(10000),
|
|
14
29
|
});
|
|
30
|
+
if (!res.ok) throw new Error(`Token fetch failed: ${res.status}`);
|
|
15
31
|
const data = await res.json();
|
|
16
|
-
return data
|
|
32
|
+
return data?.t;
|
|
17
33
|
}
|
|
18
34
|
|
|
19
35
|
function getCaptionType(url) {
|
|
20
|
-
return url.toLowerCase().includes(
|
|
36
|
+
return String(url).toLowerCase().includes('.vtt') ? 'vtt' : 'srt';
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
export async function getStream(args) {
|
|
24
40
|
const { id, s, e } = args;
|
|
25
|
-
const
|
|
41
|
+
const isTv = s != null && e != null;
|
|
42
|
+
const type = isTv ? 'tv' : 'movie';
|
|
26
43
|
|
|
27
44
|
try {
|
|
28
45
|
const token = await getToken();
|
|
46
|
+
if (!token) return null;
|
|
47
|
+
|
|
48
|
+
const payload = {
|
|
49
|
+
type,
|
|
50
|
+
tmdbId: String(id),
|
|
51
|
+
};
|
|
29
52
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
payload.
|
|
33
|
-
payload.episode = String(e);
|
|
53
|
+
if (isTv) {
|
|
54
|
+
payload.season = Number(s);
|
|
55
|
+
payload.episode = Number(e);
|
|
34
56
|
}
|
|
35
57
|
|
|
36
|
-
const res = await fetch(`${
|
|
37
|
-
method:
|
|
58
|
+
const res = await fetch(`${API_BASE}/download-proxy`, {
|
|
59
|
+
method: 'POST',
|
|
38
60
|
headers: {
|
|
39
61
|
...HEADERS,
|
|
40
|
-
|
|
41
|
-
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
'x-request-token': token,
|
|
42
64
|
},
|
|
43
|
-
body: JSON.stringify(payload)
|
|
65
|
+
body: JSON.stringify(payload),
|
|
66
|
+
signal: AbortSignal.timeout(15000),
|
|
44
67
|
});
|
|
68
|
+
|
|
69
|
+
if (!res.ok) return null;
|
|
45
70
|
const data = await res.json();
|
|
46
71
|
|
|
47
72
|
const allUrls = [];
|
|
48
73
|
const subtitles = [];
|
|
74
|
+
const titleTag = encodeURIComponent(isTv ? `Show_${id}_S${s}E${e}` : `Movie_${id}`);
|
|
49
75
|
|
|
50
76
|
const mp4 = data?.mp4Data;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
77
|
+
const downloadData = mp4?.downloadInfo?.data || mp4?.data?.data || mp4?.data || mp4;
|
|
78
|
+
|
|
79
|
+
if (downloadData) {
|
|
80
|
+
const streams = downloadData.streams || downloadData.downloads || [];
|
|
81
|
+
for (const item of streams) {
|
|
82
|
+
if (!item?.url) continue;
|
|
83
|
+
|
|
84
|
+
const res = item.resolutions || item.resolution || 'Auto';
|
|
85
|
+
const sizeStr = item.size ? ` (${typeof item.size === 'number' ? formatBytes(item.size) : item.size})` : '';
|
|
86
|
+
const proxiedUrl = `${DL_PROXY_BASE}/${encodeURIComponent(item.url)}?n=${titleTag}`;
|
|
87
|
+
|
|
59
88
|
allUrls.push({
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
url: proxiedUrl,
|
|
90
|
+
server: `VidVault - MP4 ${res}p${sizeStr}`.trim(),
|
|
91
|
+
quality: typeof res === 'number' ? `${res}p` : res,
|
|
92
|
+
type: 'mp4',
|
|
93
|
+
headers: HEADERS,
|
|
64
94
|
});
|
|
65
95
|
}
|
|
66
96
|
|
|
67
|
-
const
|
|
68
|
-
for (const
|
|
69
|
-
if (!
|
|
97
|
+
const captions = downloadData.captions || [];
|
|
98
|
+
for (const cap of captions) {
|
|
99
|
+
if (!cap?.url) continue;
|
|
100
|
+
const subUrl = `${SUB_PROXY_BASE}/?url=${encodeURIComponent(cap.url)}&title=${titleTag}`;
|
|
70
101
|
subtitles.push({
|
|
71
|
-
label:
|
|
72
|
-
type: getCaptionType(
|
|
73
|
-
language:
|
|
74
|
-
url:
|
|
102
|
+
label: cap.lanName || cap.lan || 'Unknown',
|
|
103
|
+
type: getCaptionType(cap.url),
|
|
104
|
+
language: cap.lan || '',
|
|
105
|
+
url: subUrl,
|
|
75
106
|
});
|
|
76
107
|
}
|
|
77
108
|
}
|
|
78
109
|
|
|
79
110
|
const mkvFiles = data?.mkvData?.files || [];
|
|
80
|
-
for (const
|
|
81
|
-
if (!
|
|
111
|
+
for (const file of mkvFiles) {
|
|
112
|
+
if (!file?.url) continue;
|
|
113
|
+
const sizeStr = file.size ? ` (${typeof file.size === 'number' ? formatBytes(file.size) : file.size})` : '';
|
|
82
114
|
allUrls.push({
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
115
|
+
url: file.url,
|
|
116
|
+
server: `VidVault - MKV 480p${sizeStr}`.trim(),
|
|
117
|
+
quality: '480p',
|
|
118
|
+
type: 'mp4',
|
|
119
|
+
headers: HEADERS,
|
|
87
120
|
});
|
|
88
121
|
}
|
|
89
122
|
|
|
90
123
|
const mkvV2 = data?.mkvV2Data;
|
|
91
|
-
|
|
124
|
+
const mkvV2List = Array.isArray(mkvV2)
|
|
125
|
+
? mkvV2
|
|
126
|
+
: Array.isArray(mkvV2?.files)
|
|
127
|
+
? mkvV2.files
|
|
128
|
+
: (mkvV2?.url ? [mkvV2] : []);
|
|
129
|
+
|
|
130
|
+
for (const file of mkvV2List) {
|
|
131
|
+
if (!file?.url) continue;
|
|
132
|
+
const quality = file.quality ? String(file.quality).replace(/p$/i, '') : 'Auto';
|
|
133
|
+
const sizeStr = file.size ? ` (${typeof file.size === 'number' ? formatBytes(file.size) : file.size})` : '';
|
|
134
|
+
const meta = [file.country, file.language].filter(Boolean).join(' • ');
|
|
135
|
+
const metaStr = meta ? ` [${meta}]` : '';
|
|
136
|
+
|
|
92
137
|
allUrls.push({
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
138
|
+
url: file.url,
|
|
139
|
+
server: `VidVault - MKV v2 ${quality}p${sizeStr}${metaStr}`.trim(),
|
|
140
|
+
quality: `${quality}p`,
|
|
141
|
+
type: 'mp4',
|
|
142
|
+
headers: HEADERS,
|
|
97
143
|
});
|
|
98
144
|
}
|
|
99
145
|
|
|
100
146
|
const mkvV3 = data?.mkvV3Data;
|
|
101
|
-
if (mkvV3?.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
147
|
+
if (mkvV3?.downloads && Array.isArray(mkvV3.downloads)) {
|
|
148
|
+
const country = mkvV3.country || '';
|
|
149
|
+
const language = mkvV3.language || '';
|
|
150
|
+
|
|
151
|
+
for (const down of mkvV3.downloads) {
|
|
152
|
+
if (Array.isArray(down?.qualities)) {
|
|
153
|
+
for (const qObj of down.qualities) {
|
|
154
|
+
for (const ep of qObj?.episodes || []) {
|
|
155
|
+
if (!ep?.url) continue;
|
|
156
|
+
const quality = qObj.quality ? String(qObj.quality).replace(/p$/i, '') : 'Auto';
|
|
157
|
+
const sizeStr = ep.size ? ` (${typeof ep.size === 'number' ? formatBytes(ep.size) : ep.size})` : '';
|
|
158
|
+
const meta = [ep.country || country, ep.language || language].filter(Boolean).join(' • ');
|
|
159
|
+
const metaStr = meta ? ` [${meta}]` : '';
|
|
160
|
+
|
|
161
|
+
allUrls.push({
|
|
162
|
+
url: ep.url,
|
|
163
|
+
server: `VidVault - MKV v3 ${quality}p${sizeStr}${metaStr}`.trim(),
|
|
164
|
+
quality: `${quality}p`,
|
|
165
|
+
type: 'mp4',
|
|
166
|
+
headers: HEADERS,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} else if (down?.url) {
|
|
171
|
+
const quality = down.quality ? String(down.quality).replace(/p$/i, '') : 'Auto';
|
|
172
|
+
const sizeStr = down.size ? ` (${typeof down.size === 'number' ? formatBytes(down.size) : down.size})` : '';
|
|
173
|
+
allUrls.push({
|
|
174
|
+
url: down.url,
|
|
175
|
+
server: `VidVault - MKV v3 ${quality}p${sizeStr}`.trim(),
|
|
176
|
+
quality: `${quality}p`,
|
|
177
|
+
type: 'mp4',
|
|
178
|
+
headers: HEADERS,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
108
182
|
}
|
|
109
183
|
|
|
110
|
-
return allUrls.length ? { allUrls, subtitles } : null;
|
|
184
|
+
return allUrls.length ? { allUrls, subtitles: subtitles.length ? subtitles : undefined } : null;
|
|
111
185
|
} catch (err) {
|
|
112
186
|
return null;
|
|
113
187
|
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { getUA } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const API_BASE = 'https://vuflix.co';
|
|
4
|
+
const REFERER = 'https://vuflix.co/';
|
|
5
|
+
const ORIGIN = 'https://vuflix.co';
|
|
6
|
+
|
|
7
|
+
let cachedProviders = null;
|
|
8
|
+
let lastProvidersFetch = 0;
|
|
9
|
+
|
|
10
|
+
const FALLBACK_PROVIDERS = [
|
|
11
|
+
{ id: 'vsembed', name: 'Sigma' },
|
|
12
|
+
{ id: 'moonflix', name: 'Source 40' },
|
|
13
|
+
{ id: 'megasource', name: 'Source 39' },
|
|
14
|
+
{ id: 'hdghar', name: 'Source 44' },
|
|
15
|
+
{ id: 'moviebox', name: 'Pi' },
|
|
16
|
+
{ id: 'cineplay', name: '4K' },
|
|
17
|
+
{ id: 'huhu', name: 'Beta' },
|
|
18
|
+
{ id: 'bingr', name: 'Upsilon' },
|
|
19
|
+
{ id: 'onlyflix', name: 'Gamma' },
|
|
20
|
+
{ id: 'vaplayer', name: 'Alpha' },
|
|
21
|
+
{ id: 'flixhqz', name: 'Gamma' },
|
|
22
|
+
{ id: 'castle', name: 'Source 40' },
|
|
23
|
+
{ id: 'cinejoy', name: '4K2' },
|
|
24
|
+
{ id: 'filesun', name: 'Tau' },
|
|
25
|
+
{ id: 'yoru', name: 'Yoru' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
async function getProviders(ua) {
|
|
29
|
+
if (cachedProviders && Date.now() - lastProvidersFetch < 900000) {
|
|
30
|
+
return cachedProviders;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch(`${API_BASE}/api/player/providers`, {
|
|
35
|
+
headers: {
|
|
36
|
+
'User-Agent': ua,
|
|
37
|
+
Referer: REFERER,
|
|
38
|
+
Origin: ORIGIN,
|
|
39
|
+
Accept: 'application/json, text/plain, */*',
|
|
40
|
+
},
|
|
41
|
+
signal: AbortSignal.timeout(6000),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (res.ok) {
|
|
45
|
+
const data = await res.json();
|
|
46
|
+
if (data?.ok && Array.isArray(data.providers)) {
|
|
47
|
+
const list = data.providers
|
|
48
|
+
.filter(p => p && typeof p.id === 'string' && p.id.trim().length > 0)
|
|
49
|
+
.map(p => ({
|
|
50
|
+
id: p.id.trim(),
|
|
51
|
+
name: p.publicLabel || p.providerName || p.name || p.id,
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
if (list.length > 0) {
|
|
55
|
+
cachedProviders = list;
|
|
56
|
+
lastProvidersFetch = Date.now();
|
|
57
|
+
return list;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} catch { }
|
|
62
|
+
|
|
63
|
+
return FALLBACK_PROVIDERS;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function unwrapUrl(rawUrl, ua) {
|
|
67
|
+
const defaultHeaders = {
|
|
68
|
+
'User-Agent': ua,
|
|
69
|
+
Referer: REFERER,
|
|
70
|
+
Origin: ORIGIN,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (!rawUrl || typeof rawUrl !== 'string') {
|
|
74
|
+
return { url: '', headers: defaultHeaders };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!rawUrl.includes('v-relay?t=') && !rawUrl.includes('a-relay?t=')) {
|
|
78
|
+
return { url: rawUrl, headers: defaultHeaders };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const parsedUrl = new URL(rawUrl);
|
|
83
|
+
const t = parsedUrl.searchParams.get('t');
|
|
84
|
+
if (!t) return { url: rawUrl, headers: defaultHeaders };
|
|
85
|
+
|
|
86
|
+
let b64 = t.replace(/-/g, '+').replace(/_/g, '/');
|
|
87
|
+
while (b64.length % 4 !== 0) {
|
|
88
|
+
b64 += '=';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const jsonStr = Buffer.from(b64, 'base64').toString('utf8');
|
|
92
|
+
const parsed = JSON.parse(jsonStr);
|
|
93
|
+
|
|
94
|
+
if (parsed && typeof parsed === 'object') {
|
|
95
|
+
const directUrl = (parsed.u || '').trim();
|
|
96
|
+
const headersMap = { ...defaultHeaders };
|
|
97
|
+
|
|
98
|
+
if (parsed.h && typeof parsed.h === 'object') {
|
|
99
|
+
for (const [key, value] of Object.entries(parsed.h)) {
|
|
100
|
+
headersMap[key] = String(value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (directUrl) {
|
|
105
|
+
return { url: directUrl, headers: headersMap };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch { }
|
|
109
|
+
|
|
110
|
+
return { url: rawUrl, headers: defaultHeaders };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function getStream({ id, s, e }) {
|
|
114
|
+
const isTv = Boolean(s && e);
|
|
115
|
+
const mediaType = isTv ? 'tv' : 'movie';
|
|
116
|
+
const ua = getUA();
|
|
117
|
+
|
|
118
|
+
const baseParams = new URLSearchParams({
|
|
119
|
+
type: mediaType,
|
|
120
|
+
tmdbId: String(id),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (isTv) {
|
|
124
|
+
baseParams.set('season', String(s));
|
|
125
|
+
baseParams.set('episode', String(e));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const providers = await getProviders(ua);
|
|
129
|
+
const seenUrls = new Set();
|
|
130
|
+
const allUrls = [];
|
|
131
|
+
|
|
132
|
+
const defaultHeaders = {
|
|
133
|
+
'User-Agent': ua,
|
|
134
|
+
Referer: REFERER,
|
|
135
|
+
Origin: ORIGIN,
|
|
136
|
+
Accept: 'application/json, text/plain, */*',
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const tasks = providers.map(async (prov) => {
|
|
140
|
+
try {
|
|
141
|
+
const url = `${API_BASE}/api/player/sources?${baseParams.toString()}&provider=${encodeURIComponent(prov.id)}`;
|
|
142
|
+
const res = await fetch(url, {
|
|
143
|
+
headers: defaultHeaders,
|
|
144
|
+
signal: AbortSignal.timeout(8000),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (!res.ok) return;
|
|
148
|
+
const data = await res.json();
|
|
149
|
+
if (!data?.ok || !Array.isArray(data.sources)) return;
|
|
150
|
+
|
|
151
|
+
for (const item of data.sources) {
|
|
152
|
+
if (!item || typeof item !== 'object') continue;
|
|
153
|
+
|
|
154
|
+
const providerName = item.providerName || item.publicLabel || prov.name;
|
|
155
|
+
const primaryRawUrl = (item.url || '').trim();
|
|
156
|
+
const itemType = (item.type || 'hls').toLowerCase();
|
|
157
|
+
const itemLabel = item.label || '';
|
|
158
|
+
|
|
159
|
+
if (Array.isArray(item.qualities) && item.qualities.length > 0) {
|
|
160
|
+
for (const q of item.qualities) {
|
|
161
|
+
if (!q || typeof q !== 'object') continue;
|
|
162
|
+
const qRawUrl = (q.url || '').trim();
|
|
163
|
+
if (!qRawUrl) continue;
|
|
164
|
+
|
|
165
|
+
const unwrapped = unwrapUrl(qRawUrl, ua);
|
|
166
|
+
if (!unwrapped.url || seenUrls.has(unwrapped.url)) continue;
|
|
167
|
+
seenUrls.add(unwrapped.url);
|
|
168
|
+
|
|
169
|
+
const qQuality = q.quality || 'Auto';
|
|
170
|
+
allUrls.push({
|
|
171
|
+
url: unwrapped.url,
|
|
172
|
+
label: `${providerName} · ${qQuality}`,
|
|
173
|
+
headers: unwrapped.headers,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (Array.isArray(item.candidates) && item.candidates.length > 0) {
|
|
179
|
+
let candIndex = 1;
|
|
180
|
+
for (const c of item.candidates) {
|
|
181
|
+
if (!c || typeof c !== 'object') continue;
|
|
182
|
+
const cRawUrl = (c.url || '').trim();
|
|
183
|
+
if (!cRawUrl) continue;
|
|
184
|
+
|
|
185
|
+
const unwrapped = unwrapUrl(cRawUrl, ua);
|
|
186
|
+
if (!unwrapped.url || seenUrls.has(unwrapped.url)) continue;
|
|
187
|
+
seenUrls.add(unwrapped.url);
|
|
188
|
+
|
|
189
|
+
const cQuality = c.quality || '1080p';
|
|
190
|
+
allUrls.push({
|
|
191
|
+
url: unwrapped.url,
|
|
192
|
+
label: `${providerName} · Mirror ${candIndex} · ${cQuality}`,
|
|
193
|
+
headers: unwrapped.headers,
|
|
194
|
+
});
|
|
195
|
+
candIndex++;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (Array.isArray(item.audioTracks) && item.audioTracks.length > 0) {
|
|
200
|
+
for (const a of item.audioTracks) {
|
|
201
|
+
if (!a || typeof a !== 'object') continue;
|
|
202
|
+
const aRawUrl = (a.switchUrl || a.url || '').trim();
|
|
203
|
+
if (!aRawUrl) continue;
|
|
204
|
+
|
|
205
|
+
const unwrapped = unwrapUrl(aRawUrl, ua);
|
|
206
|
+
if (!unwrapped.url || seenUrls.has(unwrapped.url)) continue;
|
|
207
|
+
seenUrls.add(unwrapped.url);
|
|
208
|
+
|
|
209
|
+
const rawLabel = (a.label || a.name || a.language || 'Audio').trim();
|
|
210
|
+
const cleanLabel = rawLabel.replace(/\s*audio\s*$/i, '').trim() || 'Audio';
|
|
211
|
+
|
|
212
|
+
allUrls.push({
|
|
213
|
+
url: unwrapped.url,
|
|
214
|
+
label: `${providerName} · ${cleanLabel} Audio`,
|
|
215
|
+
headers: unwrapped.headers,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (primaryRawUrl) {
|
|
221
|
+
const unwrapped = unwrapUrl(primaryRawUrl, ua);
|
|
222
|
+
if (unwrapped.url && !seenUrls.has(unwrapped.url)) {
|
|
223
|
+
seenUrls.add(unwrapped.url);
|
|
224
|
+
|
|
225
|
+
let displayQuality = item.quality || (itemType === 'mp4' ? 'MP4' : 'HD');
|
|
226
|
+
let cleanLabel = itemLabel || providerName;
|
|
227
|
+
let label = cleanLabel.startsWith('[') ? cleanLabel : `${providerName} · ${displayQuality}`;
|
|
228
|
+
|
|
229
|
+
allUrls.push({
|
|
230
|
+
url: unwrapped.url,
|
|
231
|
+
label,
|
|
232
|
+
headers: unwrapped.headers,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} catch { }
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
await Promise.all(tasks);
|
|
241
|
+
|
|
242
|
+
if (allUrls.length === 0) return null;
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
url: allUrls[0].url,
|
|
246
|
+
allUrls,
|
|
247
|
+
headers: allUrls[0].headers,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const BASE_URL = 'https://www.films365.org';
|
|
2
|
+
const HEADERS = {
|
|
3
|
+
Authorization: 'Bearer 79a02956be35835728a044b11e2ae793149d45fb2c89cb6d029ec01aac19bfdb',
|
|
4
|
+
'Content-Type': 'application/json',
|
|
5
|
+
'User-Agent': 'MovieDownloader/1.0',
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
async function resolveTitle(sdk, id, isTv) {
|
|
9
|
+
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
|
|
10
|
+
if (!apiKey) return null;
|
|
11
|
+
const type = isTv ? 'tv' : 'movie';
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}`, {
|
|
14
|
+
signal: AbortSignal.timeout(6000),
|
|
15
|
+
});
|
|
16
|
+
if (!res.ok) return null;
|
|
17
|
+
const data = await res.json();
|
|
18
|
+
return isTv ? data.name : data.title;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getStream({ id, s, e, sdk }) {
|
|
25
|
+
const isTv = Boolean(s && e);
|
|
26
|
+
const targetType = isTv ? 'tv' : 'movie';
|
|
27
|
+
|
|
28
|
+
const title = await resolveTitle(sdk, id, isTv);
|
|
29
|
+
const query = title || String(id);
|
|
30
|
+
|
|
31
|
+
const searchRes = await fetch(`${BASE_URL}/api/mobile/search`, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: HEADERS,
|
|
34
|
+
body: JSON.stringify({ query }),
|
|
35
|
+
signal: AbortSignal.timeout(8000),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!searchRes.ok) return null;
|
|
39
|
+
const searchJson = await searchRes.json();
|
|
40
|
+
const resultsObj = searchJson?.results;
|
|
41
|
+
if (!resultsObj) return null;
|
|
42
|
+
|
|
43
|
+
const items = resultsObj.all || (targetType === 'tv' ? resultsObj.tvs : resultsObj.movies) || [];
|
|
44
|
+
if (!Array.isArray(items) || items.length === 0) return null;
|
|
45
|
+
|
|
46
|
+
const cleanSearchTitle = query.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
47
|
+
let matchedItem = null;
|
|
48
|
+
|
|
49
|
+
for (const item of items) {
|
|
50
|
+
const itemType = String(item?.type || '').toLowerCase();
|
|
51
|
+
if (itemType && itemType !== targetType) continue;
|
|
52
|
+
|
|
53
|
+
const itemTitle = String(item?.title || '');
|
|
54
|
+
const cleanItemTitle = itemTitle.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
55
|
+
|
|
56
|
+
if (cleanItemTitle === cleanSearchTitle || cleanItemTitle.includes(cleanSearchTitle)) {
|
|
57
|
+
matchedItem = item;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!matchedItem) {
|
|
63
|
+
matchedItem = items.find(i => String(i?.type || '').toLowerCase() === targetType) || items[0];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const itemId = matchedItem?.id || matchedItem?.tmdbId;
|
|
67
|
+
if (!itemId) return null;
|
|
68
|
+
|
|
69
|
+
const detailsRes = await fetch(`${BASE_URL}/api/mobile/details?id=${itemId}&type=${targetType}`, {
|
|
70
|
+
headers: HEADERS,
|
|
71
|
+
signal: AbortSignal.timeout(8000),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (!detailsRes.ok) return null;
|
|
75
|
+
const detailsJson = await detailsRes.json();
|
|
76
|
+
const data = detailsJson?.data;
|
|
77
|
+
if (!data) return null;
|
|
78
|
+
|
|
79
|
+
const spoken = Array.isArray(data.spokenLanguages)
|
|
80
|
+
? data.spokenLanguages.map(l => String(l).trim()).filter(Boolean)
|
|
81
|
+
: [];
|
|
82
|
+
const langSuffix = spoken.length > 0 ? ` · ${spoken.join(', ')}` : '';
|
|
83
|
+
|
|
84
|
+
const allUrls = [];
|
|
85
|
+
|
|
86
|
+
if (!isTv) {
|
|
87
|
+
const streamUrl = data.downloadUrl || data.videoUrl;
|
|
88
|
+
if (streamUrl && typeof streamUrl === 'string') {
|
|
89
|
+
allUrls.push({
|
|
90
|
+
url: streamUrl.trim(),
|
|
91
|
+
label: `X-Downloader${langSuffix}`,
|
|
92
|
+
headers: {
|
|
93
|
+
'User-Agent': HEADERS['User-Agent'],
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
const seasonNum = Number(s);
|
|
99
|
+
const episodeNum = Number(e);
|
|
100
|
+
const seasons = Array.isArray(data.seasons) ? data.seasons : [];
|
|
101
|
+
const targetSeason = seasons.find(sn => Number(sn.seasonNumber) === seasonNum);
|
|
102
|
+
|
|
103
|
+
if (targetSeason) {
|
|
104
|
+
const episodes = Array.isArray(targetSeason.episodes) ? targetSeason.episodes : [];
|
|
105
|
+
const targetEp = episodes.find(ep => Number(ep.episodeNumber) === episodeNum);
|
|
106
|
+
|
|
107
|
+
if (targetEp) {
|
|
108
|
+
const streamUrl = targetEp.downloadUrl || targetEp.videoUrl;
|
|
109
|
+
if (streamUrl && typeof streamUrl === 'string') {
|
|
110
|
+
allUrls.push({
|
|
111
|
+
url: streamUrl.trim(),
|
|
112
|
+
label: `X-Downloader (S${seasonNum}E${episodeNum})${langSuffix}`,
|
|
113
|
+
headers: {
|
|
114
|
+
'User-Agent': HEADERS['User-Agent'],
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (allUrls.length === 0) return null;
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
url: allUrls[0].url,
|
|
126
|
+
allUrls,
|
|
127
|
+
headers: allUrls[0].headers,
|
|
128
|
+
};
|
|
129
|
+
}
|