@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
package/src/sources/kisskh.js
CHANGED
|
@@ -1,60 +1,115 @@
|
|
|
1
|
-
import { fetchJson,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
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
|
-
|
|
1
|
+
import { cleanTitle, fetchJson, getTmdbMetadata, resolveTmdbKey, USER_AGENT } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const ENC_API = 'https://enc-dec.app/api';
|
|
4
|
+
const BASE_URL = 'https://kisskh.do';
|
|
5
|
+
|
|
6
|
+
const HEADERS = { 'User-Agent': USER_AGENT, Accept: 'application/json' };
|
|
7
|
+
|
|
8
|
+
async function encKey(episodeId, type) {
|
|
9
|
+
try {
|
|
10
|
+
const data = await fetchJson(`${ENC_API}/enc-kisskh?text=${episodeId}&type=${type}`, { signal: AbortSignal.timeout(6000) });
|
|
11
|
+
return data?.status === 200 ? data.result : null;
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function getStream(args) {
|
|
18
|
+
const { id, s, e, sdk } = args || {};
|
|
19
|
+
const isTv = s != null && e != null;
|
|
20
|
+
const apiKey = resolveTmdbKey(args, sdk);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const meta = await getTmdbMetadata(id, isTv, apiKey);
|
|
24
|
+
if (!meta?.titles?.length) return null;
|
|
25
|
+
|
|
26
|
+
let drama = null;
|
|
27
|
+
let bestScore = -1;
|
|
28
|
+
const searchTitles = meta.titles.length > 1 ? [...meta.titles].reverse() : meta.titles;
|
|
29
|
+
|
|
30
|
+
for (const title of searchTitles) {
|
|
31
|
+
const results = await fetchJson(`${BASE_URL}/api/DramaList/Search?q=${encodeURIComponent(title)}`, {
|
|
32
|
+
headers: HEADERS,
|
|
33
|
+
signal: AbortSignal.timeout(6000),
|
|
34
|
+
}).catch(() => []);
|
|
35
|
+
|
|
36
|
+
const targetClean = cleanTitle(title);
|
|
37
|
+
for (const item of results) {
|
|
38
|
+
const parts = (item.title || '').split(/\s*-\s*/).map(cleanTitle);
|
|
39
|
+
let score = 0;
|
|
40
|
+
if (parts.some(p => p === targetClean)) score += 5;
|
|
41
|
+
else if (parts.some(p => p.includes(targetClean) || targetClean.includes(p))) score += 3;
|
|
42
|
+
else continue;
|
|
43
|
+
if (score > bestScore) {
|
|
44
|
+
bestScore = score;
|
|
45
|
+
drama = item;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (drama && bestScore >= 5) break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!drama) return null;
|
|
52
|
+
|
|
53
|
+
const detail = await fetchJson(`${BASE_URL}/api/DramaList/Drama/${drama.id}`, {
|
|
54
|
+
headers: HEADERS,
|
|
55
|
+
signal: AbortSignal.timeout(8000),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const episodeId = detail?.episodes?.find(ep => Math.floor(ep.number) === Number(e || 1))?.id;
|
|
59
|
+
if (!episodeId) return null;
|
|
60
|
+
|
|
61
|
+
const vidKey = await encKey(episodeId, 'vid');
|
|
62
|
+
if (!vidKey) return null;
|
|
63
|
+
|
|
64
|
+
const videoData = await fetchJson(`${BASE_URL}/api/DramaList/Episode/${episodeId}.png?err=false&ts=&time=&kkey=${vidKey}`, {
|
|
65
|
+
headers: HEADERS,
|
|
66
|
+
signal: AbortSignal.timeout(10000),
|
|
67
|
+
});
|
|
68
|
+
if (!videoData?.Video) return null;
|
|
69
|
+
|
|
70
|
+
let subtitles = [];
|
|
71
|
+
const subKey = await encKey(episodeId, 'sub');
|
|
72
|
+
if (subKey) {
|
|
73
|
+
try {
|
|
74
|
+
const subList = await fetchJson(`${BASE_URL}/api/Sub/${episodeId}?kkey=${subKey}`, {
|
|
75
|
+
headers: HEADERS,
|
|
76
|
+
signal: AbortSignal.timeout(8000),
|
|
77
|
+
});
|
|
78
|
+
if (Array.isArray(subList)) {
|
|
79
|
+
subtitles = subList.map(sub => ({
|
|
80
|
+
url: sub.src,
|
|
81
|
+
label: sub.label,
|
|
82
|
+
lang: sub.land,
|
|
83
|
+
default: Boolean(sub.default),
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
} catch { }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const streamUrl = videoData.Video;
|
|
90
|
+
const allUrls = [{
|
|
91
|
+
url: streamUrl,
|
|
92
|
+
type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
|
|
93
|
+
audio: 'sub',
|
|
94
|
+
server: 'KissKH',
|
|
95
|
+
quality: 'Auto',
|
|
96
|
+
headers: HEADERS,
|
|
97
|
+
skipProxy: false,
|
|
98
|
+
subtitles,
|
|
99
|
+
}];
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
url: streamUrl,
|
|
103
|
+
headers: HEADERS,
|
|
104
|
+
allUrls,
|
|
105
|
+
subtitles,
|
|
106
|
+
};
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function getSources(args) {
|
|
113
|
+
const stream = await getStream(args);
|
|
114
|
+
return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : ['KissKH'];
|
|
60
115
|
}
|
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
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const BASE_URL = 'https://api.meowtv.ru';
|
|
2
|
+
const DEC_API = 'https://enc-dec.app/api';
|
|
3
|
+
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';
|
|
4
|
+
|
|
5
|
+
const HEADERS = {
|
|
6
|
+
'User-Agent': UA,
|
|
7
|
+
Origin: 'https://meowtv.ru',
|
|
8
|
+
Referer: 'https://meowtv.ru/',
|
|
9
|
+
Accept: 'application/json, text/plain, */*',
|
|
10
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const SERVERS = [
|
|
14
|
+
{ id: 'acme', label: 'Acme' },
|
|
15
|
+
{ id: 'dcloud', label: 'Dcloud' },
|
|
16
|
+
{ id: 'tik', label: 'TCloud' },
|
|
17
|
+
{ id: 'ipcloud', label: 'IPcloud' },
|
|
18
|
+
{ id: 'turkce', label: 'Türkçe', movieOnly: true },
|
|
19
|
+
{ id: 'hindi', label: 'Hindi' },
|
|
20
|
+
{ id: 'english', label: 'English' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
async function decryptMeow(data) {
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(`${DEC_API}/dec-meowtv`, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: {
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
'User-Agent': UA,
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify({ data }),
|
|
32
|
+
});
|
|
33
|
+
if (!res.ok) return null;
|
|
34
|
+
const json = await res.json();
|
|
35
|
+
return json?.status === 200 ? json.result : null;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function extractUrls(result) {
|
|
42
|
+
const urls = [];
|
|
43
|
+
if (!result) return urls;
|
|
44
|
+
if (typeof result === 'string' && /^https?:\/\//.test(result)) {
|
|
45
|
+
urls.push({ url: result, quality: 'Auto', type: result.includes('.m3u8') ? 'hls' : 'mp4' });
|
|
46
|
+
} else if (result?.url && typeof result.url === 'string') {
|
|
47
|
+
urls.push({
|
|
48
|
+
url: result.url,
|
|
49
|
+
quality: result.quality || 'Auto',
|
|
50
|
+
type: result.type || (result.url.includes('.m3u8') ? 'hls' : 'mp4'),
|
|
51
|
+
});
|
|
52
|
+
} else if (Array.isArray(result?.sources)) {
|
|
53
|
+
for (const s of result.sources) {
|
|
54
|
+
if (s?.url) {
|
|
55
|
+
urls.push({
|
|
56
|
+
url: s.url,
|
|
57
|
+
quality: s.quality || 'Auto',
|
|
58
|
+
type: s.type || (s.url.includes('.m3u8') ? 'hls' : 'mp4'),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} else if (result?.stream) {
|
|
63
|
+
const sUrl = typeof result.stream === 'string' ? result.stream : result.stream?.url;
|
|
64
|
+
if (sUrl) {
|
|
65
|
+
urls.push({
|
|
66
|
+
url: sUrl,
|
|
67
|
+
quality: result.stream?.quality || 'Auto',
|
|
68
|
+
type: sUrl.includes('.m3u8') ? 'hls' : 'mp4',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return urls;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function getStream(args) {
|
|
76
|
+
const { id, s, e } = args || {};
|
|
77
|
+
const isTv = s != null && e != null;
|
|
78
|
+
|
|
79
|
+
const ticketRes = await fetch(`${BASE_URL}/streams/ticket`, {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: HEADERS,
|
|
82
|
+
}).then(r => (r.ok ? r.json() : null)).catch(() => null);
|
|
83
|
+
|
|
84
|
+
const ticket = ticketRes?.ticket;
|
|
85
|
+
if (!ticket) return null;
|
|
86
|
+
|
|
87
|
+
const requestHeaders = {
|
|
88
|
+
...HEADERS,
|
|
89
|
+
'X-Stream-Ticket': ticket,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const targetServers = isTv ? SERVERS.filter(srv => !srv.movieOnly) : SERVERS;
|
|
93
|
+
|
|
94
|
+
const responses = await Promise.allSettled(
|
|
95
|
+
targetServers.map(async server => {
|
|
96
|
+
const url = isTv
|
|
97
|
+
? `${BASE_URL}/streams/tv/${id}/${s}/${e}?s=${encodeURIComponent(server.id)}`
|
|
98
|
+
: `${BASE_URL}/streams/movie/${id}?s=${encodeURIComponent(server.id)}`;
|
|
99
|
+
|
|
100
|
+
const res = await fetch(url, { headers: requestHeaders });
|
|
101
|
+
if (!res.ok) return null;
|
|
102
|
+
const data = await res.json();
|
|
103
|
+
const decrypted = await decryptMeow(data);
|
|
104
|
+
return { server, decrypted };
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const allUrls = [];
|
|
109
|
+
const seen = new Set();
|
|
110
|
+
|
|
111
|
+
for (const res of responses) {
|
|
112
|
+
if (res.status !== 'fulfilled' || !res.value?.decrypted) continue;
|
|
113
|
+
const { server, decrypted } = res.value;
|
|
114
|
+
const found = extractUrls(decrypted);
|
|
115
|
+
|
|
116
|
+
for (const item of found) {
|
|
117
|
+
if (!item?.url || seen.has(item.url)) continue;
|
|
118
|
+
seen.add(item.url);
|
|
119
|
+
allUrls.push({
|
|
120
|
+
url: item.url,
|
|
121
|
+
server: `MeowTV - ${server.label}`,
|
|
122
|
+
quality: item.quality || 'Auto',
|
|
123
|
+
type: item.type || (item.url.includes('.m3u8') ? 'hls' : 'mp4'),
|
|
124
|
+
headers: {
|
|
125
|
+
Referer: 'https://meowtv.ru/',
|
|
126
|
+
Origin: 'https://meowtv.ru',
|
|
127
|
+
'User-Agent': UA,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!allUrls.length) return null;
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
url: allUrls[0].url,
|
|
137
|
+
headers: allUrls[0].headers,
|
|
138
|
+
allUrls,
|
|
139
|
+
};
|
|
140
|
+
}
|