From 419cb8eb4a00c96cb17f17754bad376360a443f8 Mon Sep 17 00:00:00 2001 From: smartfrigde <37928912+smartfrigde@users.noreply.github.com> Date: Wed, 15 May 2024 18:14:49 +0200 Subject: [PATCH] feat: new folder structure --- src/common/config.ts | 157 ++++++ src/common/dom.ts | 20 + src/common/flags.ts | 47 ++ src/common/lang.ts | 67 +++ src/common/sleep.ts | 3 + src/common/version.ts | 19 + src/common/windowState.ts | 34 ++ src/{ => discord}/content/css/discord.css | 0 .../content/css/inAppSettings.css | 0 src/{ => discord}/content/css/logos.css | 0 src/{ => discord}/content/css/mobile.css | 0 src/{ => discord}/content/css/screenshare.css | 0 src/{ => discord}/content/css/settings.css | 0 src/{ => discord}/content/css/settingsEng.css | 0 src/{ => discord}/content/css/setup.css | 0 src/{ => discord}/content/css/splash.css | 0 src/{ => discord}/content/css/titlebar.css | 0 .../content/js/disableAutogain.js | 0 src/{ => discord}/content/js/rpc.js | 0 .../mods.ts => discord/extensions/csp.ts} | 2 +- src/discord/extensions/mods.ts | 124 +++++ src/{ => discord}/extensions/plugin.ts | 0 src/{ => discord}/ipc.ts | 33 +- src/{ => discord}/menu.ts | 2 +- src/{ => discord}/preload/bridge.ts | 0 src/{ => discord}/preload/capturer.ts | 2 +- src/{ => discord}/preload/mobile.ts | 2 +- src/{ => discord}/preload/optimizer.ts | 0 src/{ => discord}/preload/preload.ts | 14 +- src/{ => discord}/preload/settings.ts | 9 +- src/{ => discord}/preload/titlebar.ts | 6 +- src/{ => discord}/screenshare/main.ts | 2 +- src/{ => discord}/screenshare/picker.html | 0 src/{ => discord}/screenshare/preload.ts | 0 src/{ => discord}/window.ts | 19 +- src/keybindMaker/main.ts | 66 --- src/keybindMaker/maker.html | 392 -------------- src/keybindMaker/preload.ts | 36 -- src/main.ts | 38 +- src/settings/main.ts | 2 +- src/setup/main.ts | 3 +- src/setup/preload.ts | 3 +- src/splash/main.ts | 2 +- src/themeManager/main.ts | 4 +- src/themeManager/preload.ts | 2 +- src/tray.ts | 5 +- src/utils.ts | 478 ------------------ tsconfig.json | 2 +- 48 files changed, 536 insertions(+), 1059 deletions(-) create mode 100644 src/common/config.ts create mode 100644 src/common/dom.ts create mode 100644 src/common/flags.ts create mode 100644 src/common/lang.ts create mode 100644 src/common/sleep.ts create mode 100644 src/common/version.ts create mode 100644 src/common/windowState.ts rename src/{ => discord}/content/css/discord.css (100%) rename src/{ => discord}/content/css/inAppSettings.css (100%) rename src/{ => discord}/content/css/logos.css (100%) rename src/{ => discord}/content/css/mobile.css (100%) rename src/{ => discord}/content/css/screenshare.css (100%) rename src/{ => discord}/content/css/settings.css (100%) rename src/{ => discord}/content/css/settingsEng.css (100%) rename src/{ => discord}/content/css/setup.css (100%) rename src/{ => discord}/content/css/splash.css (100%) rename src/{ => discord}/content/css/titlebar.css (100%) rename src/{ => discord}/content/js/disableAutogain.js (100%) rename src/{ => discord}/content/js/rpc.js (100%) rename src/{extensions/mods.ts => discord/extensions/csp.ts} (95%) create mode 100644 src/discord/extensions/mods.ts rename src/{ => discord}/extensions/plugin.ts (100%) rename src/{ => discord}/ipc.ts (91%) rename src/{ => discord}/menu.ts (98%) rename src/{ => discord}/preload/bridge.ts (100%) rename src/{ => discord}/preload/capturer.ts (97%) rename src/{ => discord}/preload/mobile.ts (92%) rename src/{ => discord}/preload/optimizer.ts (100%) rename src/{ => discord}/preload/preload.ts (87%) rename src/{ => discord}/preload/settings.ts (75%) rename src/{ => discord}/preload/titlebar.ts (94%) rename src/{ => discord}/screenshare/main.ts (98%) rename src/{ => discord}/screenshare/picker.html (100%) rename src/{ => discord}/screenshare/preload.ts (100%) rename src/{ => discord}/window.ts (97%) delete mode 100644 src/keybindMaker/main.ts delete mode 100644 src/keybindMaker/maker.html delete mode 100644 src/keybindMaker/preload.ts delete mode 100644 src/utils.ts diff --git a/src/common/config.ts b/src/common/config.ts new file mode 100644 index 0000000..9b1f62b --- /dev/null +++ b/src/common/config.ts @@ -0,0 +1,157 @@ +import {app, dialog} from "electron"; +import path from "path"; +import fs from "fs"; +import {getWindowStateLocation} from "./windowState"; +export let firstRun: boolean; +export function checkForDataFolder(): void { + const dataPath = path.join(path.dirname(app.getPath("exe")), "armcord-data"); + if (fs.existsSync(dataPath) && fs.statSync(dataPath).isDirectory()) { + console.log("Found armcord-data folder. Running in portable mode."); + app.setPath("userData", dataPath); + } +} + +export interface Settings { + // Referenced for detecting a broken config. + "0"?: string; + // Referenced once for disabling mod updating. + noBundleUpdates?: boolean; + // Only used for external url warning dialog. + ignoreProtocolWarning?: boolean; + customIcon: string; + windowStyle: string; + channel: string; + armcordCSP: boolean; + minimizeToTray: boolean; + multiInstance: boolean; + spellcheck: boolean; + mods: string; + dynamicIcon: boolean; + mobileMode: boolean; + skipSplash: boolean; + performanceMode: string; + customJsBundle: RequestInfo | URL; + customCssBundle: RequestInfo | URL; + startMinimized: boolean; + useLegacyCapturer: boolean; + tray: boolean; + keybinds: Array; + inviteWebsocket: boolean; + disableAutogain: boolean; + trayIcon: string; + doneSetup: boolean; + clientName: string; +} +export function getConfigLocation(): string { + const userDataPath = app.getPath("userData"); + const storagePath = path.join(userDataPath, "/storage/"); + return `${storagePath}settings.json`; +} +export async function getConfig(object: K): Promise { + let rawdata = fs.readFileSync(getConfigLocation(), "utf-8"); + let returndata = JSON.parse(rawdata); + return returndata[object]; +} +export function getConfigSync(object: K) { + let rawdata = fs.readFileSync(getConfigLocation(), "utf-8"); + let returndata = JSON.parse(rawdata); + return returndata[object]; +} +export async function setConfig(object: K, toSet: Settings[K]): Promise { + let rawdata = fs.readFileSync(getConfigLocation(), "utf-8"); + let parsed = JSON.parse(rawdata); + parsed[object] = toSet; + let toSave = JSON.stringify(parsed, null, 4); + fs.writeFileSync(getConfigLocation(), toSave, "utf-8"); +} +export async function setConfigBulk(object: Settings): Promise { + let existingData = {}; + try { + const existingDataBuffer = fs.readFileSync(getConfigLocation(), "utf-8"); + existingData = JSON.parse(existingDataBuffer.toString()); + } catch (error) { + // Ignore errors when the file doesn't exist or parsing fails + } + // Merge the existing data with the new data + const mergedData = {...existingData, ...object}; + // Write the merged data back to the file + const toSave = JSON.stringify(mergedData, null, 4); + fs.writeFileSync(getConfigLocation(), toSave, "utf-8"); +} +export async function checkIfConfigExists(): Promise { + const userDataPath = app.getPath("userData"); + const storagePath = path.join(userDataPath, "/storage/"); + const settingsFile = `${storagePath}settings.json`; + + if (!fs.existsSync(settingsFile)) { + if (!fs.existsSync(storagePath)) { + fs.mkdirSync(storagePath); + console.log("Created missing storage folder"); + } + console.log("First run of the ArmCord. Starting setup."); + setup(); + firstRun = true; + } else if ((await getConfig("doneSetup")) == false) { + console.log("First run of the ArmCord. Starting setup."); + setup(); + firstRun = true; + } else { + console.log("ArmCord has been run before. Skipping setup."); + } +} +export async function checkIfConfigIsBroken(): Promise { + try { + let settingsData = fs.readFileSync(getConfigLocation(), "utf-8"); + JSON.parse(settingsData); + console.log("Config is fine"); + } catch (e) { + console.error(e); + console.log("Detected a corrupted config"); + setup(); + dialog.showErrorBox( + "Oops, something went wrong.", + "ArmCord has detected that your configuration file is corrupted, please restart the app and set your settings again. If this issue persists, report it on the support server/Github issues." + ); + } + try { + let windowData = fs.readFileSync(getWindowStateLocation(), "utf-8"); + JSON.parse(windowData); + console.log("Window config is fine"); + } catch (e) { + console.error(e); + fs.writeFileSync(getWindowStateLocation(), "{}", "utf-8"); + console.log("Detected a corrupted window config"); + } +} + +export function setup(): void { + console.log("Setting up temporary ArmCord settings."); + const defaults: Settings = { + windowStyle: "default", + channel: "stable", + armcordCSP: true, + minimizeToTray: true, + keybinds: [], + multiInstance: false, + mods: "none", + spellcheck: true, + performanceMode: "none", + skipSplash: false, + inviteWebsocket: true, + startMinimized: false, + dynamicIcon: false, + tray: true, + customJsBundle: "https://armcord.app/placeholder.js", + customCssBundle: "https://armcord.app/placeholder.css", + disableAutogain: false, + useLegacyCapturer: false, + mobileMode: false, + trayIcon: "default", + doneSetup: false, + clientName: "ArmCord", + customIcon: path.join(__dirname, "../", "/assets/desktop.png") + }; + setConfigBulk({ + ...defaults + }); +} diff --git a/src/common/dom.ts b/src/common/dom.ts new file mode 100644 index 0000000..7f0a73b --- /dev/null +++ b/src/common/dom.ts @@ -0,0 +1,20 @@ +export function addStyle(styleString: string): void { + const style = document.createElement("style"); + style.textContent = styleString; + document.head.append(style); +} + +export function addScript(scriptString: string): void { + let script = document.createElement("script"); + script.textContent = scriptString; + document.body.append(script); +} +export async function injectJS(inject: string): Promise { + const js = await (await fetch(`${inject}`)).text(); + + const el = document.createElement("script"); + + el.appendChild(document.createTextNode(js)); + + document.body.appendChild(el); +} diff --git a/src/common/flags.ts b/src/common/flags.ts new file mode 100644 index 0000000..65db241 --- /dev/null +++ b/src/common/flags.ts @@ -0,0 +1,47 @@ +import {app} from "electron"; +import {getConfig} from "./config"; + +export let transparency: boolean; +export async function injectElectronFlags(): Promise { + // MIT License + + // Copyright (c) 2022 GooseNest + + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + + // The above copyright notice and this permission notice shall be included in all + // copies or substantial portions of the Software. + + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + // SOFTWARE. + const presets = { + performance: `--enable-gpu-rasterization --enable-zero-copy --ignore-gpu-blocklist --enable-hardware-overlays=single-fullscreen,single-on-top,underlay --enable-features=EnableDrDc,CanvasOopRasterization,BackForwardCache:TimeToLiveInBackForwardCacheInSeconds/300/should_ignore_blocklists/true/enable_same_site/true,ThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes,UseSkiaRenderer,WebAssemblyLazyCompilation --disable-features=Vulkan --force_high_performance_gpu`, // Performance + battery: "--enable-features=TurnOffStreamingMediaCachingOnBattery --force_low_power_gpu", // Known to have better battery life for Chromium? + vaapi: "--ignore-gpu-blocklist --enable-features=VaapiVideoDecoder --enable-gpu-rasterization --enable-zero-copy --force_high_performance_gpu --use-gl=desktop --disable-features=UseChromeOSDirectVideoDecoder" + }; + switch (await getConfig("performanceMode")) { + case "performance": + console.log("Performance mode enabled"); + app.commandLine.appendArgument(presets.performance); + break; + case "battery": + console.log("Battery mode enabled"); + app.commandLine.appendArgument(presets.battery); + break; + default: + console.log("No performance modes set"); + } + if ((await getConfig("windowStyle")) == "transparent" && process.platform === "win32") { + transparency = true; + } +} diff --git a/src/common/lang.ts b/src/common/lang.ts new file mode 100644 index 0000000..1db80cd --- /dev/null +++ b/src/common/lang.ts @@ -0,0 +1,67 @@ +import {app} from "electron"; +import path from "path"; +import fs from "fs"; +export async function setLang(language: string): Promise { + const langConfigFile = `${path.join(app.getPath("userData"), "/storage/")}lang.json`; + if (!fs.existsSync(langConfigFile)) { + fs.writeFileSync(langConfigFile, "{}", "utf-8"); + } + let rawdata = fs.readFileSync(langConfigFile, "utf-8"); + let parsed = JSON.parse(rawdata); + parsed.lang = language; + let toSave = JSON.stringify(parsed, null, 4); + fs.writeFileSync(langConfigFile, toSave, "utf-8"); +} +let language: string; +export async function getLang(object: string): Promise { + if (language == undefined) { + try { + const userDataPath = app.getPath("userData"); + const storagePath = path.join(userDataPath, "/storage/"); + const langConfigFile = `${storagePath}lang.json`; + let rawdata = fs.readFileSync(langConfigFile, "utf-8"); + let parsed = JSON.parse(rawdata); + language = parsed.lang; + } catch (_e) { + console.log("Language config file doesn't exist. Fallback to English."); + language = "en-US"; + } + } + if (language.length == 2) { + language = `${language}-${language.toUpperCase()}`; + } + let langPath = path.join(__dirname, "../", `/assets/lang/${language}.json`); + if (!fs.existsSync(langPath)) { + langPath = path.join(__dirname, "../", "/assets/lang/en-US.json"); + } + let rawdata = fs.readFileSync(langPath, "utf-8"); + let parsed = JSON.parse(rawdata); + if (parsed[object] == undefined) { + console.log(`${object} is undefined in ${language}`); + langPath = path.join(__dirname, "../", "/assets/lang/en-US.json"); + rawdata = fs.readFileSync(langPath, "utf-8"); + parsed = JSON.parse(rawdata); + return parsed[object]; + } else { + return parsed[object]; + } +} +export async function getLangName(): Promise { + if (language == undefined) { + try { + const userDataPath = app.getPath("userData"); + const storagePath = path.join(userDataPath, "/storage/"); + const langConfigFile = `${storagePath}lang.json`; + let rawdata = fs.readFileSync(langConfigFile, "utf-8"); + let parsed = JSON.parse(rawdata); + language = parsed.lang; + } catch (_e) { + console.log("Language config file doesn't exist. Fallback to English."); + language = "en-US"; + } + } + if (language.length == 2) { + language = `${language}-${language.toUpperCase()}`; + } + return language; +} diff --git a/src/common/sleep.ts b/src/common/sleep.ts new file mode 100644 index 0000000..ff47479 --- /dev/null +++ b/src/common/sleep.ts @@ -0,0 +1,3 @@ +export async function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/src/common/version.ts b/src/common/version.ts new file mode 100644 index 0000000..1fb97d4 --- /dev/null +++ b/src/common/version.ts @@ -0,0 +1,19 @@ +import {app} from "electron"; + +export const packageVersion = require("../package.json").version; + +export function getVersion(): string { + return packageVersion; +} +export function getDisplayVersion(): string { + //Checks if the app version # has 4 sections (3.1.0.0) instead of 3 (3.1.0) / Shitty way to check if Kernel Mod is installed + if ((app.getVersion() == packageVersion) == false) { + if ((app.getVersion() == process.versions.electron) == true) { + return `Dev Build (${packageVersion})`; + } else { + return `${packageVersion} [Modified]`; + } + } else { + return packageVersion; + } +} diff --git a/src/common/windowState.ts b/src/common/windowState.ts new file mode 100644 index 0000000..efc9847 --- /dev/null +++ b/src/common/windowState.ts @@ -0,0 +1,34 @@ +import {app} from "electron"; +import path from "path"; +import fs from "fs"; +export interface WindowState { + width: number; + height: number; + x: number; + y: number; + isMaximized: boolean; +} +export function getWindowStateLocation() { + const userDataPath = app.getPath("userData"); + const storagePath = path.join(userDataPath, "/storage/"); + return `${storagePath}window.json`; +} +export async function setWindowState(object: WindowState): Promise { + const userDataPath = app.getPath("userData"); + const storagePath = path.join(userDataPath, "/storage/"); + const saveFile = `${storagePath}window.json`; + let toSave = JSON.stringify(object, null, 4); + fs.writeFileSync(saveFile, toSave, "utf-8"); +} +export async function getWindowState(object: K): Promise { + const userDataPath = app.getPath("userData"); + const storagePath = path.join(userDataPath, "/storage/"); + const settingsFile = `${storagePath}window.json`; + if (!fs.existsSync(settingsFile)) { + fs.writeFileSync(settingsFile, "{}", "utf-8"); + } + let rawdata = fs.readFileSync(settingsFile, "utf-8"); + let returndata = JSON.parse(rawdata); + console.log(`[Window state manager] ${returndata}`); + return returndata[object]; +} diff --git a/src/content/css/discord.css b/src/discord/content/css/discord.css similarity index 100% rename from src/content/css/discord.css rename to src/discord/content/css/discord.css diff --git a/src/content/css/inAppSettings.css b/src/discord/content/css/inAppSettings.css similarity index 100% rename from src/content/css/inAppSettings.css rename to src/discord/content/css/inAppSettings.css diff --git a/src/content/css/logos.css b/src/discord/content/css/logos.css similarity index 100% rename from src/content/css/logos.css rename to src/discord/content/css/logos.css diff --git a/src/content/css/mobile.css b/src/discord/content/css/mobile.css similarity index 100% rename from src/content/css/mobile.css rename to src/discord/content/css/mobile.css diff --git a/src/content/css/screenshare.css b/src/discord/content/css/screenshare.css similarity index 100% rename from src/content/css/screenshare.css rename to src/discord/content/css/screenshare.css diff --git a/src/content/css/settings.css b/src/discord/content/css/settings.css similarity index 100% rename from src/content/css/settings.css rename to src/discord/content/css/settings.css diff --git a/src/content/css/settingsEng.css b/src/discord/content/css/settingsEng.css similarity index 100% rename from src/content/css/settingsEng.css rename to src/discord/content/css/settingsEng.css diff --git a/src/content/css/setup.css b/src/discord/content/css/setup.css similarity index 100% rename from src/content/css/setup.css rename to src/discord/content/css/setup.css diff --git a/src/content/css/splash.css b/src/discord/content/css/splash.css similarity index 100% rename from src/content/css/splash.css rename to src/discord/content/css/splash.css diff --git a/src/content/css/titlebar.css b/src/discord/content/css/titlebar.css similarity index 100% rename from src/content/css/titlebar.css rename to src/discord/content/css/titlebar.css diff --git a/src/content/js/disableAutogain.js b/src/discord/content/js/disableAutogain.js similarity index 100% rename from src/content/js/disableAutogain.js rename to src/discord/content/js/disableAutogain.js diff --git a/src/content/js/rpc.js b/src/discord/content/js/rpc.js similarity index 100% rename from src/content/js/rpc.js rename to src/discord/content/js/rpc.js diff --git a/src/extensions/mods.ts b/src/discord/extensions/csp.ts similarity index 95% rename from src/extensions/mods.ts rename to src/discord/extensions/csp.ts index 251e507..08ff28d 100644 --- a/src/extensions/mods.ts +++ b/src/discord/extensions/csp.ts @@ -1,5 +1,5 @@ import electron from "electron"; -import {getConfig} from "../utils"; +import {getConfig} from "../../common/config"; const unstrictCSP = (): void => { console.log("Setting up CSP unstricter..."); diff --git a/src/discord/extensions/mods.ts b/src/discord/extensions/mods.ts new file mode 100644 index 0000000..1c76380 --- /dev/null +++ b/src/discord/extensions/mods.ts @@ -0,0 +1,124 @@ +import {app, dialog} from "electron"; +import extract from "extract-zip"; +import path from "path"; +import {getConfig} from "../../common/config"; +import fs from "fs"; +import util from "util"; +const streamPipeline = util.promisify(require("stream").pipeline); +async function updateModBundle(): Promise { + if ((await getConfig("noBundleUpdates")) == undefined ?? false) { + try { + console.log("Downloading mod bundle"); + const distFolder = `${app.getPath("userData")}/plugins/loader/dist/`; + while (!fs.existsSync(distFolder)) { + //waiting + } + let name: string = await getConfig("mods"); + if (name == "custom") { + // aspy fix + let bundle: string = await (await fetch(await getConfig("customJsBundle"))).text(); + fs.writeFileSync(`${distFolder}bundle.js`, bundle, "utf-8"); + let css: string = await (await fetch(await getConfig("customCssBundle"))).text(); + fs.writeFileSync(`${distFolder}bundle.css`, css, "utf-8"); + } else { + const clientMods = { + vencord: "https://github.com/Vendicated/Vencord/releases/download/devbuild/browser.js", + shelter: "https://raw.githubusercontent.com/uwu/shelter-builds/main/shelter.js" + }; + const clientModsCss = { + vencord: "https://github.com/Vendicated/Vencord/releases/download/devbuild/browser.css", + shelter: "https://armcord.app/placeholder.css" + }; + console.log(clientMods[name as keyof typeof clientMods]); + let bundle: string = await (await fetch(clientMods[name as keyof typeof clientMods])).text(); + fs.writeFileSync(`${distFolder}bundle.js`, bundle, "utf-8"); + let css: string = await (await fetch(clientModsCss[name as keyof typeof clientModsCss])).text(); + fs.writeFileSync(`${distFolder}bundle.css`, css, "utf-8"); + } + } catch (e) { + console.log("[Mod loader] Failed to install mods"); + console.error(e); + dialog.showErrorBox( + "Oops, something went wrong.", + "ArmCord couldn't install mods, please check if you have stable internet connection and restart the app. If this issue persists, report it on the support server/Github issues." + ); + } + } else { + console.log("[Mod loader] Skipping mod bundle update"); + } +} + +export let modInstallState: string; +export function updateModInstallState() { + modInstallState = "modDownload"; + + updateModBundle(); + import("./plugin"); + + modInstallState = "done"; +} + +export async function installModLoader(): Promise { + if ((await getConfig("mods")) == "none") { + modInstallState = "none"; + fs.rmSync(`${app.getPath("userData")}/plugins/loader`, {recursive: true, force: true}); + + import("./plugin"); + console.log("[Mod loader] Skipping"); + + return; + } + + const pluginFolder = `${app.getPath("userData")}/plugins/`; + if (fs.existsSync(`${pluginFolder}loader`) && fs.existsSync(`${pluginFolder}loader/dist/bundle.css`)) { + updateModInstallState(); + return; + } + + try { + fs.rmSync(`${app.getPath("userData")}/plugins/loader`, {recursive: true, force: true}); + modInstallState = "installing"; + + let zipPath = `${app.getPath("temp")}/loader.zip`; + + if (!fs.existsSync(pluginFolder)) { + fs.mkdirSync(pluginFolder); + console.log("[Mod loader] Created missing plugin folder"); + } + + // Add more of these later if needed! + let URLs = [ + "https://armcord.app/loader.zip", + "https://armcord.vercel.app/loader.zip", + "https://raw.githubusercontent.com/ArmCord/website/new/public/loader.zip" + ]; + let loaderZip: any; + + while (true) { + if (URLs.length <= 0) throw new Error(`unexpected response ${loaderZip.statusText}`); + + try { + loaderZip = await fetch(URLs[0]); + } catch (err) { + console.log("[Mod loader] Failed to download. Links left to try: " + (URLs.length - 1)); + URLs.splice(0, 1); + + continue; + } + + break; + } + + await streamPipeline(loaderZip.body, fs.createWriteStream(zipPath)); + await extract(zipPath, {dir: path.join(app.getPath("userData"), "plugins")}); + + updateModInstallState(); + } catch (e) { + console.log("[Mod loader] Failed to install modloader"); + console.error(e); + dialog.showErrorBox( + "Oops, something went wrong.", + "ArmCord couldn't install internal mod loader, please check if you have stable internet connection and restart the app. If this issue persists, report it on the support server/Github issues." + ); + } +} diff --git a/src/extensions/plugin.ts b/src/discord/extensions/plugin.ts similarity index 100% rename from src/extensions/plugin.ts rename to src/discord/extensions/plugin.ts diff --git a/src/ipc.ts b/src/discord/ipc.ts similarity index 91% rename from src/ipc.ts rename to src/discord/ipc.ts index 53ed8f2..e37aefa 100644 --- a/src/ipc.ts +++ b/src/discord/ipc.ts @@ -1,28 +1,20 @@ //ipc stuff import {app, clipboard, desktopCapturer, ipcMain, nativeImage, shell} from "electron"; import {mainWindow} from "./window"; -import { - Settings, - getConfig, - getConfigLocation, - getDisplayVersion, - getLang, - getLangName, - getVersion, - modInstallState, - packageVersion, - setConfigBulk, - setLang, - sleep -} from "./utils"; -import {customTitlebar} from "./main"; -import {createSettingsWindow} from "./settings/main"; + import os from "os"; import fs from "fs"; import path from "path"; -import {createTManagerWindow} from "./themeManager/main"; -import {splashWindow} from "./splash/main"; -import {createKeybindWindow} from "./keybindMaker/main"; +import {getConfig, setConfigBulk, getConfigLocation, Settings} from "../common/config"; +import {setLang, getLang, getLangName} from "../common/lang"; +import {sleep} from "../common/sleep"; +import {getVersion, getDisplayVersion, packageVersion} from "../common/version"; +import {customTitlebar} from "../main"; +import {createSettingsWindow} from "../settings/main"; +import {splashWindow} from "../splash/main"; +import {createTManagerWindow} from "../themeManager/main"; +import {modInstallState} from "./extensions/mods"; + const userDataPath = app.getPath("userData"); const storagePath = path.join(userDataPath, "/storage/"); const themesPath = path.join(userDataPath, "/themes/"); @@ -136,9 +128,6 @@ export function registerIpc(): void { ipcMain.on("openManagerWindow", () => { createTManagerWindow(); }); - ipcMain.on("openKeybindWindow", () => { - createKeybindWindow(); - }); ipcMain.on("setting-armcordCSP", async (event) => { if (await getConfig("armcordCSP")) { event.returnValue = true; diff --git a/src/menu.ts b/src/discord/menu.ts similarity index 98% rename from src/menu.ts rename to src/discord/menu.ts index bc1e96c..214f478 100644 --- a/src/menu.ts +++ b/src/discord/menu.ts @@ -1,6 +1,6 @@ import {BrowserWindow, Menu, app, clipboard} from "electron"; import {mainWindow} from "./window"; -import {createSettingsWindow} from "./settings/main"; +import {createSettingsWindow} from "../settings/main"; function paste(contents: any): void { const contentTypes = clipboard.availableFormats().toString(); diff --git a/src/preload/bridge.ts b/src/discord/preload/bridge.ts similarity index 100% rename from src/preload/bridge.ts rename to src/discord/preload/bridge.ts diff --git a/src/preload/capturer.ts b/src/discord/preload/capturer.ts similarity index 97% rename from src/preload/capturer.ts rename to src/discord/preload/capturer.ts index 04852d6..eca5dd2 100644 --- a/src/preload/capturer.ts +++ b/src/discord/preload/capturer.ts @@ -1,8 +1,8 @@ //Fixed context isolation version https://github.com/getferdi/ferdi/blob/develop/src/webview/screenshare.ts //original https://github.com/electron/electron/issues/16513#issuecomment-602070250 -import {addScript, addStyle} from "../utils"; import fs from "fs"; import path from "path"; +import {addScript, addStyle} from "../../common/dom"; const CANCEL_ID = "desktop-capturer-selection__cancel"; diff --git a/src/preload/mobile.ts b/src/discord/preload/mobile.ts similarity index 92% rename from src/preload/mobile.ts rename to src/discord/preload/mobile.ts index f9d8c03..20964d3 100644 --- a/src/preload/mobile.ts +++ b/src/discord/preload/mobile.ts @@ -1,4 +1,4 @@ -import {addStyle} from "../utils"; +import {addStyle} from "../../common/dom"; import * as fs from "fs"; import * as path from "path"; export function injectMobileStuff(): void { diff --git a/src/preload/optimizer.ts b/src/discord/preload/optimizer.ts similarity index 100% rename from src/preload/optimizer.ts rename to src/discord/preload/optimizer.ts diff --git a/src/preload/preload.ts b/src/discord/preload/preload.ts similarity index 87% rename from src/preload/preload.ts rename to src/discord/preload/preload.ts index 10aa8b0..83b9830 100644 --- a/src/preload/preload.ts +++ b/src/discord/preload/preload.ts @@ -4,10 +4,11 @@ import "./settings"; import {ipcRenderer} from "electron"; import * as fs from "fs"; import * as path from "path"; -import {addScript, addStyle, sleep} from "../utils"; import {injectMobileStuff} from "./mobile"; import {fixTitlebar, injectTitlebar} from "./titlebar"; import {injectSettings} from "./settings"; +import {addStyle, addScript} from "../../common/dom"; +import {sleep} from "../../common/sleep"; window.localStorage.setItem("hideNag", "true"); @@ -56,10 +57,10 @@ sleep(5000).then(async () => { })(); `); if (ipcRenderer.sendSync("disableAutogain")) { - addScript(fs.readFileSync(path.join(__dirname, "../", "/content/js/disableAutogain.js"), "utf8")); + addScript(fs.readFileSync(path.join(__dirname, "../", "../", "/content/js/disableAutogain.js"), "utf8")); } - addScript(fs.readFileSync(path.join(__dirname, "../", "/content/js/rpc.js"), "utf8")); - const cssPath = path.join(__dirname, "../", "/content/css/discord.css"); + addScript(fs.readFileSync(path.join(__dirname, "../", "../", "/content/js/rpc.js"), "utf8")); + const cssPath = path.join(__dirname, "../", "../", "/content/css/discord.css"); addStyle(fs.readFileSync(cssPath, "utf8")); await updateLang(); }); @@ -107,21 +108,16 @@ setInterval(() => { const acSettings = advanced.cloneNode(true) as HTMLElement; const tManager = advanced.cloneNode(true) as HTMLElement; const fQuit = advanced.cloneNode(true) as HTMLElement; - const keybindMaker = advanced.cloneNode(true) as HTMLElement; acSettings.textContent = "ArmCord Settings"; acSettings.id = "acSettings"; acSettings.onclick = () => injectSettings(); tManager.textContent = "Themes"; tManager.id = "acThemes"; tManager.onclick = () => ipcRenderer.send("openManagerWindow"); - keybindMaker.textContent = "Global keybinds"; - keybindMaker.id = "acKeybinds"; - keybindMaker.onclick = () => ipcRenderer.send("openKeybindWindow"); fQuit.textContent = "Force Quit"; fQuit.id = "acForceQuit"; fQuit.onclick = () => ipcRenderer.send("win-quit"); advanced.insertAdjacentElement("afterend", acSettings); advanced.insertAdjacentElement("afterend", tManager); - advanced.insertAdjacentElement("afterend", keybindMaker); advanced.insertAdjacentElement("afterend", fQuit); }, 1000); diff --git a/src/preload/settings.ts b/src/discord/preload/settings.ts similarity index 75% rename from src/preload/settings.ts rename to src/discord/preload/settings.ts index 6060600..846cbd1 100644 --- a/src/preload/settings.ts +++ b/src/discord/preload/settings.ts @@ -1,14 +1,15 @@ import * as path from "path"; import * as fs from "fs"; -import {addStyle} from "../utils"; +import {addStyle} from "../../common/dom"; import {WebviewTag} from "electron"; -var webview = ``; + "../", + "/settings/settings.html" +)}" preload="${path.join("file://", __dirname, "../", "../", "/settings/preload.js")}" id="inAppSettings">`; export function injectSettings() { document.getElementById("webviewSettingsContainer")!.innerHTML = webview; diff --git a/src/preload/titlebar.ts b/src/discord/preload/titlebar.ts similarity index 94% rename from src/preload/titlebar.ts rename to src/discord/preload/titlebar.ts index ce76ae4..cd14cc6 100644 --- a/src/preload/titlebar.ts +++ b/src/discord/preload/titlebar.ts @@ -1,5 +1,5 @@ import {ipcRenderer} from "electron"; -import {addStyle} from "../utils"; +import {addStyle} from "../../common/dom"; import * as fs from "fs"; import * as path from "path"; import os from "os"; @@ -21,8 +21,8 @@ export function injectTitlebar(): void { } else { document.getElementById("app-mount")!.prepend(elem); } - const titlebarcssPath = path.join(__dirname, "../", "/content/css/titlebar.css"); - const wordmarkcssPath = path.join(__dirname, "../", "/content/css/logos.css"); + const titlebarcssPath = path.join(__dirname, "../", "../", "/content/css/titlebar.css"); + const wordmarkcssPath = path.join(__dirname, "../", "../", "/content/css/logos.css"); addStyle(fs.readFileSync(titlebarcssPath, "utf8")); addStyle(fs.readFileSync(wordmarkcssPath, "utf8")); document.body.setAttribute("customTitlebar", ""); diff --git a/src/screenshare/main.ts b/src/discord/screenshare/main.ts similarity index 98% rename from src/screenshare/main.ts rename to src/discord/screenshare/main.ts index 155fccc..68de431 100644 --- a/src/screenshare/main.ts +++ b/src/discord/screenshare/main.ts @@ -1,6 +1,6 @@ import {BrowserWindow, MessageBoxOptions, desktopCapturer, dialog, ipcMain, session} from "electron"; import path from "path"; -import {iconPath} from "../main"; +import {iconPath} from "../../main"; let capturerWindow: BrowserWindow; function showAudioDialog(): boolean { const options: MessageBoxOptions = { diff --git a/src/screenshare/picker.html b/src/discord/screenshare/picker.html similarity index 100% rename from src/screenshare/picker.html rename to src/discord/screenshare/picker.html diff --git a/src/screenshare/preload.ts b/src/discord/screenshare/preload.ts similarity index 100% rename from src/screenshare/preload.ts rename to src/discord/screenshare/preload.ts diff --git a/src/window.ts b/src/discord/window.ts similarity index 97% rename from src/window.ts rename to src/discord/window.ts index 9eba9a8..ee2477f 100644 --- a/src/window.ts +++ b/src/discord/window.ts @@ -4,22 +4,15 @@ // I'm sorry for this mess but I'm not sure how to fix it. import {BrowserWindow, MessageBoxOptions, app, dialog, nativeImage, shell} from "electron"; import path from "path"; -import { - contentPath, - firstRun, - getConfig, - getWindowState, - registerGlobalKeybinds, - setConfig, - setWindowState -} from "./utils"; import {registerIpc} from "./ipc"; import {setMenu} from "./menu"; import * as fs from "fs"; import contextMenu from "electron-context-menu"; import os from "os"; -import {tray} from "./tray"; -import {iconPath} from "./main"; +import {tray} from "../tray"; +import {iconPath} from "../main"; +import {getConfig, setConfig, firstRun} from "../common/config"; +import {getWindowState, setWindowState} from "../common/windowState"; export let mainWindow: BrowserWindow; export let inviteWindow: BrowserWindow; let forceQuit = false; @@ -196,7 +189,6 @@ async function doAfterDefiningTheWindow(): Promise { if (!fs.existsSync(`${userDataPath}/disabled.txt`)) { fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), ""); } - registerGlobalKeybinds(); mainWindow.webContents.on("did-finish-load", () => { fs.readdirSync(themesFolder).forEach((file) => { try { @@ -265,7 +257,6 @@ async function doAfterDefiningTheWindow(): Promise { mainWindow.on("unmaximize", () => { mainWindow.webContents.executeJavaScript(`document.body.removeAttribute("isMaximized");`); }); - console.log(contentPath); if ((await getConfig("inviteWebsocket")) == true) { require("arrpc"); //await startServer(); @@ -274,7 +265,7 @@ async function doAfterDefiningTheWindow(): Promise { mainWindow.close(); } //loadURL broke for no good reason after E28 - mainWindow.loadFile(`${__dirname}/splash/redirect.html`); + mainWindow.loadFile(`${__dirname}/../splash/redirect.html`); if (await getConfig("skipSplash")) { mainWindow.show(); diff --git a/src/keybindMaker/main.ts b/src/keybindMaker/main.ts deleted file mode 100644 index 21e1405..0000000 --- a/src/keybindMaker/main.ts +++ /dev/null @@ -1,66 +0,0 @@ -import {BrowserWindow, globalShortcut, ipcMain, shell} from "electron"; -import path from "path"; -import {getConfig, registerGlobalKeybinds, setConfig} from "../utils"; -let keybindWindow: BrowserWindow; -let instance = 0; - -export function createKeybindWindow(): void { - console.log("Creating keybind maker window."); - instance += 1; - if (instance > 1) { - if (keybindWindow) { - keybindWindow.show(); - keybindWindow.restore(); - } - } else { - keybindWindow = new BrowserWindow({ - width: 720, - height: 670, - title: `ArmCord Global Keybinds Maker`, - darkTheme: true, - frame: true, - backgroundColor: "#2f3136", - autoHideMenuBar: true, - webPreferences: { - sandbox: false, - preload: path.join(__dirname, "preload.js") - } - }); - async function makerLoadPage(): Promise { - globalShortcut.unregisterAll(); - keybindWindow.loadURL(`file://${__dirname}/maker.html`); - } - keybindWindow.webContents.setWindowOpenHandler(({url}) => { - shell.openExternal(url); - return {action: "deny"}; - }); - ipcMain.on("addKeybind", async (_event, keybind) => { - var keybinds = await getConfig("keybinds"); - keybind.replace(" ", "Space"); - if (keybinds.includes(keybind)) return; - keybinds.push(keybind); - await setConfig("keybinds", keybinds); - keybindWindow.webContents.reload(); - }); - ipcMain.on("removeKeybind", async (_event, keybind) => { - var keybinds = await getConfig("keybinds"); - const index = keybinds.indexOf(keybind); - keybinds.splice(index, 1); - await setConfig("keybinds", keybinds); - keybindWindow.webContents.reload(); - }); - keybindWindow.webContents.on("did-finish-load", async () => { - for (const keybind of await getConfig("keybinds")) { - console.log(keybind); - keybindWindow.webContents.send("keybindCombo", keybind); - } - }); - keybindWindow.on("close", () => { - registerGlobalKeybinds(); - }); - makerLoadPage(); - keybindWindow.on("close", () => { - instance = 0; - }); - } -} diff --git a/src/keybindMaker/maker.html b/src/keybindMaker/maker.html deleted file mode 100644 index 325c0fe..0000000 --- a/src/keybindMaker/maker.html +++ /dev/null @@ -1,392 +0,0 @@ - - - - - - - -

Global keybinds are disabled while this window is open.

- -
-
- - - diff --git a/src/keybindMaker/preload.ts b/src/keybindMaker/preload.ts deleted file mode 100644 index 523b529..0000000 --- a/src/keybindMaker/preload.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {contextBridge, ipcRenderer} from "electron"; -import {sleep} from "../utils"; -contextBridge.exposeInMainWorld("manager", { - add: (keybindName: string) => ipcRenderer.send("addKeybind", keybindName), - remove: (keybindName: string) => ipcRenderer.send("removeKeybind", keybindName) -}); -ipcRenderer.on("keybindCombo", (_event, keybindName) => { - sleep(1000); - console.log(keybindName); - let e = document.getElementById("cardBox"); - var keys = keybindName.split("+"); - var id = keybindName.replace("+", ""); - var html = ""; - for (var key in keys) { - html += `${keys[key]}`; - } - e?.insertAdjacentHTML( - "beforeend", - ` -
-
- ${html} - - -
-
- ` - ); - (document.getElementById(id) as HTMLInputElement)!.checked = true; - (document.getElementById(id) as HTMLInputElement)!.addEventListener("input", function (evt) { - ipcRenderer.send("removeKeybind", keybindName); - }); -}); -sleep(3000).then(() => { - document.getElementById("warning")!.style.display = "none"; -}); diff --git a/src/main.ts b/src/main.ts index 0f4f279..50068c6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,27 +1,28 @@ // Modules to control application life and create native browser window import {BrowserWindow, app, crashReporter, session} from "electron"; import "v8-compile-cache"; -import { - Settings, - checkForDataFolder, - checkIfConfigExists, - firstRun, - checkIfConfigIsBroken, - getConfig, - getConfigSync, - injectElectronFlags, - installModLoader, - setConfig, - setLang -} from "./utils"; -import "./extensions/mods"; +import "./discord/extensions/csp"; import "./tray"; -import {createCustomWindow, createNativeWindow, createTransparentWindow} from "./window"; +import fs from "fs"; +import {createCustomWindow, createNativeWindow, createTransparentWindow} from "./discord/window"; import path from "path"; import {createTManagerWindow} from "./themeManager/main"; import {createSplashWindow} from "./splash/main"; import {createSetupWindow} from "./setup/main"; -import {createKeybindWindow} from "./keybindMaker/main"; +import { + setConfig, + getConfigSync, + checkForDataFolder, + checkIfConfigExists, + checkIfConfigIsBroken, + getConfig, + firstRun, + Settings, + getConfigLocation +} from "./common/config"; +import {injectElectronFlags} from "./common/flags"; +import {setLang} from "./common/lang"; +import {installModLoader} from "./discord/extensions/mods"; export let iconPath: string; export let settings: any; export let customTitlebar: boolean; @@ -47,10 +48,6 @@ async function args(): Promise { app.whenReady().then(async () => { createTManagerWindow(); }); - } else if (args == "keybinds") { - app.whenReady().then(async () => { - createKeybindWindow(); - }); } } args(); // i want my top level awaits @@ -84,6 +81,7 @@ if (!app.requestSingleInstanceLock() && getConfigSync("multiInstance") == (false checkIfConfigExists(); checkIfConfigIsBroken(); injectElectronFlags(); + console.log("[Config Manager] Current config: " + fs.readFileSync(getConfigLocation(), "utf-8")); app.whenReady().then(async () => { if ((await getConfig("customIcon")) !== undefined ?? null) { iconPath = await getConfig("customIcon"); diff --git a/src/settings/main.ts b/src/settings/main.ts index d91994d..78cc5b8 100644 --- a/src/settings/main.ts +++ b/src/settings/main.ts @@ -1,7 +1,7 @@ import {BrowserWindow, app, shell} from "electron"; -import {getDisplayVersion} from "../utils"; import path from "path"; import fs from "fs"; +import {getDisplayVersion} from "../common/version"; let settingsWindow: BrowserWindow; let instance = 0; diff --git a/src/setup/main.ts b/src/setup/main.ts index f57935e..4f98583 100644 --- a/src/setup/main.ts +++ b/src/setup/main.ts @@ -2,7 +2,8 @@ import {BrowserWindow, app, ipcMain} from "electron"; import path from "path"; import * as fs from "fs"; import {iconPath} from "../main"; -import {Settings, getConfigLocation, setConfigBulk} from "../utils"; +import {setConfigBulk, getConfigLocation, Settings} from "../common/config"; + let setupWindow: BrowserWindow; export function createSetupWindow(): void { setupWindow = new BrowserWindow({ diff --git a/src/setup/preload.ts b/src/setup/preload.ts index 96f1174..d7b8313 100644 --- a/src/setup/preload.ts +++ b/src/setup/preload.ts @@ -1,5 +1,6 @@ import {contextBridge, ipcRenderer} from "electron"; -import {injectTitlebar} from "../preload/titlebar"; +import {injectTitlebar} from "../discord/preload/titlebar"; + injectTitlebar(); contextBridge.exposeInMainWorld("armcordinternal", { restart: () => ipcRenderer.send("restart"), diff --git a/src/splash/main.ts b/src/splash/main.ts index 948e0b3..b86609e 100644 --- a/src/splash/main.ts +++ b/src/splash/main.ts @@ -19,5 +19,5 @@ export async function createSplashWindow(): Promise { preload: path.join(__dirname, "preload.js") } }); - splashWindow.loadURL(`file://${__dirname}/splash.html`); + splashWindow.loadFile(path.join(__dirname, "splash.html")); } diff --git a/src/themeManager/main.ts b/src/themeManager/main.ts index b672e9d..e88eb28 100644 --- a/src/themeManager/main.ts +++ b/src/themeManager/main.ts @@ -1,8 +1,8 @@ import {BrowserWindow, app, dialog, ipcMain, shell} from "electron"; -import {sleep} from "../utils"; import path from "path"; import fs from "fs"; -import {createInviteWindow, mainWindow} from "../window"; +import {sleep} from "../common/sleep"; +import {createInviteWindow, mainWindow} from "../discord/window"; let themeWindow: BrowserWindow; let instance = 0; interface ThemeManifest { diff --git a/src/themeManager/preload.ts b/src/themeManager/preload.ts index 2750486..3c4ca31 100644 --- a/src/themeManager/preload.ts +++ b/src/themeManager/preload.ts @@ -1,5 +1,5 @@ import {ipcRenderer, contextBridge} from "electron"; -import {sleep} from "../utils"; +import {sleep} from "../common/sleep"; contextBridge.exposeInMainWorld("themes", { install: (url: string) => ipcRenderer.send("installBDTheme", url), uninstall: (id: string) => ipcRenderer.send("uninstallTheme", id) diff --git a/src/tray.ts b/src/tray.ts index c47a75c..81ef4b2 100644 --- a/src/tray.ts +++ b/src/tray.ts @@ -1,9 +1,10 @@ import * as fs from "fs"; import {Menu, MessageBoxOptions, Tray, app, dialog, nativeImage} from "electron"; -import {createInviteWindow, mainWindow} from "./window"; -import {getConfig, getConfigLocation, getDisplayVersion, setConfig} from "./utils"; +import {createInviteWindow, mainWindow} from "./discord/window"; import * as path from "path"; import {createSettingsWindow} from "./settings/main"; +import {getConfig, getConfigLocation, setConfig} from "./common/config"; +import {getDisplayVersion} from "./common/version"; export let tray: any = null; let trayIcon = "ac_plug_colored"; app.whenReady().then(async () => { diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index 637648d..0000000 --- a/src/utils.ts +++ /dev/null @@ -1,478 +0,0 @@ -import * as fs from "fs"; -import {app, dialog, globalShortcut} from "electron"; -import path from "path"; -import fetch from "cross-fetch"; -import extract from "extract-zip"; -import util from "util"; -const streamPipeline = util.promisify(require("stream").pipeline); -export let firstRun: boolean; -export let contentPath: string; -export let transparency: boolean; -//utility functions that are used all over the codebase or just too obscure to be put in the file used in -export function addStyle(styleString: string): void { - const style = document.createElement("style"); - style.textContent = styleString; - document.head.append(style); -} - -export function addScript(scriptString: string): void { - let script = document.createElement("script"); - script.textContent = scriptString; - document.body.append(script); -} - -export async function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -export async function checkIfConfigIsBroken(): Promise { - try { - let settingsData = fs.readFileSync(getConfigLocation(), "utf-8"); - JSON.parse(settingsData); - console.log("Config is fine"); - } catch (e) { - console.error(e); - console.log("Detected a corrupted config"); - setup(); - dialog.showErrorBox( - "Oops, something went wrong.", - "ArmCord has detected that your configuration file is corrupted, please restart the app and set your settings again. If this issue persists, report it on the support server/Github issues." - ); - } - try { - let windowData = fs.readFileSync(getWindowStateLocation(), "utf-8"); - JSON.parse(windowData); - console.log("Window config is fine"); - } catch (e) { - console.error(e); - fs.writeFileSync(getWindowStateLocation(), "{}", "utf-8"); - console.log("Detected a corrupted window config"); - } -} - -export function setup(): void { - console.log("Setting up temporary ArmCord settings."); - const defaults: Settings = { - windowStyle: "default", - channel: "stable", - armcordCSP: true, - minimizeToTray: true, - keybinds: [], - multiInstance: false, - mods: "none", - spellcheck: true, - performanceMode: "none", - skipSplash: false, - inviteWebsocket: true, - startMinimized: false, - dynamicIcon: false, - tray: true, - customJsBundle: "https://armcord.app/placeholder.js", - customCssBundle: "https://armcord.app/placeholder.css", - disableAutogain: false, - useLegacyCapturer: false, - mobileMode: false, - trayIcon: "default", - doneSetup: false, - clientName: "ArmCord", - customIcon: path.join(__dirname, "../", "/assets/desktop.png") - }; - setConfigBulk({ - ...defaults - }); -} - -//Get the version value from the "package.json" file -export const packageVersion = require("../package.json").version; - -export function getVersion(): string { - return packageVersion; -} -export function getDisplayVersion(): string { - //Checks if the app version # has 4 sections (3.1.0.0) instead of 3 (3.1.0) / Shitty way to check if Kernel Mod is installed - if ((app.getVersion() == packageVersion) == false) { - if ((app.getVersion() == process.versions.electron) == true) { - return `Dev Build (${packageVersion})`; - } else { - return `${packageVersion} [Modified]`; - } - } else { - return packageVersion; - } -} -export async function injectJS(inject: string): Promise { - const js = await (await fetch(`${inject}`)).text(); - - const el = document.createElement("script"); - - el.appendChild(document.createTextNode(js)); - - document.body.appendChild(el); -} -export async function injectElectronFlags(): Promise { - // MIT License - - // Copyright (c) 2022 GooseNest - - // Permission is hereby granted, free of charge, to any person obtaining a copy - // of this software and associated documentation files (the "Software"), to deal - // in the Software without restriction, including without limitation the rights - // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - // copies of the Software, and to permit persons to whom the Software is - // furnished to do so, subject to the following conditions: - - // The above copyright notice and this permission notice shall be included in all - // copies or substantial portions of the Software. - - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - // SOFTWARE. - const presets = { - performance: `--enable-gpu-rasterization --enable-zero-copy --ignore-gpu-blocklist --enable-hardware-overlays=single-fullscreen,single-on-top,underlay --enable-features=EnableDrDc,CanvasOopRasterization,BackForwardCache:TimeToLiveInBackForwardCacheInSeconds/300/should_ignore_blocklists/true/enable_same_site/true,ThrottleDisplayNoneAndVisibilityHiddenCrossOriginIframes,UseSkiaRenderer,WebAssemblyLazyCompilation --disable-features=Vulkan --force_high_performance_gpu`, // Performance - battery: "--enable-features=TurnOffStreamingMediaCachingOnBattery --force_low_power_gpu", // Known to have better battery life for Chromium? - vaapi: "--ignore-gpu-blocklist --enable-features=VaapiVideoDecoder --enable-gpu-rasterization --enable-zero-copy --force_high_performance_gpu --use-gl=desktop --disable-features=UseChromeOSDirectVideoDecoder" - }; - switch (await getConfig("performanceMode")) { - case "performance": - console.log("Performance mode enabled"); - app.commandLine.appendArgument(presets.performance); - break; - case "battery": - console.log("Battery mode enabled"); - app.commandLine.appendArgument(presets.battery); - break; - default: - console.log("No performance modes set"); - } - if ((await getConfig("windowStyle")) == "transparent" && process.platform === "win32") { - transparency = true; - } -} -export async function setLang(language: string): Promise { - const langConfigFile = `${path.join(app.getPath("userData"), "/storage/")}lang.json`; - if (!fs.existsSync(langConfigFile)) { - fs.writeFileSync(langConfigFile, "{}", "utf-8"); - } - let rawdata = fs.readFileSync(langConfigFile, "utf-8"); - let parsed = JSON.parse(rawdata); - parsed.lang = language; - let toSave = JSON.stringify(parsed, null, 4); - fs.writeFileSync(langConfigFile, toSave, "utf-8"); -} -let language: string; -export async function getLang(object: string): Promise { - if (language == undefined) { - try { - const userDataPath = app.getPath("userData"); - const storagePath = path.join(userDataPath, "/storage/"); - const langConfigFile = `${storagePath}lang.json`; - let rawdata = fs.readFileSync(langConfigFile, "utf-8"); - let parsed = JSON.parse(rawdata); - language = parsed.lang; - } catch (_e) { - console.log("Language config file doesn't exist. Fallback to English."); - language = "en-US"; - } - } - if (language.length == 2) { - language = `${language}-${language.toUpperCase()}`; - } - let langPath = path.join(__dirname, "../", `/assets/lang/${language}.json`); - if (!fs.existsSync(langPath)) { - langPath = path.join(__dirname, "../", "/assets/lang/en-US.json"); - } - let rawdata = fs.readFileSync(langPath, "utf-8"); - let parsed = JSON.parse(rawdata); - if (parsed[object] == undefined) { - console.log(`${object} is undefined in ${language}`); - langPath = path.join(__dirname, "../", "/assets/lang/en-US.json"); - rawdata = fs.readFileSync(langPath, "utf-8"); - parsed = JSON.parse(rawdata); - return parsed[object]; - } else { - return parsed[object]; - } -} -export async function getLangName(): Promise { - if (language == undefined) { - try { - const userDataPath = app.getPath("userData"); - const storagePath = path.join(userDataPath, "/storage/"); - const langConfigFile = `${storagePath}lang.json`; - let rawdata = fs.readFileSync(langConfigFile, "utf-8"); - let parsed = JSON.parse(rawdata); - language = parsed.lang; - } catch (_e) { - console.log("Language config file doesn't exist. Fallback to English."); - language = "en-US"; - } - } - if (language.length == 2) { - language = `${language}-${language.toUpperCase()}`; - } - return language; -} -//ArmCord Window State manager -export interface WindowState { - width: number; - height: number; - x: number; - y: number; - isMaximized: boolean; -} -function getWindowStateLocation() { - const userDataPath = app.getPath("userData"); - const storagePath = path.join(userDataPath, "/storage/"); - return `${storagePath}window.json`; -} -export async function setWindowState(object: WindowState): Promise { - const userDataPath = app.getPath("userData"); - const storagePath = path.join(userDataPath, "/storage/"); - const saveFile = `${storagePath}window.json`; - let toSave = JSON.stringify(object, null, 4); - fs.writeFileSync(saveFile, toSave, "utf-8"); -} -export async function getWindowState(object: K): Promise { - const userDataPath = app.getPath("userData"); - const storagePath = path.join(userDataPath, "/storage/"); - const settingsFile = `${storagePath}window.json`; - if (!fs.existsSync(settingsFile)) { - fs.writeFileSync(settingsFile, "{}", "utf-8"); - } - let rawdata = fs.readFileSync(settingsFile, "utf-8"); - let returndata = JSON.parse(rawdata); - console.log(`[Window state manager] ${returndata}`); - return returndata[object]; -} -//ArmCord Settings/Storage manager - -export function checkForDataFolder(): void { - const dataPath = path.join(path.dirname(app.getPath("exe")), "armcord-data"); - if (fs.existsSync(dataPath) && fs.statSync(dataPath).isDirectory()) { - console.log("Found armcord-data folder. Running in portable mode."); - app.setPath("userData", dataPath); - } -} - -export interface Settings { - // Referenced for detecting a broken config. - "0"?: string; - // Referenced once for disabling mod updating. - noBundleUpdates?: boolean; - // Only used for external url warning dialog. - ignoreProtocolWarning?: boolean; - customIcon: string; - windowStyle: string; - channel: string; - armcordCSP: boolean; - minimizeToTray: boolean; - multiInstance: boolean; - spellcheck: boolean; - mods: string; - dynamicIcon: boolean; - mobileMode: boolean; - skipSplash: boolean; - performanceMode: string; - customJsBundle: RequestInfo | URL; - customCssBundle: RequestInfo | URL; - startMinimized: boolean; - useLegacyCapturer: boolean; - tray: boolean; - keybinds: Array; - inviteWebsocket: boolean; - disableAutogain: boolean; - trayIcon: string; - doneSetup: boolean; - clientName: string; -} -export function getConfigLocation(): string { - const userDataPath = app.getPath("userData"); - const storagePath = path.join(userDataPath, "/storage/"); - return `${storagePath}settings.json`; -} -export async function getConfig(object: K): Promise { - let rawdata = fs.readFileSync(getConfigLocation(), "utf-8"); - let returndata = JSON.parse(rawdata); - console.log(`[Config manager] ${object}: ${returndata[object]}`); - return returndata[object]; -} -export function getConfigSync(object: K) { - let rawdata = fs.readFileSync(getConfigLocation(), "utf-8"); - let returndata = JSON.parse(rawdata); - console.log(`[Config manager] ${object}: ${returndata[object]}`); - return returndata[object]; -} -export async function setConfig(object: K, toSet: Settings[K]): Promise { - let rawdata = fs.readFileSync(getConfigLocation(), "utf-8"); - let parsed = JSON.parse(rawdata); - parsed[object] = toSet; - let toSave = JSON.stringify(parsed, null, 4); - fs.writeFileSync(getConfigLocation(), toSave, "utf-8"); -} -export async function setConfigBulk(object: Settings): Promise { - let existingData = {}; - try { - const existingDataBuffer = fs.readFileSync(getConfigLocation(), "utf-8"); - existingData = JSON.parse(existingDataBuffer.toString()); - } catch (error) { - // Ignore errors when the file doesn't exist or parsing fails - } - // Merge the existing data with the new data - const mergedData = {...existingData, ...object}; - // Write the merged data back to the file - const toSave = JSON.stringify(mergedData, null, 4); - fs.writeFileSync(getConfigLocation(), toSave, "utf-8"); -} -export async function checkIfConfigExists(): Promise { - const userDataPath = app.getPath("userData"); - const storagePath = path.join(userDataPath, "/storage/"); - const settingsFile = `${storagePath}settings.json`; - - if (!fs.existsSync(settingsFile)) { - if (!fs.existsSync(storagePath)) { - fs.mkdirSync(storagePath); - console.log("Created missing storage folder"); - } - console.log("First run of the ArmCord. Starting setup."); - setup(); - firstRun = true; - } else if ((await getConfig("doneSetup")) == false) { - console.log("First run of the ArmCord. Starting setup."); - setup(); - firstRun = true; - } else { - console.log("ArmCord has been run before. Skipping setup."); - } -} - -// Mods -async function updateModBundle(): Promise { - if ((await getConfig("noBundleUpdates")) == undefined ?? false) { - try { - console.log("Downloading mod bundle"); - const distFolder = `${app.getPath("userData")}/plugins/loader/dist/`; - while (!fs.existsSync(distFolder)) { - //waiting - } - let name: string = await getConfig("mods"); - if (name == "custom") { - // aspy fix - let bundle: string = await (await fetch(await getConfig("customJsBundle"))).text(); - fs.writeFileSync(`${distFolder}bundle.js`, bundle, "utf-8"); - let css: string = await (await fetch(await getConfig("customCssBundle"))).text(); - fs.writeFileSync(`${distFolder}bundle.css`, css, "utf-8"); - } else { - const clientMods = { - vencord: "https://github.com/Vendicated/Vencord/releases/download/devbuild/browser.js", - shelter: "https://raw.githubusercontent.com/uwu/shelter-builds/main/shelter.js" - }; - const clientModsCss = { - vencord: "https://github.com/Vendicated/Vencord/releases/download/devbuild/browser.css", - shelter: "https://armcord.app/placeholder.css" - }; - console.log(clientMods[name as keyof typeof clientMods]); - let bundle: string = await (await fetch(clientMods[name as keyof typeof clientMods])).text(); - fs.writeFileSync(`${distFolder}bundle.js`, bundle, "utf-8"); - let css: string = await (await fetch(clientModsCss[name as keyof typeof clientModsCss])).text(); - fs.writeFileSync(`${distFolder}bundle.css`, css, "utf-8"); - } - } catch (e) { - console.log("[Mod loader] Failed to install mods"); - console.error(e); - dialog.showErrorBox( - "Oops, something went wrong.", - "ArmCord couldn't install mods, please check if you have stable internet connection and restart the app. If this issue persists, report it on the support server/Github issues." - ); - } - } else { - console.log("[Mod loader] Skipping mod bundle update"); - } -} - -export let modInstallState: string; -export function updateModInstallState() { - modInstallState = "modDownload"; - - updateModBundle(); - import("./extensions/plugin"); - - modInstallState = "done"; -} - -export async function installModLoader(): Promise { - if ((await getConfig("mods")) == "none") { - modInstallState = "none"; - fs.rmSync(`${app.getPath("userData")}/plugins/loader`, {recursive: true, force: true}); - - import("./extensions/plugin"); - console.log("[Mod loader] Skipping"); - - return; - } - - const pluginFolder = `${app.getPath("userData")}/plugins/`; - if (fs.existsSync(`${pluginFolder}loader`) && fs.existsSync(`${pluginFolder}loader/dist/bundle.css`)) { - updateModInstallState(); - return; - } - - try { - fs.rmSync(`${app.getPath("userData")}/plugins/loader`, {recursive: true, force: true}); - modInstallState = "installing"; - - let zipPath = `${app.getPath("temp")}/loader.zip`; - - if (!fs.existsSync(pluginFolder)) { - fs.mkdirSync(pluginFolder); - console.log("[Mod loader] Created missing plugin folder"); - } - - // Add more of these later if needed! - let URLs = [ - "https://armcord.app/loader.zip", - "https://armcord.vercel.app/loader.zip", - "https://raw.githubusercontent.com/ArmCord/website/new/public/loader.zip" - ]; - let loaderZip: any; - - while (true) { - if (URLs.length <= 0) throw new Error(`unexpected response ${loaderZip.statusText}`); - - try { - loaderZip = await fetch(URLs[0]); - } catch (err) { - console.log("[Mod loader] Failed to download. Links left to try: " + (URLs.length - 1)); - URLs.splice(0, 1); - - continue; - } - - break; - } - - await streamPipeline(loaderZip.body, fs.createWriteStream(zipPath)); - await extract(zipPath, {dir: path.join(app.getPath("userData"), "plugins")}); - - updateModInstallState(); - } catch (e) { - console.log("[Mod loader] Failed to install modloader"); - console.error(e); - dialog.showErrorBox( - "Oops, something went wrong.", - "ArmCord couldn't install internal mod loader, please check if you have stable internet connection and restart the app. If this issue persists, report it on the support server/Github issues." - ); - } -} - -export async function registerGlobalKeybinds(): Promise { - const keybinds = await getConfig("keybinds"); - keybinds.forEach((keybind) => { - globalShortcut.register(keybind, () => { - console.log(keybind); - }); - }); -} diff --git a/tsconfig.json b/tsconfig.json index 8d5bd25..9a5836c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ -// Reference: https://www.typescriptlang.org/tsconfig { + // Reference: https://www.typescriptlang.org/tsconfig "include": ["src/**/*"], // This makes it so that the compiler won't compile anything outside of "src". //"exclude": ["src/**/*.test.ts"], // Exclude .test.ts files since they're for Jest only. "compilerOptions": {