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
|
||||
```
|
||||
|
||||
## Usage
|
||||
Start by creating a VylaSDK element with your TMDB api key.
|
||||
```js
|
||||
import VylaSDK from "@vyla-entertainment/sdk";
|
||||
View our full documentation at https://docs.vyla.cc/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)
|
||||
|
||||
@@ -29,4 +29,4 @@
|
||||
"publishConfig": {
|
||||
"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 = [
|
||||
{
|
||||
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',
|
||||
label: 'FlaxMovies',
|
||||
@@ -512,6 +531,7 @@ export const SOURCES = [
|
||||
retries: 2,
|
||||
skipProxy: true,
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
export const HEALTH_PROBE_ID = '155';
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { getDownloads, getDownloadsTv } from "./sources/trendimovies.js";
|
||||
|
||||
export async function fetchDownloads(id, s = null, e = null) {
|
||||
const res = s != null && e != null
|
||||
? await getDownloadsTv(id, s, e)
|
||||
: await getDownloads(id);
|
||||
return res;
|
||||
try {
|
||||
const res = s != null && e != null
|
||||
? await getDownloadsTv(id, s, e)
|
||||
: 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 dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
const BACKROOMS = '1083381';
|
||||
|
||||
const sdk = new VylaSDK({
|
||||
tmdbApiKey: process.env.TMDB_API_KEY
|
||||
});
|
||||
|
||||
// 'true' is excludeDisabled
|
||||
const sources = sdk.getSources(true);
|
||||
const source = sources.find(s => s.key === 'goated');
|
||||
try {
|
||||
const movieSubs = await sdk.getSubtitles("155");
|
||||
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);
|
||||
console.log("Result:", result);
|
||||
try {
|
||||
const movieDownloads = await sdk.getDownloads("155");
|
||||
console.log("Movie Downloads:", movieDownloads);
|
||||
} catch (err) {
|
||||
console.error("Movie Downloads Error:", err.message);
|
||||
}
|
||||
|
||||
// const tests = {
|
||||
// probeAll(log) {
|
||||
// const sources = sdk.getSources(true);
|
||||
// let index = 0;
|
||||
try {
|
||||
const tvDownloads = await sdk.getDownloads("1399", "1", "1");
|
||||
console.log("TV Downloads:", tvDownloads);
|
||||
} catch (err) {
|
||||
console.error("TV Downloads Error:", err.message);
|
||||
}
|
||||
|
||||
// Promise.all(sources.map(async (source, idx) => {
|
||||
// try {
|
||||
// const result = await sdk.probeSource(source.key);
|
||||
// index++;
|
||||
// log(`(${index}/${sources.length}) source #${idx + 1} (${source.label}):`, result);
|
||||
// } catch (err) {
|
||||
// log(`Error probing source ${source.key}:`, err);
|
||||
// }
|
||||
// })).then(() => {
|
||||
// log("All sources probed.");
|
||||
// });
|
||||
// },
|
||||
// tryHealthProbe(log) {
|
||||
// const sources = sdk.getSources(true);
|
||||
// let index = 0;
|
||||
try {
|
||||
const movieResult = await sdk.getStream("cinejoy", "155");
|
||||
console.log("Movie Stream:", movieResult);
|
||||
} catch (err) {
|
||||
console.error("Movie Stream Error:", err.message);
|
||||
}
|
||||
|
||||
// Promise.all(sources.map(async (source, idx) => {
|
||||
// try {
|
||||
// const result = await sdk.getStream(source.key, HEALTH_PROBE_ID, null, null, null);
|
||||
// index++;
|
||||
// log(`(${index}/${sources.length}) source #${idx + 1} (${source.label}):`, result?.allUrls?.[0]?.url || result?.url || "no sources :(");
|
||||
// } catch (err) {
|
||||
// log(`Error probing source ${source.key}:`, err);
|
||||
// }
|
||||
// })).then(() => {
|
||||
// log("All sources probed.");
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
try {
|
||||
const tvResult = await sdk.getStream("cinejoy", "1399", "1", "1");
|
||||
console.log("TV Stream:", tvResult);
|
||||
} catch (err) {
|
||||
console.error("TV Stream Error:", err.message);
|
||||
}
|
||||
|
||||
// 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