mirror of
				https://github.com/smartfrigde/armcord.git
				synced 2024-08-14 23:56:58 +00:00 
			
		
		
		
	feat: new folder structure
This commit is contained in:
		
							parent
							
								
									8aca371346
								
							
						
					
					
						commit
						419cb8eb4a
					
				
					 48 changed files with 536 additions and 1059 deletions
				
			
		
							
								
								
									
										157
									
								
								src/common/config.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								src/common/config.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<string>; | ||||||
|  |     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<K extends keyof Settings>(object: K): Promise<Settings[K]> { | ||||||
|  |     let rawdata = fs.readFileSync(getConfigLocation(), "utf-8"); | ||||||
|  |     let returndata = JSON.parse(rawdata); | ||||||
|  |     return returndata[object]; | ||||||
|  | } | ||||||
|  | export function getConfigSync<K extends keyof Settings>(object: K) { | ||||||
|  |     let rawdata = fs.readFileSync(getConfigLocation(), "utf-8"); | ||||||
|  |     let returndata = JSON.parse(rawdata); | ||||||
|  |     return returndata[object]; | ||||||
|  | } | ||||||
|  | export async function setConfig<K extends keyof Settings>(object: K, toSet: Settings[K]): Promise<void> { | ||||||
|  |     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<void> { | ||||||
|  |     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<void> { | ||||||
|  |     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<void> { | ||||||
|  |     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 | ||||||
|  |     }); | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								src/common/dom.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/common/dom.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<void> { | ||||||
|  |     const js = await (await fetch(`${inject}`)).text(); | ||||||
|  | 
 | ||||||
|  |     const el = document.createElement("script"); | ||||||
|  | 
 | ||||||
|  |     el.appendChild(document.createTextNode(js)); | ||||||
|  | 
 | ||||||
|  |     document.body.appendChild(el); | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								src/common/flags.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/common/flags.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | ||||||
|  | import {app} from "electron"; | ||||||
|  | import {getConfig} from "./config"; | ||||||
|  | 
 | ||||||
|  | export let transparency: boolean; | ||||||
|  | export async function injectElectronFlags(): Promise<void> { | ||||||
|  |     //     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; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										67
									
								
								src/common/lang.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/common/lang.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | ||||||
|  | import {app} from "electron"; | ||||||
|  | import path from "path"; | ||||||
|  | import fs from "fs"; | ||||||
|  | export async function setLang(language: string): Promise<void> { | ||||||
|  |     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<string> { | ||||||
|  |     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<string> { | ||||||
|  |     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; | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								src/common/sleep.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/common/sleep.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | export async function sleep(ms: number): Promise<void> { | ||||||
|  |     return new Promise((resolve) => setTimeout(resolve, ms)); | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								src/common/version.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/common/version.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								src/common/windowState.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/common/windowState.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<void> { | ||||||
|  |     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<K extends keyof WindowState>(object: K): Promise<WindowState[K]> { | ||||||
|  |     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]; | ||||||
|  | } | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import electron from "electron"; | import electron from "electron"; | ||||||
| import {getConfig} from "../utils"; | import {getConfig} from "../../common/config"; | ||||||
| 
 | 
 | ||||||
| const unstrictCSP = (): void => { | const unstrictCSP = (): void => { | ||||||
|     console.log("Setting up CSP unstricter..."); |     console.log("Setting up CSP unstricter..."); | ||||||
							
								
								
									
										124
									
								
								src/discord/extensions/mods.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/discord/extensions/mods.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<void> { | ||||||
|  |     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<void> { | ||||||
|  |     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." | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,28 +1,20 @@ | ||||||
| //ipc stuff
 | //ipc stuff
 | ||||||
| import {app, clipboard, desktopCapturer, ipcMain, nativeImage, shell} from "electron"; | import {app, clipboard, desktopCapturer, ipcMain, nativeImage, shell} from "electron"; | ||||||
| import {mainWindow} from "./window"; | 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 os from "os"; | ||||||
| import fs from "fs"; | import fs from "fs"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import {createTManagerWindow} from "./themeManager/main"; | import {getConfig, setConfigBulk, getConfigLocation, Settings} from "../common/config"; | ||||||
| import {splashWindow} from "./splash/main"; | import {setLang, getLang, getLangName} from "../common/lang"; | ||||||
| import {createKeybindWindow} from "./keybindMaker/main"; | 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 userDataPath = app.getPath("userData"); | ||||||
| const storagePath = path.join(userDataPath, "/storage/"); | const storagePath = path.join(userDataPath, "/storage/"); | ||||||
| const themesPath = path.join(userDataPath, "/themes/"); | const themesPath = path.join(userDataPath, "/themes/"); | ||||||
|  | @ -136,9 +128,6 @@ export function registerIpc(): void { | ||||||
|     ipcMain.on("openManagerWindow", () => { |     ipcMain.on("openManagerWindow", () => { | ||||||
|         createTManagerWindow(); |         createTManagerWindow(); | ||||||
|     }); |     }); | ||||||
|     ipcMain.on("openKeybindWindow", () => { |  | ||||||
|         createKeybindWindow(); |  | ||||||
|     }); |  | ||||||
|     ipcMain.on("setting-armcordCSP", async (event) => { |     ipcMain.on("setting-armcordCSP", async (event) => { | ||||||
|         if (await getConfig("armcordCSP")) { |         if (await getConfig("armcordCSP")) { | ||||||
|             event.returnValue = true; |             event.returnValue = true; | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import {BrowserWindow, Menu, app, clipboard} from "electron"; | import {BrowserWindow, Menu, app, clipboard} from "electron"; | ||||||
| import {mainWindow} from "./window"; | import {mainWindow} from "./window"; | ||||||
| import {createSettingsWindow} from "./settings/main"; | import {createSettingsWindow} from "../settings/main"; | ||||||
| 
 | 
 | ||||||
| function paste(contents: any): void { | function paste(contents: any): void { | ||||||
|     const contentTypes = clipboard.availableFormats().toString(); |     const contentTypes = clipboard.availableFormats().toString(); | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| //Fixed context isolation version https://github.com/getferdi/ferdi/blob/develop/src/webview/screenshare.ts
 | //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
 | //original https://github.com/electron/electron/issues/16513#issuecomment-602070250
 | ||||||
| import {addScript, addStyle} from "../utils"; |  | ||||||
| import fs from "fs"; | import fs from "fs"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
|  | import {addScript, addStyle} from "../../common/dom"; | ||||||
| 
 | 
 | ||||||
| const CANCEL_ID = "desktop-capturer-selection__cancel"; | const CANCEL_ID = "desktop-capturer-selection__cancel"; | ||||||
| 
 | 
 | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {addStyle} from "../utils"; | import {addStyle} from "../../common/dom"; | ||||||
| import * as fs from "fs"; | import * as fs from "fs"; | ||||||
| import * as path from "path"; | import * as path from "path"; | ||||||
| export function injectMobileStuff(): void { | export function injectMobileStuff(): void { | ||||||
|  | @ -4,10 +4,11 @@ import "./settings"; | ||||||
| import {ipcRenderer} from "electron"; | import {ipcRenderer} from "electron"; | ||||||
| import * as fs from "fs"; | import * as fs from "fs"; | ||||||
| import * as path from "path"; | import * as path from "path"; | ||||||
| import {addScript, addStyle, sleep} from "../utils"; |  | ||||||
| import {injectMobileStuff} from "./mobile"; | import {injectMobileStuff} from "./mobile"; | ||||||
| import {fixTitlebar, injectTitlebar} from "./titlebar"; | import {fixTitlebar, injectTitlebar} from "./titlebar"; | ||||||
| import {injectSettings} from "./settings"; | import {injectSettings} from "./settings"; | ||||||
|  | import {addStyle, addScript} from "../../common/dom"; | ||||||
|  | import {sleep} from "../../common/sleep"; | ||||||
| 
 | 
 | ||||||
| window.localStorage.setItem("hideNag", "true"); | window.localStorage.setItem("hideNag", "true"); | ||||||
| 
 | 
 | ||||||
|  | @ -56,10 +57,10 @@ sleep(5000).then(async () => { | ||||||
|         })(); |         })(); | ||||||
|         `);
 |         `);
 | ||||||
|     if (ipcRenderer.sendSync("disableAutogain")) { |     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")); |     addScript(fs.readFileSync(path.join(__dirname, "../", "../", "/content/js/rpc.js"), "utf8")); | ||||||
|     const cssPath = path.join(__dirname, "../", "/content/css/discord.css"); |     const cssPath = path.join(__dirname, "../", "../", "/content/css/discord.css"); | ||||||
|     addStyle(fs.readFileSync(cssPath, "utf8")); |     addStyle(fs.readFileSync(cssPath, "utf8")); | ||||||
|     await updateLang(); |     await updateLang(); | ||||||
| }); | }); | ||||||
|  | @ -107,21 +108,16 @@ setInterval(() => { | ||||||
|     const acSettings = advanced.cloneNode(true) as HTMLElement; |     const acSettings = advanced.cloneNode(true) as HTMLElement; | ||||||
|     const tManager = advanced.cloneNode(true) as HTMLElement; |     const tManager = advanced.cloneNode(true) as HTMLElement; | ||||||
|     const fQuit = advanced.cloneNode(true) as HTMLElement; |     const fQuit = advanced.cloneNode(true) as HTMLElement; | ||||||
|     const keybindMaker = advanced.cloneNode(true) as HTMLElement; |  | ||||||
|     acSettings.textContent = "ArmCord Settings"; |     acSettings.textContent = "ArmCord Settings"; | ||||||
|     acSettings.id = "acSettings"; |     acSettings.id = "acSettings"; | ||||||
|     acSettings.onclick = () => injectSettings(); |     acSettings.onclick = () => injectSettings(); | ||||||
|     tManager.textContent = "Themes"; |     tManager.textContent = "Themes"; | ||||||
|     tManager.id = "acThemes"; |     tManager.id = "acThemes"; | ||||||
|     tManager.onclick = () => ipcRenderer.send("openManagerWindow"); |     tManager.onclick = () => ipcRenderer.send("openManagerWindow"); | ||||||
|     keybindMaker.textContent = "Global keybinds"; |  | ||||||
|     keybindMaker.id = "acKeybinds"; |  | ||||||
|     keybindMaker.onclick = () => ipcRenderer.send("openKeybindWindow"); |  | ||||||
|     fQuit.textContent = "Force Quit"; |     fQuit.textContent = "Force Quit"; | ||||||
|     fQuit.id = "acForceQuit"; |     fQuit.id = "acForceQuit"; | ||||||
|     fQuit.onclick = () => ipcRenderer.send("win-quit"); |     fQuit.onclick = () => ipcRenderer.send("win-quit"); | ||||||
|     advanced.insertAdjacentElement("afterend", acSettings); |     advanced.insertAdjacentElement("afterend", acSettings); | ||||||
|     advanced.insertAdjacentElement("afterend", tManager); |     advanced.insertAdjacentElement("afterend", tManager); | ||||||
|     advanced.insertAdjacentElement("afterend", keybindMaker); |  | ||||||
|     advanced.insertAdjacentElement("afterend", fQuit); |     advanced.insertAdjacentElement("afterend", fQuit); | ||||||
| }, 1000); | }, 1000); | ||||||
|  | @ -1,14 +1,15 @@ | ||||||
| import * as path from "path"; | import * as path from "path"; | ||||||
| import * as fs from "fs"; | import * as fs from "fs"; | ||||||
| import {addStyle} from "../utils"; | import {addStyle} from "../../common/dom"; | ||||||
| import {WebviewTag} from "electron"; | import {WebviewTag} from "electron"; | ||||||
| 
 | 
 | ||||||
| var webview = `<webview src="${path.join("file://", __dirname, "../", "/settings/settings.html")}" preload="${path.join( | var webview = `<webview src="${path.join( | ||||||
|     "file://", |     "file://", | ||||||
|     __dirname, |     __dirname, | ||||||
|     "../", |     "../", | ||||||
|     "/settings/preload.js" |     "../", | ||||||
| )}" id="inAppSettings"></webview>`;
 |     "/settings/settings.html" | ||||||
|  | )}" preload="${path.join("file://", __dirname, "../", "../", "/settings/preload.js")}" id="inAppSettings"></webview>`;
 | ||||||
| 
 | 
 | ||||||
| export function injectSettings() { | export function injectSettings() { | ||||||
|     document.getElementById("webviewSettingsContainer")!.innerHTML = webview; |     document.getElementById("webviewSettingsContainer")!.innerHTML = webview; | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import {ipcRenderer} from "electron"; | import {ipcRenderer} from "electron"; | ||||||
| import {addStyle} from "../utils"; | import {addStyle} from "../../common/dom"; | ||||||
| import * as fs from "fs"; | import * as fs from "fs"; | ||||||
| import * as path from "path"; | import * as path from "path"; | ||||||
| import os from "os"; | import os from "os"; | ||||||
|  | @ -21,8 +21,8 @@ export function injectTitlebar(): void { | ||||||
|         } else { |         } else { | ||||||
|             document.getElementById("app-mount")!.prepend(elem); |             document.getElementById("app-mount")!.prepend(elem); | ||||||
|         } |         } | ||||||
|         const titlebarcssPath = path.join(__dirname, "../", "/content/css/titlebar.css"); |         const titlebarcssPath = path.join(__dirname, "../", "../", "/content/css/titlebar.css"); | ||||||
|         const wordmarkcssPath = path.join(__dirname, "../", "/content/css/logos.css"); |         const wordmarkcssPath = path.join(__dirname, "../", "../", "/content/css/logos.css"); | ||||||
|         addStyle(fs.readFileSync(titlebarcssPath, "utf8")); |         addStyle(fs.readFileSync(titlebarcssPath, "utf8")); | ||||||
|         addStyle(fs.readFileSync(wordmarkcssPath, "utf8")); |         addStyle(fs.readFileSync(wordmarkcssPath, "utf8")); | ||||||
|         document.body.setAttribute("customTitlebar", ""); |         document.body.setAttribute("customTitlebar", ""); | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import {BrowserWindow, MessageBoxOptions, desktopCapturer, dialog, ipcMain, session} from "electron"; | import {BrowserWindow, MessageBoxOptions, desktopCapturer, dialog, ipcMain, session} from "electron"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import {iconPath} from "../main"; | import {iconPath} from "../../main"; | ||||||
| let capturerWindow: BrowserWindow; | let capturerWindow: BrowserWindow; | ||||||
| function showAudioDialog(): boolean { | function showAudioDialog(): boolean { | ||||||
|     const options: MessageBoxOptions = { |     const options: MessageBoxOptions = { | ||||||
|  | @ -4,22 +4,15 @@ | ||||||
| // I'm sorry for this mess but I'm not sure how to fix it.
 | // 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 {BrowserWindow, MessageBoxOptions, app, dialog, nativeImage, shell} from "electron"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import { |  | ||||||
|     contentPath, |  | ||||||
|     firstRun, |  | ||||||
|     getConfig, |  | ||||||
|     getWindowState, |  | ||||||
|     registerGlobalKeybinds, |  | ||||||
|     setConfig, |  | ||||||
|     setWindowState |  | ||||||
| } from "./utils"; |  | ||||||
| import {registerIpc} from "./ipc"; | import {registerIpc} from "./ipc"; | ||||||
| import {setMenu} from "./menu"; | import {setMenu} from "./menu"; | ||||||
| import * as fs from "fs"; | import * as fs from "fs"; | ||||||
| import contextMenu from "electron-context-menu"; | import contextMenu from "electron-context-menu"; | ||||||
| import os from "os"; | import os from "os"; | ||||||
| import {tray} from "./tray"; | import {tray} from "../tray"; | ||||||
| import {iconPath} from "./main"; | import {iconPath} from "../main"; | ||||||
|  | import {getConfig, setConfig, firstRun} from "../common/config"; | ||||||
|  | import {getWindowState, setWindowState} from "../common/windowState"; | ||||||
| export let mainWindow: BrowserWindow; | export let mainWindow: BrowserWindow; | ||||||
| export let inviteWindow: BrowserWindow; | export let inviteWindow: BrowserWindow; | ||||||
| let forceQuit = false; | let forceQuit = false; | ||||||
|  | @ -196,7 +189,6 @@ async function doAfterDefiningTheWindow(): Promise<void> { | ||||||
|     if (!fs.existsSync(`${userDataPath}/disabled.txt`)) { |     if (!fs.existsSync(`${userDataPath}/disabled.txt`)) { | ||||||
|         fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), ""); |         fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), ""); | ||||||
|     } |     } | ||||||
|     registerGlobalKeybinds(); |  | ||||||
|     mainWindow.webContents.on("did-finish-load", () => { |     mainWindow.webContents.on("did-finish-load", () => { | ||||||
|         fs.readdirSync(themesFolder).forEach((file) => { |         fs.readdirSync(themesFolder).forEach((file) => { | ||||||
|             try { |             try { | ||||||
|  | @ -265,7 +257,6 @@ async function doAfterDefiningTheWindow(): Promise<void> { | ||||||
|     mainWindow.on("unmaximize", () => { |     mainWindow.on("unmaximize", () => { | ||||||
|         mainWindow.webContents.executeJavaScript(`document.body.removeAttribute("isMaximized");`); |         mainWindow.webContents.executeJavaScript(`document.body.removeAttribute("isMaximized");`); | ||||||
|     }); |     }); | ||||||
|     console.log(contentPath); |  | ||||||
|     if ((await getConfig("inviteWebsocket")) == true) { |     if ((await getConfig("inviteWebsocket")) == true) { | ||||||
|         require("arrpc"); |         require("arrpc"); | ||||||
|         //await startServer();
 |         //await startServer();
 | ||||||
|  | @ -274,7 +265,7 @@ async function doAfterDefiningTheWindow(): Promise<void> { | ||||||
|         mainWindow.close(); |         mainWindow.close(); | ||||||
|     } |     } | ||||||
|     //loadURL broke for no good reason after E28
 |     //loadURL broke for no good reason after E28
 | ||||||
|     mainWindow.loadFile(`${__dirname}/splash/redirect.html`); |     mainWindow.loadFile(`${__dirname}/../splash/redirect.html`); | ||||||
| 
 | 
 | ||||||
|     if (await getConfig("skipSplash")) { |     if (await getConfig("skipSplash")) { | ||||||
|         mainWindow.show(); |         mainWindow.show(); | ||||||
|  | @ -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<void> { |  | ||||||
|             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; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,392 +0,0 @@ | ||||||
| <!DOCTYPE html> |  | ||||||
| <html> |  | ||||||
|     <head> |  | ||||||
|         <style> |  | ||||||
|             :root { |  | ||||||
|                 --background-secondary: #2f3136; |  | ||||||
|                 --background-secondary-alt: #292b2f; |  | ||||||
|                 --background-floating: #18191c; |  | ||||||
|                 --background-modifier-hover: rgba(106, 116, 128, 0.16); |  | ||||||
|                 --brand-experiment: #7289da; |  | ||||||
|                 --brand-experiment-560: #5c6fb1; |  | ||||||
|                 --brand-experiment-600: #4e5d94; |  | ||||||
|                 --interactive-normal: #b9bbbe; |  | ||||||
|                 --interactive-hover: #dcddde; |  | ||||||
|                 --text-muted: #72767d; |  | ||||||
|                 --font-primary: "Whitney"; |  | ||||||
|                 --header-primary: #fff; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             html { |  | ||||||
|                 font-size: 22px; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             body { |  | ||||||
|                 padding: 1rem; |  | ||||||
|                 background: var(--background-secondary); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             @font-face { |  | ||||||
|                 font-family: Whitney; |  | ||||||
|                 font-weight: 200; |  | ||||||
|                 font-style: normal; |  | ||||||
|                 src: url(https://armcord.app/whitney_400.woff) format("woff"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             * { |  | ||||||
|                 font-family: "Whitney", sans-serif; |  | ||||||
|                 box-sizing: border-box; |  | ||||||
|                 margin: 0; |  | ||||||
|                 padding: 0; |  | ||||||
|                 cursor: default; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .card { |  | ||||||
|                 background-color: var(--background-floating); |  | ||||||
|                 color: white; |  | ||||||
|                 padding: 1rem; |  | ||||||
|                 border-color: var(--background-floating); |  | ||||||
|                 border-style: solid; |  | ||||||
|                 width: 100%; |  | ||||||
|                 border-radius: 10px; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .cards { |  | ||||||
|                 max-width: 1200px; |  | ||||||
|                 margin: 0 auto; |  | ||||||
|                 display: grid; |  | ||||||
|                 grid-gap: 1rem; |  | ||||||
|                 justify-content: space-evenly; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             /* Screen larger than 600px? 2 column */ |  | ||||||
|             @media (min-width: 600px) { |  | ||||||
|                 .cards { |  | ||||||
|                     grid-template-columns: repeat(2, 1fr); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             /* Screen larger than 900px? 3 columns */ |  | ||||||
|             @media (min-width: 900px) { |  | ||||||
|                 .cards { |  | ||||||
|                     grid-template-columns: repeat(3, 1fr); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .flex-box { |  | ||||||
|                 display: flex; |  | ||||||
|                 justify-content: space-between; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             /* switches */ |  | ||||||
|             .tgl { |  | ||||||
|                 display: none; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .tgl::-moz-selection, |  | ||||||
|             .tgl:after::-moz-selection, |  | ||||||
|             .tgl:before::-moz-selection, |  | ||||||
|             .tgl *::-moz-selection, |  | ||||||
|             .tgl *:after::-moz-selection, |  | ||||||
|             .tgl *:before::-moz-selection, |  | ||||||
|             .tgl + .tgl-btn::-moz-selection { |  | ||||||
|                 background: none; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .tgl::selection, |  | ||||||
|             .tgl:after::selection, |  | ||||||
|             .tgl:before::selection, |  | ||||||
|             .tgl *::selection, |  | ||||||
|             .tgl *:after::selection, |  | ||||||
|             .tgl *:before::selection, |  | ||||||
|             .tgl + .tgl-btn::selection { |  | ||||||
|                 background: none; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .tgl + .tgl-btn { |  | ||||||
|                 outline: 0; |  | ||||||
|                 display: block; |  | ||||||
|                 width: 3em; |  | ||||||
|                 position: relative; |  | ||||||
|                 cursor: pointer; |  | ||||||
|                 -webkit-user-select: none; |  | ||||||
|                 -moz-user-select: none; |  | ||||||
|                 -ms-user-select: none; |  | ||||||
|                 user-select: none; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .tgl + .tgl-btn:after, |  | ||||||
|             .tgl + .tgl-btn:before { |  | ||||||
|                 position: relative; |  | ||||||
|                 display: block; |  | ||||||
|                 content: ""; |  | ||||||
|                 width: 50%; |  | ||||||
|                 height: 100%; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .tgl + .tgl-btn:after { |  | ||||||
|                 left: 1px; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .tgl + .tgl-btn:before { |  | ||||||
|                 display: none; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .tgl:checked + .tgl-btn:after { |  | ||||||
|                 left: 56%; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .tgl-light + .tgl-btn { |  | ||||||
|                 background: var(--text-muted); |  | ||||||
|                 border-radius: 25px; |  | ||||||
|                 padding: 4px; |  | ||||||
|                 transition: all 0.4s ease; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .tgl-light + .tgl-btn:after { |  | ||||||
|                 border-radius: 50px; |  | ||||||
|                 position: relative; |  | ||||||
|                 top: 50%; |  | ||||||
|                 transform: translateY(-50%); |  | ||||||
|                 width: 24px; |  | ||||||
|                 height: 24px; |  | ||||||
|                 background: rgb(255, 255, 255); |  | ||||||
|                 transition: all 0.2s ease; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .tgl-light:checked + .tgl-btn { |  | ||||||
|                 background: var(--brand-experiment); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             input[type="text"], |  | ||||||
|             select { |  | ||||||
|                 width: 100%; |  | ||||||
|                 padding: 12px 20px; |  | ||||||
|                 margin: 8px 0; |  | ||||||
|                 display: inline-block; |  | ||||||
|                 border: 1px solid #72767d; |  | ||||||
|                 background-color: #46484d; |  | ||||||
|                 color: white; |  | ||||||
|                 border-radius: 4px; |  | ||||||
|                 box-sizing: border-box; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             #download { |  | ||||||
|                 margin-top: 3px; |  | ||||||
|                 height: 50px; |  | ||||||
|                 vertical-align: middle; |  | ||||||
|                 text-align: right; |  | ||||||
|                 z-index: 99; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             /* The Modal (background) */ |  | ||||||
|             .modal { |  | ||||||
|                 display: none; |  | ||||||
|                 /* Hidden by default */ |  | ||||||
|                 position: fixed; |  | ||||||
|                 /* Stay in place */ |  | ||||||
|                 z-index: 1; |  | ||||||
|                 /* Sit on top */ |  | ||||||
|                 padding-top: 100px; |  | ||||||
|                 /* Location of the box */ |  | ||||||
|                 background-color: var(--background-secondary); |  | ||||||
|                 left: 0; |  | ||||||
|                 top: 0; |  | ||||||
|                 width: 100%; |  | ||||||
|                 /* Full width */ |  | ||||||
|                 height: 100%; |  | ||||||
|                 /* Full height */ |  | ||||||
|                 overflow: auto; |  | ||||||
|                 /* Enable scroll if needed */ |  | ||||||
|                 background-color: rgb(0, 0, 0); |  | ||||||
|                 /* Fallback color */ |  | ||||||
|                 background-color: rgba(0, 0, 0, 0.4); |  | ||||||
|                 /* Black w/ opacity */ |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             /* Modal Content */ |  | ||||||
|             .modal-content { |  | ||||||
|                 position: relative; |  | ||||||
|                 margin: auto; |  | ||||||
|                 padding: 1rem; |  | ||||||
|                 background-color: var(--background-secondary); |  | ||||||
|                 border-color: var(--background-floating); |  | ||||||
|                 border-style: solid; |  | ||||||
|                 border-radius: 10px; |  | ||||||
|                 width: 80%; |  | ||||||
|                 box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); |  | ||||||
|                 -webkit-animation-name: animatetop; |  | ||||||
|                 -webkit-animation-duration: 0.4s; |  | ||||||
|                 animation-name: animatetop; |  | ||||||
|                 animation-duration: 0.4s; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             /* Add Animation */ |  | ||||||
|             @-webkit-keyframes animatetop { |  | ||||||
|                 from { |  | ||||||
|                     top: -300px; |  | ||||||
|                     opacity: 0; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 to { |  | ||||||
|                     top: 0; |  | ||||||
|                     opacity: 1; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             @keyframes animatetop { |  | ||||||
|                 from { |  | ||||||
|                     top: -300px; |  | ||||||
|                     opacity: 0; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 to { |  | ||||||
|                     top: 0; |  | ||||||
|                     opacity: 1; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             /* The Close Button */ |  | ||||||
|             .close { |  | ||||||
|                 color: white; |  | ||||||
|                 float: right; |  | ||||||
|                 font-size: 28px; |  | ||||||
|                 font-weight: bold; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .close:hover, |  | ||||||
|             .close:focus { |  | ||||||
|                 color: red; |  | ||||||
|                 text-decoration: none; |  | ||||||
|                 cursor: pointer; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .modal-header { |  | ||||||
|                 padding: 2px 16px; |  | ||||||
|                 color: white; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .modal-body { |  | ||||||
|                 padding: 2px 16px; |  | ||||||
|                 color: white; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             .modal-footer { |  | ||||||
|                 padding: 2px 16px; |  | ||||||
|                 color: white; |  | ||||||
|             } |  | ||||||
|             a.button { |  | ||||||
|                 color: var(--brand-experiment-560); |  | ||||||
|             } |  | ||||||
|             .button { |  | ||||||
|                 margin-right: 10px; |  | ||||||
|             } |  | ||||||
|             img.themeInfoIcon { |  | ||||||
|                 height: 30px; |  | ||||||
|                 position: relative; |  | ||||||
|                 top: 5px; |  | ||||||
|             } |  | ||||||
|             .addKbd { |  | ||||||
|                 width: 100%; |  | ||||||
|                 height: 30px; |  | ||||||
|                 color: var(--header-primary); |  | ||||||
|                 border-radius: 8px; |  | ||||||
|                 border: none; |  | ||||||
|                 font-weight: bold; |  | ||||||
|                 background-color: var(--brand-experiment); |  | ||||||
|             } |  | ||||||
|             .addKbd:hover { |  | ||||||
|                 background-color: var(--brand-experiment-560); |  | ||||||
|                 color: var(--interactive-hover); |  | ||||||
|             } |  | ||||||
|             kbd { |  | ||||||
|                 display: inline-block; |  | ||||||
|                 border: 1px solid #ccc; |  | ||||||
|                 border-radius: 4px; |  | ||||||
|                 padding: 0.1em 0.5em; |  | ||||||
|                 position: relative; |  | ||||||
| 
 |  | ||||||
|                 margin: 0 0.2em; |  | ||||||
|                 box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 0 0 2px var(--background-floating) inset; |  | ||||||
|                 background-color: #41434a; |  | ||||||
|                 color: var(--header-primary); |  | ||||||
|             } |  | ||||||
|             #output { |  | ||||||
|                 margin-top: 10px; |  | ||||||
|                 width: 100%; |  | ||||||
|                 margin-bottom: 10px; |  | ||||||
|                 min-height: 50px; |  | ||||||
|                 background-color: var(--background-secondary-alt); |  | ||||||
|                 border-radius: 8px; |  | ||||||
|                 border: 3px dashed var(--interactive-normal); |  | ||||||
|             } |  | ||||||
|             .preview { |  | ||||||
|                 top: 5px; |  | ||||||
|             } |  | ||||||
|             #warning { |  | ||||||
|                 color: yellow; |  | ||||||
|                 text-align: center; |  | ||||||
|                 font-size: 20px; |  | ||||||
|                 margin-bottom: 20px; |  | ||||||
|             } |  | ||||||
|         </style> |  | ||||||
|     </head> |  | ||||||
| 
 |  | ||||||
|     <body> |  | ||||||
|         <p id="warning">Global keybinds are disabled while this window is open.</p> |  | ||||||
|         <button class="addKbd" id="startButton">Add a global keybind</button> |  | ||||||
|         <div id="output"></div> |  | ||||||
|         <div class="cards" id="cardBox"></div> |  | ||||||
|         <script> |  | ||||||
|             let captureStarted = false; |  | ||||||
| 
 |  | ||||||
|             const outputElement = document.getElementById("output"); |  | ||||||
| 
 |  | ||||||
|             function startCapture() { |  | ||||||
|                 if (captureStarted) return; |  | ||||||
|                 let keys = ""; |  | ||||||
|                 captureStarted = true; |  | ||||||
|                 outputElement.innerHTML = ""; |  | ||||||
| 
 |  | ||||||
|                 const kbdElement = document.createElement("div"); |  | ||||||
|                 outputElement.appendChild(kbdElement); |  | ||||||
| 
 |  | ||||||
|                 let capturedKeystrokes = ""; |  | ||||||
|                 const startTime = Date.now(); |  | ||||||
| 
 |  | ||||||
|                 function displayKeystrokes() { |  | ||||||
|                     const currentTime = Date.now(); |  | ||||||
|                     const elapsedSeconds = Math.floor((currentTime - startTime) / 1000); |  | ||||||
| 
 |  | ||||||
|                     if (elapsedSeconds >= 5) { |  | ||||||
|                         captureStarted = false; |  | ||||||
|                         //remove last character from keys |  | ||||||
|                         manager.add(keys.slice(0, -1)); |  | ||||||
|                         outputElement.innerHTML = ""; |  | ||||||
|                         keys = ""; |  | ||||||
|                         return; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     kbdElement.innerHTML = capturedKeystrokes; |  | ||||||
|                     requestAnimationFrame(displayKeystrokes); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 function handleKeystroke(event) { |  | ||||||
|                     const key = event.key; |  | ||||||
|                     if (keys.includes(key)) { |  | ||||||
|                         console.log("already in array"); |  | ||||||
|                     } else { |  | ||||||
|                         keys += key + "+"; //electron friendly format |  | ||||||
|                         capturedKeystrokes += `<kbd class="preview">${key}</kbd>`; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 document.addEventListener("keydown", handleKeystroke); |  | ||||||
|                 displayKeystrokes(); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             const startButton = document.getElementById("startButton"); |  | ||||||
|             startButton.addEventListener("click", startCapture); |  | ||||||
|         </script> |  | ||||||
|     </body> |  | ||||||
| </html> |  | ||||||
|  | @ -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 += `<kbd>${keys[key]}</kbd>`; |  | ||||||
|     } |  | ||||||
|     e?.insertAdjacentHTML( |  | ||||||
|         "beforeend", |  | ||||||
|         ` |  | ||||||
|         <div class="card"> |  | ||||||
|                 <div class="flex-box"> |  | ||||||
|                     ${html} |  | ||||||
|                     <input id="${id}" class="tgl tgl-light left" type="checkbox" /> |  | ||||||
|                     <label class="tgl-btn left" for="${id}"></label> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         ` |  | ||||||
|     ); |  | ||||||
|     (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"; |  | ||||||
| }); |  | ||||||
							
								
								
									
										38
									
								
								src/main.ts
									
										
									
									
									
								
							
							
						
						
									
										38
									
								
								src/main.ts
									
										
									
									
									
								
							|  | @ -1,27 +1,28 @@ | ||||||
| // Modules to control application life and create native browser window
 | // Modules to control application life and create native browser window
 | ||||||
| import {BrowserWindow, app, crashReporter, session} from "electron"; | import {BrowserWindow, app, crashReporter, session} from "electron"; | ||||||
| import "v8-compile-cache"; | import "v8-compile-cache"; | ||||||
| import { | import "./discord/extensions/csp"; | ||||||
|     Settings, |  | ||||||
|     checkForDataFolder, |  | ||||||
|     checkIfConfigExists, |  | ||||||
|     firstRun, |  | ||||||
|     checkIfConfigIsBroken, |  | ||||||
|     getConfig, |  | ||||||
|     getConfigSync, |  | ||||||
|     injectElectronFlags, |  | ||||||
|     installModLoader, |  | ||||||
|     setConfig, |  | ||||||
|     setLang |  | ||||||
| } from "./utils"; |  | ||||||
| import "./extensions/mods"; |  | ||||||
| import "./tray"; | import "./tray"; | ||||||
| import {createCustomWindow, createNativeWindow, createTransparentWindow} from "./window"; | import fs from "fs"; | ||||||
|  | import {createCustomWindow, createNativeWindow, createTransparentWindow} from "./discord/window"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import {createTManagerWindow} from "./themeManager/main"; | import {createTManagerWindow} from "./themeManager/main"; | ||||||
| import {createSplashWindow} from "./splash/main"; | import {createSplashWindow} from "./splash/main"; | ||||||
| import {createSetupWindow} from "./setup/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 iconPath: string; | ||||||
| export let settings: any; | export let settings: any; | ||||||
| export let customTitlebar: boolean; | export let customTitlebar: boolean; | ||||||
|  | @ -47,10 +48,6 @@ async function args(): Promise<void> { | ||||||
|         app.whenReady().then(async () => { |         app.whenReady().then(async () => { | ||||||
|             createTManagerWindow(); |             createTManagerWindow(); | ||||||
|         }); |         }); | ||||||
|     } else if (args == "keybinds") { |  | ||||||
|         app.whenReady().then(async () => { |  | ||||||
|             createKeybindWindow(); |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| args(); // i want my top level awaits
 | args(); // i want my top level awaits
 | ||||||
|  | @ -84,6 +81,7 @@ if (!app.requestSingleInstanceLock() && getConfigSync("multiInstance") == (false | ||||||
|     checkIfConfigExists(); |     checkIfConfigExists(); | ||||||
|     checkIfConfigIsBroken(); |     checkIfConfigIsBroken(); | ||||||
|     injectElectronFlags(); |     injectElectronFlags(); | ||||||
|  |     console.log("[Config Manager] Current config: " + fs.readFileSync(getConfigLocation(), "utf-8")); | ||||||
|     app.whenReady().then(async () => { |     app.whenReady().then(async () => { | ||||||
|         if ((await getConfig("customIcon")) !== undefined ?? null) { |         if ((await getConfig("customIcon")) !== undefined ?? null) { | ||||||
|             iconPath = await getConfig("customIcon"); |             iconPath = await getConfig("customIcon"); | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import {BrowserWindow, app, shell} from "electron"; | import {BrowserWindow, app, shell} from "electron"; | ||||||
| import {getDisplayVersion} from "../utils"; |  | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import fs from "fs"; | import fs from "fs"; | ||||||
|  | import {getDisplayVersion} from "../common/version"; | ||||||
| let settingsWindow: BrowserWindow; | let settingsWindow: BrowserWindow; | ||||||
| let instance = 0; | let instance = 0; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,8 @@ import {BrowserWindow, app, ipcMain} from "electron"; | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import * as fs from "fs"; | import * as fs from "fs"; | ||||||
| import {iconPath} from "../main"; | import {iconPath} from "../main"; | ||||||
| import {Settings, getConfigLocation, setConfigBulk} from "../utils"; | import {setConfigBulk, getConfigLocation, Settings} from "../common/config"; | ||||||
|  | 
 | ||||||
| let setupWindow: BrowserWindow; | let setupWindow: BrowserWindow; | ||||||
| export function createSetupWindow(): void { | export function createSetupWindow(): void { | ||||||
|     setupWindow = new BrowserWindow({ |     setupWindow = new BrowserWindow({ | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| import {contextBridge, ipcRenderer} from "electron"; | import {contextBridge, ipcRenderer} from "electron"; | ||||||
| import {injectTitlebar} from "../preload/titlebar"; | import {injectTitlebar} from "../discord/preload/titlebar"; | ||||||
|  | 
 | ||||||
| injectTitlebar(); | injectTitlebar(); | ||||||
| contextBridge.exposeInMainWorld("armcordinternal", { | contextBridge.exposeInMainWorld("armcordinternal", { | ||||||
|     restart: () => ipcRenderer.send("restart"), |     restart: () => ipcRenderer.send("restart"), | ||||||
|  |  | ||||||
|  | @ -19,5 +19,5 @@ export async function createSplashWindow(): Promise<void> { | ||||||
|             preload: path.join(__dirname, "preload.js") |             preload: path.join(__dirname, "preload.js") | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|     splashWindow.loadURL(`file://${__dirname}/splash.html`); |     splashWindow.loadFile(path.join(__dirname, "splash.html")); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import {BrowserWindow, app, dialog, ipcMain, shell} from "electron"; | import {BrowserWindow, app, dialog, ipcMain, shell} from "electron"; | ||||||
| import {sleep} from "../utils"; |  | ||||||
| import path from "path"; | import path from "path"; | ||||||
| import fs from "fs"; | import fs from "fs"; | ||||||
| import {createInviteWindow, mainWindow} from "../window"; | import {sleep} from "../common/sleep"; | ||||||
|  | import {createInviteWindow, mainWindow} from "../discord/window"; | ||||||
| let themeWindow: BrowserWindow; | let themeWindow: BrowserWindow; | ||||||
| let instance = 0; | let instance = 0; | ||||||
| interface ThemeManifest { | interface ThemeManifest { | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import {ipcRenderer, contextBridge} from "electron"; | import {ipcRenderer, contextBridge} from "electron"; | ||||||
| import {sleep} from "../utils"; | import {sleep} from "../common/sleep"; | ||||||
| contextBridge.exposeInMainWorld("themes", { | contextBridge.exposeInMainWorld("themes", { | ||||||
|     install: (url: string) => ipcRenderer.send("installBDTheme", url), |     install: (url: string) => ipcRenderer.send("installBDTheme", url), | ||||||
|     uninstall: (id: string) => ipcRenderer.send("uninstallTheme", id) |     uninstall: (id: string) => ipcRenderer.send("uninstallTheme", id) | ||||||
|  |  | ||||||
|  | @ -1,9 +1,10 @@ | ||||||
| import * as fs from "fs"; | import * as fs from "fs"; | ||||||
| import {Menu, MessageBoxOptions, Tray, app, dialog, nativeImage} from "electron"; | import {Menu, MessageBoxOptions, Tray, app, dialog, nativeImage} from "electron"; | ||||||
| import {createInviteWindow, mainWindow} from "./window"; | import {createInviteWindow, mainWindow} from "./discord/window"; | ||||||
| import {getConfig, getConfigLocation, getDisplayVersion, setConfig} from "./utils"; |  | ||||||
| import * as path from "path"; | import * as path from "path"; | ||||||
| import {createSettingsWindow} from "./settings/main"; | import {createSettingsWindow} from "./settings/main"; | ||||||
|  | import {getConfig, getConfigLocation, setConfig} from "./common/config"; | ||||||
|  | import {getDisplayVersion} from "./common/version"; | ||||||
| export let tray: any = null; | export let tray: any = null; | ||||||
| let trayIcon = "ac_plug_colored"; | let trayIcon = "ac_plug_colored"; | ||||||
| app.whenReady().then(async () => { | app.whenReady().then(async () => { | ||||||
|  |  | ||||||
							
								
								
									
										478
									
								
								src/utils.ts
									
										
									
									
									
								
							
							
						
						
									
										478
									
								
								src/utils.ts
									
										
									
									
									
								
							|  | @ -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<void> { |  | ||||||
|     return new Promise((resolve) => setTimeout(resolve, ms)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export async function checkIfConfigIsBroken(): Promise<void> { |  | ||||||
|     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<void> { |  | ||||||
|     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<void> { |  | ||||||
|     //     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<void> { |  | ||||||
|     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<string> { |  | ||||||
|     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<string> { |  | ||||||
|     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<void> { |  | ||||||
|     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<K extends keyof WindowState>(object: K): Promise<WindowState[K]> { |  | ||||||
|     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<string>; |  | ||||||
|     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<K extends keyof Settings>(object: K): Promise<Settings[K]> { |  | ||||||
|     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<K extends keyof Settings>(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<K extends keyof Settings>(object: K, toSet: Settings[K]): Promise<void> { |  | ||||||
|     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<void> { |  | ||||||
|     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<void> { |  | ||||||
|     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<void> { |  | ||||||
|     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<void> { |  | ||||||
|     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<void> { |  | ||||||
|     const keybinds = await getConfig("keybinds"); |  | ||||||
|     keybinds.forEach((keybind) => { |  | ||||||
|         globalShortcut.register(keybind, () => { |  | ||||||
|             console.log(keybind); |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
|  | @ -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". |     "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. |     //"exclude": ["src/**/*.test.ts"], // Exclude .test.ts files since they're for Jest only. | ||||||
|     "compilerOptions": { |     "compilerOptions": { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue