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 {getConfig} from "../utils";
|
||||
import {getConfig} from "../../common/config";
|
||||
|
||||
const unstrictCSP = (): void => {
|
||||
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
|
||||
import {app, clipboard, desktopCapturer, ipcMain, nativeImage, shell} from "electron";
|
||||
import {mainWindow} from "./window";
|
||||
import {
|
||||
Settings,
|
||||
getConfig,
|
||||
getConfigLocation,
|
||||
getDisplayVersion,
|
||||
getLang,
|
||||
getLangName,
|
||||
getVersion,
|
||||
modInstallState,
|
||||
packageVersion,
|
||||
setConfigBulk,
|
||||
setLang,
|
||||
sleep
|
||||
} from "./utils";
|
||||
import {customTitlebar} from "./main";
|
||||
import {createSettingsWindow} from "./settings/main";
|
||||
|
||||
import os from "os";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import {createTManagerWindow} from "./themeManager/main";
|
||||
import {splashWindow} from "./splash/main";
|
||||
import {createKeybindWindow} from "./keybindMaker/main";
|
||||
import {getConfig, setConfigBulk, getConfigLocation, Settings} from "../common/config";
|
||||
import {setLang, getLang, getLangName} from "../common/lang";
|
||||
import {sleep} from "../common/sleep";
|
||||
import {getVersion, getDisplayVersion, packageVersion} from "../common/version";
|
||||
import {customTitlebar} from "../main";
|
||||
import {createSettingsWindow} from "../settings/main";
|
||||
import {splashWindow} from "../splash/main";
|
||||
import {createTManagerWindow} from "../themeManager/main";
|
||||
import {modInstallState} from "./extensions/mods";
|
||||
|
||||
const userDataPath = app.getPath("userData");
|
||||
const storagePath = path.join(userDataPath, "/storage/");
|
||||
const themesPath = path.join(userDataPath, "/themes/");
|
||||
|
@ -136,9 +128,6 @@ export function registerIpc(): void {
|
|||
ipcMain.on("openManagerWindow", () => {
|
||||
createTManagerWindow();
|
||||
});
|
||||
ipcMain.on("openKeybindWindow", () => {
|
||||
createKeybindWindow();
|
||||
});
|
||||
ipcMain.on("setting-armcordCSP", async (event) => {
|
||||
if (await getConfig("armcordCSP")) {
|
||||
event.returnValue = true;
|
|
@ -1,6 +1,6 @@
|
|||
import {BrowserWindow, Menu, app, clipboard} from "electron";
|
||||
import {mainWindow} from "./window";
|
||||
import {createSettingsWindow} from "./settings/main";
|
||||
import {createSettingsWindow} from "../settings/main";
|
||||
|
||||
function paste(contents: any): void {
|
||||
const contentTypes = clipboard.availableFormats().toString();
|
|
@ -1,8 +1,8 @@
|
|||
//Fixed context isolation version https://github.com/getferdi/ferdi/blob/develop/src/webview/screenshare.ts
|
||||
//original https://github.com/electron/electron/issues/16513#issuecomment-602070250
|
||||
import {addScript, addStyle} from "../utils";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import {addScript, addStyle} from "../../common/dom";
|
||||
|
||||
const CANCEL_ID = "desktop-capturer-selection__cancel";
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import {addStyle} from "../utils";
|
||||
import {addStyle} from "../../common/dom";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
export function injectMobileStuff(): void {
|
|
@ -4,10 +4,11 @@ import "./settings";
|
|||
import {ipcRenderer} from "electron";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import {addScript, addStyle, sleep} from "../utils";
|
||||
import {injectMobileStuff} from "./mobile";
|
||||
import {fixTitlebar, injectTitlebar} from "./titlebar";
|
||||
import {injectSettings} from "./settings";
|
||||
import {addStyle, addScript} from "../../common/dom";
|
||||
import {sleep} from "../../common/sleep";
|
||||
|
||||
window.localStorage.setItem("hideNag", "true");
|
||||
|
||||
|
@ -56,10 +57,10 @@ sleep(5000).then(async () => {
|
|||
})();
|
||||
`);
|
||||
if (ipcRenderer.sendSync("disableAutogain")) {
|
||||
addScript(fs.readFileSync(path.join(__dirname, "../", "/content/js/disableAutogain.js"), "utf8"));
|
||||
addScript(fs.readFileSync(path.join(__dirname, "../", "../", "/content/js/disableAutogain.js"), "utf8"));
|
||||
}
|
||||
addScript(fs.readFileSync(path.join(__dirname, "../", "/content/js/rpc.js"), "utf8"));
|
||||
const cssPath = path.join(__dirname, "../", "/content/css/discord.css");
|
||||
addScript(fs.readFileSync(path.join(__dirname, "../", "../", "/content/js/rpc.js"), "utf8"));
|
||||
const cssPath = path.join(__dirname, "../", "../", "/content/css/discord.css");
|
||||
addStyle(fs.readFileSync(cssPath, "utf8"));
|
||||
await updateLang();
|
||||
});
|
||||
|
@ -107,21 +108,16 @@ setInterval(() => {
|
|||
const acSettings = advanced.cloneNode(true) as HTMLElement;
|
||||
const tManager = advanced.cloneNode(true) as HTMLElement;
|
||||
const fQuit = advanced.cloneNode(true) as HTMLElement;
|
||||
const keybindMaker = advanced.cloneNode(true) as HTMLElement;
|
||||
acSettings.textContent = "ArmCord Settings";
|
||||
acSettings.id = "acSettings";
|
||||
acSettings.onclick = () => injectSettings();
|
||||
tManager.textContent = "Themes";
|
||||
tManager.id = "acThemes";
|
||||
tManager.onclick = () => ipcRenderer.send("openManagerWindow");
|
||||
keybindMaker.textContent = "Global keybinds";
|
||||
keybindMaker.id = "acKeybinds";
|
||||
keybindMaker.onclick = () => ipcRenderer.send("openKeybindWindow");
|
||||
fQuit.textContent = "Force Quit";
|
||||
fQuit.id = "acForceQuit";
|
||||
fQuit.onclick = () => ipcRenderer.send("win-quit");
|
||||
advanced.insertAdjacentElement("afterend", acSettings);
|
||||
advanced.insertAdjacentElement("afterend", tManager);
|
||||
advanced.insertAdjacentElement("afterend", keybindMaker);
|
||||
advanced.insertAdjacentElement("afterend", fQuit);
|
||||
}, 1000);
|
|
@ -1,14 +1,15 @@
|
|||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import {addStyle} from "../utils";
|
||||
import {addStyle} from "../../common/dom";
|
||||
import {WebviewTag} from "electron";
|
||||
|
||||
var webview = `<webview src="${path.join("file://", __dirname, "../", "/settings/settings.html")}" preload="${path.join(
|
||||
var webview = `<webview src="${path.join(
|
||||
"file://",
|
||||
__dirname,
|
||||
"../",
|
||||
"/settings/preload.js"
|
||||
)}" id="inAppSettings"></webview>`;
|
||||
"../",
|
||||
"/settings/settings.html"
|
||||
)}" preload="${path.join("file://", __dirname, "../", "../", "/settings/preload.js")}" id="inAppSettings"></webview>`;
|
||||
|
||||
export function injectSettings() {
|
||||
document.getElementById("webviewSettingsContainer")!.innerHTML = webview;
|
|
@ -1,5 +1,5 @@
|
|||
import {ipcRenderer} from "electron";
|
||||
import {addStyle} from "../utils";
|
||||
import {addStyle} from "../../common/dom";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import os from "os";
|
||||
|
@ -21,8 +21,8 @@ export function injectTitlebar(): void {
|
|||
} else {
|
||||
document.getElementById("app-mount")!.prepend(elem);
|
||||
}
|
||||
const titlebarcssPath = path.join(__dirname, "../", "/content/css/titlebar.css");
|
||||
const wordmarkcssPath = path.join(__dirname, "../", "/content/css/logos.css");
|
||||
const titlebarcssPath = path.join(__dirname, "../", "../", "/content/css/titlebar.css");
|
||||
const wordmarkcssPath = path.join(__dirname, "../", "../", "/content/css/logos.css");
|
||||
addStyle(fs.readFileSync(titlebarcssPath, "utf8"));
|
||||
addStyle(fs.readFileSync(wordmarkcssPath, "utf8"));
|
||||
document.body.setAttribute("customTitlebar", "");
|
|
@ -1,6 +1,6 @@
|
|||
import {BrowserWindow, MessageBoxOptions, desktopCapturer, dialog, ipcMain, session} from "electron";
|
||||
import path from "path";
|
||||
import {iconPath} from "../main";
|
||||
import {iconPath} from "../../main";
|
||||
let capturerWindow: BrowserWindow;
|
||||
function showAudioDialog(): boolean {
|
||||
const options: MessageBoxOptions = {
|
|
@ -4,22 +4,15 @@
|
|||
// I'm sorry for this mess but I'm not sure how to fix it.
|
||||
import {BrowserWindow, MessageBoxOptions, app, dialog, nativeImage, shell} from "electron";
|
||||
import path from "path";
|
||||
import {
|
||||
contentPath,
|
||||
firstRun,
|
||||
getConfig,
|
||||
getWindowState,
|
||||
registerGlobalKeybinds,
|
||||
setConfig,
|
||||
setWindowState
|
||||
} from "./utils";
|
||||
import {registerIpc} from "./ipc";
|
||||
import {setMenu} from "./menu";
|
||||
import * as fs from "fs";
|
||||
import contextMenu from "electron-context-menu";
|
||||
import os from "os";
|
||||
import {tray} from "./tray";
|
||||
import {iconPath} from "./main";
|
||||
import {tray} from "../tray";
|
||||
import {iconPath} from "../main";
|
||||
import {getConfig, setConfig, firstRun} from "../common/config";
|
||||
import {getWindowState, setWindowState} from "../common/windowState";
|
||||
export let mainWindow: BrowserWindow;
|
||||
export let inviteWindow: BrowserWindow;
|
||||
let forceQuit = false;
|
||||
|
@ -196,7 +189,6 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
|||
if (!fs.existsSync(`${userDataPath}/disabled.txt`)) {
|
||||
fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), "");
|
||||
}
|
||||
registerGlobalKeybinds();
|
||||
mainWindow.webContents.on("did-finish-load", () => {
|
||||
fs.readdirSync(themesFolder).forEach((file) => {
|
||||
try {
|
||||
|
@ -265,7 +257,6 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
|||
mainWindow.on("unmaximize", () => {
|
||||
mainWindow.webContents.executeJavaScript(`document.body.removeAttribute("isMaximized");`);
|
||||
});
|
||||
console.log(contentPath);
|
||||
if ((await getConfig("inviteWebsocket")) == true) {
|
||||
require("arrpc");
|
||||
//await startServer();
|
||||
|
@ -274,7 +265,7 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
|||
mainWindow.close();
|
||||
}
|
||||
//loadURL broke for no good reason after E28
|
||||
mainWindow.loadFile(`${__dirname}/splash/redirect.html`);
|
||||
mainWindow.loadFile(`${__dirname}/../splash/redirect.html`);
|
||||
|
||||
if (await getConfig("skipSplash")) {
|
||||
mainWindow.show();
|
|
@ -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
|
||||
import {BrowserWindow, app, crashReporter, session} from "electron";
|
||||
import "v8-compile-cache";
|
||||
import {
|
||||
Settings,
|
||||
checkForDataFolder,
|
||||
checkIfConfigExists,
|
||||
firstRun,
|
||||
checkIfConfigIsBroken,
|
||||
getConfig,
|
||||
getConfigSync,
|
||||
injectElectronFlags,
|
||||
installModLoader,
|
||||
setConfig,
|
||||
setLang
|
||||
} from "./utils";
|
||||
import "./extensions/mods";
|
||||
import "./discord/extensions/csp";
|
||||
import "./tray";
|
||||
import {createCustomWindow, createNativeWindow, createTransparentWindow} from "./window";
|
||||
import fs from "fs";
|
||||
import {createCustomWindow, createNativeWindow, createTransparentWindow} from "./discord/window";
|
||||
import path from "path";
|
||||
import {createTManagerWindow} from "./themeManager/main";
|
||||
import {createSplashWindow} from "./splash/main";
|
||||
import {createSetupWindow} from "./setup/main";
|
||||
import {createKeybindWindow} from "./keybindMaker/main";
|
||||
import {
|
||||
setConfig,
|
||||
getConfigSync,
|
||||
checkForDataFolder,
|
||||
checkIfConfigExists,
|
||||
checkIfConfigIsBroken,
|
||||
getConfig,
|
||||
firstRun,
|
||||
Settings,
|
||||
getConfigLocation
|
||||
} from "./common/config";
|
||||
import {injectElectronFlags} from "./common/flags";
|
||||
import {setLang} from "./common/lang";
|
||||
import {installModLoader} from "./discord/extensions/mods";
|
||||
export let iconPath: string;
|
||||
export let settings: any;
|
||||
export let customTitlebar: boolean;
|
||||
|
@ -47,10 +48,6 @@ async function args(): Promise<void> {
|
|||
app.whenReady().then(async () => {
|
||||
createTManagerWindow();
|
||||
});
|
||||
} else if (args == "keybinds") {
|
||||
app.whenReady().then(async () => {
|
||||
createKeybindWindow();
|
||||
});
|
||||
}
|
||||
}
|
||||
args(); // i want my top level awaits
|
||||
|
@ -84,6 +81,7 @@ if (!app.requestSingleInstanceLock() && getConfigSync("multiInstance") == (false
|
|||
checkIfConfigExists();
|
||||
checkIfConfigIsBroken();
|
||||
injectElectronFlags();
|
||||
console.log("[Config Manager] Current config: " + fs.readFileSync(getConfigLocation(), "utf-8"));
|
||||
app.whenReady().then(async () => {
|
||||
if ((await getConfig("customIcon")) !== undefined ?? null) {
|
||||
iconPath = await getConfig("customIcon");
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {BrowserWindow, app, shell} from "electron";
|
||||
import {getDisplayVersion} from "../utils";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import {getDisplayVersion} from "../common/version";
|
||||
let settingsWindow: BrowserWindow;
|
||||
let instance = 0;
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ import {BrowserWindow, app, ipcMain} from "electron";
|
|||
import path from "path";
|
||||
import * as fs from "fs";
|
||||
import {iconPath} from "../main";
|
||||
import {Settings, getConfigLocation, setConfigBulk} from "../utils";
|
||||
import {setConfigBulk, getConfigLocation, Settings} from "../common/config";
|
||||
|
||||
let setupWindow: BrowserWindow;
|
||||
export function createSetupWindow(): void {
|
||||
setupWindow = new BrowserWindow({
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {contextBridge, ipcRenderer} from "electron";
|
||||
import {injectTitlebar} from "../preload/titlebar";
|
||||
import {injectTitlebar} from "../discord/preload/titlebar";
|
||||
|
||||
injectTitlebar();
|
||||
contextBridge.exposeInMainWorld("armcordinternal", {
|
||||
restart: () => ipcRenderer.send("restart"),
|
||||
|
|
|
@ -19,5 +19,5 @@ export async function createSplashWindow(): Promise<void> {
|
|||
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 {sleep} from "../utils";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import {createInviteWindow, mainWindow} from "../window";
|
||||
import {sleep} from "../common/sleep";
|
||||
import {createInviteWindow, mainWindow} from "../discord/window";
|
||||
let themeWindow: BrowserWindow;
|
||||
let instance = 0;
|
||||
interface ThemeManifest {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {ipcRenderer, contextBridge} from "electron";
|
||||
import {sleep} from "../utils";
|
||||
import {sleep} from "../common/sleep";
|
||||
contextBridge.exposeInMainWorld("themes", {
|
||||
install: (url: string) => ipcRenderer.send("installBDTheme", url),
|
||||
uninstall: (id: string) => ipcRenderer.send("uninstallTheme", id)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import * as fs from "fs";
|
||||
import {Menu, MessageBoxOptions, Tray, app, dialog, nativeImage} from "electron";
|
||||
import {createInviteWindow, mainWindow} from "./window";
|
||||
import {getConfig, getConfigLocation, getDisplayVersion, setConfig} from "./utils";
|
||||
import {createInviteWindow, mainWindow} from "./discord/window";
|
||||
import * as path from "path";
|
||||
import {createSettingsWindow} from "./settings/main";
|
||||
import {getConfig, getConfigLocation, setConfig} from "./common/config";
|
||||
import {getDisplayVersion} from "./common/version";
|
||||
export let tray: any = null;
|
||||
let trayIcon = "ac_plug_colored";
|
||||
app.whenReady().then(async () => {
|
||||
|
|
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".
|
||||
//"exclude": ["src/**/*.test.ts"], // Exclude .test.ts files since they're for Jest only.
|
||||
"compilerOptions": {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue