Files
nightcord/nightcord-index.js

253 lines
12 KiB
JavaScript

// Nightcord entry point
"use strict";
const path = require("path");
const Module = require("module");
const fs = require("fs");
const { app } = require("electron");
// ── CRITIQUE : userData = dossier Nightcord pour les settings/plugins
// Mais on garde le cache Discord (images, données serveurs) du vrai Discord
// pour éviter de devoir tout retélécharger à chaque installation.
const nightcordData = path.join(app.getPath("appData"), "Nightcord");
app.setPath("userData", nightcordData);
// FIX BUG SALONS INVISIBLES : NE PAS partager le cache HTTP de Discord stable.
// Le cache de Discord stable contient des données de session/permissions calculées
// pour le token Discord stable. Quand Nightcord charge ce cache, il lit des permissions
// appartenant à la session stable — résultat : seuls les salons publics (@everyone) sont
// visibles, les salons privés/restreints disparaissent même pour l'owner.
// Chaque client doit avoir son propre cache isolé (dans userData = Equicord).
// Le léger délai au premier chargement est acceptable vs le bug de permissions.
// AppUserModelId unique — Windows reconnaît Nightcord comme app séparée de Discord
app.setAppUserModelId("com.squirrel.Discord.Discord");
// ── HOTFIX : Partage d'écran (Chargement infini & Crash) ──────────────────
// NOTE : Les flags disable-gpu-sandbox et WebRtcHideLocalIpsWithMdns ont été retirés
// car ils causaient :
// - Messages vocaux qui ne s'envoient pas (WebRTC encodage cassé)
// - Double appel / double connexion vocale
// - Plugin voicedictation qui ne détecte pas le micro
// Ces flags désactivaient la protection IP locale et le sandbox GPU,
// ce qui empêchait WebRTC de fonctionner correctement.
app.commandLine.appendSwitch("enable-features", "WebRTCPipeWireCapturer");
// ── FIX MODULES 403 : discord_overlay / discord_rpc / discord_dispatch ────
// Discord essaie de télécharger ces modules natifs depuis ses CDN mais reçoit
// un 403 (version de client inconnue). L'échec d'installation de discord_overlay
// provoque : "Overlay crashed for pid -1" → "Overlay module not found" →
// webpack.findByProps/findCssClasses échouent → removeChild crash en cascade.
//
// Solution : on intercepte le module updater AVANT qu'il tente ces téléchargements,
// et on lui fait croire que ces modules sont déjà installés et à jour.
app.once("ready", () => {
try {
// Liste des modules natifs qui causent des erreurs 403 inutiles
// NB: discord_overlay est intentionnellement ABSENT de cette liste —
// il doit pouvoir s'initialiser localement pour que l'overlay en jeu fonctionne.
// Seuls les modules vraiment inutiles pour Nightcord sont bloqués.
const BLOCKED_MODULES = new Set([
// "discord_overlay", // RETIRE — nécessaire pour l'overlay in-game
"discord_rpc",
"discord_dispatch",
"discord_erinn",
]);
// Patch du net.request d'Electron pour bloquer silencieusement les 403 prévisibles
const originalRequest = app.constructor.prototype.constructor;
const { session } = require("electron");
// Intercepter les requêtes vers discord.com/api/modules/* pour les modules bloqués
app.once("browser-window-created", () => {
try {
const ses = session.defaultSession;
ses.webRequest.onBeforeRequest(
{ urls: ["https://discord.com/api/modules/*"] },
(details, callback) => {
const url = details.url;
const isBlocked = Array.from(BLOCKED_MODULES).some(m => url.includes(m));
if (isBlocked) {
// Bloquer silencieusement — évite le 403 + les logs d'erreur
console.log("[Nightcord] Module bloqué (inutile pour Nightcord):", url.split("/").slice(-2).join("/"));
callback({ cancel: true });
} else {
callback({});
}
}
);
console.log("[Nightcord] Filtre modules 403 activé ✓");
} catch (e) {
console.warn("[Nightcord] Impossible d'activer le filtre modules:", e.message);
}
});
} catch (e) {
console.warn("[Nightcord] FIX modules 403 failed:", e.message);
}
});
// Protection contre le freeze après crash — vérifier et réparer le LevelDB localStorage
// Quand Discord crash pendant une écriture localStorage, le fichier LevelDB peut se
// corrompre et géler le renderer au démarrage suivant.
try {
const lsPath = path.join(nightcordData, "Local Storage", "leveldb");
if (fs.existsSync(lsPath)) {
// Détecter la corruption : fichier LOCK verrouillé ou fichier LOG manquant
const lockFile = path.join(lsPath, "LOCK");
const logFile = path.join(lsPath, "LOG");
let corrupted = false;
if (fs.existsSync(lockFile)) {
try {
// Essayer d'ouvrir le LOCK en écriture — si échoue, un process zombie le tient
const fd = fs.openSync(lockFile, "r+");
fs.closeSync(fd);
} catch (e) {
// LOCK verrouillé par un zombie — supprimer pour débloquer
try { fs.unlinkSync(lockFile); } catch { }
corrupted = true;
}
}
// Vérifier aussi les fichiers .ldb corrompus (taille 0)
if (!corrupted) {
const files = fs.readdirSync(lsPath).filter(f => f.endsWith(".ldb"));
for (const f of files) {
const size = fs.statSync(path.join(lsPath, f)).size;
if (size === 0) { corrupted = true; break; }
}
}
if (corrupted) {
console.warn("[Nightcord] LevelDB localStorage corrompu détecté — réparation...");
try { fs.rmSync(lsPath, { recursive: true, force: true }); } catch { }
console.warn("[Nightcord] LevelDB supprimé — les données localStorage seront récréées");
}
}
} catch (e) { console.warn("[Nightcord] LevelDB check failed:", e.message); }
// Modules bundlés dans nightcord-dist/modules/
const bundledModulesPath = path.join(path.dirname(process.execPath), "modules");
const moduleDataPath = path.join(app.getPath("appData"), "discord", "module_data");
// ── DÉTECTION AUTOMATIQUE du dossier modules de Discord stable ───────────────
// Les modules natifs (discord_voice, discord_krisp...) sont dans AppData\Local\Discord\app-X.X.XXXX\modules\
// et NON dans AppData\Roaming\discord\module_data\ (qui est souvent vide).
// On détecte automatiquement la version installée pour avoir le bon chemin.
const discordLocalBase = path.join(app.getPath("appData"), "..", "Local", "Discord");
let discordNativeModulesPath = null;
try {
const entries = fs.readdirSync(discordLocalBase)
.filter(e => e.startsWith("app-"))
.map(e => ({ name: e, full: path.join(discordLocalBase, e, "modules") }))
.filter(e => fs.existsSync(e.full))
.sort((a, b) => b.name.localeCompare(a.name, undefined, { numeric: true }));
if (entries.length > 0) {
discordNativeModulesPath = entries[0].full;
console.log("[Nightcord] Modules natifs Discord détectés:", discordNativeModulesPath);
}
} catch (e) {
console.warn("[Nightcord] Impossible de détecter les modules natifs Discord:", e.message);
}
function addGlobalPath(p) {
try { if (fs.existsSync(p) && !Module.globalPaths.includes(p)) Module.globalPaths.push(p); } catch (_) { }
}
// Priorité aux modules bundlés (portables, dans nightcord-dist/modules/)
addGlobalPath(bundledModulesPath);
// Ajout des modules natifs Discord (discord_voice, discord_krisp, etc.)
if (discordNativeModulesPath) {
addGlobalPath(discordNativeModulesPath);
try {
for (const mod of fs.readdirSync(discordNativeModulesPath)) {
const modDir = path.join(discordNativeModulesPath, mod);
if (!fs.existsSync(modDir) || !fs.statSync(modDir).isDirectory()) continue;
addGlobalPath(modDir);
// Entrer dans le sous-dossier du module (ex: discord_voice-1/discord_voice/)
for (const sub of fs.readdirSync(modDir)) {
const subDir = path.join(modDir, sub);
if (fs.existsSync(subDir) && fs.statSync(subDir).isDirectory()) addGlobalPath(subDir);
}
}
} catch (e) { console.warn("[Nightcord] Erreur lors du scan des modules natifs:", e.message); }
}
try {
for (const mod of fs.readdirSync(bundledModulesPath)) {
const modDir = path.join(bundledModulesPath, mod);
if (!fs.existsSync(modDir) || !fs.statSync(modDir).isDirectory()) continue;
addGlobalPath(modDir);
for (const ver of fs.readdirSync(modDir)) {
const verDir = path.join(modDir, ver);
if (fs.existsSync(verDir) && fs.statSync(verDir).isDirectory()) addGlobalPath(verDir);
}
}
} catch (e) { }
// Fallback : module_data utilisateur
addGlobalPath(moduleDataPath);
try {
for (const mod of fs.readdirSync(moduleDataPath)) {
const modDir = path.join(moduleDataPath, mod);
if (!fs.statSync(modDir).isDirectory()) continue;
addGlobalPath(modDir);
for (const ver of fs.readdirSync(modDir)) {
const verDir = path.join(modDir, ver);
if (fs.existsSync(verDir) && fs.statSync(verDir).isDirectory()) addGlobalPath(verDir);
}
}
} catch (e) { }
// Patch Module._resolveLookupPaths une seule fois avec un Set pour éviter
// la concatenation de tableau à chaque require() — source de latence majeure
const _globalPathsSet = new Set(Module.globalPaths);
const _origResolve = Module._resolveLookupPaths;
Module._resolveLookupPaths = function (request, parent) {
if (parent) {
if (!parent.paths) parent.paths = [..._globalPathsSet];
else if (parent.paths.length > 0) {
// Ajouter uniquement les paths manquants — évite la concat à chaque appel
for (const p of _globalPathsSet) {
if (!parent.paths.includes(p)) parent.paths.push(p);
}
}
}
return _origResolve.call(this, request, parent);
};
// Chercher discord_desktop_core dans cet ordre :
// 1. modules bundlés (portable)
// 2. modules natifs Discord local (AppData\Local\Discord\app-X\modules\)
// 3. module_data Roaming (fallback)
const coreModuleDir = path.join(bundledModulesPath, "discord_desktop_core-1", "discord_desktop_core");
const coreModuleDirNative = discordNativeModulesPath
? path.join(discordNativeModulesPath, "discord_desktop_core-1", "discord_desktop_core")
: null;
global.mainAppDirname = fs.existsSync(coreModuleDir)
? coreModuleDir
: (coreModuleDirNative && fs.existsSync(coreModuleDirNative))
? coreModuleDirNative
: path.join(moduleDataPath, "discord_desktop_core");
console.log("[Nightcord] mainAppDirname:", global.mainAppDirname);
// ── FIX AUDIO NATIF : patch build_info.json pour que Discord trouve les modules ──
// paths.js (dans _app.asar) utilise buildInfo.localModulesRoot comme chemin prioritaire
// pour tous les modules natifs (discord_voice, discord_krisp, gain micro, Krisp...).
// Sans ça, il cherche dans AppData\Roaming\discord\module_data\ qui est VIDE.
try {
const buildInfoPath = path.join(
path.dirname(process.execPath), "resources", "build_info.json"
);
const buildInfoRaw = fs.readFileSync(buildInfoPath, "utf-8");
const buildInfo = JSON.parse(buildInfoRaw);
const nativeModulesDir = path.join(path.dirname(process.execPath), "modules");
if (fs.existsSync(nativeModulesDir) && !buildInfo.localModulesRoot) {
buildInfo.localModulesRoot = nativeModulesDir;
fs.writeFileSync(buildInfoPath, JSON.stringify(buildInfo, null, 2));
console.log("[Nightcord] build_info.json patché → localModulesRoot:", nativeModulesDir);
}
} catch (e) {
console.warn("[Nightcord] Impossible de patcher build_info.json:", e.message);
}
require(path.join(__dirname, "dist", "desktop", "patcher.js"));