@vyla-entertainment/sdk 1.0.0 → 1.2.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/.env.example +1 -0
- package/.github/workflows/publish.yml +38 -0
- package/.gitlab-ci.yml +22 -0
- package/LICENSE +0 -0
- package/README.md +13 -26
- package/package.json +5 -3
- package/src/config.js +63 -77
- package/src/downloads/downloads.js +8 -4
- package/src/downloads/sources/trendimovies.js +0 -0
- package/src/health.js +0 -0
- package/src/sdk.d.ts +123 -0
- package/src/sdk.js +0 -0
- package/src/sources/123anime.js +0 -0
- package/src/sources/anihq.js +14 -2
- package/src/sources/anineko.js +122 -15
- package/src/sources/anipm.js +0 -0
- package/src/sources/cinejoy.js +210 -0
- package/src/sources/flaxmovies.js +114 -10
- package/src/sources/frame.js +88 -0
- package/src/sources/fsharetv.js +0 -0
- package/src/sources/fsonic.js +2 -2
- package/src/sources/fsonline.js +2 -2
- package/src/sources/hexa.js +0 -0
- package/src/sources/kisskh.js +0 -0
- package/src/sources/lmscript.js +43 -0
- package/src/sources/lookmovie.js +0 -0
- package/src/sources/mapple.js +40 -4
- package/src/sources/meowtv.js +1 -9
- package/src/sources/peestream.js +122 -0
- package/src/sources/pengu.js +0 -0
- package/src/sources/purstream.js +0 -0
- package/src/sources/vidapi.js +0 -0
- package/src/sources/vidcore.js +0 -0
- package/src/sources/videasy.js +6 -12
- package/src/sources/vidfast.js +0 -0
- package/src/sources/vidlink.js +0 -0
- package/src/sources/vidnest.js +0 -0
- package/src/sources/vidrift.js +0 -0
- package/src/sources/vidrock.js +0 -0
- package/src/sources/vidsync.js +0 -0
- package/src/sources/vidup.js +0 -0
- package/src/sources/vidvault.js +114 -0
- package/src/sources/vidzee.js +73 -87
- package/src/sources/vixsrc.js +28 -14
- package/src/sources/xpass.js +0 -0
- package/src/subtitles.js +0 -0
- package/src/utils/helpers.js +56 -20
- package/src/sources/1embed.js +0 -92
- package/src/sources/goated.js +0 -262
- package/src/sources/nebula.js +0 -62
- package/src/sources/nekowatch.js +0 -57
- package/src/sources/opstream.js +0 -123
- package/src/sources/peachify.js +0 -78
package/src/sources/anineko.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getTmdbInfo } from '../utils/helpers.js';
|
|
1
|
+
import { getTmdbInfo, USER_AGENT, fetchText } from '../utils/helpers.js';
|
|
2
2
|
|
|
3
3
|
const BASE_URL = "https://anineko.to";
|
|
4
4
|
|
|
@@ -10,7 +10,10 @@ function decodeEntities(str) {
|
|
|
10
10
|
|
|
11
11
|
async function search(query) {
|
|
12
12
|
try {
|
|
13
|
-
const html = await fetchText(`${BASE_URL}/browser?keyword=${encodeURIComponent(query)}`, {
|
|
13
|
+
const html = await fetchText(`${BASE_URL}/browser?keyword=${encodeURIComponent(query)}`, {
|
|
14
|
+
headers: { 'User-Agent': USER_AGENT },
|
|
15
|
+
signal: AbortSignal.timeout(6000)
|
|
16
|
+
});
|
|
14
17
|
const results = [];
|
|
15
18
|
let idx = 0;
|
|
16
19
|
while ((idx = html.indexOf('nv-anime-thumb', idx)) !== -1) {
|
|
@@ -27,40 +30,144 @@ async function search(query) {
|
|
|
27
30
|
}
|
|
28
31
|
idx += 14;
|
|
29
32
|
}
|
|
33
|
+
if (results.length === 0) {
|
|
34
|
+
const linkMatches = [...html.matchAll(/href=["']\/watch\/([^"']+)["']/gi)];
|
|
35
|
+
for (const match of linkMatches) {
|
|
36
|
+
if (!results.find(r => r.slug === match[1])) {
|
|
37
|
+
const linkIdx = html.indexOf(match[0]);
|
|
38
|
+
const titleStart = html.lastIndexOf('>', linkIdx);
|
|
39
|
+
const titleEnd = html.indexOf('<', linkIdx);
|
|
40
|
+
if (titleStart !== -1 && titleEnd !== -1 && titleEnd > linkIdx) {
|
|
41
|
+
const title = html.slice(titleStart + 1, Math.min(titleEnd, linkIdx + 200)).trim();
|
|
42
|
+
if (title.length > 2 && title.length < 100) {
|
|
43
|
+
results.push({ slug: match[1], text: title });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
30
49
|
return results;
|
|
31
50
|
} catch { return []; }
|
|
32
51
|
}
|
|
33
52
|
|
|
34
53
|
async function extractHls(embedUrl) {
|
|
35
54
|
try {
|
|
36
|
-
const html = await fetchText(embedUrl, {
|
|
37
|
-
|
|
55
|
+
const html = await fetchText(embedUrl, {
|
|
56
|
+
headers: {
|
|
57
|
+
'User-Agent': USER_AGENT,
|
|
58
|
+
Referer: `${BASE_URL}/`
|
|
59
|
+
},
|
|
60
|
+
signal: AbortSignal.timeout(6000)
|
|
61
|
+
});
|
|
62
|
+
const m = html.match(/const\s+src\s*=\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) ||
|
|
63
|
+
html.match(/file\s*:\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) ||
|
|
64
|
+
html.match(/["'](https?:\/\/[^"']+\/master\.m3u8[^"']*)["']/i) ||
|
|
65
|
+
html.match(/["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) ||
|
|
66
|
+
html.match(/src:\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i);
|
|
38
67
|
if (m) return decodeEntities(m[1]);
|
|
39
68
|
} catch { }
|
|
40
69
|
return null;
|
|
41
70
|
}
|
|
42
71
|
|
|
43
|
-
export async function getStream({ id, s, e, audio }) {
|
|
72
|
+
export async function getStream({ id, s, e, audio, tmdbApiKey }) {
|
|
44
73
|
try {
|
|
45
|
-
const info = await getTmdbInfo(id, s ? 'tv' : 'movie', s);
|
|
74
|
+
const info = await getTmdbInfo(tmdbApiKey, id, s ? 'tv' : 'movie', s);
|
|
46
75
|
if (!info || !info.isAnime) return null;
|
|
47
76
|
let seriesSlug = null;
|
|
48
77
|
for (const title of info.titles) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
78
|
+
const potentialSlug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
79
|
+
if (!potentialSlug) continue;
|
|
80
|
+
try {
|
|
81
|
+
const checkHtml = await fetchText(`${BASE_URL}/watch/${potentialSlug}/ep-${e || 1}`, {
|
|
82
|
+
headers: { 'User-Agent': USER_AGENT, Referer: `${BASE_URL}/watch/${potentialSlug}` },
|
|
83
|
+
signal: AbortSignal.timeout(4000)
|
|
84
|
+
});
|
|
85
|
+
if (checkHtml && checkHtml.includes('nv-watch-page')) {
|
|
86
|
+
seriesSlug = potentialSlug;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
} catch {}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!seriesSlug) {
|
|
93
|
+
for (const title of info.titles) {
|
|
94
|
+
const results = await search(title);
|
|
95
|
+
const targetClean = cleanTitle(title);
|
|
96
|
+
|
|
97
|
+
const exactSlugMatch = results.find(r => {
|
|
98
|
+
const slugClean = cleanTitle(r.slug);
|
|
99
|
+
return slugClean === targetClean || r.slug === targetClean;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (exactSlugMatch) {
|
|
103
|
+
seriesSlug = exactSlugMatch.slug;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const sortedResults = [...results].sort((a, b) => a.slug.length - b.slug.length);
|
|
108
|
+
|
|
109
|
+
for (const r of sortedResults) {
|
|
110
|
+
const resultClean = cleanTitle(r.text);
|
|
111
|
+
const isExactMatch = resultClean === targetClean;
|
|
112
|
+
const isCloseMatch = r.text.toLowerCase().includes(title.toLowerCase());
|
|
113
|
+
if (isExactMatch || isCloseMatch) {
|
|
114
|
+
seriesSlug = r.slug;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (seriesSlug) break;
|
|
119
|
+
}
|
|
53
120
|
}
|
|
54
121
|
if (!seriesSlug && info.titles.length) {
|
|
55
122
|
const fSearch = await search(info.titles[0]);
|
|
56
|
-
|
|
123
|
+
const sortedResults = [...fSearch].sort((a, b) => a.slug.length - b.slug.length);
|
|
124
|
+
if (sortedResults.length) seriesSlug = sortedResults[0].slug;
|
|
57
125
|
}
|
|
58
126
|
if (!seriesSlug) return null;
|
|
59
|
-
const watchHtml = await fetchText(`${BASE_URL}/watch/${seriesSlug}/ep-${e || 1}`, {
|
|
127
|
+
const watchHtml = await fetchText(`${BASE_URL}/watch/${seriesSlug}/ep-${e || 1}`, {
|
|
128
|
+
headers: {
|
|
129
|
+
'User-Agent': USER_AGENT,
|
|
130
|
+
Referer: `${BASE_URL}/watch/${seriesSlug}`
|
|
131
|
+
},
|
|
132
|
+
signal: AbortSignal.timeout(6000)
|
|
133
|
+
});
|
|
60
134
|
const byAudio = { sub: [], dub: [] };
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
135
|
+
|
|
136
|
+
const iframeMatch = watchHtml.match(/<iframe[^>]*src=["']([^"']+)["'][^>]*>/i);
|
|
137
|
+
if (iframeMatch) {
|
|
138
|
+
const iframeSrc = decodeEntities(iframeMatch[1]);
|
|
139
|
+
const hls = await extractHls(iframeSrc);
|
|
140
|
+
if (hls) {
|
|
141
|
+
const audioType = audio === 'dub' ? 'dub' : 'sub';
|
|
142
|
+
byAudio[audioType].push(iframeSrc);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (byAudio.sub.length === 0 && byAudio.dub.length === 0) {
|
|
147
|
+
const tabMatches = [...watchHtml.matchAll(/<button[^>]*class=["'][^"']*nv-server-tab[^"']*["'][^>]*data-id=["']([^"']+)["'][^>]*>/gi)];
|
|
148
|
+
const tabMap = {};
|
|
149
|
+
for (const match of tabMatches) {
|
|
150
|
+
const tabId = match[1].toLowerCase();
|
|
151
|
+
const tabClassMatch = match[0].match(/class=["'][^"']*(tab_\d+)[^"']/i);
|
|
152
|
+
if (tabClassMatch) {
|
|
153
|
+
const tabClass = tabClassMatch[1];
|
|
154
|
+
tabMap[tabClass] = tabId.includes('dub') ? 'dub' : 'sub';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const btnRegex = /<button\s+[^>]*class=["'][^"']*server-video[^"']*["'][^>]*>/gi;
|
|
159
|
+
const btnMatches = [...watchHtml.matchAll(btnRegex)];
|
|
160
|
+
for (const match of btnMatches) {
|
|
161
|
+
const btnHtml = match[0];
|
|
162
|
+
const videoMatch = btnHtml.match(/data-video=["']([^"']+)["']/i);
|
|
163
|
+
const tabMatch = btnHtml.match(/data-tab=["']([^"']+)["']/i);
|
|
164
|
+
if (videoMatch && tabMatch) {
|
|
165
|
+
const videoUrl = decodeEntities(videoMatch[1]);
|
|
166
|
+
const tabClass = tabMatch[1];
|
|
167
|
+
const audioType = tabMap[tabClass] || 'sub';
|
|
168
|
+
byAudio[audioType].push(videoUrl);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
64
171
|
}
|
|
65
172
|
const audiosToTry = audio === "all" ? ["sub", "dub"] : (audio === "dub" ? ["dub", "sub"] : ["sub", "dub"]);
|
|
66
173
|
const allUrls = [];
|
package/src/sources/anipm.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
|
|
3
|
+
export const ID = "cinejoy";
|
|
4
|
+
export const DOMAIN = "https://cinejoy.to";
|
|
5
|
+
export const API_DOMAIN = "https://api.shegu.st";
|
|
6
|
+
export const ENC_DEC_API = "https://enc-dec.app/api";
|
|
7
|
+
export const RETURN_ALL_URLS = true;
|
|
8
|
+
|
|
9
|
+
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";
|
|
10
|
+
|
|
11
|
+
function countLeadingZeroBits(buffer) {
|
|
12
|
+
let count = 0;
|
|
13
|
+
for (const value of buffer) {
|
|
14
|
+
if (value === 0) {
|
|
15
|
+
count += 8;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
count += 8 - value.toString(2).length;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
return count;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function solveChallenge(data) {
|
|
25
|
+
const saltStr = `pow2-salt|${data.s}|${data.b}`;
|
|
26
|
+
const salt = crypto.createHash("sha256").update(saltStr).digest();
|
|
27
|
+
const n = data.n;
|
|
28
|
+
const r = data.r;
|
|
29
|
+
const p = data.p;
|
|
30
|
+
const d = data.d;
|
|
31
|
+
const b = data.b;
|
|
32
|
+
const s = data.s;
|
|
33
|
+
|
|
34
|
+
for (let counter = 0; counter < 1000000; counter++) {
|
|
35
|
+
const payload = `pow2|${b}|${s}|${counter}`;
|
|
36
|
+
const result = crypto.scryptSync(payload, salt, 32, {
|
|
37
|
+
N: n,
|
|
38
|
+
r: r,
|
|
39
|
+
p: p,
|
|
40
|
+
maxmem: 128 * n * r * 2
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (countLeadingZeroBits(result) >= d) {
|
|
44
|
+
const solved = { ...data, c: counter };
|
|
45
|
+
return Buffer.from(JSON.stringify(solved)).toString("base64");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
throw new Error("Challenge solve failed");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function getTmdbMetadata(tmdbId, isTv) {
|
|
52
|
+
const apiKey = process.env.TMDB_API_KEY;
|
|
53
|
+
if (!apiKey) return null;
|
|
54
|
+
const type = isTv ? "tv" : "movie";
|
|
55
|
+
try {
|
|
56
|
+
const url = `https://api.themoviedb.org/3/${type}/${tmdbId}?api_key=${apiKey}&append_to_response=external_ids`;
|
|
57
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(10000) });
|
|
58
|
+
if (!res.ok) return null;
|
|
59
|
+
const data = await res.json();
|
|
60
|
+
const title = isTv ? data.name : data.title;
|
|
61
|
+
const year = isTv ? (data.first_air_date ? data.first_air_date.split("-")[0] : "") : (data.release_date ? data.release_date.split("-")[0] : "");
|
|
62
|
+
const imdbId = data.external_ids?.imdb_id || "";
|
|
63
|
+
return { title, year, imdbId };
|
|
64
|
+
} catch (err) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function getStream({ id, s, e, clientIP }) {
|
|
70
|
+
try {
|
|
71
|
+
const isTv = s && e && s !== "null" && e !== "null";
|
|
72
|
+
const meta = await getTmdbMetadata(id, isTv);
|
|
73
|
+
if (!meta) return null;
|
|
74
|
+
|
|
75
|
+
let servers = [];
|
|
76
|
+
try {
|
|
77
|
+
const serverRes = await fetch(`${API_DOMAIN}/servers`, {
|
|
78
|
+
headers: {
|
|
79
|
+
"Origin": DOMAIN,
|
|
80
|
+
"Referer": DOMAIN + "/",
|
|
81
|
+
"User-Agent": UA
|
|
82
|
+
},
|
|
83
|
+
signal: AbortSignal.timeout(15000)
|
|
84
|
+
});
|
|
85
|
+
if (serverRes.ok) {
|
|
86
|
+
const data = await serverRes.json();
|
|
87
|
+
servers = data.servers || [];
|
|
88
|
+
}
|
|
89
|
+
} catch (err) { }
|
|
90
|
+
|
|
91
|
+
const activeServers = servers
|
|
92
|
+
.filter(srv => srv.status === "ok" && srv.name)
|
|
93
|
+
.map(srv => srv.name);
|
|
94
|
+
|
|
95
|
+
if (activeServers.length === 0) return null;
|
|
96
|
+
|
|
97
|
+
const allUrls = [];
|
|
98
|
+
|
|
99
|
+
for (const serverName of activeServers) {
|
|
100
|
+
try {
|
|
101
|
+
const queryParts = [
|
|
102
|
+
`title=${encodeURIComponent(meta.title)}`,
|
|
103
|
+
`type=${isTv ? "series" : "movie"}`,
|
|
104
|
+
`year=${meta.year}`,
|
|
105
|
+
`imdb=${meta.imdbId}`,
|
|
106
|
+
`tmdb=${id}`,
|
|
107
|
+
`server=${serverName}`
|
|
108
|
+
];
|
|
109
|
+
if (isTv) {
|
|
110
|
+
queryParts.push(`season=${s}`, `episode=${e}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const plainUrl = `${API_DOMAIN}/?${queryParts.join("&")}`;
|
|
114
|
+
|
|
115
|
+
const encRes = await fetch(`${ENC_DEC_API}/enc-cinejoy?url=${encodeURIComponent(plainUrl)}`, {
|
|
116
|
+
headers: { "User-Agent": UA },
|
|
117
|
+
signal: AbortSignal.timeout(15000)
|
|
118
|
+
});
|
|
119
|
+
if (!encRes.ok) continue;
|
|
120
|
+
const encData = await encRes.json();
|
|
121
|
+
if (encData.status !== 200) continue;
|
|
122
|
+
|
|
123
|
+
const rid = encData.result;
|
|
124
|
+
|
|
125
|
+
const challengeRes = await fetch(`${API_DOMAIN}/challenge?rid=${rid}`, {
|
|
126
|
+
headers: {
|
|
127
|
+
"Origin": DOMAIN,
|
|
128
|
+
"Referer": DOMAIN + "/",
|
|
129
|
+
"User-Agent": UA
|
|
130
|
+
},
|
|
131
|
+
signal: AbortSignal.timeout(30000)
|
|
132
|
+
});
|
|
133
|
+
if (!challengeRes.ok) continue;
|
|
134
|
+
|
|
135
|
+
const challengeData = await challengeRes.json();
|
|
136
|
+
|
|
137
|
+
const solved = solveChallenge(challengeData);
|
|
138
|
+
|
|
139
|
+
const streamRes = await fetch(`${API_DOMAIN}/${rid}`, {
|
|
140
|
+
headers: {
|
|
141
|
+
"Origin": DOMAIN,
|
|
142
|
+
"Referer": DOMAIN + "/",
|
|
143
|
+
"User-Agent": UA,
|
|
144
|
+
"X-At": solved
|
|
145
|
+
},
|
|
146
|
+
signal: AbortSignal.timeout(30000)
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (!streamRes.ok) continue;
|
|
150
|
+
|
|
151
|
+
const encryptedText = await streamRes.text();
|
|
152
|
+
|
|
153
|
+
const decRes = await fetch(`${ENC_DEC_API}/dec-cinejoy`, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers: {
|
|
156
|
+
"Content-Type": "application/json",
|
|
157
|
+
"User-Agent": UA
|
|
158
|
+
},
|
|
159
|
+
body: JSON.stringify({ text: encryptedText }),
|
|
160
|
+
signal: AbortSignal.timeout(15000)
|
|
161
|
+
});
|
|
162
|
+
if (!decRes.ok) continue;
|
|
163
|
+
const decData = await decRes.json();
|
|
164
|
+
if (decData.status !== 200) continue;
|
|
165
|
+
|
|
166
|
+
let decryptedText = decData.result;
|
|
167
|
+
|
|
168
|
+
if (!decryptedText) continue;
|
|
169
|
+
|
|
170
|
+
if (typeof decryptedText === "string") {
|
|
171
|
+
try {
|
|
172
|
+
decryptedText = JSON.parse(decryptedText);
|
|
173
|
+
} catch (e) { }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let streamUrl = "";
|
|
177
|
+
if (decryptedText && typeof decryptedText === "object") {
|
|
178
|
+
if (decryptedText.stream && decryptedText.stream[0]) {
|
|
179
|
+
streamUrl = decryptedText.stream[0].playlist || decryptedText.stream[0].url || "";
|
|
180
|
+
} else {
|
|
181
|
+
streamUrl = decryptedText.url || decryptedText.playlist || decryptedText.file || "";
|
|
182
|
+
}
|
|
183
|
+
} else if (typeof decryptedText === "string") {
|
|
184
|
+
streamUrl = decryptedText.trim();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (streamUrl.startsWith("http")) {
|
|
188
|
+
allUrls.push({
|
|
189
|
+
url: streamUrl,
|
|
190
|
+
headers: {
|
|
191
|
+
"User-Agent": UA,
|
|
192
|
+
"Referer": DOMAIN + "/",
|
|
193
|
+
"Origin": DOMAIN
|
|
194
|
+
},
|
|
195
|
+
server: serverName,
|
|
196
|
+
quality: "Auto",
|
|
197
|
+
type: "hls",
|
|
198
|
+
skipVerify: true,
|
|
199
|
+
skipProxy: true
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
} catch (err) { }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (allUrls.length > 0) {
|
|
206
|
+
return { ...allUrls[0], allUrls };
|
|
207
|
+
}
|
|
208
|
+
} catch (err) { }
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
@@ -1,14 +1,118 @@
|
|
|
1
|
-
import { fetchJson } from '../utils/helpers.js';
|
|
1
|
+
import { USER_AGENT, fetchJson } from '../utils/helpers.js';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const APIKEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Iml0amlvY3VuYWhja3F4Y256cG95Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzczMzM1MjQsImV4cCI6MjA5MjkwOTUyNH0.9x9ykdHAzrv_GSvnawPeQaOxQeh3sZg0QAh4u9VOF4M';
|
|
5
|
-
const HEADERS = { 'apikey': APIKEY, 'authorization': `Bearer ${APIKEY}`, 'content-type': 'application/json', 'origin': 'https://flaxmovies.xyz', 'referer': 'https://flaxmovies.xyz/' };
|
|
3
|
+
const BASE_URL = 'https://flaxmovies.xyz';
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
async function getWorkerUrls(embedUrl) {
|
|
6
|
+
const foundUrls = [];
|
|
8
7
|
try {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
const res = await fetch(embedUrl, {
|
|
9
|
+
headers: {
|
|
10
|
+
'User-Agent': USER_AGENT,
|
|
11
|
+
'Referer': `${BASE_URL}/`,
|
|
12
|
+
'Origin': BASE_URL
|
|
13
|
+
},
|
|
14
|
+
signal: AbortSignal.timeout(8000)
|
|
15
|
+
});
|
|
16
|
+
if (res.ok) {
|
|
17
|
+
const html = await res.text();
|
|
18
|
+
const directMatch = html.match(/WORKER_URL\s*[:=]\s*["'](https?:\/\/[^"']+)["']/i);
|
|
19
|
+
if (directMatch && directMatch[1]) {
|
|
20
|
+
foundUrls.push(directMatch[1].replace(/\/+$/, ''));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const scriptMatches = [...html.matchAll(/<script[^>]+src=["']([^"']+)["']/gi)];
|
|
24
|
+
for (const sm of scriptMatches) {
|
|
25
|
+
let scriptUrl = sm[1];
|
|
26
|
+
if (scriptUrl.startsWith('//')) scriptUrl = `https:${scriptUrl}`;
|
|
27
|
+
else if (scriptUrl.startsWith('/')) scriptUrl = `${BASE_URL}${scriptUrl}`;
|
|
28
|
+
else if (!scriptUrl.startsWith('http')) scriptUrl = `${BASE_URL}/${scriptUrl}`;
|
|
29
|
+
|
|
30
|
+
if (scriptUrl.includes('jsdelivr') || scriptUrl.includes('google') || scriptUrl.includes('cloudflare')) continue;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const sRes = await fetch(scriptUrl, {
|
|
34
|
+
headers: { 'User-Agent': USER_AGENT, 'Referer': embedUrl },
|
|
35
|
+
signal: AbortSignal.timeout(6000)
|
|
36
|
+
});
|
|
37
|
+
if (sRes.ok) {
|
|
38
|
+
const sCode = await sRes.text();
|
|
39
|
+
const m = sCode.match(/WORKER_URL\s*[:=]\s*["'](https?:\/\/[^"']+)["']/i);
|
|
40
|
+
if (m && m[1]) {
|
|
41
|
+
const wUrl = m[1].replace(/\/+$/, '');
|
|
42
|
+
if (!foundUrls.includes(wUrl)) foundUrls.push(wUrl);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch { }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch { }
|
|
49
|
+
|
|
50
|
+
const defaults = ['https://freakyniki.elaxo.lol', 'https://vidlove.nabilekson.workers.dev'];
|
|
51
|
+
for (const d of defaults) {
|
|
52
|
+
if (!foundUrls.includes(d)) foundUrls.push(d);
|
|
53
|
+
}
|
|
54
|
+
return foundUrls;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function getStream(args) {
|
|
58
|
+
const { id, s, e, server } = args;
|
|
59
|
+
const isTv = s != null && e != null;
|
|
60
|
+
const embedUrl = isTv
|
|
61
|
+
? `${BASE_URL}/embed/tv/${id}/${s}/${e}`
|
|
62
|
+
: `${BASE_URL}/embed/movie/${id}`;
|
|
63
|
+
|
|
64
|
+
const workerUrls = await getWorkerUrls(embedUrl);
|
|
65
|
+
const query = isTv
|
|
66
|
+
? `tmdb_id=${id}&tmdbId=${id}&season=${s}&episode=${e}`
|
|
67
|
+
: `tmdb_id=${id}&tmdbId=${id}`;
|
|
68
|
+
|
|
69
|
+
for (const worker of workerUrls) {
|
|
70
|
+
try {
|
|
71
|
+
const url = `${worker}/?${query}`;
|
|
72
|
+
const data = await fetchJson(url, {
|
|
73
|
+
headers: {
|
|
74
|
+
'User-Agent': USER_AGENT,
|
|
75
|
+
'Referer': `${BASE_URL}/`,
|
|
76
|
+
'Origin': BASE_URL,
|
|
77
|
+
'Accept': 'application/json, text/plain, */*'
|
|
78
|
+
},
|
|
79
|
+
signal: AbortSignal.timeout(12000)
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (data && Array.isArray(data.streams) && data.streams.length > 0) {
|
|
83
|
+
let allUrls = data.streams
|
|
84
|
+
.filter(st => st && typeof st.url === 'string')
|
|
85
|
+
.map(st => {
|
|
86
|
+
const isHls = (st.format || '').toLowerCase().includes('hls') || st.url.includes('.m3u8');
|
|
87
|
+
return {
|
|
88
|
+
url: st.url,
|
|
89
|
+
server: `FlaxMovies - ${st.provider || 'Default'}`,
|
|
90
|
+
quality: st.resolution || 'Auto',
|
|
91
|
+
type: isHls ? 'hls' : 'mp4',
|
|
92
|
+
headers: {
|
|
93
|
+
'User-Agent': USER_AGENT,
|
|
94
|
+
'Referer': `${BASE_URL}/`,
|
|
95
|
+
'Origin': BASE_URL
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (server && server !== 'all') {
|
|
101
|
+
const clean = server.toLowerCase().replace('flaxmovies - ', '');
|
|
102
|
+
const filtered = allUrls.filter(u => u.server.toLowerCase().includes(clean));
|
|
103
|
+
if (filtered.length) allUrls = filtered;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (allUrls.length) {
|
|
107
|
+
return { allUrls };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch { }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function getSources() {
|
|
117
|
+
return ['FlaxMovies - Airflix', 'FlaxMovies - Xpass-VIP 1'];
|
|
14
118
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { USER_AGENT, fetchJson } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = 'https://api.peestream.in';
|
|
4
|
+
|
|
5
|
+
const PROVIDERS = [
|
|
6
|
+
{ id: 'vaplayer', name: 'FRAME Zephyr' },
|
|
7
|
+
{ id: 'castle', name: 'FRAME Atlas' },
|
|
8
|
+
{ id: 'hera', name: 'FRAME Luna' },
|
|
9
|
+
{ id: 'multivid', name: 'FRAME Volt' },
|
|
10
|
+
{ id: 'netmirror', name: 'FRAME Echo' },
|
|
11
|
+
{ id: 'vidsuper-castle', name: 'FRAME Rift' },
|
|
12
|
+
{ id: 'vidsuper-vixsrc', name: 'FRAME Quill' }
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
async function fetchProviderStreams(provider, type, id, s, e) {
|
|
16
|
+
try {
|
|
17
|
+
const queryParams = new URLSearchParams({
|
|
18
|
+
q: String(id),
|
|
19
|
+
type,
|
|
20
|
+
provider: provider.id
|
|
21
|
+
});
|
|
22
|
+
if (type === 'tv') {
|
|
23
|
+
queryParams.set('season', String(s));
|
|
24
|
+
queryParams.set('episode', String(e));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const url = `${BASE_URL}/api/search?${queryParams.toString()}`;
|
|
28
|
+
const data = await fetchJson(url, {
|
|
29
|
+
headers: {
|
|
30
|
+
'User-Agent': USER_AGENT,
|
|
31
|
+
'Accept': 'application/json',
|
|
32
|
+
'Referer': 'https://peestream.in/'
|
|
33
|
+
},
|
|
34
|
+
signal: AbortSignal.timeout(12000)
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!data || !Array.isArray(data.results) || !data.results.length) return [];
|
|
38
|
+
|
|
39
|
+
const streams = [];
|
|
40
|
+
for (const result of data.results) {
|
|
41
|
+
for (const stream of result.streams || []) {
|
|
42
|
+
if (!stream || !stream.url) continue;
|
|
43
|
+
streams.push({
|
|
44
|
+
url: stream.url,
|
|
45
|
+
server: provider.name,
|
|
46
|
+
quality: stream.quality || 'Auto',
|
|
47
|
+
type: stream.type === 'm3u8' || stream.url.includes('.m3u8') ? 'hls' : 'mp4',
|
|
48
|
+
headers: stream.headers || {
|
|
49
|
+
'User-Agent': USER_AGENT
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return streams;
|
|
55
|
+
} catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function getStream(args) {
|
|
61
|
+
const { id, s, e, server } = args;
|
|
62
|
+
const isTv = s != null && e != null;
|
|
63
|
+
const type = isTv ? 'tv' : 'movie';
|
|
64
|
+
|
|
65
|
+
let targets = PROVIDERS;
|
|
66
|
+
if (server && server !== 'all') {
|
|
67
|
+
const clean = server.toLowerCase().replace('frame ', '').trim();
|
|
68
|
+
targets = PROVIDERS.filter(p => p.id.toLowerCase() === clean || p.name.toLowerCase().includes(clean));
|
|
69
|
+
if (!targets.length) targets = PROVIDERS;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const settled = await Promise.allSettled(
|
|
73
|
+
targets.map(p => fetchProviderStreams(p, type, id, s, e))
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const allUrls = [];
|
|
77
|
+
for (const r of settled) {
|
|
78
|
+
if (r.status === 'fulfilled' && Array.isArray(r.value)) {
|
|
79
|
+
allUrls.push(...r.value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return allUrls.length ? { allUrls } : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function getSources() {
|
|
87
|
+
return PROVIDERS.map(p => p.name);
|
|
88
|
+
}
|
package/src/sources/fsharetv.js
CHANGED
|
File without changes
|
package/src/sources/fsonic.js
CHANGED
|
@@ -4,10 +4,10 @@ const BASE_URL = 'https://www.fsonic.net';
|
|
|
4
4
|
const FSHARE_BASE = 'https://fsharetv.co';
|
|
5
5
|
const HEADERS = { 'User-Agent': USER_AGENT, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.9' };
|
|
6
6
|
|
|
7
|
-
export async function getStream({ id, s }) {
|
|
7
|
+
export async function getStream({ id, s, tmdbApiKey }) {
|
|
8
8
|
if (s != null) return null;
|
|
9
9
|
try {
|
|
10
|
-
const info = await getTmdbInfo(id, 'movie');
|
|
10
|
+
const info = await getTmdbInfo(tmdbApiKey, id, 'movie');
|
|
11
11
|
if (!info?.titles?.length) return null;
|
|
12
12
|
let watchSlug = null;
|
|
13
13
|
for (const title of info.titles) {
|
package/src/sources/fsonline.js
CHANGED
|
@@ -4,10 +4,10 @@ const ORIGIN = 'https://www3.fsonline.app';
|
|
|
4
4
|
const AJAX_URL = `${ORIGIN}/wp-admin/admin-ajax.php`;
|
|
5
5
|
const HEADERS = { 'User-Agent': USER_AGENT, 'Origin': ORIGIN, 'Referer': `${ORIGIN}/` };
|
|
6
6
|
|
|
7
|
-
export async function getStream({ id, s, e }) {
|
|
7
|
+
export async function getStream({ id, s, e, tmdbApiKey }) {
|
|
8
8
|
try {
|
|
9
9
|
const isTv = s != null && e != null;
|
|
10
|
-
const info = await getTmdbInfo(id, isTv ? 'tv' : 'movie');
|
|
10
|
+
const info = await getTmdbInfo(tmdbApiKey, id, isTv ? 'tv' : 'movie');
|
|
11
11
|
if (!info?.titles?.length) return null;
|
|
12
12
|
const searchHtml = await fetchText(`${ORIGIN}/?s=${encodeURIComponent(info.titles[0] + (info.year ? ' ' + info.year : ''))}`, { headers: HEADERS });
|
|
13
13
|
const typeFolder = isTv ? 'seriale' : 'film';
|
package/src/sources/hexa.js
CHANGED
|
File without changes
|
package/src/sources/kisskh.js
CHANGED
|
File without changes
|