@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.
Files changed (52) hide show
  1. package/package.json +3 -2
  2. package/src/config.js +10 -85
  3. package/src/sdk.d.ts +94 -121
  4. package/src/sdk.js +82 -78
  5. package/src/sources/111477.js +88 -100
  6. package/src/sources/123anime.js +120 -56
  7. package/src/sources/3eyedraven.js +82 -135
  8. package/src/sources/cinecat.js +37 -18
  9. package/src/sources/cinemaarmy.js +179 -180
  10. package/src/sources/cinesrc.js +401 -397
  11. package/src/sources/cno.js +101 -94
  12. package/src/sources/dulo.js +134 -149
  13. package/src/sources/flaxmovies.js +114 -105
  14. package/src/sources/frost.js +38 -29
  15. package/src/sources/fsonline.js +95 -47
  16. package/src/sources/hdhub.js +43 -36
  17. package/src/sources/hexa.js +60 -53
  18. package/src/sources/hyper.js +42 -35
  19. package/src/sources/kickassanime.js +117 -124
  20. package/src/sources/kisskh.js +114 -59
  21. package/src/sources/lmscript.js +72 -70
  22. package/src/sources/lookmovie.js +80 -79
  23. package/src/sources/movenerds.js +74 -124
  24. package/src/sources/movy.js +197 -209
  25. package/src/sources/net79.js +59 -55
  26. package/src/sources/peestream.js +126 -138
  27. package/src/sources/pengu.js +46 -49
  28. package/src/sources/pstream.js +73 -67
  29. package/src/sources/purstream.js +96 -104
  30. package/src/sources/rivestream.js +182 -176
  31. package/src/sources/rivo.js +58 -53
  32. package/src/sources/streamaggregator.js +114 -128
  33. package/src/sources/thugtv.js +70 -62
  34. package/src/sources/vidapi.js +122 -114
  35. package/src/sources/vidcore.js +14 -122
  36. package/src/sources/vidfast.js +14 -122
  37. package/src/sources/vidlink.js +78 -27
  38. package/src/sources/vidup.js +14 -122
  39. package/src/sources/vidvault.js +133 -187
  40. package/src/sources/vidy.js +192 -187
  41. package/src/sources/vidzee.js +189 -183
  42. package/src/sources/vixsrc.js +123 -113
  43. package/src/sources/watchout.js +60 -46
  44. package/src/sources/xdownloader.js +127 -128
  45. package/src/sources/yapgrid.js +68 -62
  46. package/src/sources/yastream.js +35 -29
  47. package/src/subtitles.js +105 -122
  48. package/src/utils/helpers.js +586 -247
  49. package/src/sources/bcine.js +0 -202
  50. package/src/sources/cinezo.js +0 -55
  51. package/src/sources/vidora.js +0 -62
  52. package/src/sources/vidrock.js +0 -145
