@vyla-entertainment/sdk 1.0.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 (44) hide show
  1. package/LICENSE +407 -0
  2. package/README.md +44 -0
  3. package/package.json +31 -0
  4. package/src/config.js +519 -0
  5. package/src/downloads/downloads.js +8 -0
  6. package/src/downloads/sources/trendimovies.js +63 -0
  7. package/src/health.js +38 -0
  8. package/src/sdk.js +65 -0
  9. package/src/sources/123anime.js +57 -0
  10. package/src/sources/1embed.js +92 -0
  11. package/src/sources/anihq.js +112 -0
  12. package/src/sources/anineko.js +81 -0
  13. package/src/sources/anipm.js +70 -0
  14. package/src/sources/flaxmovies.js +14 -0
  15. package/src/sources/fsharetv.js +44 -0
  16. package/src/sources/fsonic.js +47 -0
  17. package/src/sources/fsonline.js +46 -0
  18. package/src/sources/goated.js +262 -0
  19. package/src/sources/hexa.js +82 -0
  20. package/src/sources/kisskh.js +60 -0
  21. package/src/sources/lookmovie.js +78 -0
  22. package/src/sources/mapple.js +115 -0
  23. package/src/sources/meowtv.js +111 -0
  24. package/src/sources/nebula.js +62 -0
  25. package/src/sources/nekowatch.js +57 -0
  26. package/src/sources/opstream.js +123 -0
  27. package/src/sources/peachify.js +78 -0
  28. package/src/sources/pengu.js +45 -0
  29. package/src/sources/purstream.js +104 -0
  30. package/src/sources/vidapi.js +115 -0
  31. package/src/sources/vidcore.js +43 -0
  32. package/src/sources/videasy.js +98 -0
  33. package/src/sources/vidfast.js +53 -0
  34. package/src/sources/vidlink.js +28 -0
  35. package/src/sources/vidnest.js +77 -0
  36. package/src/sources/vidrift.js +95 -0
  37. package/src/sources/vidrock.js +145 -0
  38. package/src/sources/vidsync.js +38 -0
  39. package/src/sources/vidup.js +79 -0
  40. package/src/sources/vidzee.js +120 -0
  41. package/src/sources/vixsrc.js +98 -0
  42. package/src/sources/xpass.js +40 -0
  43. package/src/subtitles.js +127 -0
  44. package/src/utils/helpers.js +194 -0
