@vyla-entertainment/sdk 1.4.0 → 1.6.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/README.md +1 -29
- package/package.json +1 -1
- package/src/config.js +20 -0
- package/src/downloads/downloads.js +8 -4
- package/src/sdk.d.ts +1 -1
- package/src/sources/cinejoy.js +210 -0
package/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
TMDB_API_KEY=your_tmdb_api_key_here
|
package/README.md
CHANGED
|
@@ -11,36 +11,8 @@ npm install @vyla-entertainment/sdk
|
|
|
11
11
|
pnpm install @vyla-entertainment/sdk
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
Start by creating a VylaSDK element with your TMDB api key.
|
|
16
|
-
```js
|
|
17
|
-
import VylaSDK from "@vyla-entertainment/sdk";
|
|
18
|
-
|
|
19
|
-
const sdk = new VylaSDK({
|
|
20
|
-
tmdbApiKey: ...
|
|
21
|
-
});
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
You can now use this to access Vyla's content.
|
|
25
|
-
```js
|
|
26
|
-
// Get sources
|
|
27
|
-
// Args: excludeDisabled
|
|
28
|
-
sdk.getSources(false); -> {key, timeout, label, ...}[]
|
|
29
|
-
|
|
30
|
-
// Get subtitles
|
|
31
|
-
// Args: tmdb id, season?, episode?
|
|
32
|
-
await sdk.getSubtitles('0000000', null, null); -> {label, file, type, source}[]
|
|
33
|
-
|
|
34
|
-
// Probe & test a source
|
|
35
|
-
// Args: source key
|
|
36
|
-
await sdk.probeSource('nebula'); -> {ok, ms}
|
|
37
|
-
|
|
38
|
-
// Probe & test all sources
|
|
39
|
-
await sdk.probeAllSources(); -> {'key': {ok, ms}, ...}
|
|
14
|
+
View our full documentation at https://docs.vyla.cc/sdk
|
|
40
15
|
|
|
41
|
-
// Get HLS streams
|
|
42
|
-
// Args: source key, tmdb id, season?, episode?, clientIP?
|
|
43
|
-
await sdk.getStream('nebula', '0000000', null, null, null) -> {allUrls: {url, quality, label?, server?, type?, skipProxy?, skipVerify?, headers? ...}[]}
|
|
44
16
|
```
|
|
45
17
|
|
|
46
18
|
# [DMCA](https://docs.vyla.cc/misc/dmca)
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
// Currently active sources reported on 7:47:50 PM PST 7/6/2026
|
|
2
|
+
|
|
3
|
+
// None Anime Tested: 936075
|
|
4
|
+
// Anime Tested: 37854
|
|
5
|
+
|
|
6
|
+
// Fshare/Fsonic Tested: 155 ( they don't have Michael )
|
|
7
|
+
|
|
1
8
|
export const SOURCES = [
|
|
2
9
|
{
|
|
3
10
|
key: '1embed',
|
|
@@ -107,6 +114,18 @@ export const SOURCES = [
|
|
|
107
114
|
},
|
|
108
115
|
},
|
|
109
116
|
|
|
117
|
+
{
|
|
118
|
+
key: 'cinejoy',
|
|
119
|
+
label: 'Cinejoy',
|
|
120
|
+
sourceFile: 'cinejoy',
|
|
121
|
+
proxyParam: 'cj',
|
|
122
|
+
timeout: 30000,
|
|
123
|
+
jitter: 500,
|
|
124
|
+
retries: 1,
|
|
125
|
+
multiUrl: true,
|
|
126
|
+
skipProxy: true,
|
|
127
|
+
},
|
|
128
|
+
|
|
110
129
|
{
|
|
111
130
|
key: 'flaxmovies',
|
|
112
131
|
label: 'FlaxMovies',
|
|
@@ -512,6 +531,7 @@ export const SOURCES = [
|
|
|
512
531
|
retries: 2,
|
|
513
532
|
skipProxy: true,
|
|
514
533
|
},
|
|
534
|
+
|
|
515
535
|
];
|
|
516
536
|
|
|
517
537
|
export const HEALTH_PROBE_ID = '155';
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { getDownloads, getDownloadsTv } from "./sources/trendimovies.js";
|
|
2
2
|
|
|
3
3
|
export async function fetchDownloads(id, s = null, e = null) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
try {
|
|
5
|
+
const res = s != null && e != null
|
|
6
|
+
? await getDownloadsTv(id, s, e)
|
|
7
|
+
: await getDownloads(id);
|
|
8
|
+
return res || [];
|
|
9
|
+
} catch {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
8
12
|
}
|
package/src/sdk.d.ts
CHANGED
|
@@ -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
|
+
}
|