@@ -1,54 +1,61 @@
1
- import { fetchJson, fetchText } from '../utils/helpers.js';
2
-
3
- const API = "https://enc-dec.app/api";
4
-
5
- function getRandomHex(size = 32) {
6
- const array = new Uint8Array(size);
7
- crypto.getRandomValues(array);
8
- return Array.from(array).map(b => b.toString(16).padStart(2, '0')).join('');
9
- }
10
-
11
- export async function getStream({ id, s, e }) {
12
- const isTv = s != null && e != null;
13
- const key = getRandomHex(32);
14
-
15
- const headers = {
16
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
17
- "Referer": "https://hexa.su/",
18
- "Accept": "text/plain",
19
- "X-Fingerprint-Lite": "e9136c41504646444",
20
- "X-Api-Key": key
21
- };
22
-
23
- const encHexaRes = await fetchJson(`${API}/enc-hexa`);
24
- if (!encHexaRes || encHexaRes.status !== 200) return null;
25
- headers["X-Cap-Token"] = encHexaRes.result.token;
26
-
27
- const url = isTv
28
- ? `https://theemoviedb.hexa.su/api/tmdb/tv/${id}/season/${s}/episode/${e}/images`
29
- : `https://theemoviedb.hexa.su/api/tmdb/movie/${id}/images`;
30
-
31
- const encrypted = await fetchText(url, { headers });
32
- if (!encrypted) return null;
33
-
34
- const decRes = await fetchJson(`${API}/dec-hexa`, {
35
- method: "POST",
36
- headers: { "Content-Type": "application/json" },
37
- body: JSON.stringify({ text: encrypted, key })
38
- });
39
-
40
- if (!decRes || decRes.status !== 200 || !decRes.result) return null;
41
-
42
- const data = decRes.result;
43
- const streamUrl = data.url || data.file || (Array.isArray(data.sources) ? data.sources[0]?.url : null);
44
-
45
- if (!streamUrl) return null;
46
-
47
- return {
48
- url: streamUrl,
49
- headers: {
50
- Referer: "https://hexa.su/",
51
- Origin: "https://hexa.su"
52
- }
53
- };
1
+ import { fetchJson, fetchText, getRandomHex } from '../utils/helpers.js';
2
+
3
+ const API_BASE = 'https://enc-dec.app/api';
4
+ const BASE_URL = 'https://hexa.su';
5
+ const TMDB_PROXY_BASE = 'https://theemoviedb.hexa.su';
6
+
7
+ export async function getStream(args) {
8
+ const { id, s, e } = args || {};
9
+ const isTv = s != null && e != null;
10
+ const key = getRandomHex(32);
11
+
12
+ const headers = {
13
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
14
+ Referer: `${BASE_URL}/`,
15
+ Accept: 'text/plain',
16
+ 'X-Fingerprint-Lite': 'e9136c41504646444',
17
+ 'X-Api-Key': key,
18
+ };
19
+
20
+ const encHexaRes = await fetchJson(`${API_BASE}/enc-hexa`);
21
+ if (!encHexaRes || encHexaRes.status !== 200) return null;
22
+ headers['X-Cap-Token'] = encHexaRes.result.token;
23
+
24
+ const url = isTv
25
+ ? `${TMDB_PROXY_BASE}/api/tmdb/tv/${id}/season/${s}/episode/${e}/images`
26
+ : `${TMDB_PROXY_BASE}/api/tmdb/movie/${id}/images`;
27
+
28
+ const encrypted = await fetchText(url, { headers });
29
+ if (!encrypted) return null;
30
+
31
+ const decRes = await fetchJson(`${API_BASE}/dec-hexa`, {
32
+ method: 'POST',
33
+ headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify({ text: encrypted, key }),
35
+ });
36
+
37
+ if (!decRes || decRes.status !== 200 || !decRes.result) return null;
38
+
39
+ const data = decRes.result;
40
+ const streamUrl = data.url || data.file || (Array.isArray(data.sources) ? data.sources[0]?.url : null);
41
+ if (!streamUrl) return null;
42
+
43
+ const streamHeaders = {
44
+ Referer: `${BASE_URL}/`,
45
+ Origin: BASE_URL,
46
+ };
47
+
48
+ const allUrls = [{
49
+ url: streamUrl,
50
+ server: 'Hexa',
51
+ quality: 'Auto',
52
+ type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
53
+ headers: streamHeaders,
54
+ }];
55
+
56
+ return {
57
+ url: streamUrl,
58
+ headers: streamHeaders,
59
+ allUrls,
60
+ };
54
61
  }
@@ -1,36 +1,43 @@
1
- import { fetchJson } from '../utils/helpers.js';
2
-
3
- export async function getStream({ id, s, e }) {
4
- const isTv = s != null && e != null;
5
- const path = isTv
6
- ? `/local/stream/series/tmdb:${id}:${s}:${e}.json`
7
- : `/local/stream/movie/tmdb:${id}.json`;
8
-
9
- const url = `https://hyper.stravo.site${path}`;
10
- const res = await fetchJson(url);
11
- if (!res || !Array.isArray(res.streams)) return null;
12
-
13
- const validStreams = res.streams.filter(item => {
14
- if (!item.url || item.externalUrl) return false;
15
- if (typeof item.title === 'string' && item.title.includes('🔒')) return false;
16
- return true;
17
- });
18
-
19
- if (!validStreams.length) return null;
20
-
21
- const allUrls = validStreams.map(item => ({
22
- url: item.url,
23
- server: `Hyper - ${item.title?.replace(/[^\x20-\x7E]+/g, '').trim() || 'Server'}`,
24
- headers: item.behaviorHints?.proxyHeaders?.request || {
25
- Referer: 'https://mzfi.me/',
26
- Accept: '*/*'
27
- },
28
- skipProxy: item.behaviorHints?.notWebReady === false
29
- }));
30
-
31
- return {
32
- url: allUrls[0].url,
33
- headers: allUrls[0].headers,
34
- allUrls
35
- };
1
+ import { fetchJson } from '../utils/helpers.js';
2
+
3
+ const BASE_URL = 'https://hyper.stravo.site';
4
+ const DEFAULT_ORIGIN = 'https://mzfi.me';
5
+
6
+ export async function getStream(args) {
7
+ const { id, s, e } = args || {};
8
+ const isTv = s != null && e != null;
9
+
10
+ const path = isTv
11
+ ? `/local/stream/series/tmdb:${id}:${s}:${e}.json`
12
+ : `/local/stream/movie/tmdb:${id}.json`;
13
+
14
+ const url = `${BASE_URL}${path}`;
15
+ const res = await fetchJson(url).catch(() => null);
16
+ if (!res || !Array.isArray(res.streams)) return null;
17
+
18
+ const validStreams = res.streams.filter(item => {
19
+ if (!item.url || item.externalUrl) return false;
20
+ if (typeof item.title === 'string' && item.title.includes('🔒')) return false;
21
+ return true;
22
+ });
23
+
24
+ if (!validStreams.length) return null;
25
+
26
+ const allUrls = validStreams.map(item => ({
27
+ url: item.url,
28
+ server: `Hyper - ${item.title?.replace(/[^\x20-\x7E]+/g, '').trim() || 'Server'}`,
29
+ quality: 'Auto',
30
+ type: item.url.includes('.m3u8') ? 'hls' : 'mp4',
31
+ headers: item.behaviorHints?.proxyHeaders?.request || {
32
+ Referer: `${DEFAULT_ORIGIN}/`,
33
+ Accept: '*/*',
34
+ },
35
+ skipProxy: item.behaviorHints?.notWebReady === false,
36
+ }));
37
+
38
+ return {
39
+ url: allUrls[0].url,
40
+ headers: allUrls[0].headers,
41
+ allUrls,
42
+ };
36
43
  }
@@ -1,125 +1,118 @@
1
- import { fetchJson, USER_AGENT } from '../utils/helpers.js';
2
-
3
- const BASE = "https://kaa.lt";
4
- const HLS_BASE = "https://hls.krussdomi.com/manifest";
5
- const HEADERS = { "User-Agent": USER_AGENT, Accept: "application/json" };
6
-
7
- async function kaaSearch(query) {
8
- const res = await fetchJson(`${BASE}/api/fsearch`, {
9
- method: "POST",
10
- headers: { ...HEADERS, "Content-Type": "application/json" },
11
- body: JSON.stringify({ page: 1, query }),
12
- }).catch(() => null);
13
- return Array.isArray(res?.result) ? res.result : [];
14
- }
15
-
16
- async function kaaShowInfo(showSlug) {
17
- return fetchJson(`${BASE}/api/show/${showSlug}`, { headers: HEADERS }).catch(() => null);
18
- }
19
-
20
- async function kaaEpisodePage(showSlug, ep) {
21
- return fetchJson(`${BASE}/api/show/${showSlug}/episodes?ep=${ep}&lang=ja-JP`, { headers: HEADERS }).catch(() => null);
22
- }
23
-
24
- async function kaaAllEpisodes(showSlug) {
25
- const first = await kaaEpisodePage(showSlug, 1);
26
- if (!first) return [];
27
- const pages = Array.isArray(first.pages) ? first.pages : [];
28
- const all = Array.isArray(first.result) ? [...first.result] : [];
29
-
30
- if (pages.length > 1) {
31
- const rest = await Promise.all(
32
- pages.slice(1).map(async (pg) => {
33
- const startEp = pg.eps?.[0];
34
- if (!startEp) return [];
35
- const d = await kaaEpisodePage(showSlug, startEp);
36
- return Array.isArray(d?.result) ? d.result : [];
37
- })
38
- );
39
- for (const batch of rest) all.push(...batch);
40
- }
41
-
42
- return all;
43
- }
44
-
45
- async function kaaEpisodeServers(showSlug, fullEpSlug) {
46
- return fetchJson(`${BASE}/api/show/${showSlug}/episode/${fullEpSlug}`, { headers: HEADERS }).catch(() => null);
47
- }
48
-
49
- export async function getStream({ id, s, e, tmdbApiKey, sdk }) {
50
- try {
51
- const apiKey = tmdbApiKey || sdk?.tmdbApiKey || process.env.TMDB_API_KEY || '45dbdd5e714da212424bda0336167447';
52
- const isTv = !!s;
53
- const mediaType = isTv ? 'tv' : 'movie';
54
- const epNum = e ? Number(e) : 1;
55
-
56
- let titles = [];
57
- try {
58
- const tmdbRes = await fetchJson(`https://api.themoviedb.org/3/${mediaType}/${id}?api_key=${apiKey}&append_to_response=alternative_titles`);
59
- if (tmdbRes) {
60
- const primary = tmdbRes.title || tmdbRes.name;
61
- const original = tmdbRes.original_title || tmdbRes.original_name;
62
- if (primary) titles.push(primary);
63
- if (original && !titles.includes(original)) titles.push(original);
64
-
65
- const alts = tmdbRes.alternative_titles?.titles || tmdbRes.alternative_titles?.results || [];
66
- for (const alt of alts) {
67
- const candidate = alt.title || alt.name;
68
- if (candidate && !titles.includes(candidate)) {
69
- titles.push(candidate);
70
- }
71
- }
72
- }
73
- } catch { }
74
-
75
- if (!titles.length) return null;
76
-
77
- let showSlug = null;
78
- for (const title of titles) {
79
- const results = await kaaSearch(title);
80
- if (results.length) {
81
- showSlug = results[0].slug;
82
- break;
83
- }
84
- }
85
-
86
- if (!showSlug) return null;
87
-
88
- const showInfo = await kaaShowInfo(showSlug);
89
- let fullEpSlug = null;
90
-
91
- if (showInfo?.type === "movie") {
92
- const m = (showInfo.watch_uri || "").match(/\/(ep-(\d+)-([a-f0-9]+))$/i);
93
- if (m) fullEpSlug = m[1];
94
- } else {
95
- const episodes = await kaaAllEpisodes(showSlug);
96
- const epItem = episodes.find((item) => item.episode_number === epNum);
97
- if (epItem) fullEpSlug = `ep-${epItem.episode_number}-${epItem.slug}`;
98
- }
99
-
100
- if (!fullEpSlug) return null;
101
-
102
- const episodeData = await kaaEpisodeServers(showSlug, fullEpSlug);
103
- const servers = Array.isArray(episodeData?.servers) ? episodeData.servers : [];
104
-
105
- const urls = [];
106
- for (const srv of servers) {
107
- if (!srv.src) continue;
108
- const m = srv.src.match(/[?&]id=([^&]+)/);
109
- if (m) {
110
- urls.push({
111
- url: `${HLS_BASE}/${m[1]}/master.m3u8`,
112
- server: `KickAssAnime (${srv.name || 'Server'})`,
113
- });
114
- }
115
- }
116
-
117
- return urls.length ? { allUrls: urls } : null;
118
- } catch (err) {
119
- return null;
120
- }
121
- }
122
-
123
- export async function getSources() {
124
- return ['KickAssAnime'];
1
+ import { fetchJson, getTmdbMetadata, resolveTmdbKey, USER_AGENT } from '../utils/helpers.js';
2
+
3
+ const BASE_URL = 'https://kaa.lt';
4
+ const HLS_BASE = 'https://hls.krussdomi.com/manifest';
5
+ const HEADERS = { 'User-Agent': USER_AGENT, Accept: 'application/json' };
6
+
7
+ async function kaaSearch(query) {
8
+ const res = await fetchJson(`${BASE_URL}/api/fsearch`, {
9
+ method: 'POST',
10
+ headers: { ...HEADERS, 'Content-Type': 'application/json' },
11
+ body: JSON.stringify({ page: 1, query }),
12
+ }).catch(() => null);
13
+ return Array.isArray(res?.result) ? res.result : [];
14
+ }
15
+
16
+ async function kaaShowInfo(showSlug) {
17
+ return fetchJson(`${BASE_URL}/api/show/${showSlug}`, { headers: HEADERS }).catch(() => null);
18
+ }
19
+
20
+ async function kaaEpisodePage(showSlug, ep) {
21
+ return fetchJson(`${BASE_URL}/api/show/${showSlug}/episodes?ep=${ep}&lang=ja-JP`, { headers: HEADERS }).catch(() => null);
22
+ }
23
+
24
+ async function kaaAllEpisodes(showSlug) {
25
+ const first = await kaaEpisodePage(showSlug, 1);
26
+ if (!first) return [];
27
+ const pages = Array.isArray(first.pages) ? first.pages : [];
28
+ const all = Array.isArray(first.result) ? [...first.result] : [];
29
+
30
+ if (pages.length > 1) {
31
+ const rest = await Promise.all(
32
+ pages.slice(1).map(async pg => {
33
+ const startEp = pg.eps?.[0];
34
+ if (!startEp) return [];
35
+ const d = await kaaEpisodePage(showSlug, startEp);
36
+ return Array.isArray(d?.result) ? d.result : [];
37
+ })
38
+ );
39
+ for (const batch of rest) all.push(...batch);
40
+ }
41
+
42
+ return all;
43
+ }
44
+
45
+ async function kaaEpisodeServers(showSlug, fullEpSlug) {
46
+ return fetchJson(`${BASE_URL}/api/show/${showSlug}/episode/${fullEpSlug}`, { headers: HEADERS }).catch(() => null);
47
+ }
48
+
49
+ export async function getStream(args) {
50
+ const { id, s, e, sdk } = args || {};
51
+ const isTv = s != null && e != null;
52
+ const apiKey = resolveTmdbKey(args, sdk);
53
+ const epNum = e ? Number(e) : 1;
54
+
55
+ try {
56
+ const meta = await getTmdbMetadata(id, isTv, apiKey);
57
+ const titles = meta?.titles || [];
58
+ if (!titles.length) return null;
59
+
60
+ let showSlug = null;
61
+ for (const title of titles) {
62
+ const results = await kaaSearch(title);
63
+ if (results.length) {
64
+ showSlug = results[0].slug;
65
+ break;
66
+ }
67
+ }
68
+
69
+ if (!showSlug) return null;
70
+
71
+ const showInfo = await kaaShowInfo(showSlug);
72
+ let fullEpSlug = null;
73
+
74
+ if (showInfo?.type === 'movie') {
75
+ const m = (showInfo.watch_uri || '').match(/\/(ep-(\d+)-([a-f0-9]+))$/i);
76
+ if (m) fullEpSlug = m[1];
77
+ } else {
78
+ const episodes = await kaaAllEpisodes(showSlug);
79
+ const epItem = episodes.find(item => item.episode_number === epNum);
80
+ if (epItem) fullEpSlug = `ep-${epItem.episode_number}-${epItem.slug}`;
81
+ }
82
+
83
+ if (!fullEpSlug) return null;
84
+
85
+ const episodeData = await kaaEpisodeServers(showSlug, fullEpSlug);
86
+ const servers = Array.isArray(episodeData?.servers) ? episodeData.servers : [];
87
+
88
+ const allUrls = [];
89
+ for (const srv of servers) {
90
+ if (!srv.src) continue;
91
+ const m = srv.src.match(/[?&]id=([^&]+)/);
92
+ if (m) {
93
+ allUrls.push({
94
+ url: `${HLS_BASE}/${m[1]}/master.m3u8`,
95
+ server: `KickAssAnime (${srv.name || 'Server'})`,
96
+ quality: 'Auto',
97
+ type: 'hls',
98
+ headers: HEADERS,
99
+ });
100
+ }
101
+ }
102
+
103
+ if (!allUrls.length) return null;
104
+
105
+ return {
106
+ url: allUrls[0].url,
107
+ headers: allUrls[0].headers,
108
+ allUrls,
109
+ };
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ export async function getSources(args) {
116
+ const stream = await getStream(args);
117
+ return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : ['KickAssAnime'];
125
118
  }
@@ -1,60 +1,115 @@
1
- import { fetchJson, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
2
-
3
- export const SKIP_VERIFY = true;
4
- export const MULTI_URL = false;
5
-
6
- const ENC_API = "https://enc-dec.app/api";
7
- const BASE = "https://kisskh.do";
8
- const HEADERS = { 'User-Agent': USER_AGENT, Accept: 'application/json' };
9
-
10
- function cleanTitle(t) { return t ? t.toLowerCase().replace(/\(\d{4}\)/g, '').replace(/[^a-z0-9]/g, '') : ''; }
11
-
12
- async function encKey(episodeId, type) {
13
- try {
14
- const data = await fetchJson(`${ENC_API}/enc-kisskh?text=${episodeId}&type=${type}`, { signal: AbortSignal.timeout(6000) });
15
- return data?.status === 200 ? data.result : null;
16
- } catch { return null; }
17
- }
18
-
19
- export async function getStream({ id, s, e }) {
20
- try {
21
- const info = await getTmdbInfo(id, s ? 'tv' : 'movie', s);
22
- if (!info) return null;
23
- let drama = null, bestScore = -1;
24
- for (const title of (info.titles.length > 1 ? [...info.titles].reverse() : info.titles)) {
25
- const results = await fetchJson(`${BASE}/api/DramaList/Search?q=${encodeURIComponent(title)}`, { headers: HEADERS, signal: AbortSignal.timeout(6000) }).catch(() => []);
26
- const targetClean = cleanTitle(title);
27
- for (const item of results) {
28
- const parts = (item.title || '').split(/\s*-\s*/).map(cleanTitle);
29
- let score = 0;
30
- if (parts.some(p => p === targetClean)) score += 5;
31
- else if (parts.some(p => p.includes(targetClean) || targetClean.includes(p))) score += 3;
32
- else continue;
33
- if (score > bestScore) { bestScore = score; drama = item; }
34
- }
35
- if (drama && bestScore >= 5) break;
36
- }
37
- if (!drama) return null;
38
- const detail = await fetchJson(`${BASE}/api/DramaList/Drama/${drama.id}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
39
- const episodeId = detail?.episodes?.find(ep => Math.floor(ep.number) === Number(e || 1))?.id;
40
- if (!episodeId) return null;
41
- const vidKey = await encKey(episodeId, 'vid');
42
- if (!vidKey) return null;
43
- const videoData = await fetchJson(`${BASE}/api/DramaList/Episode/${episodeId}.png?err=false&ts=&time=&kkey=${vidKey}`, { headers: HEADERS, signal: AbortSignal.timeout(10000) });
44
- if (!videoData?.Video) return null;
45
- let subtitles = [];
46
- const subKey = await encKey(episodeId, 'sub');
47
- if (subKey) {
48
- try {
49
- const subList = await fetchJson(`${BASE}/api/Sub/${episodeId}?kkey=${subKey}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
50
- if (Array.isArray(subList)) subtitles = subList.map(sub => ({ url: sub.src, label: sub.label, lang: sub.land, default: !!sub.default }));
51
- } catch { }
52
- }
53
- return { allUrls: [{ url: videoData.Video, type: videoData.Video.includes('.m3u8') ? 'hls' : 'mp4', audio: 'sub', server: 'KissKH', skipProxy: false, subtitles }] };
54
- } catch { return null; }
55
- }
56
-
57
- export async function getSources(args) {
58
- const stream = await getStream(args);
59
- return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : [];
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
  }