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