MovixOpenSource/API/Mainapi/services/kisskh/kkey.js
Movix eae5a24c51 feat: ajoute KissKH et fiabilise les sources, les sous-titres et le Cast — Android 2.5.8
- corrige l’extraction Fsvid/Vidzy dans proxiesembed, les extensions et le userscript avec un décodage XOR/P.A.C.K.E.R plus robuste
- rejette les URL « troll » et supprime les résultats Fsvid/Vidzy contaminés des caches
- ajoute KissKH pour les films et séries avec matching TMDB, catalogue, cache distribué, verrouillage, rotation des proxys et fallback sécurisé
- ajoute le relais KissKH HLS/MP4 dans proxiesembed, avec retry SOCKS5 sur les erreurs 403 et streaming des segments BYTERANGE
- prend en charge les sous-titres KissKH SRT/TXT/TXT2, leur déchiffrement AES et leur conversion en WebVTT
- intègre KissKH au lecteur, aux priorités de sources, aux extensions Chrome/Firefox et au userscript
- améliore la sélection, le formatage sécurisé, la traduction et l’affichage Picture-in-Picture des sous-titres
- corrige le Cast web et Android pour transmettre les sous-titres internes, externes, KissKH et traduits
- ajoute le support des pistes WebVTT inline via le relais LAN Android et améliore le suivi des sessions Cast
- protège les liens jouables des caches Cpasmal et Coflix contre les rafraîchissements vides ou concurrents
- fiabilise la sélection des saisons FStream et améliore le matching DarkiWorld par TMDB, titre et année
- simplifie les nouvelles URL TMDB tout en conservant le décodage des anciens identifiants encodés
- corrige l’opacité des icônes SVG dans toute l’interface en séparant la couleur de leur niveau d’opacité
- met à jour le Dockerfile vers Node.js 22 et installe uniquement les dépendances runtime du workspace Hono
- ajoute le workspace server avec ses dépendances dédiées et un arrêt gracieux sur SIGTERM/SIGINT
- accélère le healthcheck Docker et évite de recopier tous les node_modules du builder dans l’image finale
- retire les migrations et créations de tables automatiques au démarrage de la Main API au profit de db:init
- attribue un port CycleTLS distinct à chaque worker pour éviter les conflits en mode cluster
- ajoute des tests de non-régression pour KissKH, les caches de sources, CycleTLS et le Cast Android
- publie Android 2.5.8 build 17 avec le nouvel APK
- met à jour les extensions Chrome 1.3.13 et Firefox 1.5.10

Co-authored-by: GPT-5.6 Sol <noreply@openai.com>
2026-08-03 21:37:41 +02:00

88 lines
3.2 KiB
JavaScript

const crypto = require('node:crypto');
const APPROVED_FIXED_MARKER = 'mg3c3b04ba';
function jsHashCode(value) {
const input = String(value);
let hash = 0;
for (let index = 0; index < input.length; index += 1) {
hash = (hash << 5) - hash + input.charCodeAt(index);
}
return hash;
}
function canonicalPayload({ episodeId, contextId, fixedMarker, appVersion, platformVersion, environmentFields }) {
if (fixedMarker !== APPROVED_FIXED_MARKER) {
throw new TypeError('fixedMarker kkey invalide');
}
if (!Array.isArray(environmentFields) || environmentFields.length !== 6) {
throw new TypeError('environmentFields invalides');
}
if (environmentFields.some((field) => typeof field !== 'string')) {
throw new TypeError('environmentFields invalides');
}
const normalize = (part) => part == null ? '' : String(part);
const truncate48 = (part) => String(part || '').substr(0, 48);
const fields = [
'',
episodeId,
null,
fixedMarker,
appVersion,
contextId,
platformVersion,
truncate48(environmentFields[0]),
truncate48(String(environmentFields[1]).toLowerCase()),
truncate48(environmentFields[2]),
environmentFields[3],
environmentFields[4],
environmentFields[5],
'00',
'',
];
const seed = fields.map(normalize).join('|');
fields.splice(1, 0, jsHashCode(seed));
return fields.map(normalize).join('|');
}
function decodeAes128Hex(value, label) {
if (typeof value !== 'string' || !/^[0-9a-f]{32}$/i.test(value)) {
throw new TypeError(`${label} AES-128 invalide`);
}
return Buffer.from(value, 'hex');
}
function computeKkey({ context, episodeId, algorithm }) {
if (!Number.isSafeInteger(episodeId) || episodeId <= 0) throw new TypeError('episodeId invalide');
if (!['episode', 'sub'].includes(context)) throw new TypeError('context invalide');
const key = decodeAes128Hex(algorithm?.keyHex, 'key');
const iv = decodeAes128Hex(algorithm?.ivHex, 'iv');
const contextSpec = algorithm?.contexts?.[context];
if (!contextSpec || typeof contextSpec.contextId !== 'string' || !contextSpec.contextId) {
throw new TypeError('contexte kkey absent');
}
if (!Number.isSafeInteger(contextSpec.expectedLength) || contextSpec.expectedLength <= 0) {
throw new TypeError('longueur kkey invalide');
}
if (typeof algorithm.appVersion !== 'string' || !algorithm.appVersion
|| !Number.isSafeInteger(algorithm.platformVersion) || algorithm.platformVersion <= 0) {
throw new TypeError('version kkey invalide');
}
const payload = canonicalPayload({
episodeId,
contextId: contextSpec.contextId,
fixedMarker: algorithm.fixedMarker,
appVersion: algorithm.appVersion,
platformVersion: algorithm.platformVersion,
environmentFields: algorithm.environmentFields,
});
const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
cipher.setAutoPadding(true);
const output = Buffer.concat([cipher.update(payload, 'utf8'), cipher.final()]).toString('hex').toUpperCase();
if (!/^[0-9A-F]+$/.test(output) || output.length !== contextSpec.expectedLength) {
throw new TypeError('sortie kkey invalide');
}
return output;
}
module.exports = { canonicalPayload, computeKkey, jsHashCode };