@@ -0,0 +1,111 @@
1
+ import { fetchJson, USER_AGENT } from '../utils/helpers.js';
2
+
3
+ const API_BASE = 'https://api.meowtv.ru';
4
+ const REFERER = 'https://meowtv.ru/';
5
+ const ENC_DEC_API = 'https://enc-dec.app/api/dec-meowtv';
6
+
7
+ const HEADERS = {
8
+ 'User-Agent': USER_AGENT,
9
+ 'Accept': 'application/json',
10
+ 'Referer': REFERER,
11
+ 'Origin': 'https://meowtv.ru',
12
+ 'Accept-Language': 'en-US,en;q=0.9'
13
+ };
14
+
15
+ const SERVERS = [
16
+ 'pseudo',
17
+ 'lynx',
18
+ 'tik',
19
+ 'ipcloud',
20
+ 'v4:English',
21
+ 'turkce',
22
+ 'v5:Hindi',
23
+ 'v4:Hindi',
24
+ 'v6:Hindi'
25
+ ];
26
+
27
+ const TIMEOUT_MS = 15000;
28
+
29
+ export const VERIFY_HEADERS = { 'User-Agent': USER_AGENT, 'Referer': REFERER, 'Origin': 'https://meowtv.ru' };
30
+
31
+ async function decryptPayload(payload) {
32
+ try {
33
+ const response = await fetchJson(ENC_DEC_API, {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify({ data: payload })
37
+ });
38
+
39
+ if (response?.status !== 200 || !response?.result) {
40
+ return null;
41
+ }
42
+
43
+ return typeof response.result === 'string' ? JSON.parse(response.result) : response.result;
44
+ } catch (e) {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ export async function getStream({ id, s, e, server }) {
50
+ try {
51
+ const isTv = !!s;
52
+ let targets = SERVERS;
53
+ if (server && server !== 'all') {
54
+ const cleanName = server.replace('MeowTV - ', '');
55
+ targets = SERVERS.includes(cleanName) ? [cleanName] : [targets[0]];
56
+ }
57
+
58
+ const results = await Promise.all(targets.map(async srv => {
59
+ try {
60
+ return await Promise.race([
61
+ (async () => {
62
+ const path = isTv
63
+ ? `/streams/tv/${id}/${s}/${e}?s=${encodeURIComponent(srv)}`
64
+ : `/streams/movie/${id}?s=${encodeURIComponent(srv)}`;
65
+
66
+ const payload = await fetchJson(`${API_BASE}${path}`, {
67
+ headers: HEADERS
68
+ });
69
+
70
+ if (!payload) return null;
71
+
72
+ const data = await decryptPayload(payload);
73
+ if (!data) return null;
74
+
75
+ const urls = [];
76
+ if (data?.url?.startsWith('http')) {
77
+ urls.push({
78
+ url: data.url,
79
+ server: `MeowTV - ${srv}`,
80
+ });
81
+ }
82
+
83
+ if (Array.isArray(data?.streams)) {
84
+ for (const stream of data.streams) {
85
+ if (stream?.url?.startsWith('http')) {
86
+ urls.push({
87
+ url: stream.url,
88
+ server: `MeowTV - ${srv} (${stream.language || 'Unknown'})`,
89
+ });
90
+ }
91
+ }
92
+ }
93
+ return urls;
94
+ })(),
95
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout exceeded')), TIMEOUT_MS))
96
+ ]);
97
+ } catch (err) {
98
+ return null;
99
+ }
100
+ }));
101
+
102
+ const allUrls = results.filter(Boolean).flat();
103
+ return allUrls.length ? { allUrls } : null;
104
+ } catch (e) {
105
+ return null;
106
+ }
107
+ }
108
+
109
+ export async function getSources() {
110
+ return SERVERS.map(s => `MeowTV - ${s}`);
111
+ }
@@ -0,0 +1,62 @@
1
+ import { fetchJson } from '../utils/helpers.js';
2
+
3
+ const BASE_URL = 'https://nebula.work.gd/private/7f6b9d179ba7ecd77a39520e';
4
+
5
+ async function getImdbId(tmdbApiKey, tmdbId, s, e) {
6
+ const key = tmdbApiKey;
7
+ if (!key) return null;
8
+ try {
9
+ const type = s != null && e != null ? 'tv' : 'movie';
10
+ const url = `https://api.themoviedb.org/3/${type}/${tmdbId}/external_ids?api_key=${key}`;
11
+ const res = await fetchJson(url);
12
+ return res?.imdb_id || null;
13
+ } catch(err) {
14
+ return null;
15
+ }
16
+ }
17
+
18
+ export async function getStream({ id, s, e, tmdbApiKey }) {
19
+ try {
20
+ const imdbId = await getImdbId(tmdbApiKey, id, s, e);
21
+ if (!imdbId) return null;
22
+
23
+ const path = s != null && e != null
24
+ ? `/stream/series/${imdbId}:${s}:${e}.json`
25
+ : `/stream/movie/${imdbId}.json`;
26
+
27
+ const response = await fetch(`${BASE_URL}${path}`, {
28
+ headers: {
29
+ 'Accept': 'application/json'
30
+ },
31
+ signal: AbortSignal.timeout(15000)
32
+ });
33
+
34
+ if (!response.ok) return null;
35
+
36
+ const data = await response.json();
37
+ if (!data?.streams?.length) return null;
38
+
39
+ const allUrls = data.streams
40
+ .filter(stream => stream.url && stream.url.trim() !== '' && !stream.url.includes('hubcloud'))
41
+ .map(stream => {
42
+ const headers = stream.behaviorHints?.proxyHeaders?.request || {};
43
+
44
+ let serverName = stream.name || 'Unknown';
45
+ if (serverName.includes('\n')) serverName = serverName.split('\n')[0];
46
+
47
+ const filename = stream.filename || stream.behaviorHints?.filename;
48
+ if (filename) serverName += ` - ${filename}`;
49
+
50
+ return {
51
+ url: stream.url,
52
+ server: `Nebula - ${serverName}`,
53
+ type: stream.url.includes('.m3u8') ? 'hls' : 'mp4',
54
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
55
+ skipProxy: stream.behaviorHints?.notWebReady === false
56
+ };
57
+ }).filter(Boolean);
58
+ return allUrls.length ? { allUrls } : null;
59
+ } catch (err) {
60
+ return null;
61
+ }
62
+ }
@@ -0,0 +1,57 @@
1
+ import { fetchJson, fetchText, getTmdbInfo, tmdbToAnilist } from '../utils/helpers.js';
2
+
3
+ const BASE_URL = "https://nekowatch.xyz";
4
+ const ANIME_JS_URL = `${BASE_URL}/js/anime.js?v=v263_fast_player_start`;
5
+ const FALLBACK_ORDER = ['kiwi', 'ally', 'bee', 'arc', 'anikoto', 'jet', 'bonk', 'moo', 'hop', 'pulsar', 'pahe'];
6
+
7
+ let providerOrderCache = null, providerOrderTs = 0;
8
+
9
+ async function getProviderOrder() {
10
+ const now = Date.now();
11
+ if (providerOrderCache && now - providerOrderTs < 1800000) return providerOrderCache;
12
+ try {
13
+ const js = await fetchText(ANIME_JS_URL, { signal: AbortSignal.timeout(6000) });
14
+ const match = js.match(/PROVIDER_ORDER\s*=\s*\[([\s\S]*?)\]/);
15
+ if (match) {
16
+ const order = [...match[1].matchAll(/['"]([a-z0-9_]+)['"]/gi)].map(m => m[1]);
17
+ if (order.length) { providerOrderCache = order; providerOrderTs = now; return order; }
18
+ }
19
+ } catch { }
20
+ return providerOrderCache || FALLBACK_ORDER;
21
+ }
22
+
23
+ export async function getStream({ id, s, e, audio }) {
24
+ try {
25
+ const info = await getTmdbInfo(id, s ? 'tv' : 'movie', s);
26
+ if (!info || !info.isAnime) return null;
27
+ const anilistId = await tmdbToAnilist(id, s ? 'tv' : 'movie', s, info.titles, info.year);
28
+ if (!anilistId) return null;
29
+ const ep = e || 1;
30
+ const audiosToTry = audio === "all" ? ["sub", "dub"] : (audio === "dub" ? ["dub", "sub"] : ["sub", "dub"]);
31
+ const providerOrder = await getProviderOrder();
32
+ for (const aud of audiosToTry) {
33
+ const results = await Promise.allSettled(providerOrder.map(p => fetchJson(`${BASE_URL}/api/anime/watch/${p}/${anilistId}/${aud}/${p}-${ep}`, { signal: AbortSignal.timeout(8000) })));
34
+ const allUrls = [], seenActual = new Set();
35
+ for (const r of results) {
36
+ if (r.status !== 'fulfilled' || !r.value) continue;
37
+ const data = r.value;
38
+ let streams = [], actual = data.attempted?.actualProvider || data.provider;
39
+ if (Array.isArray(data.streams)) streams = data.streams;
40
+ else {
41
+ for (const key of Object.keys(data)) if (data[key] && Array.isArray(data[key].streams)) { streams.push(...data[key].streams); if (!actual) actual = data[key].provider || key; }
42
+ }
43
+ if (!streams.length) continue;
44
+ if (actual) { if (seenActual.has(actual)) continue; seenActual.add(actual); }
45
+ const valid = streams.filter(st => st.type === "hls" && st.url).sort((a, b) => (b.priority || 0) - (a.priority || 0));
46
+ for (const st of valid) allUrls.push({ url: st.url, type: "hls", audio: aud, server: st.server ? `NekoWatch-${st.server}` : "NekoWatch", headers: st.referer ? { Referer: st.referer } : undefined, skipProxy: /\.workers\.dev(\/|$)/i.test(st.url) });
47
+ }
48
+ if (allUrls.length) return { allUrls };
49
+ }
50
+ return null;
51
+ } catch { return null; }
52
+ }
53
+
54
+ export async function getSources(args) {
55
+ const stream = await getStream(args);
56
+ return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : [];
57
+ }
@@ -0,0 +1,123 @@
1
+ import { getTmdbInfo, USER_AGENT } from '../utils/helpers.js';
2
+
3
+ const BASE_URL = 'https://opstream.fun';
4
+ const API_BASE = `${BASE_URL}/api/resolve`;
5
+
6
+ const API_HEADERS = {
7
+ 'User-Agent': USER_AGENT,
8
+ 'Accept': 'application/x-ndjson; charset=utf-8',
9
+ 'Referer': `${BASE_URL}/`,
10
+ 'Origin': BASE_URL,
11
+ };
12
+
13
+ const STREAM_HEADERS = {
14
+ 'User-Agent': USER_AGENT,
15
+ 'Referer': `${BASE_URL}/`,
16
+ 'Origin': BASE_URL,
17
+ };
18
+
19
+ export async function getStream({ id, s, e }) {
20
+ try {
21
+ const isTv = s != null && e != null;
22
+ const info = await getTmdbInfo(id, isTv ? 'tv' : 'movie');
23
+
24
+ if (!info?.titles?.length || !info.imdbId) return null;
25
+
26
+ const params = new URLSearchParams({
27
+ type: isTv ? 'tv' : 'movie',
28
+ tmdbId: id,
29
+ title: info.titles[0],
30
+ year: info.year || '',
31
+ imdbId: info.imdbId,
32
+ progress: '1'
33
+ });
34
+
35
+ if (isTv) {
36
+ params.append('season', s);
37
+ params.append('episode', e);
38
+ }
39
+
40
+ const res = await fetch(`${API_BASE}?${params}`, {
41
+ headers: API_HEADERS,
42
+ signal: AbortSignal.timeout(15000)
43
+ });
44
+
45
+ if (!res.ok) return null;
46
+
47
+ const text = await res.text();
48
+ const lines = text.split('\n');
49
+
50
+ let streamData = null;
51
+ for (const line of lines) {
52
+ const trimmed = line.trim();
53
+ if (!trimmed) continue;
54
+ try {
55
+ const json = JSON.parse(trimmed);
56
+ if (json.t === 'done' && json.data) {
57
+ streamData = json.data;
58
+ break;
59
+ }
60
+ } catch { continue; }
61
+ }
62
+
63
+ if (!streamData) return null;
64
+
65
+ const subtitles = (streamData.captions || []).map(c => ({
66
+ url: c.url.startsWith('http') ? c.url : `${BASE_URL}${c.url}`,
67
+ lang: c.label || 'Unknown'
68
+ }));
69
+
70
+ const allUrls = [];
71
+
72
+ if (streamData.qualities && streamData.qualities.length > 0) {
73
+ for (const q of streamData.qualities) {
74
+ let finalUrl = q.url;
75
+ if (!finalUrl.startsWith('http')) {
76
+ finalUrl = `${BASE_URL}${finalUrl}`;
77
+ }
78
+
79
+ const isDash = finalUrl.includes('.mpd');
80
+ const isHls = finalUrl.includes('.m3u8');
81
+
82
+ allUrls.push({
83
+ url: finalUrl,
84
+ server: `OpStream - ${q.label}`,
85
+ type: isDash ? 'dash' : (isHls ? 'hls' : 'mp4'),
86
+ headers: STREAM_HEADERS,
87
+ subtitles,
88
+ skipProxy: true
89
+ });
90
+ }
91
+ } else if (streamData.url) {
92
+ let finalUrl = streamData.url;
93
+ if (finalUrl.includes('?u=')) {
94
+ try {
95
+ const uParam = new URL(finalUrl, BASE_URL).searchParams.get('u');
96
+ if (uParam) finalUrl = Buffer.from(uParam, 'base64').toString('utf8');
97
+ } catch {}
98
+ } else if (!finalUrl.startsWith('http')) {
99
+ finalUrl = `${BASE_URL}${finalUrl}`;
100
+ }
101
+
102
+ const isDash = streamData.kind === 'dash' || finalUrl.includes('.mpd');
103
+ const isHls = streamData.kind === 'hls' || finalUrl.includes('.m3u8');
104
+
105
+ allUrls.push({
106
+ url: finalUrl,
107
+ server: 'OpStream',
108
+ type: isDash ? 'dash' : (isHls ? 'hls' : 'mp4'),
109
+ headers: STREAM_HEADERS,
110
+ subtitles,
111
+ skipProxy: true
112
+ });
113
+ }
114
+
115
+ if (allUrls.length === 0) return null;
116
+
117
+ return { allUrls };
118
+ } catch { return null; }
119
+ }
120
+
121
+ export async function getSources() {
122
+ return ['OpStream'];
123
+ }
@@ -0,0 +1,78 @@
1
+ import { fetchJson, USER_AGENT } from '../utils/helpers.js';
2
+
3
+ const DEC_API = "https://enc-dec.app/api/dec-peachify";
4
+ const ORIGIN = "https://peachify.top";
5
+ const HEADERS = { "User-Agent": USER_AGENT, "Origin": ORIGIN, "Referer": `${ORIGIN}/` };
6
+
7
+ const SERVERS = [
8
+ { label: "Wolf", path: "air", api: "https://usa.eat-peach.sbs" },
9
+ { label: "Spider", path: "holly", api: "https://usa.eat-peach.sbs" },
10
+ { label: "Iron", path: "moviebox", api: "https://uwu.eat-peach.sbs" },
11
+ { label: "Multi", path: "multi", api: "https://usa.eat-peach.sbs" },
12
+ { label: "Dark", path: "net", api: "https://uwu.eat-peach.sbs" },
13
+ ];
14
+
15
+ export async function getStream({ id, s, e, server }) {
16
+ try {
17
+ const isTv = s != null && e != null;
18
+ const segment = isTv ? `tv/${id}/${s}/${e}` : `movie/${id}`;
19
+
20
+ let targets = SERVERS;
21
+ if (server && server !== 'all') {
22
+ const cleanName = server.replace('Peachify - ', '');
23
+ targets = SERVERS.filter(srv => srv.label === cleanName);
24
+ if (!targets.length) targets = SERVERS;
25
+ }
26
+
27
+ const settled = await Promise.allSettled(targets.map(async srv => {
28
+ const url = `${srv.api}/${srv.path}/${segment}`;
29
+ const rawData = await fetchJson(url, { headers: HEADERS, signal: AbortSignal.timeout(10000) });
30
+
31
+ if (!rawData?.data) throw new Error();
32
+
33
+ const decJson = await fetchJson(DEC_API, {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify({ text: rawData.data }),
37
+ signal: AbortSignal.timeout(10000)
38
+ });
39
+
40
+ if (decJson.status !== 200 || !decJson.result) throw new Error();
41
+ const res = decJson.result;
42
+
43
+ const sources = Array.isArray(res) ? res : (res.sources || [res]);
44
+ const subs = res.subtitles || [];
45
+
46
+ return sources.map(source => {
47
+ const streamUrl = source.url || source.file || source.link;
48
+ if (!streamUrl) return null;
49
+
50
+ return {
51
+ url: streamUrl,
52
+ server: `Peachify - ${srv.label}`,
53
+ quality: source.quality || "Auto",
54
+ type: streamUrl.includes('.m3u8') ? 'hls' : 'mp4',
55
+ headers: { ...HEADERS, "Origin": ORIGIN },
56
+ subtitles: subs.map(sub => ({
57
+ url: sub.url || sub.file,
58
+ lang: sub.language || sub.lang || sub.label || 'Unknown'
59
+ })),
60
+ skipProxy: false,
61
+ skipVerify: true,
62
+ skipHlsCheck: true
63
+ };
64
+ }).filter(Boolean);
65
+ }));
66
+
67
+ const allUrls = [];
68
+ for (const r of settled) {
69
+ if (r.status === 'fulfilled' && r.value) allUrls.push(...r.value);
70
+ }
71
+
72
+ return allUrls.length ? { allUrls } : null;
73
+ } catch { return null; }
74
+ }
75
+
76
+ export async function getSources() {
77
+ return SERVERS.map(s => `Peachify - ${s.label}`);
78
+ }
@@ -0,0 +1,45 @@
1
+ import { fetchJson } from '../utils/helpers.js';
2
+
3
+ const PENGU_CONFIG = '%7B%22source_4khdhub%22%3A%22on%22%2C%22source_moviebox%22%3A%22on%22%2C%22source_moviesdrives%22%3A%22on%22%2C%22source_vaplayer%22%3A%22on%22%2C%22source_hdghartv%22%3A%22on%22%2C%22res_2160%22%3A%22on%22%2C%22res_1080%22%3A%22on%22%2C%22res_720%22%3A%22on%22%2C%22disable_direct%22%3A%22on%22%7D';
4
+ const BASE_URL = `https://pengu.uk/${PENGU_CONFIG}`;
5
+
6
+ async function getImdbId(tmdbApiKey, tmdbId, s, e) {
7
+ const key = tmdbApiKey;
8
+ if (!key) return null;
9
+ try {
10
+ const url = s != null && e != null
11
+ ? `https://api.themoviedb.org/3/tv/${tmdbId}/external_ids?api_key=${key}`
12
+ : `https://api.themoviedb.org/3/movie/${tmdbId}?api_key=${key}`;
13
+ const res = await fetchJson(url);
14
+ return res?.imdb_id || null;
15
+ } catch { return null; }
16
+ }
17
+
18
+ export async function getStream({ id, s, e, tmdbApiKey }) {
19
+ try {
20
+ const imdbId = await getImdbId(tmdbApiKey, id, s, e);
21
+ if (!imdbId) return null;
22
+
23
+ const path = s != null && e != null
24
+ ? `/stream/series/${imdbId}:${s}:${e}.json`
25
+ : `/stream/movie/${imdbId}.json`;
26
+
27
+ const res = await fetchJson(`${BASE_URL}${path}`);
28
+ if (!res?.streams?.length) return null;
29
+
30
+ const allUrls = res.streams.map(stream => {
31
+ if (!stream.url) return null;
32
+ const headers = stream.behaviorHints?.proxyHeaders?.request || {};
33
+ return {
34
+ url: stream.url,
35
+ server: `Pengu - ${stream.name || 'Server'}`,
36
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
37
+ skipProxy: stream.behaviorHints?.notWebReady === false
38
+ };
39
+ }).filter(Boolean);
40
+
41
+ return allUrls.length ? { allUrls } : null;
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
@@ -0,0 +1,104 @@
1
+ import { fetchJson, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
2
+
3
+ const DOMAIN = 'https://purstream.club';
4
+ const API_BASE = 'https://api.purstream.club/api/v1';
5
+
6
+ const HEADERS = {
7
+ 'User-Agent': USER_AGENT,
8
+ 'Accept': 'application/json, text/plain, */*',
9
+ 'Referer': `${DOMAIN}/`,
10
+ 'Origin': DOMAIN,
11
+ 'X-Requested-With': 'XMLHttpRequest',
12
+ 'Sec-Fetch-Dest': 'empty',
13
+ 'Sec-Fetch-Mode': 'cors',
14
+ 'Sec-Fetch-Site': 'same-site'
15
+ };
16
+
17
+ export async function getStream({ id, s, e }) {
18
+ try {
19
+ const isTv = s != null;
20
+
21
+ const info = await getTmdbInfo(id, isTv ? 'tv' : 'movie');
22
+
23
+
24
+ if (!info?.titles?.length) {
25
+ return null;
26
+ }
27
+
28
+ const title = info.titles[0];
29
+
30
+ const searchUrl = `${API_BASE}/search-bar/search/${encodeURIComponent(title)}`;
31
+
32
+ const searchData = await fetchJson(searchUrl, {
33
+ headers: HEADERS,
34
+ signal: AbortSignal.timeout(10000)
35
+ });
36
+
37
+ const items = searchData?.data?.items?.movies?.items || [];
38
+
39
+ const type = isTv ? 'tv' : 'movie';
40
+ const lowerTitle = title.toLowerCase();
41
+
42
+ let match = items.find(item =>
43
+ item.type === type &&
44
+ item.title?.toLowerCase() === lowerTitle &&
45
+ (!info.year || item.release_date?.startsWith(String(info.year)))
46
+ );
47
+
48
+ if (!match) {
49
+ match = items.find(item => item.type === type);
50
+ }
51
+
52
+ if (!match?.id) {
53
+ return null;
54
+ }
55
+
56
+ const streamUrl = isTv
57
+ ? `${API_BASE}/stream/${match.id}/episode?season=${s}&episode=${e}`
58
+ : `${API_BASE}/stream/${match.id}`;
59
+
60
+ const json = await fetchJson(streamUrl, {
61
+ headers: HEADERS,
62
+ signal: AbortSignal.timeout(10000)
63
+ });
64
+
65
+ const sources = json?.data?.items?.sources;
66
+
67
+ if (json?.type !== 'success' || !Array.isArray(sources) || !sources.length) {
68
+ return null;
69
+ }
70
+
71
+ const chosen = sources.find(src => src.stream_url);
72
+
73
+ if (!chosen?.stream_url) {
74
+ return null;
75
+ }
76
+
77
+ const result = {
78
+ url: chosen.stream_url,
79
+ headers: {
80
+ 'User-Agent': USER_AGENT,
81
+ 'Referer': `${DOMAIN}/`
82
+ },
83
+ server: 'purstream',
84
+ quality: chosen.source_name || 'Auto',
85
+ type: chosen.format || 'hls',
86
+ skipProxy: true
87
+ };
88
+
89
+ return result;
90
+
91
+ } catch (err) {
92
+ return null;
93
+ }
94
+ }
95
+
96
+ export async function getSources(args) {
97
+ const stream = await getStream(args);
98
+
99
+ return stream ? [stream] : [];
100
+ }
101
+
102
+ export const VERIFY_HEADERS = {
103
+ ...HEADERS
104
+ };