@vyla-entertainment/sdk 1.20.0 → 1.21.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 +10 -85
- 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/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/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/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/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/vidora.js +0 -62
- package/src/sources/vidrock.js +0 -145
package/src/sources/lmscript.js
CHANGED
|
@@ -1,71 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const found = data.items?.find(item => {
|
|
25
|
-
const tmdbPrefixMatch = `${item.tmdb_prefix}` === `${id}`;
|
|
26
|
-
const tmdbIdMatch = `${item.tmdb_id}` === `${id}`;
|
|
27
|
-
const titleMatch = item.title?.toLowerCase() === title.toLowerCase();
|
|
28
|
-
return tmdbPrefixMatch || tmdbIdMatch || titleMatch;
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
if (found) {
|
|
32
|
-
movie = found;
|
|
33
|
-
break;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!movie) return null;
|
|
38
|
-
|
|
39
|
-
const streams = movie.streams || {};
|
|
40
|
-
const
|
|
41
|
-
const sortedStreams = Object.keys(streams).sort((a, b) => {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
return
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
allUrls
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
1
|
+
import { fetchJson, getTmdbMetadata, resolveTmdbKey, USER_AGENT } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = 'https://lmscript.xyz';
|
|
4
|
+
const REFERER = 'https://lookmovie2.to/';
|
|
5
|
+
|
|
6
|
+
export async function getStream(args) {
|
|
7
|
+
const { id, s, e, sdk } = args || {};
|
|
8
|
+
const isTv = s != null && e != null;
|
|
9
|
+
if (isTv) return null;
|
|
10
|
+
|
|
11
|
+
const apiKey = resolveTmdbKey(args, sdk);
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const meta = await getTmdbMetadata(id, isTv, apiKey);
|
|
15
|
+
const titles = meta?.titles || [];
|
|
16
|
+
if (!titles.length) return null;
|
|
17
|
+
|
|
18
|
+
let movie = null;
|
|
19
|
+
for (const title of titles) {
|
|
20
|
+
const url = `${BASE_URL}/v1/movies?filters[q]=${encodeURIComponent(title)}&expand=streams`;
|
|
21
|
+
const data = await fetchJson(url).catch(() => null);
|
|
22
|
+
if (!data) continue;
|
|
23
|
+
|
|
24
|
+
const found = data.items?.find(item => {
|
|
25
|
+
const tmdbPrefixMatch = `${item.tmdb_prefix}` === `${id}`;
|
|
26
|
+
const tmdbIdMatch = `${item.tmdb_id}` === `${id}`;
|
|
27
|
+
const titleMatch = item.title?.toLowerCase() === title.toLowerCase();
|
|
28
|
+
return tmdbPrefixMatch || tmdbIdMatch || titleMatch;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (found) {
|
|
32
|
+
movie = found;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!movie) return null;
|
|
38
|
+
|
|
39
|
+
const streams = movie.streams || {};
|
|
40
|
+
const sourceOrder = ['1080p', '720p', '480p', '360p'];
|
|
41
|
+
const sortedStreams = Object.keys(streams).sort((a, b) => {
|
|
42
|
+
const idxA = sourceOrder.indexOf(a);
|
|
43
|
+
const idxB = sourceOrder.indexOf(b);
|
|
44
|
+
if (idxA !== -1 && idxB !== -1) return idxA - idxB;
|
|
45
|
+
if (idxA !== -1) return -1;
|
|
46
|
+
if (idxB !== -1) return 1;
|
|
47
|
+
return 0;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const headers = {
|
|
51
|
+
Referer: REFERER,
|
|
52
|
+
'User-Agent': USER_AGENT,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const allUrls = sortedStreams.map(res => ({
|
|
56
|
+
url: streams[res],
|
|
57
|
+
quality: res,
|
|
58
|
+
server: `LookMovie - ${res}`,
|
|
59
|
+
type: streams[res].includes('.m3u8') ? 'hls' : 'mp4',
|
|
60
|
+
headers,
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
if (!allUrls.length) return null;
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
url: allUrls[0].url,
|
|
67
|
+
headers: allUrls[0].headers,
|
|
68
|
+
allUrls,
|
|
69
|
+
};
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
71
73
|
}
|
package/src/sources/lookmovie.js
CHANGED
|
@@ -1,80 +1,81 @@
|
|
|
1
|
-
import { USER_AGENT, fetchText, fetchJson } from '../utils/helpers.js';
|
|
2
|
-
|
|
3
|
-
const TMDB_BASE = 'https://api.themoviedb.org/3';
|
|
4
|
-
const LM_DOMAINS = ['https://www.lookmovie2.to', 'https://lookmovie2.to', 'https://lookmovie.foundation'];
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
1
|
+
import { USER_AGENT, fetchText, fetchJson } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const TMDB_BASE = 'https://api.themoviedb.org/3';
|
|
4
|
+
const LM_DOMAINS = ['https://www.lookmovie2.to', 'https://lookmovie2.to', 'https://lookmovie.foundation'];
|
|
5
|
+
|
|
6
|
+
const HEADERS_BASE = { 'User-Agent': USER_AGENT, 'Accept-Language': 'en-US,en;q=0.9' };
|
|
7
|
+
|
|
8
|
+
async function searchLookMovie(type, title, year, clientIP) {
|
|
9
|
+
for (const base of LM_DOMAINS) {
|
|
10
|
+
try {
|
|
11
|
+
const headers = { ...HEADERS_BASE, 'Accept': 'application/json', 'Referer': `${base}/`, 'X-Requested-With': 'XMLHttpRequest', ...(clientIP && { 'X-Forwarded-For': clientIP }) };
|
|
12
|
+
const data = await fetchJson(`${base}/api/v1/${type}/do-search/?q=${encodeURIComponent(title)}`, { headers, signal: AbortSignal.timeout(4000) });
|
|
13
|
+
const results = data?.result;
|
|
14
|
+
if (results?.length) {
|
|
15
|
+
const match = results.find(r => String(r.year) === String(year)) ?? results.find(r => r.title?.toLowerCase() === title.toLowerCase()) ?? results[0];
|
|
16
|
+
if (match) return { match, base };
|
|
17
|
+
}
|
|
18
|
+
} catch { }
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const STORAGE_RE = /window\[['"](?:movie|show)_storage['"]\]\s*=\s*\{([^}]+)\}/s;
|
|
24
|
+
const ID_MOVIE_RE = /['"]?(?:id_movie|movieId)['"]?\s*[:=]\s*['"]?(\d+)['"]?/i;
|
|
25
|
+
const SEASONS_RE = /seasons\s*:\s*(\[[\s\S]+?\])\s*[,}]/;
|
|
26
|
+
|
|
27
|
+
function getEpisodeIdFast(html, s, e) {
|
|
28
|
+
const sMatch = html.match(STORAGE_RE);
|
|
29
|
+
if (sMatch) {
|
|
30
|
+
const block = sMatch[1], sm = block.match(SEASONS_RE);
|
|
31
|
+
if (sm) {
|
|
32
|
+
try {
|
|
33
|
+
const seasons = JSON.parse(sm[1]);
|
|
34
|
+
const season = seasons.find(x => String(x?.season ?? x?.meta?.season) === String(s));
|
|
35
|
+
if (season) {
|
|
36
|
+
const ep = Array.isArray(season.episodes) ? season.episodes.find(x => String(x.episode) === String(e)) : (season.episodes?.[String(e)] || Object.values(season.episodes || {}).find(x => String(x.episode) === String(e)));
|
|
37
|
+
if (ep) return String(ep.id_episode ?? ep.id);
|
|
38
|
+
}
|
|
39
|
+
} catch { }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const parts = html.split(/id_episode['"]?\s*[:=]\s*['"]?(\d+)['"]?/i);
|
|
43
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
44
|
+
const context = parts[i - 1].slice(-300) + parts[i + 1].slice(0, 300);
|
|
45
|
+
if (context.match(new RegExp(`['"]?episode['"]?\\s*[:=]\\s*['"]?${e}['"]?`, 'i')) && context.match(new RegExp(`['"]?season['"]?\\s*[:=]\\s*['"]?${s}['"]?`, 'i'))) return parts[i];
|
|
46
|
+
}
|
|
47
|
+
const am = html.match(new RegExp(`data-season=["']${s}["'][^>]*?data-episode=["']${e}["'][^>]*?data-id=["'](\\d+)["']`, 'i')) || html.match(new RegExp(`data-episode=["']${e}["'][^>]*?data-season=["']${s}["'][^>]*?data-id=["'](\\d+)["']`, 'i'));
|
|
48
|
+
return am ? am[1] : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function getStream({ id, s, e, sdk, clientIP }) {
|
|
52
|
+
const tmdbApiKey = sdk?.tmdbApiKey || null;
|
|
53
|
+
if (!tmdbApiKey) return null;
|
|
54
|
+
try {
|
|
55
|
+
const tmdbKey = tmdbApiKey;
|
|
56
|
+
if (!tmdbKey) return null;
|
|
57
|
+
const isTV = s != null && e != null;
|
|
58
|
+
const typeStr = isTV ? 'shows' : 'movies';
|
|
59
|
+
const tmdbData = await fetchJson(`${TMDB_BASE}/${isTV ? 'tv' : 'movie'}/${id}?api_key=${tmdbKey}`, { signal: AbortSignal.timeout(3000) });
|
|
60
|
+
const title = tmdbData?.title || tmdbData?.name;
|
|
61
|
+
const year = (tmdbData?.first_air_date || tmdbData?.release_date || '').slice(0, 4);
|
|
62
|
+
if (!title) return null;
|
|
63
|
+
const searchRes = await searchLookMovie(typeStr, title, year, clientIP);
|
|
64
|
+
if (!searchRes?.match?.slug) return null;
|
|
65
|
+
const { match, base } = searchRes;
|
|
66
|
+
const headers = { ...HEADERS_BASE, 'Accept': 'text/html', 'Referer': `${base}/`, ...(clientIP && { 'X-Forwarded-For': clientIP }) };
|
|
67
|
+
const html = await fetchText(`${base}/${typeStr}/play/${match.slug}`, { headers, signal: AbortSignal.timeout(8000) });
|
|
68
|
+
const storageMatch = html.match(STORAGE_RE);
|
|
69
|
+
if (!storageMatch) return null;
|
|
70
|
+
const hashMatch = storageMatch[1].match(/hash\s*:\s*['"]([^'"]+)['"]/);
|
|
71
|
+
const expiresMatch = storageMatch[1].match(/expires\s*:\s*(\d+)/);
|
|
72
|
+
if (!hashMatch || !expiresMatch) return null;
|
|
73
|
+
const streamId = isTV ? getEpisodeIdFast(html, s, e) : (match.id_movie || match.id || html.match(ID_MOVIE_RE)?.[1]);
|
|
74
|
+
if (!streamId) return null;
|
|
75
|
+
const accessHeaders = { ...HEADERS_BASE, 'Accept': 'application/json', 'Referer': `${base}/`, 'X-Requested-With': 'XMLHttpRequest', ...(clientIP && { 'X-Forwarded-For': clientIP }) };
|
|
76
|
+
const data = await fetchJson(`${base}/api/v1/security/${isTV ? 'episode' : 'movie'}-access?id_${isTV ? 'episode' : 'movie'}=${streamId}&hash=${hashMatch[1]}&expires=${expiresMatch[1]}`, { headers: accessHeaders, signal: AbortSignal.timeout(8000) });
|
|
77
|
+
const streams = data?.streams ?? data?.result?.streams ?? data?.data?.streams ?? data;
|
|
78
|
+
const allUrls = Object.entries(streams || {}).filter(([, v]) => typeof v === 'string' && v.includes('.m3u8')).map(([quality, url]) => ({ url, quality }));
|
|
79
|
+
return allUrls.length ? { allUrls } : null;
|
|
80
|
+
} catch { return null; }
|
|
80
81
|
}
|
package/src/sources/movenerds.js
CHANGED
|
@@ -1,125 +1,75 @@
|
|
|
1
|
-
import { getUA } from
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const params = new URLSearchParams({
|
|
79
|
-
tmdbId: String(id),
|
|
80
|
-
type: isTv ? "tv" : "movie",
|
|
81
|
-
title: meta.title,
|
|
82
|
-
});
|
|
83
|
-
if (meta.year) params.set("year", String(meta.year));
|
|
84
|
-
if (isTv) {
|
|
85
|
-
params.set("season", String(s));
|
|
86
|
-
params.set("episode", String(e));
|
|
87
|
-
params.set("language", "en");
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const url = `${API_BASE}/stream?${params.toString()}`;
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
const res = await fetch(url, {
|
|
94
|
-
headers: getHeaders(),
|
|
95
|
-
signal: AbortSignal.timeout(20000),
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
if (!res.ok) {
|
|
99
|
-
if (process.env.DEBUG_CAUSE) {
|
|
100
|
-
console.error(`[movenerds] HTTP ${res.status} for ${url}`);
|
|
101
|
-
}
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const data = await res.json();
|
|
106
|
-
const primary = Array.isArray(data?.primarySources) ? data.primarySources : [];
|
|
107
|
-
if (!primary.length) {
|
|
108
|
-
if (process.env.DEBUG_CAUSE) {
|
|
109
|
-
console.error(`[movenerds] No primarySources in response for ${url}`);
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const allUrls = primary.map(mapSource).filter(Boolean);
|
|
115
|
-
if (!allUrls.length) return null;
|
|
116
|
-
|
|
117
|
-
return { allUrls };
|
|
118
|
-
} catch (err) {
|
|
119
|
-
if (process.env.DEBUG_CAUSE) {
|
|
120
|
-
console.error(`[movenerds] fetch error for ${url}:`, err.message);
|
|
121
|
-
if (err.cause) console.error(" cause:", err.cause.code, err.cause.message);
|
|
122
|
-
}
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
1
|
+
import { getTmdbMetadata, getUA, resolveTmdbKey } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = 'https://movenerds.space';
|
|
4
|
+
const API_BASE = 'https://api.movenerds.space/api';
|
|
5
|
+
|
|
6
|
+
function mapSource(source) {
|
|
7
|
+
if (!source || !source.url) return null;
|
|
8
|
+
if (source.type !== 'hls' && source.type !== 'mp4') return null;
|
|
9
|
+
if (!/^https?:\/\//.test(source.url)) return null;
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
url: source.url,
|
|
13
|
+
headers: {
|
|
14
|
+
'User-Agent': source.headers?.['User-Agent'] || getUA(),
|
|
15
|
+
Referer: source.headers?.Referer || `${BASE_URL}/`,
|
|
16
|
+
Origin: source.headers?.Origin || BASE_URL,
|
|
17
|
+
},
|
|
18
|
+
server: source.name || source.providerName || 'MoverNerds',
|
|
19
|
+
quality: source.quality || '1080p',
|
|
20
|
+
type: source.type,
|
|
21
|
+
skipProxy: true,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function getStream(args) {
|
|
26
|
+
const { id, s, e, sdk } = args || {};
|
|
27
|
+
const isTv = s != null && e != null;
|
|
28
|
+
const apiKey = resolveTmdbKey(args, sdk);
|
|
29
|
+
|
|
30
|
+
const meta = await getTmdbMetadata(id, isTv, apiKey);
|
|
31
|
+
if (!meta?.title) return null;
|
|
32
|
+
|
|
33
|
+
const params = new URLSearchParams({
|
|
34
|
+
tmdbId: String(id),
|
|
35
|
+
type: isTv ? 'tv' : 'movie',
|
|
36
|
+
title: meta.title,
|
|
37
|
+
});
|
|
38
|
+
if (meta.year) params.set('year', String(meta.year));
|
|
39
|
+
if (isTv) {
|
|
40
|
+
params.set('season', String(s));
|
|
41
|
+
params.set('episode', String(e));
|
|
42
|
+
params.set('language', 'en');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const url = `${API_BASE}/stream?${params.toString()}`;
|
|
46
|
+
const headers = {
|
|
47
|
+
'User-Agent': getUA(),
|
|
48
|
+
Accept: 'application/json',
|
|
49
|
+
Referer: `${BASE_URL}/`,
|
|
50
|
+
Origin: BASE_URL,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const res = await fetch(url, {
|
|
55
|
+
headers,
|
|
56
|
+
signal: AbortSignal.timeout(20000),
|
|
57
|
+
});
|
|
58
|
+
if (!res.ok) return null;
|
|
59
|
+
|
|
60
|
+
const data = await res.json();
|
|
61
|
+
const primary = Array.isArray(data?.primarySources) ? data.primarySources : [];
|
|
62
|
+
if (!primary.length) return null;
|
|
63
|
+
|
|
64
|
+
const allUrls = primary.map(mapSource).filter(Boolean);
|
|
65
|
+
if (!allUrls.length) return null;
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
url: allUrls[0].url,
|
|
69
|
+
headers: allUrls[0].headers,
|
|
70
|
+
allUrls,
|
|
71
|
+
};
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
125
75
|
}
|