@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
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { getUA } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const INITIAL_BASE_URL = 'https://4khdhub.one';
|
|
4
|
+
let cachedBaseUrl = null;
|
|
5
|
+
let cachedBaseUrlExpiry = 0;
|
|
6
|
+
|
|
7
|
+
function rot13(str) {
|
|
8
|
+
return str.replace(/[a-zA-Z]/g, (c) => {
|
|
9
|
+
const code = c.charCodeAt(0);
|
|
10
|
+
if (code >= 65 && code <= 90) {
|
|
11
|
+
return String.fromCharCode(((code - 65 + 13) % 26) + 65);
|
|
12
|
+
}
|
|
13
|
+
return String.fromCharCode(((code - 97 + 13) % 26) + 97);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function levenshtein(a, b) {
|
|
18
|
+
if (a === b) return 0;
|
|
19
|
+
if (!a.length) return b.length;
|
|
20
|
+
if (!b.length) return a.length;
|
|
21
|
+
const la = a.length;
|
|
22
|
+
const lb = b.length;
|
|
23
|
+
const d = Array.from({ length: la + 1 }, () => new Array(lb + 1).fill(0));
|
|
24
|
+
for (let i = 0; i <= la; i++) d[i][0] = i;
|
|
25
|
+
for (let j = 0; j <= lb; j++) d[0][j] = j;
|
|
26
|
+
for (let i = 1; i <= la; i++) {
|
|
27
|
+
for (let j = 1; j <= lb; j++) {
|
|
28
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
29
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return d[la][lb];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function getBaseUrl(ua) {
|
|
36
|
+
if (cachedBaseUrl && Date.now() < cachedBaseUrlExpiry) {
|
|
37
|
+
return cachedBaseUrl;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let currentUrl = INITIAL_BASE_URL;
|
|
41
|
+
for (let i = 0; i < 10; i++) {
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch(currentUrl, {
|
|
44
|
+
method: 'HEAD',
|
|
45
|
+
headers: { 'User-Agent': ua },
|
|
46
|
+
redirect: 'manual',
|
|
47
|
+
signal: AbortSignal.timeout(10000),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (res.status >= 300 && res.status < 400) {
|
|
51
|
+
const location = res.headers.get('location');
|
|
52
|
+
if (location) {
|
|
53
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
} catch {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const parsed = new URL(currentUrl);
|
|
64
|
+
cachedBaseUrl = `${parsed.protocol}//${parsed.host}`;
|
|
65
|
+
cachedBaseUrlExpiry = Date.now() + 3600000;
|
|
66
|
+
return cachedBaseUrl;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function resolveMetadata(sdk, id, isTv) {
|
|
70
|
+
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
|
|
71
|
+
if (!apiKey) return null;
|
|
72
|
+
const type = isTv ? 'tv' : 'movie';
|
|
73
|
+
try {
|
|
74
|
+
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}`, {
|
|
75
|
+
signal: AbortSignal.timeout(8000),
|
|
76
|
+
});
|
|
77
|
+
if (!res.ok) return null;
|
|
78
|
+
const data = await res.json();
|
|
79
|
+
const title = isTv ? data.name : data.title;
|
|
80
|
+
const releaseDate = isTv ? data.first_air_date : data.release_date;
|
|
81
|
+
const year = releaseDate ? parseInt(releaseDate.split('-')[0], 10) : null;
|
|
82
|
+
return { title, year };
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function sanitizeStreamUrl(rawUrl) {
|
|
89
|
+
try {
|
|
90
|
+
const parsed = new URL(rawUrl);
|
|
91
|
+
parsed.pathname = parsed.pathname.replaceAll('::', '%3A%3A');
|
|
92
|
+
return parsed.toString();
|
|
93
|
+
} catch {
|
|
94
|
+
return rawUrl.replaceAll('::', '%3A%3A');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function validateStreamUrl(url, ua) {
|
|
99
|
+
try {
|
|
100
|
+
const res = await fetch(url, {
|
|
101
|
+
headers: {
|
|
102
|
+
'User-Agent': ua,
|
|
103
|
+
Range: 'bytes=0-1024',
|
|
104
|
+
},
|
|
105
|
+
signal: AbortSignal.timeout(4000),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const contentType = (res.headers.get('content-type') || '').toLowerCase();
|
|
109
|
+
const isSuccess = (res.status >= 200 && res.status < 300) || res.status === 206;
|
|
110
|
+
const isHtmlOrJsonError = contentType.includes('text/html') || contentType.includes('application/json');
|
|
111
|
+
|
|
112
|
+
if (!isSuccess || isHtmlOrJsonError) return false;
|
|
113
|
+
return true;
|
|
114
|
+
} catch {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function resolveRedirectUrl(redirectUrl, ua) {
|
|
120
|
+
try {
|
|
121
|
+
const res = await fetch(redirectUrl, {
|
|
122
|
+
headers: { 'User-Agent': ua },
|
|
123
|
+
signal: AbortSignal.timeout(15000),
|
|
124
|
+
});
|
|
125
|
+
if (!res.ok) return null;
|
|
126
|
+
const html = await res.text();
|
|
127
|
+
|
|
128
|
+
let nextUrl = '';
|
|
129
|
+
const downloadMatch = html.match(/<a id="download" href="(.*?)"/);
|
|
130
|
+
if (downloadMatch) {
|
|
131
|
+
nextUrl = downloadMatch[1];
|
|
132
|
+
} else {
|
|
133
|
+
const varMatch = html.match(/var url = '(.*?)';/);
|
|
134
|
+
if (varMatch) {
|
|
135
|
+
nextUrl = varMatch[1];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!nextUrl) {
|
|
140
|
+
const match = html.match(/'o','(.*?)'/);
|
|
141
|
+
if (!match) return null;
|
|
142
|
+
const rawData = match[1];
|
|
143
|
+
const step1 = Buffer.from(rawData, 'base64').toString('utf8');
|
|
144
|
+
const step2 = Buffer.from(step1, 'base64').toString('utf8');
|
|
145
|
+
const step3 = rot13(step2);
|
|
146
|
+
const step4 = Buffer.from(step3, 'base64').toString('utf8');
|
|
147
|
+
const data = JSON.parse(step4);
|
|
148
|
+
return Buffer.from(data.o, 'base64').toString('utf8');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const interRes = await fetch(nextUrl, {
|
|
152
|
+
headers: { 'User-Agent': ua },
|
|
153
|
+
signal: AbortSignal.timeout(15000),
|
|
154
|
+
});
|
|
155
|
+
if (!interRes.ok) return null;
|
|
156
|
+
const interHtml = await interRes.text();
|
|
157
|
+
|
|
158
|
+
const finalMatch = interHtml.match(/<a href="([^"]+)"[^>]*class="[^"]*btn-success/);
|
|
159
|
+
if (finalMatch) {
|
|
160
|
+
return finalMatch[1];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return null;
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function extractSourceResults(itemHtml, ua) {
|
|
170
|
+
const sizeMatch = itemHtml.match(/([\d.]+ ?[GM]B)/);
|
|
171
|
+
const sizeStr = sizeMatch ? sizeMatch[1] : null;
|
|
172
|
+
|
|
173
|
+
const heightMatch = itemHtml.match(/\d{3,}p/);
|
|
174
|
+
const heightStr = heightMatch ? heightMatch[0] : null;
|
|
175
|
+
|
|
176
|
+
const fileTitleMatch = itemHtml.match(/class="(?:file-title|episode-file-title)"[^>]*>([^<]+)/);
|
|
177
|
+
const fileTitle = fileTitleMatch ? fileTitleMatch[1].trim() : 'Unknown';
|
|
178
|
+
|
|
179
|
+
const displayParts = [fileTitle];
|
|
180
|
+
if (heightStr) displayParts.push(heightStr);
|
|
181
|
+
if (sizeStr) displayParts.push(sizeStr);
|
|
182
|
+
|
|
183
|
+
let hubCloudUrl = null;
|
|
184
|
+
let hubDriveUrl = null;
|
|
185
|
+
|
|
186
|
+
const linkRegex = /<a\s+[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
187
|
+
let match;
|
|
188
|
+
while ((match = linkRegex.exec(itemHtml)) !== null) {
|
|
189
|
+
const href = match[1];
|
|
190
|
+
const text = match[2];
|
|
191
|
+
if (text.includes('HubCloud') && !hubCloudUrl) {
|
|
192
|
+
hubCloudUrl = href;
|
|
193
|
+
} else if (text.includes('HubDrive') && !hubDriveUrl) {
|
|
194
|
+
hubDriveUrl = href;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const redirectUrl = hubCloudUrl || hubDriveUrl;
|
|
199
|
+
if (!redirectUrl) return null;
|
|
200
|
+
|
|
201
|
+
const resolvedUrl = await resolveRedirectUrl(redirectUrl, ua);
|
|
202
|
+
if (!resolvedUrl) return null;
|
|
203
|
+
|
|
204
|
+
const safeUrl = sanitizeStreamUrl(resolvedUrl);
|
|
205
|
+
const isValid = await validateStreamUrl(safeUrl, ua);
|
|
206
|
+
if (!isValid) return null;
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
url: safeUrl,
|
|
210
|
+
label: displayParts.join(' · '),
|
|
211
|
+
headers: {
|
|
212
|
+
'User-Agent': ua,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export async function getStream({ id, s, e, sdk }) {
|
|
218
|
+
const isTv = Boolean(s && e);
|
|
219
|
+
const ua = getUA();
|
|
220
|
+
|
|
221
|
+
const meta = await resolveMetadata(sdk, id, isTv);
|
|
222
|
+
if (!meta?.title) return null;
|
|
223
|
+
|
|
224
|
+
const baseUrl = await getBaseUrl(ua);
|
|
225
|
+
const searchUrl = `${baseUrl}/?s=${encodeURIComponent(meta.title)}`;
|
|
226
|
+
|
|
227
|
+
const searchRes = await fetch(searchUrl, {
|
|
228
|
+
headers: { 'User-Agent': ua },
|
|
229
|
+
signal: AbortSignal.timeout(15000),
|
|
230
|
+
});
|
|
231
|
+
if (!searchRes.ok) return null;
|
|
232
|
+
const searchHtml = await searchRes.text();
|
|
233
|
+
|
|
234
|
+
const cardRegex = /<a\s+([^>]*\bclass="[^"]*movie-card[^"]*"[^>]*)>([\s\S]*?)<\/a>/gi;
|
|
235
|
+
const formatFilter = isTv ? 'Series' : 'Movies';
|
|
236
|
+
let targetPageUrl = null;
|
|
237
|
+
|
|
238
|
+
let cardMatch;
|
|
239
|
+
while ((cardMatch = cardRegex.exec(searchHtml)) !== null) {
|
|
240
|
+
const attributes = cardMatch[1];
|
|
241
|
+
const inner = cardMatch[2];
|
|
242
|
+
|
|
243
|
+
const hrefMatch = attributes.match(/href="([^"]+)"/);
|
|
244
|
+
if (!hrefMatch) continue;
|
|
245
|
+
const href = hrefMatch[1];
|
|
246
|
+
|
|
247
|
+
const formatMatches = [...inner.matchAll(/class="movie-card-format"[^>]*>([\s\S]*?)<\//gi)];
|
|
248
|
+
const formatTexts = formatMatches.map(m => m[1].trim());
|
|
249
|
+
if (!formatTexts.some(t => t.includes(formatFilter))) continue;
|
|
250
|
+
|
|
251
|
+
if (meta.year != null) {
|
|
252
|
+
const metaMatch = inner.match(/class="movie-card-meta"[^>]*>([\s\S]*?)<\//i);
|
|
253
|
+
const yearMatch = metaMatch ? metaMatch[1].match(/\d{4}/) : null;
|
|
254
|
+
if (yearMatch) {
|
|
255
|
+
const cardYear = parseInt(yearMatch[0], 10);
|
|
256
|
+
if (Math.abs(cardYear - meta.year) > 1) continue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const titleMatch = inner.match(/class="movie-card-title"[^>]*>([\s\S]*?)<\//i);
|
|
261
|
+
let cardTitle = titleMatch ? titleMatch[1] : '';
|
|
262
|
+
cardTitle = cardTitle.replace(/\[.*?\]/g, '').trim();
|
|
263
|
+
|
|
264
|
+
const diff = levenshtein(cardTitle.toLowerCase(), meta.title.toLowerCase());
|
|
265
|
+
const isMatch = diff < 5 || (cardTitle.toLowerCase().includes(meta.title.toLowerCase()) && diff < 16);
|
|
266
|
+
if (!isMatch) continue;
|
|
267
|
+
|
|
268
|
+
targetPageUrl = new URL(href, baseUrl).toString();
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!targetPageUrl) return null;
|
|
273
|
+
|
|
274
|
+
const pageRes = await fetch(targetPageUrl, {
|
|
275
|
+
headers: { 'User-Agent': ua },
|
|
276
|
+
signal: AbortSignal.timeout(15000),
|
|
277
|
+
});
|
|
278
|
+
if (!pageRes.ok) return null;
|
|
279
|
+
const pageHtml = await pageRes.text();
|
|
280
|
+
|
|
281
|
+
const matchedHtmlBlocks = [];
|
|
282
|
+
|
|
283
|
+
if (isTv) {
|
|
284
|
+
const seasonStr = String(s).padStart(2, '0');
|
|
285
|
+
const episodeStr = String(e).padStart(2, '0');
|
|
286
|
+
|
|
287
|
+
const epItemRegex = /class="[^"]*episode-item[^"]*"[^>]*>([\s\S]*?)(?=(?:class="[^"]*episode-item"|<\/main>|$))/gi;
|
|
288
|
+
let epMatch;
|
|
289
|
+
while ((epMatch = epItemRegex.exec(pageHtml)) !== null) {
|
|
290
|
+
const epBlock = epMatch[1];
|
|
291
|
+
const titleMatch = epBlock.match(/class="episode-title"[^>]*>([\s\S]*?)<\//i);
|
|
292
|
+
const titleText = titleMatch ? titleMatch[1] : '';
|
|
293
|
+
if (!titleText.includes(`S${seasonStr}`)) continue;
|
|
294
|
+
|
|
295
|
+
const dlItemRegex = /class="[^"]*episode-download-item[^"]*"[^>]*>([\s\S]*?)(?=(?:class="[^"]*episode-download-item"|<\/div>\s*<\/div>|$))/gi;
|
|
296
|
+
let dlMatch;
|
|
297
|
+
while ((dlMatch = dlItemRegex.exec(epBlock)) !== null) {
|
|
298
|
+
const dlBlock = dlMatch[1];
|
|
299
|
+
if (dlBlock.includes(`Episode-${episodeStr}`)) {
|
|
300
|
+
matchedHtmlBlocks.push(dlBlock);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
const dlItemRegex = /class="[^"]*download-item[^"]*"[^>]*>([\s\S]*?)(?=(?:class="[^"]*download-item"|<\/main>|$))/gi;
|
|
306
|
+
let dlMatch;
|
|
307
|
+
while ((dlMatch = dlItemRegex.exec(pageHtml)) !== null) {
|
|
308
|
+
matchedHtmlBlocks.push(dlMatch[1]);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (matchedHtmlBlocks.length === 0) return null;
|
|
313
|
+
|
|
314
|
+
const results = await Promise.all(matchedHtmlBlocks.map((block) => extractSourceResults(block, ua)));
|
|
315
|
+
const allUrls = results.filter(Boolean);
|
|
316
|
+
|
|
317
|
+
if (allUrls.length === 0) return null;
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
url: allUrls[0].url,
|
|
321
|
+
allUrls,
|
|
322
|
+
headers: allUrls[0].headers,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { getUA } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const SLAVE_URL = 'https://slave.downloadeverythingfromeverywhere.com/';
|
|
4
|
+
const REFERER = 'https://downloadeverythingfromeverywhere.com/';
|
|
5
|
+
const ORIGIN = 'https://downloadeverythingfromeverywhere.com';
|
|
6
|
+
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36';
|
|
7
|
+
|
|
8
|
+
function extractJsonObjects(text) {
|
|
9
|
+
const objects = [];
|
|
10
|
+
let depth = 0;
|
|
11
|
+
let start = -1;
|
|
12
|
+
let inString = false;
|
|
13
|
+
let escape = false;
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < text.length; i++) {
|
|
16
|
+
const char = text[i];
|
|
17
|
+
if (escape) {
|
|
18
|
+
escape = false;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (char === '\\') {
|
|
22
|
+
escape = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (char === '"') {
|
|
26
|
+
inString = !inString;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (!inString) {
|
|
30
|
+
if (char === '{') {
|
|
31
|
+
if (depth === 0) start = i;
|
|
32
|
+
depth++;
|
|
33
|
+
} else if (char === '}') {
|
|
34
|
+
depth--;
|
|
35
|
+
if (depth === 0 && start !== -1) {
|
|
36
|
+
const jsonStr = text.substring(start, i + 1);
|
|
37
|
+
try {
|
|
38
|
+
objects.push(JSON.parse(jsonStr));
|
|
39
|
+
} catch { }
|
|
40
|
+
start = -1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return objects;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function resolveDetails(sdk, id, isTv) {
|
|
49
|
+
const apiKey = sdk?.tmdbApiKey || process.env.TMDB_API_KEY;
|
|
50
|
+
if (!apiKey) return null;
|
|
51
|
+
const type = isTv ? 'tv' : 'movie';
|
|
52
|
+
try {
|
|
53
|
+
const res = await fetch(`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}&append_to_response=external_ids`, {
|
|
54
|
+
signal: AbortSignal.timeout(6000),
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) return null;
|
|
57
|
+
const data = await res.json();
|
|
58
|
+
const title = isTv ? data.name : data.title;
|
|
59
|
+
const releaseDate = isTv ? data.first_air_date : data.release_date;
|
|
60
|
+
const year = releaseDate ? parseInt(releaseDate.split('-')[0], 10) : null;
|
|
61
|
+
const imdbId = data.imdb_id || data.external_ids?.imdb_id || null;
|
|
62
|
+
return { title, year, imdbId };
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function resolveHubCloud(hubUrl, ua) {
|
|
69
|
+
try {
|
|
70
|
+
const res1 = await fetch(hubUrl, {
|
|
71
|
+
headers: {
|
|
72
|
+
'User-Agent': ua,
|
|
73
|
+
Referer: REFERER,
|
|
74
|
+
},
|
|
75
|
+
signal: AbortSignal.timeout(8000),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const html = await res1.text();
|
|
79
|
+
const hubPhpMatch = html.match(/https?:\/\/[^\s"<>]+\/hubcloud\.php\?[^\s"<>]+/i);
|
|
80
|
+
if (!hubPhpMatch) return null;
|
|
81
|
+
|
|
82
|
+
const phpUrl = hubPhpMatch[0];
|
|
83
|
+
const phpRes = await fetch(phpUrl, {
|
|
84
|
+
headers: {
|
|
85
|
+
'User-Agent': ua,
|
|
86
|
+
Referer: hubUrl,
|
|
87
|
+
},
|
|
88
|
+
signal: AbortSignal.timeout(8000),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const phpBody = await phpRes.text();
|
|
92
|
+
|
|
93
|
+
const r2Match = phpBody.match(/https?:\/\/[a-zA-Z0-9.\-_]+\.r2\.cloudflarestorage\.com\/[^\s"<>]+/i);
|
|
94
|
+
if (r2Match) {
|
|
95
|
+
return r2Match[0].replaceAll('&', '&');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const pixelMatch = phpBody.match(/https?:\/\/pixel\.hubcloud\.[a-z]+\/\?id=[^\s"<>]+/i);
|
|
99
|
+
if (pixelMatch) {
|
|
100
|
+
return pixelMatch[0];
|
|
101
|
+
}
|
|
102
|
+
} catch { }
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function resolveClicknUpload(clicknUrl, ua) {
|
|
107
|
+
try {
|
|
108
|
+
const res1 = await fetch(clicknUrl, {
|
|
109
|
+
headers: {
|
|
110
|
+
'User-Agent': ua,
|
|
111
|
+
Referer: REFERER,
|
|
112
|
+
},
|
|
113
|
+
signal: AbortSignal.timeout(8000),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const body1 = await res1.text();
|
|
117
|
+
const formMatch1 = body1.match(/<form[^>]+method=["']POST["'][^>]*>([\s\S]*?)<\/form>/i);
|
|
118
|
+
if (!formMatch1) return null;
|
|
119
|
+
|
|
120
|
+
const inputMatches1 = [...formMatch1[1].matchAll(/<input[^>]+name=["']([^"']+)["'][^>]+value=["']([^"']*)["']/gi)];
|
|
121
|
+
const params1 = new URLSearchParams();
|
|
122
|
+
for (const m of inputMatches1) {
|
|
123
|
+
params1.set(m[1], m[2]);
|
|
124
|
+
}
|
|
125
|
+
params1.set('method_free', 'Slow Download');
|
|
126
|
+
|
|
127
|
+
const rawCookies = res1.headers.getSetCookie?.() || [res1.headers.get('set-cookie')].filter(Boolean);
|
|
128
|
+
const cookieHeader = rawCookies.join('; ');
|
|
129
|
+
|
|
130
|
+
const res2 = await fetch(clicknUrl, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: {
|
|
133
|
+
'User-Agent': ua,
|
|
134
|
+
Referer: clicknUrl,
|
|
135
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
136
|
+
...(cookieHeader ? { Cookie: cookieHeader } : {}),
|
|
137
|
+
},
|
|
138
|
+
body: params1.toString(),
|
|
139
|
+
signal: AbortSignal.timeout(8000),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const body2 = await res2.text();
|
|
143
|
+
const formMatch2 = body2.match(/<form[^>]+method=["']POST["'][^>]*>([\s\S]*?)<\/form>/i);
|
|
144
|
+
if (!formMatch2) return null;
|
|
145
|
+
|
|
146
|
+
const inputMatches2 = [...formMatch2[1].matchAll(/<input[^>]+name=["']([^"']+)["'][^>]+value=["']([^"']*)["']/gi)];
|
|
147
|
+
const params2 = new URLSearchParams();
|
|
148
|
+
for (const m of inputMatches2) {
|
|
149
|
+
params2.set(m[1], m[2]);
|
|
150
|
+
}
|
|
151
|
+
params2.set('down_script', '1');
|
|
152
|
+
|
|
153
|
+
await new Promise(r => setTimeout(r, 4500));
|
|
154
|
+
|
|
155
|
+
const res3 = await fetch(clicknUrl, {
|
|
156
|
+
method: 'POST',
|
|
157
|
+
headers: {
|
|
158
|
+
'User-Agent': ua,
|
|
159
|
+
Referer: clicknUrl,
|
|
160
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
161
|
+
...(cookieHeader ? { Cookie: cookieHeader } : {}),
|
|
162
|
+
},
|
|
163
|
+
body: params2.toString(),
|
|
164
|
+
signal: AbortSignal.timeout(8000),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const body3 = await res3.text();
|
|
168
|
+
const directMatch = body3.match(/https?:\/\/[a-zA-Z0-9.\-_:]+\/d\/[a-zA-Z0-9_\-\/]+/i) ||
|
|
169
|
+
body3.match(/window\.open\(["'](https?:\/\/[^"']+)["']\)/i);
|
|
170
|
+
|
|
171
|
+
if (directMatch) {
|
|
172
|
+
const found = directMatch[1] || directMatch[0];
|
|
173
|
+
if (found.includes('clicknupload.') && !found.includes('/d/')) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return found;
|
|
177
|
+
}
|
|
178
|
+
} catch { }
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function resolveItem(item, fallbackTitle, ua) {
|
|
183
|
+
const rawUrl = String(item.url || '').trim();
|
|
184
|
+
if (!rawUrl) return null;
|
|
185
|
+
|
|
186
|
+
if (
|
|
187
|
+
rawUrl.includes('111477.xyz') ||
|
|
188
|
+
rawUrl.includes('vadapav.mov') ||
|
|
189
|
+
rawUrl.includes('driveseed.org') ||
|
|
190
|
+
rawUrl.includes('new3.gdflix.io') ||
|
|
191
|
+
rawUrl.includes('rapidrar.cr') ||
|
|
192
|
+
rawUrl.includes('megaup.net') ||
|
|
193
|
+
rawUrl.includes('telegram.dog') ||
|
|
194
|
+
rawUrl.includes('t.me')
|
|
195
|
+
) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let directStreamUrl = null;
|
|
200
|
+
let provider = item.site || 'DownloadEverything';
|
|
201
|
+
let streamHeaders = { 'User-Agent': ua };
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
if (rawUrl.includes('hakunaymatata.com')) {
|
|
205
|
+
directStreamUrl = rawUrl;
|
|
206
|
+
provider = 'Moviebox';
|
|
207
|
+
streamHeaders = { 'User-Agent': 'Lavf/60.16.100' };
|
|
208
|
+
} else if (rawUrl.includes('pixeldrain.dev') || rawUrl.includes('pixeldrain.com')) {
|
|
209
|
+
const m = rawUrl.match(/pixeldrain\.(?:dev|com)\/(?:u|l)\/([a-zA-Z0-9_-]+)/);
|
|
210
|
+
if (m) {
|
|
211
|
+
directStreamUrl = `https://pixeldrain.com/api/file/${m[1]}`;
|
|
212
|
+
provider = 'Pixeldrain';
|
|
213
|
+
}
|
|
214
|
+
} else if (rawUrl.includes('hubcloud.') || rawUrl.includes('vcloud.zip')) {
|
|
215
|
+
directStreamUrl = await resolveHubCloud(rawUrl, ua);
|
|
216
|
+
if (directStreamUrl) {
|
|
217
|
+
provider = 'HubCloud';
|
|
218
|
+
}
|
|
219
|
+
} else if (rawUrl.includes('clicknupload.')) {
|
|
220
|
+
directStreamUrl = await resolveClicknUpload(rawUrl, ua);
|
|
221
|
+
if (directStreamUrl) {
|
|
222
|
+
provider = 'ClicknUpload';
|
|
223
|
+
}
|
|
224
|
+
} else if (
|
|
225
|
+
/\.(?:mp4|mkv)(?:\?|$)/i.test(rawUrl) &&
|
|
226
|
+
!rawUrl.includes('111477.xyz') &&
|
|
227
|
+
!rawUrl.includes('vadapav.mov') &&
|
|
228
|
+
!rawUrl.includes('.cyou/res/')
|
|
229
|
+
) {
|
|
230
|
+
try {
|
|
231
|
+
const check = await fetch(rawUrl, {
|
|
232
|
+
method: 'HEAD',
|
|
233
|
+
headers: { 'User-Agent': ua },
|
|
234
|
+
signal: AbortSignal.timeout(2000),
|
|
235
|
+
});
|
|
236
|
+
if (check.status === 200 || check.status === 206 || (check.status >= 300 && check.status < 400)) {
|
|
237
|
+
directStreamUrl = rawUrl;
|
|
238
|
+
provider = item.site || 'DirectStream';
|
|
239
|
+
}
|
|
240
|
+
} catch { }
|
|
241
|
+
}
|
|
242
|
+
} catch { }
|
|
243
|
+
|
|
244
|
+
if (!directStreamUrl) return null;
|
|
245
|
+
|
|
246
|
+
const tags = Array.isArray(item.tags) ? item.tags.map(String) : [];
|
|
247
|
+
const qualityMatch = tags.find(t => /2160p|4k|1080p|720p|480p/i.test(t)) || '1080p';
|
|
248
|
+
const rawTitle = item.name || item.release || fallbackTitle;
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
url: directStreamUrl,
|
|
252
|
+
label: `[${provider}] ${rawTitle} (${qualityMatch})`,
|
|
253
|
+
headers: streamHeaders,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function getStream({ id, s, e, sdk }) {
|
|
258
|
+
const isTv = Boolean(s && e);
|
|
259
|
+
const mediaType = isTv ? 'series' : 'movie';
|
|
260
|
+
const ua = USER_AGENT;
|
|
261
|
+
|
|
262
|
+
const details = await resolveDetails(sdk, id, isTv);
|
|
263
|
+
const title = details?.title || String(id);
|
|
264
|
+
const tmdbId = parseInt(id, 10);
|
|
265
|
+
|
|
266
|
+
const payload = {
|
|
267
|
+
mode: mediaType,
|
|
268
|
+
title,
|
|
269
|
+
...(details?.year ? { year: String(details.year) } : {}),
|
|
270
|
+
...(tmdbId ? { tmdb_id: tmdbId } : {}),
|
|
271
|
+
...(details?.imdbId ? { imdb_id: details.imdbId } : {}),
|
|
272
|
+
...(isTv && s != null ? { season: Number(s) } : {}),
|
|
273
|
+
...(isTv && e != null ? { episode: Number(e) } : {}),
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const res = await fetch(SLAVE_URL, {
|
|
277
|
+
method: 'POST',
|
|
278
|
+
headers: {
|
|
279
|
+
'User-Agent': ua,
|
|
280
|
+
Origin: ORIGIN,
|
|
281
|
+
Referer: REFERER,
|
|
282
|
+
'Content-Type': 'application/json',
|
|
283
|
+
Accept: 'application/x-ndjson',
|
|
284
|
+
'x-defe-manual': '1',
|
|
285
|
+
'sec-ch-ua': '"Microsoft Edge";v="153", "Not_A Brand";v="8", "Chromium";v="153"',
|
|
286
|
+
'sec-ch-ua-mobile': '?0',
|
|
287
|
+
'sec-ch-ua-platform': '"Windows"',
|
|
288
|
+
'sec-fetch-dest': 'empty',
|
|
289
|
+
'sec-fetch-mode': 'cors',
|
|
290
|
+
'sec-fetch-site': 'same-site',
|
|
291
|
+
},
|
|
292
|
+
body: JSON.stringify(payload),
|
|
293
|
+
signal: AbortSignal.timeout(16000),
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
if (!res.ok) return null;
|
|
297
|
+
|
|
298
|
+
let fullText = '';
|
|
299
|
+
if (res.body?.getReader) {
|
|
300
|
+
const reader = res.body.getReader();
|
|
301
|
+
const decoder = new TextDecoder();
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
while (true) {
|
|
305
|
+
const { done, value } = await reader.read();
|
|
306
|
+
if (done) break;
|
|
307
|
+
fullText += decoder.decode(value, { stream: true });
|
|
308
|
+
}
|
|
309
|
+
} catch { }
|
|
310
|
+
} else {
|
|
311
|
+
fullText = await res.text();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const objects = extractJsonObjects(fullText);
|
|
315
|
+
const candidateItems = [];
|
|
316
|
+
|
|
317
|
+
for (const parsed of objects) {
|
|
318
|
+
if (parsed?.t === 'hit' && Array.isArray(parsed.links)) {
|
|
319
|
+
const site = parsed.site || 'DownloadEverything';
|
|
320
|
+
for (const l of parsed.links) {
|
|
321
|
+
if (l && typeof l === 'object') {
|
|
322
|
+
candidateItems.push({ site, ...l });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const resolved = await Promise.all(candidateItems.map(item => resolveItem(item, title, ua)));
|
|
329
|
+
const allUrls = resolved.filter(Boolean);
|
|
330
|
+
|
|
331
|
+
if (allUrls.length === 0) return null;
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
url: allUrls[0].url,
|
|
335
|
+
allUrls,
|
|
336
|
+
headers: allUrls[0].headers,
|
|
337
|
+
};
|
|
338
|
+
}
|