Files
john-nuvio/providers/cineby.js
T
2026-07-23 13:40:48 +03:00

350 lines
12 KiB
JavaScript

// cineby - bundled from src/cineby/
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
// src/cineby/http.js
var SITE_URL = "https://www.cineby.at";
var METADATA_API_URL = "https://db.speedracelight.com/3";
var SOURCE_API_URL = "https://api.speedracelight.com";
var HEADERS = {
"Accept": "*/*",
"Origin": SITE_URL,
"Referer": `${SITE_URL}/`,
"User-Agent": "Mozilla/5.0 (Linux; Android 15) AppleWebKit/537.36 Chrome/140.0.0.0 Mobile Safari/537.36"
};
function mergeHeaders(headers) {
return __spreadValues(__spreadValues({}, HEADERS), headers || {});
}
async function fetchText(url, options = {}) {
const response = await fetch(url, __spreadProps(__spreadValues({}, options), {
headers: mergeHeaders(options.headers)
}));
if (!response.ok) {
const error = new Error(`HTTP ${response.status} for ${url}`);
error.status = response.status;
throw error;
}
return await response.text();
}
async function fetchJson(url, options = {}) {
return JSON.parse(await fetchText(url, options));
}
function buildQuery(params) {
return Object.keys(params).filter((key) => params[key] != null && params[key] !== "").map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(String(params[key]))}`).join("&");
}
// src/cineby/extractor.js
var STREAM_MAGIC = [109, 118, 109, 49];
var MIX_CONSTANTS = [
1116352408,
1899447441,
3049323471,
3921009573,
961987163,
1508970993,
2453635748,
2870763221,
3624381080,
310598401,
607225278,
1426881987,
1925078388,
2162078206,
2614888103,
3248222580
];
var CINEMETA_URL = "https://v3-cinemeta.strem.io/meta";
var TMDB_ID_CACHE = {};
function mix(value) {
value >>>= 0;
value ^= value >>> 16;
value = Math.imul(value, 2246822507) >>> 0;
value ^= value >>> 13;
value = Math.imul(value, 3266489909) >>> 0;
return (value ^= value >>> 16) >>> 0;
}
function rotateLeft(value, amount) {
value >>>= 0;
amount &= 31;
return amount === 0 ? value : (value << amount | value >>> 32 - amount) >>> 0;
}
function isEven(value) {
return (value * (value + 1) & 1) === 0;
}
function isOdd(value) {
return (value * (value + 1) & 1) === 1;
}
function decodeBase64Url(value) {
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const normalized = String(value || "").replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, "");
const bytes = [];
let buffer = 0;
let bits = 0;
for (let index = 0; index < normalized.length; index++) {
const code = alphabet.indexOf(normalized[index]);
if (code < 0) {
continue;
}
buffer = buffer << 6 | code;
bits += 6;
if (bits >= 8) {
bits -= 8;
bytes.push(buffer >>> bits & 255);
}
}
return new Uint8Array(bytes);
}
function decodeUtf8(bytes) {
if (typeof TextDecoder !== "undefined") {
return new TextDecoder("utf-8").decode(bytes);
}
let encoded = "";
for (let index = 0; index < bytes.length; index++) {
encoded += `%${`0${bytes[index].toString(16)}`.slice(-2)}`;
}
return decodeURIComponent(encoded);
}
function createKeyStream(seed, mediaId, length) {
const state = function createState(seedValue, mediaIdentifier) {
if (isOdd(seedValue.length)) {
const values2 = Array(256);
for (let index = 0; index < 256; index++)
values2[index] = index;
let cursor = 0;
for (let index = 0; index < 256; index++) {
cursor = cursor + values2[index] + seedValue.charCodeAt(index % seedValue.length) & 255;
const current = values2[index];
values2[index] = values2[cursor];
values2[cursor] = current;
}
let accumulator = 1732584193;
for (let index = 0; index < seedValue.length; index++) {
accumulator = rotateLeft(
(accumulator ^ Math.imul(seedValue.charCodeAt(index), MIX_CONSTANTS[15 & index])) >>> 0,
5
);
}
return { S: values2, acc: mix(accumulator) };
}
const values = Array(61);
let seedHash = 2166136261;
for (let index = 0; index < seedValue.length; index++) {
seedHash = Math.imul(seedHash ^ seedValue.charCodeAt(index), 16777619) >>> 0;
}
let value = mix(mix(seedHash) ^ mix(mediaIdentifier >>> 0 ^ 2654435769)) >>> 0;
for (let index = 0; index < 8; index++) {
if (isEven(index)) {
const position = value % 61;
value = rotateLeft(value + 2654435769 >>> 0, 7 + (7 & index));
values[position] = (value ^ mix(value)) >>> 0;
value = mix(value + position >>> 0);
} else {
values[index] = MIX_CONSTANTS[15 & index];
}
}
return { S: values, acc: mix(2779096485 ^ value) >>> 0 };
}(seed, mediaId);
const stream = new Uint8Array(length);
let streamIndex = 0;
let wordCounter = 0;
while (streamIndex < length) {
const word = function nextWord(currentState, counter) {
let previous;
let mixed;
const values = currentState.S;
let accumulator = currentState.acc;
const position = accumulator % 61;
const present = 0 - Number(position in values);
const stored = values[position] >>> 0;
let selected = (((previous = accumulator) ^ (mixed = (stored ^ Math.imul(2654435769, counter + 1) >>> 0) >>> 0)) >>> 0 | (previous & mixed & present) >>> 0) >>> 0;
accumulator = mix((selected = (rotateLeft(selected + accumulator >>> 0, 31 & position) ^ rotateLeft(accumulator, 31 & Math.imul(position, 7))) >>> 0) + 2654435769 >>> 0);
values[position] = accumulator >>> 0;
currentState.acc = accumulator;
return accumulator >>> 0;
}(state, wordCounter++);
stream[streamIndex++] = word & 255;
if (streamIndex < length)
stream[streamIndex++] = word >>> 8 & 255;
if (streamIndex < length)
stream[streamIndex++] = word >>> 16 & 255;
if (streamIndex < length)
stream[streamIndex++] = word >>> 24 & 255;
}
return stream;
}
function decryptPayload(raw, seed, mediaId) {
const encrypted = decodeBase64Url(raw);
const keyStream = createKeyStream(seed, mediaId, encrypted.length);
for (let index = 0; index < encrypted.length; index++) {
encrypted[index] ^= keyStream[index];
}
for (let index = 0; index < STREAM_MAGIC.length; index++) {
if (encrypted[index] !== STREAM_MAGIC[index]) {
throw new Error("Cineby source payload could not be decrypted");
}
}
return decodeUtf8(encrypted.slice(STREAM_MAGIC.length));
}
function isPositiveInteger(value) {
return Number.isInteger(Number(value)) && Number(value) > 0;
}
async function resolveTmdbId(value, mediaType) {
const id = String(value || "").trim();
if (/^\d+$/.test(id))
return id;
if (!/^tt\d+$/i.test(id))
return null;
const cacheKey = `${mediaType}:${id.toLowerCase()}`;
if (TMDB_ID_CACHE[cacheKey])
return TMDB_ID_CACHE[cacheKey];
const catalogType = mediaType === "movie" ? "movie" : "series";
const metadata = await fetchJson(
`${CINEMETA_URL}/${catalogType}/${encodeURIComponent(id)}.json`,
{ headers: { "Accept": "application/json" } }
);
const resolved = metadata && metadata.meta && metadata.meta.moviedb_id;
if (!resolved || !/^\d+$/.test(String(resolved))) {
return null;
}
TMDB_ID_CACHE[cacheKey] = String(resolved);
return TMDB_ID_CACHE[cacheKey];
}
async function getMetadata(tmdbId, mediaType) {
const endpoint = mediaType === "movie" ? "movie" : "tv";
const data = await fetchJson(
`${METADATA_API_URL}/${endpoint}/${encodeURIComponent(tmdbId)}?append_to_response=external_ids`
);
const title = mediaType === "movie" ? data.title || data.original_title : data.name || data.original_name;
const date = mediaType === "movie" ? data.release_date : data.first_air_date;
const imdbId = data.imdb_id || data.external_ids && data.external_ids.imdb_id || "";
if (!title || !date) {
throw new Error("Cineby metadata response is incomplete");
}
return {
title,
year: String(date).slice(0, 4),
imdbId
};
}
async function getSeed(mediaId) {
const response = await fetchJson(
`${SOURCE_API_URL}/seed?${buildQuery({ mediaId })}`
);
if (!response || !response.seed) {
throw new Error("Cineby did not return a source seed");
}
return response.seed;
}
async function getSources(tmdbId, mediaType, season, episode, metadata) {
const request = {
// Cineby's player pre-encodes the title, then its HTTP client serializes
// it again. Preserve that double encoding to match the live API.
title: encodeURIComponent(metadata.title),
mediaType,
year: metadata.year,
episodeId: mediaType === "movie" ? 1 : episode,
seasonId: mediaType === "movie" ? 1 : season,
tmdbId,
imdbId: metadata.imdbId,
enc: 2
};
for (let attempt = 0; attempt < 2; attempt++) {
const seed = await getSeed(tmdbId);
const url = `${SOURCE_API_URL}/cdn/sources-with-title?${buildQuery(__spreadProps(__spreadValues({}, request), { seed }))}`;
try {
const raw = (await fetchText(url)).trim();
if (raw.startsWith("{")) {
return JSON.parse(raw);
}
return JSON.parse(decryptPayload(raw, seed, Number(tmdbId)));
} catch (error) {
if (attempt === 0 && error && error.status === 401) {
continue;
}
throw error;
}
}
return { sources: [] };
}
function getQuality(source) {
const value = String(source && source.quality || "");
if (/^4k$/i.test(value))
return "4K";
const match = value.match(/(2160|1440|1080|720|480|360)p?/i);
return match ? `${match[1]}p` : "Auto";
}
function normalizeStreams(payload) {
const streams = [];
const seenUrls = {};
const sources = payload && Array.isArray(payload.sources) ? payload.sources : [];
for (const source of sources) {
const url = source && typeof source.url === "string" ? source.url.trim() : "";
if (!url || seenUrls[url])
continue;
seenUrls[url] = true;
const quality = getQuality(source);
streams.push({
name: "Cineby",
title: `Cineby \xB7 ${quality}`,
url,
quality,
headers: {
"Referer": `${SITE_URL}/`,
"Origin": SITE_URL,
"User-Agent": HEADERS["User-Agent"]
}
});
}
return streams;
}
async function extractStreams(tmdbId, mediaType, season, episode) {
if (mediaType !== "movie" && mediaType !== "tv")
return [];
if (mediaType === "tv" && (!isPositiveInteger(season) || !isPositiveInteger(episode)))
return [];
const resolvedTmdbId = await resolveTmdbId(tmdbId, mediaType);
if (!resolvedTmdbId)
return [];
const metadata = await getMetadata(resolvedTmdbId, mediaType);
const payload = await getSources(
resolvedTmdbId,
mediaType,
Number(season),
Number(episode),
metadata
);
return normalizeStreams(payload);
}
// src/cineby/index.js
async function getStreams(tmdbId, mediaType, season, episode) {
try {
const episodeLabel = mediaType === "tv" ? ` S${season}E${episode}` : "";
console.log(`[Cineby] Request: ${mediaType} ${tmdbId}${episodeLabel}`);
const streams = await extractStreams(tmdbId, mediaType, season, episode);
console.log(`[Cineby] Found ${streams.length} stream(s)`);
return streams;
} catch (error) {
console.error(`[Cineby] Error: ${error.message}`);
return [];
}
}
module.exports = { getStreams };