/* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import "./updater"; import "./ipcPlugins"; import "./settings"; import { debounce } from "@shared/debounce"; import { IpcEvents } from "@shared/IpcEvents"; import { BrowserWindow, ipcMain, nativeTheme, shell, systemPreferences } from "electron"; import monacoHtml from "file://monacoWin.html?minify&base64"; import { FSWatcher, mkdirSync, readFileSync, watch, writeFileSync } from "fs"; import { open, readdir, readFile, unlink } from "fs/promises"; import { join, normalize } from "path"; import { registerCspIpcHandlers } from "./csp/manager"; import { ALLOWED_PROTOCOLS, QUICK_CSS_PATH, SETTINGS_DIR, THEMES_DIR } from "./utils/constants"; import { makeLinksOpenExternally } from "./utils/externalLinks"; const RENDERER_CSS_PATH = join(__dirname, IS_EQUIBOP ? "equibop/renderer.css" : "desktop/renderer.css"); mkdirSync(THEMES_DIR, { recursive: true }); registerCspIpcHandlers(); export function ensureSafePath(basePath: string, path: string) { const normalizedBasePath = normalize(basePath + "/"); const newPath = join(basePath, path); const normalizedPath = normalize(newPath); return normalizedPath.startsWith(normalizedBasePath) ? normalizedPath : null; } function readCss() { return readFile(QUICK_CSS_PATH, "utf-8").catch(() => ""); } async function listThemes(): Promise<{ fileName: string; content: string; }[]> { try { const files = await readdir(THEMES_DIR); return await Promise.all(files.map(async fileName => ({ fileName, content: await getThemeData(fileName) }))); } catch { return []; } } function getThemeData(fileName: string) { fileName = fileName.replace(/\?v=\d+$/, ""); const safePath = ensureSafePath(THEMES_DIR, fileName); if (!safePath) return Promise.reject(`Unsafe path ${fileName}`); return readFile(safePath, "utf-8"); } ipcMain.handle(IpcEvents.OPEN_QUICKCSS, () => shell.openPath(QUICK_CSS_PATH)); ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => { try { var { protocol } = new URL(url); } catch { throw "Malformed URL"; } if (!ALLOWED_PROTOCOLS.includes(protocol)) throw "Disallowed protocol."; shell.openExternal(url); }); ipcMain.handle(IpcEvents.GET_QUICK_CSS, () => readCss()); ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) => writeFileSync(QUICK_CSS_PATH, css) ); ipcMain.handle(IpcEvents.GET_THEMES_DIR, () => THEMES_DIR); ipcMain.handle(IpcEvents.GET_THEMES_LIST, () => listThemes()); ipcMain.handle(IpcEvents.GET_THEME_DATA, (_, fileName) => getThemeData(fileName)); ipcMain.handle(IpcEvents.DELETE_THEME, (_, fileName) => { const safePath = ensureSafePath(THEMES_DIR, fileName); if (!safePath) return Promise.reject(`Unsafe path ${fileName}`); return unlink(safePath); }); ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => { let accentColor = systemPreferences.getAccentColor?.() ?? ""; if (accentColor.length && accentColor[0] !== "#") { accentColor = `#${accentColor}`; } return { "os-accent-color": accentColor }; }); ipcMain.handle(IpcEvents.OPEN_THEMES_FOLDER, () => shell.openPath(THEMES_DIR)); ipcMain.handle(IpcEvents.OPEN_SETTINGS_FOLDER, () => shell.openPath(SETTINGS_DIR)); ipcMain.handle(IpcEvents.INIT_FILE_WATCHERS, ({ sender }) => { let quickCssWatcher: FSWatcher | undefined; let rendererCssWatcher: FSWatcher | undefined; open(QUICK_CSS_PATH, "a+").then(fd => { fd.close(); quickCssWatcher = watch(QUICK_CSS_PATH, { persistent: false }, debounce(async () => { sender.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss()); }, 50)); }).catch(() => { }); const themesWatcher = watch(THEMES_DIR, { persistent: false }, debounce(() => { sender.postMessage(IpcEvents.THEME_UPDATE, void 0); })); if (IS_DEV) { rendererCssWatcher = watch(RENDERER_CSS_PATH, { persistent: false }, async () => { sender.postMessage(IpcEvents.RENDERER_CSS_UPDATE, await readFile(RENDERER_CSS_PATH, "utf-8")); }); } sender.once("destroyed", () => { quickCssWatcher?.close(); themesWatcher.close(); rendererCssWatcher?.close(); }); }); ipcMain.on(IpcEvents.GET_MONACO_THEME, e => { e.returnValue = nativeTheme.shouldUseDarkColors ? "vs-dark" : "vs-light"; }); ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => { const title = "Equicord QuickCSS Editor"; const existingWindow = BrowserWindow.getAllWindows().find(w => w.title === title); if (existingWindow && !existingWindow.isDestroyed()) { existingWindow.focus(); return; } const win = new BrowserWindow({ title, autoHideMenuBar: true, darkTheme: true, webPreferences: { preload: join(__dirname, "preload.js"), contextIsolation: true, nodeIntegration: false, sandbox: false } }); makeLinksOpenExternally(win); await win.loadURL(`data:text/html;base64,${monacoHtml}`); }); ipcMain.handle(IpcEvents.GET_RENDERER_CSS, () => readFile(RENDERER_CSS_PATH, "utf-8")); if (IS_DISCORD_DESKTOP) { ipcMain.on(IpcEvents.PRELOAD_GET_RENDERER_JS, e => { e.returnValue = readFileSync(join(__dirname, "renderer.js"), "utf-8"); }); }