mirror of
https://gitlab.com/vyla-entertainment/sdk.git
synced 2026-11-09 19:57:23 -05:00
Added Cinejoy, cleaned up README
This commit is contained in:
1
.env.example
Normal file
1
.env.example
Normal file
@@ -0,0 +1 @@
|
|||||||
|
TMDB_API_KEY=your_tmdb_api_key_here
|
||||||
30
README.md
30
README.md
@@ -11,36 +11,8 @@ npm install @vyla-entertainment/sdk
|
|||||||
pnpm install @vyla-entertainment/sdk
|
pnpm install @vyla-entertainment/sdk
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
View our full documentation at https://docs.vyla.cc/sdk
|
||||||
Start by creating a VylaSDK element with your TMDB api key.
|
|
||||||
```js
|
|
||||||
import VylaSDK from "@vyla-entertainment/sdk";
|
|
||||||
|
|
||||||
const sdk = new VylaSDK({
|
|
||||||
tmdbApiKey: ...
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
You can now use this to access Vyla's content.
|
|
||||||
```js
|
|
||||||
// Get sources
|
|
||||||
// Args: excludeDisabled
|
|
||||||
sdk.getSources(false); -> {key, timeout, label, ...}[]
|
|
||||||
|
|
||||||
// Get subtitles
|
|
||||||
// Args: tmdb id, season?, episode?
|
|
||||||
await sdk.getSubtitles('0000000', null, null); -> {label, file, type, source}[]
|
|
||||||
|
|
||||||
// Probe & test a source
|
|
||||||
// Args: source key
|
|
||||||
await sdk.probeSource('nebula'); -> {ok, ms}
|
|
||||||
|
|
||||||
// Probe & test all sources
|
|
||||||
await sdk.probeAllSources(); -> {'key': {ok, ms}, ...}
|
|
||||||
|
|
||||||
// Get HLS streams
|
|
||||||
// Args: source key, tmdb id, season?, episode?, clientIP?
|
|
||||||
await sdk.getStream('nebula', '0000000', null, null, null) -> {allUrls: {url, quality, label?, server?, type?, skipProxy?, skipVerify?, headers? ...}[]}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# [DMCA](https://docs.vyla.cc/misc/dmca)
|
# [DMCA](https://docs.vyla.cc/misc/dmca)
|
||||||
|
|||||||
@@ -29,4 +29,4 @@
|
|||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
// Currently active sources reported on 7:47:50 PM PST 7/6/2026
|
||||||
|
|
||||||
|
// None Anime Tested: 936075
|
||||||
|
// Anime Tested: 37854
|
||||||
|
|
||||||
|
// Fshare/Fsonic Tested: 155 ( they don't have Michael )
|
||||||
|
|
||||||
export const SOURCES = [
|
export const SOURCES = [
|
||||||
{
|
{
|
||||||
key: '1embed',
|
key: '1embed',
|
||||||
@@ -107,6 +114,18 @@ export const SOURCES = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
key: 'cinejoy',
|
||||||
|
label: 'Cinejoy',
|
||||||
|
sourceFile: 'cinejoy',
|
||||||
|
proxyParam: 'cj',
|
||||||
|
timeout: 30000,
|
||||||
|
jitter: 500,
|
||||||
|
retries: 1,
|
||||||
|
multiUrl: true,
|
||||||
|
skipProxy: true,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
key: 'flaxmovies',
|
key: 'flaxmovies',
|
||||||
label: 'FlaxMovies',
|
label: 'FlaxMovies',
|
||||||
@@ -512,6 +531,7 @@ export const SOURCES = [
|
|||||||
retries: 2,
|
retries: 2,
|
||||||
skipProxy: true,
|
skipProxy: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const HEALTH_PROBE_ID = '155';
|
export const HEALTH_PROBE_ID = '155';
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { getDownloads, getDownloadsTv } from "./sources/trendimovies.js";
|
import { getDownloads, getDownloadsTv } from "./sources/trendimovies.js";
|
||||||
|
|
||||||
export async function fetchDownloads(id, s = null, e = null) {
|
export async function fetchDownloads(id, s = null, e = null) {
|
||||||
const res = s != null && e != null
|
try {
|
||||||
? await getDownloadsTv(id, s, e)
|
const res = s != null && e != null
|
||||||
: await getDownloads(id);
|
? await getDownloadsTv(id, s, e)
|
||||||
return res;
|
: await getDownloads(id);
|
||||||
|
return res || [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
210
src/sources/cinejoy.js
Normal file
210
src/sources/cinejoy.js
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
export const ID = "cinejoy";
|
||||||
|
export const DOMAIN = "https://cinejoy.to";
|
||||||
|
export const API_DOMAIN = "https://api.shegu.st";
|
||||||
|
export const ENC_DEC_API = "https://enc-dec.app/api";
|
||||||
|
export const RETURN_ALL_URLS = true;
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
function countLeadingZeroBits(buffer) {
|
||||||
|
let count = 0;
|
||||||
|
for (const value of buffer) {
|
||||||
|
if (value === 0) {
|
||||||
|
count += 8;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
count += 8 - value.toString(2).length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
function solveChallenge(data) {
|
||||||
|
const saltStr = `pow2-salt|${data.s}|${data.b}`;
|
||||||
|
const salt = crypto.createHash("sha256").update(saltStr).digest();
|
||||||
|
const n = data.n;
|
||||||
|
const r = data.r;
|
||||||
|
const p = data.p;
|
||||||
|
const d = data.d;
|
||||||
|
const b = data.b;
|
||||||
|
const s = data.s;
|
||||||
|
|
||||||
|
for (let counter = 0; counter < 1000000; counter++) {
|
||||||
|
const payload = `pow2|${b}|${s}|${counter}`;
|
||||||
|
const result = crypto.scryptSync(payload, salt, 32, {
|
||||||
|
N: n,
|
||||||
|
r: r,
|
||||||
|
p: p,
|
||||||
|
maxmem: 128 * n * r * 2
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countLeadingZeroBits(result) >= d) {
|
||||||
|
const solved = { ...data, c: counter };
|
||||||
|
return Buffer.from(JSON.stringify(solved)).toString("base64");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("Challenge solve failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTmdbMetadata(tmdbId, isTv) {
|
||||||
|
const apiKey = process.env.TMDB_API_KEY;
|
||||||
|
if (!apiKey) return null;
|
||||||
|
const type = isTv ? "tv" : "movie";
|
||||||
|
try {
|
||||||
|
const url = `https://api.themoviedb.org/3/${type}/${tmdbId}?api_key=${apiKey}&append_to_response=external_ids`;
|
||||||
|
const res = await fetch(url, { signal: AbortSignal.timeout(10000) });
|
||||||
|
if (!res.ok) return null;
|
||||||
|
const data = await res.json();
|
||||||
|
const title = isTv ? data.name : data.title;
|
||||||
|
const year = isTv ? (data.first_air_date ? data.first_air_date.split("-")[0] : "") : (data.release_date ? data.release_date.split("-")[0] : "");
|
||||||
|
const imdbId = data.external_ids?.imdb_id || "";
|
||||||
|
return { title, year, imdbId };
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStream({ id, s, e, clientIP }) {
|
||||||
|
try {
|
||||||
|
const isTv = s && e && s !== "null" && e !== "null";
|
||||||
|
const meta = await getTmdbMetadata(id, isTv);
|
||||||
|
if (!meta) return null;
|
||||||
|
|
||||||
|
let servers = [];
|
||||||
|
try {
|
||||||
|
const serverRes = await fetch(`${API_DOMAIN}/servers`, {
|
||||||
|
headers: {
|
||||||
|
"Origin": DOMAIN,
|
||||||
|
"Referer": DOMAIN + "/",
|
||||||
|
"User-Agent": UA
|
||||||
|
},
|
||||||
|
signal: AbortSignal.timeout(15000)
|
||||||
|
});
|
||||||
|
if (serverRes.ok) {
|
||||||
|
const data = await serverRes.json();
|
||||||
|
servers = data.servers || [];
|
||||||
|
}
|
||||||
|
} catch (err) { }
|
||||||
|
|
||||||
|
const activeServers = servers
|
||||||
|
.filter(srv => srv.status === "ok" && srv.name)
|
||||||
|
.map(srv => srv.name);
|
||||||
|
|
||||||
|
if (activeServers.length === 0) return null;
|
||||||
|
|
||||||
|
const allUrls = [];
|
||||||
|
|
||||||
|
for (const serverName of activeServers) {
|
||||||
|
try {
|
||||||
|
const queryParts = [
|
||||||
|
`title=${encodeURIComponent(meta.title)}`,
|
||||||
|
`type=${isTv ? "series" : "movie"}`,
|
||||||
|
`year=${meta.year}`,
|
||||||
|
`imdb=${meta.imdbId}`,
|
||||||
|
`tmdb=${id}`,
|
||||||
|
`server=${serverName}`
|
||||||
|
];
|
||||||
|
if (isTv) {
|
||||||
|
queryParts.push(`season=${s}`, `episode=${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const plainUrl = `${API_DOMAIN}/?${queryParts.join("&")}`;
|
||||||
|
|
||||||
|
const encRes = await fetch(`${ENC_DEC_API}/enc-cinejoy?url=${encodeURIComponent(plainUrl)}`, {
|
||||||
|
headers: { "User-Agent": UA },
|
||||||
|
signal: AbortSignal.timeout(15000)
|
||||||
|
});
|
||||||
|
if (!encRes.ok) continue;
|
||||||
|
const encData = await encRes.json();
|
||||||
|
if (encData.status !== 200) continue;
|
||||||
|
|
||||||
|
const rid = encData.result;
|
||||||
|
|
||||||
|
const challengeRes = await fetch(`${API_DOMAIN}/challenge?rid=${rid}`, {
|
||||||
|
headers: {
|
||||||
|
"Origin": DOMAIN,
|
||||||
|
"Referer": DOMAIN + "/",
|
||||||
|
"User-Agent": UA
|
||||||
|
},
|
||||||
|
signal: AbortSignal.timeout(30000)
|
||||||
|
});
|
||||||
|
if (!challengeRes.ok) continue;
|
||||||
|
|
||||||
|
const challengeData = await challengeRes.json();
|
||||||
|
|
||||||
|
const solved = solveChallenge(challengeData);
|
||||||
|
|
||||||
|
const streamRes = await fetch(`${API_DOMAIN}/${rid}`, {
|
||||||
|
headers: {
|
||||||
|
"Origin": DOMAIN,
|
||||||
|
"Referer": DOMAIN + "/",
|
||||||
|
"User-Agent": UA,
|
||||||
|
"X-At": solved
|
||||||
|
},
|
||||||
|
signal: AbortSignal.timeout(30000)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!streamRes.ok) continue;
|
||||||
|
|
||||||
|
const encryptedText = await streamRes.text();
|
||||||
|
|
||||||
|
const decRes = await fetch(`${ENC_DEC_API}/dec-cinejoy`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": UA
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ text: encryptedText }),
|
||||||
|
signal: AbortSignal.timeout(15000)
|
||||||
|
});
|
||||||
|
if (!decRes.ok) continue;
|
||||||
|
const decData = await decRes.json();
|
||||||
|
if (decData.status !== 200) continue;
|
||||||
|
|
||||||
|
let decryptedText = decData.result;
|
||||||
|
|
||||||
|
if (!decryptedText) continue;
|
||||||
|
|
||||||
|
if (typeof decryptedText === "string") {
|
||||||
|
try {
|
||||||
|
decryptedText = JSON.parse(decryptedText);
|
||||||
|
} catch (e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
let streamUrl = "";
|
||||||
|
if (decryptedText && typeof decryptedText === "object") {
|
||||||
|
if (decryptedText.stream && decryptedText.stream[0]) {
|
||||||
|
streamUrl = decryptedText.stream[0].playlist || decryptedText.stream[0].url || "";
|
||||||
|
} else {
|
||||||
|
streamUrl = decryptedText.url || decryptedText.playlist || decryptedText.file || "";
|
||||||
|
}
|
||||||
|
} else if (typeof decryptedText === "string") {
|
||||||
|
streamUrl = decryptedText.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (streamUrl.startsWith("http")) {
|
||||||
|
allUrls.push({
|
||||||
|
url: streamUrl,
|
||||||
|
headers: {
|
||||||
|
"User-Agent": UA,
|
||||||
|
"Referer": DOMAIN + "/",
|
||||||
|
"Origin": DOMAIN
|
||||||
|
},
|
||||||
|
server: serverName,
|
||||||
|
quality: "Auto",
|
||||||
|
type: "hls",
|
||||||
|
skipVerify: true,
|
||||||
|
skipProxy: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allUrls.length > 0) {
|
||||||
|
return { ...allUrls[0], allUrls };
|
||||||
|
}
|
||||||
|
} catch (err) { }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -1,54 +1,54 @@
|
|||||||
import { HEALTH_PROBE_ID } from "../src/config.js";
|
// usage: npm test
|
||||||
|
// comment out the tests you don't want to run
|
||||||
|
|
||||||
import VylaSDK from "../src/sdk.js";
|
import VylaSDK from "../src/sdk.js";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const BACKROOMS = '1083381';
|
|
||||||
|
|
||||||
const sdk = new VylaSDK({
|
const sdk = new VylaSDK({
|
||||||
tmdbApiKey: process.env.TMDB_API_KEY
|
tmdbApiKey: process.env.TMDB_API_KEY
|
||||||
});
|
});
|
||||||
|
|
||||||
// 'true' is excludeDisabled
|
try {
|
||||||
const sources = sdk.getSources(true);
|
const movieSubs = await sdk.getSubtitles("155");
|
||||||
const source = sources.find(s => s.key === 'goated');
|
console.log("Movie Subtitles Count:", movieSubs.length);
|
||||||
|
console.log("First Subtitle Sample:", movieSubs[0] || "None");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Movie Subtitles Error:", err.message);
|
||||||
|
}
|
||||||
|
|
||||||
const result = await sdk.getStream(source.key, BACKROOMS);
|
try {
|
||||||
console.log("Result:", result);
|
const movieDownloads = await sdk.getDownloads("155");
|
||||||
|
console.log("Movie Downloads:", movieDownloads);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Movie Downloads Error:", err.message);
|
||||||
|
}
|
||||||
|
|
||||||
// const tests = {
|
try {
|
||||||
// probeAll(log) {
|
const tvDownloads = await sdk.getDownloads("1399", "1", "1");
|
||||||
// const sources = sdk.getSources(true);
|
console.log("TV Downloads:", tvDownloads);
|
||||||
// let index = 0;
|
} catch (err) {
|
||||||
|
console.error("TV Downloads Error:", err.message);
|
||||||
|
}
|
||||||
|
|
||||||
// Promise.all(sources.map(async (source, idx) => {
|
try {
|
||||||
// try {
|
const movieResult = await sdk.getStream("cinejoy", "155");
|
||||||
// const result = await sdk.probeSource(source.key);
|
console.log("Movie Stream:", movieResult);
|
||||||
// index++;
|
} catch (err) {
|
||||||
// log(`(${index}/${sources.length}) source #${idx + 1} (${source.label}):`, result);
|
console.error("Movie Stream Error:", err.message);
|
||||||
// } catch (err) {
|
}
|
||||||
// log(`Error probing source ${source.key}:`, err);
|
|
||||||
// }
|
|
||||||
// })).then(() => {
|
|
||||||
// log("All sources probed.");
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// tryHealthProbe(log) {
|
|
||||||
// const sources = sdk.getSources(true);
|
|
||||||
// let index = 0;
|
|
||||||
|
|
||||||
// Promise.all(sources.map(async (source, idx) => {
|
try {
|
||||||
// try {
|
const tvResult = await sdk.getStream("cinejoy", "1399", "1", "1");
|
||||||
// const result = await sdk.getStream(source.key, HEALTH_PROBE_ID, null, null, null);
|
console.log("TV Stream:", tvResult);
|
||||||
// index++;
|
} catch (err) {
|
||||||
// log(`(${index}/${sources.length}) source #${idx + 1} (${source.label}):`, result?.allUrls?.[0]?.url || result?.url || "no sources :(");
|
console.error("TV Stream Error:", err.message);
|
||||||
// } catch (err) {
|
}
|
||||||
// log(`Error probing source ${source.key}:`, err);
|
|
||||||
// }
|
|
||||||
// })).then(() => {
|
|
||||||
// log("All sources probed.");
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// tests.tryHealthProbe(console.log);
|
try {
|
||||||
|
const tvSubs = await sdk.getSubtitles("1399", "1", "1");
|
||||||
|
console.log("TV Subtitles Count:", tvSubs.length);
|
||||||
|
console.log("First TV Subtitle Sample:", tvSubs[0] || "None");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("TV Subtitles Error:", err.message);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user