Compare commits

...

13 commits

Author SHA1 Message Date
smartfrigde
53fd78dc4b Remove the build workflow for now 2022-03-05 18:07:47 +01:00
smartfrigde
1be3275375 Change the support link 2022-03-05 18:07:07 +01:00
smartfrigde
2f01b26339 Fix temporary setup and format 2022-03-05 18:04:27 +01:00
smartfridge
46f91276c2
Merge pull request #85 from lexisother/main
Install Prettier
2022-03-04 19:22:56 +01:00
Alyxia Sother
87920505db
formatting
a.k.a. "Install Prettier"
2022-03-04 18:21:48 +00:00
smartfridge
18bebfb421
Merge pull request #84 from lexisother/main
Rid ArmCord of `ts-ignore`
2022-03-04 17:37:55 +01:00
Alyxia Sother
7ed9f1bef2
Rid ArmCord of ts-ignore 2022-03-04 17:30:23 +01:00
smartfridge
bab60a4f1b
Update tray.ts 2022-03-01 16:57:05 +01:00
smartfrigde
2a736fb9e0 v3.1.0 2022-02-26 22:27:06 +01:00
smartfrigde
c98b6016ca Settings, Electron 17 and much more 2022-02-26 22:26:16 +01:00
smartfridge
fcdb247601
Merge pull request #77 from xTunio/main
Fix app verion in tray
2022-02-18 22:57:12 +01:00
xTunio
ead7e62d39
fix app verion in tray 2022-02-18 21:20:48 +01:00
smartfridge
f7711034c5
Add sourceforge 2022-02-07 18:25:47 +01:00
36 changed files with 2044 additions and 1026 deletions

View file

@ -1,37 +0,0 @@
name: Build/release
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v1
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 16
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
# GitHub token, automatically provided to the action
# (No need to define this secret in the repo settings)
github_token: ${{ secrets.github_token }}
# skip npm run build as there's no script like that
skip_build: false
# If the commit is tagged with a version (e.g. "v1.0.0"),
# release the app after building
release: ${{ startsWith(github.ref, 'refs/tags/v') }} #disabled for now as it caused problems (nvm)
- name: Archive production builds
uses: actions/upload-artifact@v2
with:
name: dist folder
path: dist/**

1
.husky/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
_

5
.husky/pre-commit Executable file
View file

@ -0,0 +1,5 @@
#!/bin/sh
. "$(dirname $0)/_/husky.sh"
npm run format
git add -A

11
.prettierignore Normal file
View file

@ -0,0 +1,11 @@
# Some prettier-specific files so it doesn't die.
**/*.png
**/*.ico
LICENSE
.gitignore
node_modules
out/
dist
ts-out/
ts-out

View file

@ -32,8 +32,8 @@
# How to run/install it? # How to run/install it?
### Recommended: ### Recommended:
Check releases tab for precompiled packages for Linux, Windows and ~~Mac OS~~ (Mac OS is broken see [#48](https://github.com/ArmCord/ArmCord/issues/48)). Check releases tab for precompiled packages for Linux, Windows and ~~Mac OS~~ (Mac OS is broken see [#48](https://github.com/ArmCord/ArmCord/issues/48)). Alternatively use our Sourceforge mirror.
<a href="https://sourceforge.net/projects/armcord/files/latest/download"><img alt="Download ArmCord" src="https://a.fsdn.com/con/app/sf-download-button" width=276 height=48 srcset="https://a.fsdn.com/con/app/sf-download-button?button_size=2x 2x"></a>
### AUR Package ### AUR Package
Armcord is also available on the Arch User Repository (AUR) [here](https://aur.archlinux.org/packages/armcord-bin/). Armcord is also available on the Arch User Repository (AUR) [here](https://aur.archlinux.org/packages/armcord-bin/).

671
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,15 @@
{ {
"name": "ArmCord", "name": "ArmCord",
"version": "3.0.4", "version": "3.1.0",
"description": "ArmCord is a custom client designed to enhance your Discord experience while keeping everything lightweight.", "description": "ArmCord is a custom client designed to enhance your Discord experience while keeping everything lightweight.",
"main": "ts-out/main.js", "main": "ts-out/main.js",
"scripts": { "scripts": {
"build": "tsc && copyfiles -u 1 src/**/*.html src/**/*.css ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/** ts-out/", "build": "tsc && copyfiles -u 1 src/**/*.html src/**/**/*.css ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/** ts-out/",
"watch": "tsc -w", "watch": "tsc -w",
"start": "npm run build && electron ./ts-out/main.js", "start": "npm run build && electron ./ts-out/main.js",
"package": "npm run build && electron-builder" "package": "npm run build && electron-builder",
"format": "prettier --write src/**/*",
"postinstall": "husky install"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -23,12 +25,17 @@
"@types/electron-json-storage": "^4.5.0", "@types/electron-json-storage": "^4.5.0",
"@types/node": "^14.18.2", "@types/node": "^14.18.2",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"electron": "^16.0.7", "electron": "^17.1.0",
"electron-builder": "^22.14.5", "electron-builder": "^22.14.13",
"husky": "^7.0.4",
"prettier": "^2.5.1",
"typescript": "^4.5.4" "typescript": "^4.5.4"
}, },
"dependencies": { "dependencies": {
"electron-context-menu": "^3.1.2",
"electron-json-storage": "^4.5.0", "electron-json-storage": "^4.5.0",
"electron-tabs": "^0.15.0",
"glasstron": "^0.1.1",
"v8-compile-cache": "^2.3.0" "v8-compile-cache": "^2.3.0"
}, },
"build": { "build": {

14
prettier.config.js Normal file
View file

@ -0,0 +1,14 @@
module.exports = {
printWidth: 120,
tabWidth: 4,
useTabs: false,
semi: true,
singleQuote: false,
quoteProps: "as-needed",
jsxSingleQuote: false,
trailingComma: "none",
bracketSpacing: false,
jsxBracketSameLine: false,
arrowParens: "always",
endOfLine: "auto"
};

View file

@ -1,5 +1,5 @@
.info-1sUqUG:last-child:before { .info-3pQQBb:last-child:before {
content: "ArmCord Version: 3.1.0"!important; content: "ArmCord Version: 3.1.0" !important;
height: auto; height: auto;
line-height: 16px; line-height: 16px;
text-align: center; text-align: center;

53
src/content/css/tabs.css Normal file
View file

@ -0,0 +1,53 @@
@import url("https://kckarnige.github.io/femboi_owo/discord-font.css");
:root {
--window-buttons: var(--header-secondary);
--cord-color: var(--header-primary);
--armcord-color: #7289da;
--titlebar-color: var(--background-tertiary);
}
.tabs {
display: block;
top: 0;
left: 0;
right: 0;
flex-shrink: 0;
overflow: hidden;
zoom: 1;
box-sizing: border-box;
width: 100%;
clear: both;
height: 30px;
line-height: 30px;
background-color: #202225;
-webkit-app-region: drag;
width: 100%;
user-select: none;
-webkit-user-select: none;
position: fixed;
z-index: 99999;
}
.tabs #tabs-controls-container {
float: left;
width: 150px;
height: 100%;
line-height: 30px;
background-color: #202225;
-webkit-app-region: no-drag;
}
.tabs-buttons {
color: white;
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
transition: 0.3s;
font-size: 20px;
}
.tabs-buttons:hover {
background-color: #4e515a;
}
.withFrame-haYltI {
height: 30px !important;
}

View file

@ -1,4 +1,4 @@
@import url("https://kckarnige.github.io/femboi_owo/discord-font.css"); @import url("https://armcord.smartfridge.space/logofont.css");
:root { :root {
--window-buttons: var(--header-secondary); --window-buttons: var(--header-secondary);
--cord-color: var(--header-primary); --cord-color: var(--header-primary);

View file

@ -1 +0,0 @@
<h1>settings, really cool</h1>

View file

@ -1,10 +1,8 @@
<!--- This is awful and should be replaced in later versions. Possibly based of current settings as of 3.1.0 version. If you have time please PR a better setup screen. --->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<link <link rel="stylesheet" href="https://cdn.metroui.org.ua/v4/css/metro-all.min.css" />
rel="stylesheet"
href="https://cdn.metroui.org.ua/v4/css/metro-all.min.css"
/>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>ArmCord Setup</title> <title>ArmCord Setup</title>
<style> <style>
@ -22,7 +20,6 @@
</div> </div>
</div> </div>
<script> <script>
function fade(element) { function fade(element) {
var op = 1; // initial opacity var op = 1; // initial opacity
var timer = setInterval(function () { var timer = setInterval(function () {
@ -42,12 +39,20 @@
} else { } else {
console.log("Starting ArmCord Setup..."); console.log("Starting ArmCord Setup...");
document.getElementById("express").addEventListener("click", function () { document.getElementById("express").addEventListener("click", function () {
window.armcordinternal.saveSettings(true, "stable", true, "cumcord"); window.armcordinternal.saveSettings({
windowStyle: "default",
channel: "stable",
armcordCSP: true,
minimizeToTray: true,
automaticPatches: false,
mods: "cumcord",
blurType: "acrylic"
});
fade(document.getElementById("setup")); fade(document.getElementById("setup"));
setTimeout(function () { setTimeout(function () {
window.armcordinternal.restart() window.armcordinternal.restart();
}, 5000); }, 5000);
}) });
document.getElementById("full").addEventListener("click", function () { document.getElementById("full").addEventListener("click", function () {
document.getElementById("setup").innerHTML = ` document.getElementById("setup").innerHTML = `
<p class="text-center setup-ask">Choose your Discord channel/instance:</p> <p class="text-center setup-ask">Choose your Discord channel/instance:</p>
@ -68,9 +73,7 @@
</div> </div>
<button id="next" class="center">Next</button> <button id="next" class="center">Next</button>
`; `;
document document.getElementById("next").addEventListener("click", function () {
.getElementById("next")
.addEventListener("click", function () {
var branch = document.getElementById("channel").value; var branch = document.getElementById("channel").value;
var csp = document.getElementById("csp").value; var csp = document.getElementById("csp").value;
if (csp === "true") { if (csp === "true") {
@ -86,22 +89,35 @@
<p>Why not all of them? Having many client mods at the same time can cause issues. If you really want to do it though, check our documentation ;)</p> <p>Why not all of them? Having many client mods at the same time can cause issues. If you really want to do it though, check our documentation ;)</p>
<button id="next" class="center">Next</button> <button id="next" class="center">Next</button>
`; `;
document document.getElementById("next").addEventListener("click", function () {
.getElementById("next")
.addEventListener("click", function () {
var mod = document.getElementById("mod").value; var mod = document.getElementById("mod").value;
window.armcordinternal.saveSettings(true, branch, true, mod); window.armcordinternal.saveSettings({
windowStyle: "default",
channel: branch,
armcordCSP: true,
minimizeToTray: true,
automaticPatches: false,
mods: mod,
blurType: "acrylic"
});
fade(document.getElementById("setup")); fade(document.getElementById("setup"));
setTimeout(function () { setTimeout(function () {
window.armcordinternal.restart(); window.armcordinternal.restart();
}, 5000); }, 5000);
}); });
} else { } else {
//saveSettings(customTitlebarSetting: boolean, channelSetting: string, armcordCSPSetting: boolean, modsSetting: string) window.armcordinternal.saveSettings({
window.armcordinternal.saveSettings(true, branch, true, "none"); windowStyle: "default",
channel: branch,
armcordCSP: true,
minimizeToTray: true,
automaticPatches: false,
mods: "none",
blurType: "acrylic"
});
fade(document.getElementById("setup")); fade(document.getElementById("setup"));
setTimeout(function () { setTimeout(function () {
window.armcordinternal.restart() window.armcordinternal.restart();
}, 5000); }, 5000);
} }
}); });

View file

@ -11,10 +11,7 @@
<body> <body>
<div class="container"> <div class="container">
<video autoplay loop class="logo" id="splashscreen-armcord"> <video autoplay loop class="logo" id="splashscreen-armcord">
<source <source src="https://armcord.smartfridge.space/discord_loading.webm" type="video/webm" />
src="https://armcord.smartfridge.space/discord_loading.webm"
type="video/webm"
/>
</video> </video>
<p id="text-splashscreen"></p> <p id="text-splashscreen"></p>
</div> </div>
@ -22,8 +19,7 @@
<script> <script>
const text = document.getElementById("text-splashscreen"); const text = document.getElementById("text-splashscreen");
if (window.navigator.onLine === false) { if (window.navigator.onLine === false) {
text.innerHTML = text.innerHTML = "You appear to be offline. Please connect to the internet and try again.";
"You appear to be offline. Please connect to the internet and try again.";
} else { } else {
text.innerHTML = "Starting ArmCord..."; text.innerHTML = "Starting ArmCord...";
fetch("https://armcord.smartfridge.space/latest.json") fetch("https://armcord.smartfridge.space/latest.json")
@ -35,10 +31,9 @@
elem.src = "https://armcord.smartfridge.space/update.webp"; elem.src = "https://armcord.smartfridge.space/update.webp";
document.body.prepend(elem); document.body.prepend(elem);
document.getElementById("splashscreen-armcord").remove(); document.getElementById("splashscreen-armcord").remove();
text.innerHTML = text.innerHTML = "A new version of ArmCord is available. Please update to the latest version.";
"A new version of ArmCord is available. Please update to the latest version.";
} else { } else {
console.log("ArmCord is up to date.") console.log("ArmCord is up to date.");
} }
}); });
setTimeout(() => { setTimeout(() => {

View file

@ -12,8 +12,8 @@ import electron from "electron";
import * as storage from "electron-json-storage"; import * as storage from "electron-json-storage";
const otherMods = { const otherMods = {
generic: { generic: {
electronProxy: require("util").types.isProxy(electron), // Many modern mods overwrite electron with a proxy with a custom BrowserWindow (copied from PowerCord) electronProxy: require("util").types.isProxy(electron) // Many modern mods overwrite electron with a proxy with a custom BrowserWindow (copied from PowerCord)
}, }
}; };
const unstrictCSP = () => { const unstrictCSP = () => {
@ -27,11 +27,10 @@ const unstrictCSP = () => {
"https://api.goosemod.com/inject.js", "https://api.goosemod.com/inject.js",
"https://raw.githubusercontent.com/Cumcord/Cumcord/stable/dist/build.js", "https://raw.githubusercontent.com/Cumcord/Cumcord/stable/dist/build.js",
"https://raw.githubusercontent.com/Cumcord/Cumcord/master/dist/build.js", "https://raw.githubusercontent.com/Cumcord/Cumcord/master/dist/build.js",
"https://raw.githubusercontent.com/FlickerMod/dist/main/build.js", "https://raw.githubusercontent.com/FlickerMod/dist/main/build.js"
]; ];
electron.session.defaultSession.webRequest.onHeadersReceived( electron.session.defaultSession.webRequest.onHeadersReceived(({responseHeaders, url}, done) => {
({ responseHeaders, url }, done) => {
let csp = responseHeaders!["content-security-policy"]; let csp = responseHeaders!["content-security-policy"];
if (otherMods.generic.electronProxy) { if (otherMods.generic.electronProxy) {
@ -53,17 +52,14 @@ const unstrictCSP = () => {
responseHeaders!["access-control-allow-origin"] = ["*"]; responseHeaders!["access-control-allow-origin"] = ["*"];
} }
done({ responseHeaders }); done({responseHeaders});
} });
);
}; };
storage.get("settings", function (error, data: any) { storage.get("settings", function (error, data: any) {
if (error) throw error; if (error) throw error;
if (data.armcordCSP) { if (data.armcordCSP) {
unstrictCSP(); unstrictCSP();
} else { } else {
console.log( console.log("ArmCord CSP is disabled. The CSP should be managed by third-party plugin.");
"ArmCord CSP is disabled. The CSP should be managed by third-party plugin."
);
} }
}); });

View file

@ -1,24 +1,18 @@
import * as fs from 'fs'; import * as fs from "fs";
import { app, session } from 'electron'; import {app, session} from "electron";
const userDataPath = app.getPath("userData"); const userDataPath = app.getPath("userData");
const pluginFolder = userDataPath + "/plugins/"; const pluginFolder = userDataPath + "/plugins/";
if (!fs.existsSync(pluginFolder)) { if (!fs.existsSync(pluginFolder)) {
fs.mkdirSync(pluginFolder); fs.mkdirSync(pluginFolder);
console.log("Created missing plugin folder"); console.log("Created missing plugin folder");
} }
app.whenReady().then(() => { app.whenReady().then(() => {
fs.readdirSync(pluginFolder).forEach((file) => { fs.readdirSync(pluginFolder).forEach((file) => {
try { try {
const manifest = fs.readFileSync( const manifest = fs.readFileSync(`${userDataPath}/plugins/${file}/manifest.json`, "utf8");
`${userDataPath}/plugins/${file}/manifest.json`,
"utf8"
);
var pluginFile = JSON.parse(manifest); var pluginFile = JSON.parse(manifest);
session.defaultSession.loadExtension(`${userDataPath}/plugins/${file}`); session.defaultSession.loadExtension(`${userDataPath}/plugins/${file}`);
console.log( console.log(`%cLoaded ${pluginFile.name} made by ${pluginFile.author}`, "color:red");
`%cLoaded ${pluginFile.name} made by ${pluginFile.author}`,
"color:red"
);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }

View file

@ -1,13 +1,16 @@
//ipc stuff //ipc stuff
import { app, ipcMain, shell, desktopCapturer } from "electron"; import {app, ipcMain, shell, desktopCapturer} from "electron";
import { mainWindow } from "./window"; import {createTabsGuest, mainWindow} from "./window";
import { saveSettings, getVersion } from "./utils"; import {saveSettings, getVersion} from "./utils";
import { settings, customTitlebar } from "./main"; import {settings, customTitlebar, tabs} from "./main";
import { createSettingsWindow } from "./settings/main"; import {createSettingsWindow} from "./settings/main";
export function registerIpc() { export function registerIpc() {
ipcMain.on("get-app-path", (event, arg) => { ipcMain.on("get-app-path", (event, arg) => {
event.reply("app-path", app.getAppPath()); event.reply("app-path", app.getAppPath());
}); });
ipcMain.on("openTab", (event, number: number) => {
createTabsGuest(number);
});
ipcMain.on("open-external-link", (event, href: string) => { ipcMain.on("open-external-link", (event, href: string) => {
shell.openExternal(href); shell.openExternal(href);
}); });
@ -29,6 +32,9 @@ export function registerIpc() {
ipcMain.on("win-hide", (event, arg) => { ipcMain.on("win-hide", (event, arg) => {
mainWindow.hide(); mainWindow.hide();
}); });
ipcMain.on("win-quit", (event, arg) => {
app.exit();
});
ipcMain.on("get-app-version", (event) => { ipcMain.on("get-app-version", (event) => {
event.returnValue = getVersion(); event.returnValue = getVersion();
}); });
@ -39,10 +45,12 @@ export function registerIpc() {
app.relaunch(); app.relaunch();
app.exit(); app.exit();
}); });
ipcMain.on("saveSettings", (event, args) => {
ipcMain.on("saveSettings", (event, ...args) => { saveSettings(args);
//@ts-ignore });
saveSettings(...args); ipcMain.on("minimizeToTray", (event) => {
console.log(settings.minimizeToTray);
event.returnValue = settings.minimizeToTray;
}); });
ipcMain.on("channel", (event) => { ipcMain.on("channel", (event) => {
event.returnValue = settings.channel; event.returnValue = settings.channel;
@ -53,6 +61,12 @@ export function registerIpc() {
ipcMain.on("titlebar", (event, arg) => { ipcMain.on("titlebar", (event, arg) => {
event.returnValue = customTitlebar; event.returnValue = customTitlebar;
}); });
ipcMain.on("tabs", (event, arg) => {
event.returnValue = tabs;
});
ipcMain.on("shouldPatch", (event, arg) => {
event.returnValue = settings.automaticPatches;
});
ipcMain.on("openSettingsWindow", (event, arg) => { ipcMain.on("openSettingsWindow", (event, arg) => {
createSettingsWindow(); createSettingsWindow();
}); });
@ -63,8 +77,5 @@ export function registerIpc() {
event.returnValue = false; event.returnValue = false;
} }
}); });
ipcMain.handle("DESKTOP_CAPTURER_GET_SOURCES", (event, opts) => ipcMain.handle("DESKTOP_CAPTURER_GET_SOURCES", (event, opts) => desktopCapturer.getSources(opts));
desktopCapturer.getSources(opts)
);
} }

View file

@ -1,22 +1,26 @@
// Modules to control application life and create native browser window // Modules to control application life and create native browser window
import { import {app, BrowserWindow, session} from "electron";
app,
BrowserWindow,
session,
} from "electron";
import * as path from "path"; import * as path from "path";
import "v8-compile-cache"; import "v8-compile-cache";
import * as storage from "electron-json-storage"; import * as storage from "electron-json-storage";
import { getConfigUnsafe, setup } from "./utils"; import {getConfigUnsafe, setup} from "./utils";
import "./extensions/mods"; import "./extensions/mods";
import "./extensions/plugin"; import "./extensions/plugin";
import "./tray"; import "./tray";
import { mainWindow, createCustomWindow, createNativeWindow } from "./window"; import {mainWindow, createCustomWindow, createNativeWindow, createGlasstronWindow, createTabsHost} from "./window";
import "./shortcuts"; import "./shortcuts";
export var contentPath: string; export var contentPath: string;
var channel: string; var channel: string;
export var settings: any; export var settings: any;
export var customTitlebar: boolean; export var customTitlebar: boolean;
export var tabs: boolean;
async function appendSwitch() {
if ((await getConfigUnsafe("windowStyle")) == "glasstron") {
console.log("Enabling transparency visuals.");
app.commandLine.appendSwitch("enable-transparent-visuals");
}
}
appendSwitch();
storage.has("settings", function (error, hasKey) { storage.has("settings", function (error, hasKey) {
if (error) throw error; if (error) throw error;
@ -42,23 +46,35 @@ storage.get("settings", function (error, data: any) {
settings = data; settings = data;
}); });
app.whenReady().then(async () => { app.whenReady().then(async () => {
if (await getConfigUnsafe("customTitlebar") == true) { switch (await getConfigUnsafe("windowStyle")) {
console.log("Creating custom titlebar window."); case "default":
customTitlebar = true;
createCustomWindow(); createCustomWindow();
} else if (await getConfigUnsafe("customTitlebar") == "setup") {
//rare case of setup window
console.log("Creating setup window.");
customTitlebar = true; customTitlebar = true;
createCustomWindow(); break;
} else { case "native":
console.log("Creating native titlebar window.");
customTitlebar = false;
createNativeWindow(); createNativeWindow();
break;
case "glasstron":
setTimeout(
createGlasstronWindow,
process.platform == "linux" ? 1000 : 0
// Electron has a bug on linux where it
// won't initialize properly when using
// transparency. To work around that, it
// is necessary to delay the window
// spawn function.
);
break;
case "tabs":
createTabsHost();
tabs = true;
break;
default:
createCustomWindow();
customTitlebar = true;
break;
} }
session session.fromPartition("some-partition").setPermissionRequestHandler((webContents, permission, callback) => {
.fromPartition("some-partition")
.setPermissionRequestHandler((webContents, permission, callback) => {
if (permission === "notifications") { if (permission === "notifications") {
// Approves the permissions request // Approves the permissions request
callback(true); callback(true);
@ -68,19 +84,21 @@ app.whenReady().then(async () => {
callback(true); callback(true);
} }
}); });
mainWindow.webContents.session.webRequest.onBeforeRequest( app.on("activate", async function () {
(details, callback) => {
if (/api\/v\d\/science$/g.test(details.url))
return callback({ cancel: true });
return callback({});
}
);
app.on("activate", function () {
if (BrowserWindow.getAllWindows().length === 0) if (BrowserWindow.getAllWindows().length === 0)
if (!settings.customTitlebar) { switch (await getConfigUnsafe("windowStyle")) {
createNativeWindow(); case "default":
} else {
createCustomWindow(); createCustomWindow();
break;
case "native":
createNativeWindow();
break;
case "glasstron":
createGlasstronWindow();
break;
default:
createCustomWindow();
break;
} }
}); });
}); });

View file

@ -1,32 +1,30 @@
import { contextBridge, ipcRenderer } from "electron"; import {contextBridge, ipcRenderer} from "electron";
import { getDisplayMediaSelector } from "./capturer"; import {getDisplayMediaSelector} from "./capturer";
import { injectTitlebar } from "./titlebar"; import {injectTitlebar} from "./titlebar";
contextBridge.exposeInMainWorld("armcord", { contextBridge.exposeInMainWorld("armcord", {
window: { window: {
show: () => ipcRenderer.send("win-show"), show: () => ipcRenderer.send("win-show"),
hide: () => ipcRenderer.send("win-hide"), hide: () => ipcRenderer.send("win-hide"),
minimize: () => ipcRenderer.send("win-minimize"), minimize: () => ipcRenderer.send("win-minimize"),
maximize: () => ipcRenderer.send("win-maximize"), maximize: () => ipcRenderer.send("win-maximize")
}, },
titlebar: { titlebar: {
injectTitlebar: () => injectTitlebar(), injectTitlebar: () => injectTitlebar(),
isTitlebar: ipcRenderer.sendSync("titlebar"), isTitlebar: ipcRenderer.sendSync("titlebar")
}, },
electron: process.versions.electron, electron: process.versions.electron,
channel: ipcRenderer.sendSync("channel"), channel: ipcRenderer.sendSync("channel"),
openTab: (number: number) => ipcRenderer.sendSync("openTab", number),
version: ipcRenderer.sendSync("get-app-version", "app-version"), version: ipcRenderer.sendSync("get-app-version", "app-version"),
getDisplayMediaSelector: getDisplayMediaSelector, getDisplayMediaSelector: getDisplayMediaSelector,
openSettingsWindow: () => ipcRenderer.send("openSettingsWindow"), openSettingsWindow: () => ipcRenderer.send("openSettingsWindow")
}); });
//to be only used inside armcord internal setup/splash etc //to be only used inside armcord internal setup/splash etc
if ( if (window.location.href.indexOf("splash.html") > -1 || window.location.href.indexOf("setup.html") > -1) {
window.location.href.indexOf("splash.html") > -1 ||
window.location.href.indexOf("setup.html") > -1
) {
contextBridge.exposeInMainWorld("armcordinternal", { contextBridge.exposeInMainWorld("armcordinternal", {
restart: () => ipcRenderer.send("restart"), restart: () => ipcRenderer.send("restart"),
saveSettings: (...args: any) => ipcRenderer.send("saveSettings", ...args), saveSettings: (...args: any) => ipcRenderer.send("saveSettings", ...args),
splashEnd: () => ipcRenderer.send("splashEnd"), splashEnd: () => ipcRenderer.send("splashEnd")
}); });
} }

View file

@ -1,28 +1,36 @@
//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 { desktopCapturer } from 'electron'; import {ipcRenderer} from "electron";
import {addStyle, addScript} from '../utils'; import {addStyle, addScript} from "../utils";
const desktopCapturer = {
getSources: (opts: any) => ipcRenderer.invoke("DESKTOP_CAPTURER_GET_SOURCES", opts)
};
const CANCEL_ID = "desktop-capturer-selection__cancel";
const CANCEL_ID = 'desktop-capturer-selection__cancel'; interface IPCSources {
id: string;
name: string;
thumbnail: HTMLCanvasElement;
}
export async function getDisplayMediaSelector() { export async function getDisplayMediaSelector() {
const sources = await desktopCapturer.getSources({ const sources: IPCSources[] = await desktopCapturer.getSources({
types: ['screen', 'window'], types: ["screen", "window"]
}); });
return `<div class="desktop-capturer-selection__scroller"> return `<div class="desktop-capturer-selection__scroller">
<ul class="desktop-capturer-selection__list"> <ul class="desktop-capturer-selection__list">
${sources ${sources
.map( .map(
({ id, name, thumbnail }) => ` ({id, name, thumbnail}) => `
<li class="desktop-capturer-selection__item"> <li class="desktop-capturer-selection__item">
<button class="desktop-capturer-selection__btn" data-id="${id}" title="${name}"> <button class="desktop-capturer-selection__btn" data-id="${id}" title="${name}">
<img class="desktop-capturer-selection__thumbnail" src="${thumbnail.toDataURL()}" /> <img class="desktop-capturer-selection__thumbnail" src="${thumbnail.toDataURL()}" />
<span class="desktop-capturer-selection__name">${name}</span> <span class="desktop-capturer-selection__name">${name}</span>
</button> </button>
</li> </li>
`, `
) )
.join('')} .join("")}
<li class="desktop-capturer-selection__item"> <li class="desktop-capturer-selection__item">
<button class="desktop-capturer-selection__btn" data-id="${CANCEL_ID}" title="Cancel"> <button class="desktop-capturer-selection__btn" data-id="${CANCEL_ID}" title="Cancel">
<span class="desktop-capturer-selection__name desktop-capturer-selection__name--cancel">Cancel</span> <span class="desktop-capturer-selection__name desktop-capturer-selection__name--cancel">Cancel</span>
@ -145,9 +153,8 @@ window.navigator.mediaDevices.getDisplayMedia = () => new Promise(async (resolve
}); });
`; `;
document.addEventListener("DOMContentLoaded", function(event) { document.addEventListener("DOMContentLoaded", function () {
addScript(screenShareJS); addScript(screenShareJS);
addStyle(screenShareCSS); addStyle(screenShareCSS);
console.log("Capturer injected.") console.log("Capturer injected.");
}); });

22
src/preload/patch.ts Normal file
View file

@ -0,0 +1,22 @@
// What does this do?
// In case of faulty update of ArmCord we can quickly push an update to the user and possibly try to fix it
// This is completely optional and is disabled by default in settings
import {ipcRenderer} from "electron";
import {injectJS} from "../utils";
var patchEndpoint = "https://patch.armcord.xyz/";
var version = ipcRenderer.sendSync("get-app-version", "app-version");
if (ipcRenderer.sendSync("shouldPatch")) {
document.addEventListener("DOMContentLoaded", function () {
fetch(patchEndpoint + version + "/info.json", {cache: "no-store"}) //lmao
.then((res) => res.json())
.then((res) => {
if (res.patch == true) {
console.log("Found a patch. Injecting...");
injectJS(patchEndpoint + version + "/patch.js");
} else {
console.log("No patches have been found.");
}
});
});
}

View file

@ -1,11 +1,12 @@
import "./bridge"; import "./bridge";
import "./capturer"; import "./capturer";
import "./patch";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import { injectTitlebar } from "./titlebar"; import {injectTitlebar} from "./titlebar";
import { sleep, addStyle } from "../utils"; import {sleep, addStyle, injectJS} from "../utils";
import { ipcRenderer } from "electron"; import {ipcRenderer} from "electron";
import {injectTabs} from "./tabs";
declare global { declare global {
interface Window { interface Window {
armcord: any; armcord: any;
@ -13,19 +14,9 @@ declare global {
} }
const clientMods = { const clientMods = {
goosemod: "https://api.goosemod.com/inject.js", goosemod: "https://api.goosemod.com/inject.js",
cumcord: cumcord: "https://raw.githubusercontent.com/Cumcord/Cumcord/stable/dist/build.js",
"https://raw.githubusercontent.com/Cumcord/Cumcord/stable/dist/build.js", flicker: "https://raw.githubusercontent.com/FlickerMod/dist/main/build.js"
flicker: "https://raw.githubusercontent.com/FlickerMod/dist/main/build.js",
}; };
async function injectJS(inject: string) {
const js = await (await fetch(`${inject}`)).text();
const el = document.createElement("script");
el.appendChild(document.createTextNode(js));
document.body.appendChild(el);
}
console.log("ArmCord"); console.log("ArmCord");
if (window.location.href.indexOf("splash.html") > -1) { if (window.location.href.indexOf("splash.html") > -1) {
@ -34,6 +25,9 @@ if (window.location.href.indexOf("splash.html") > -1) {
if (ipcRenderer.sendSync("titlebar")) { if (ipcRenderer.sendSync("titlebar")) {
injectTitlebar(); injectTitlebar();
} }
if (ipcRenderer.sendSync("tabs")) {
injectTabs();
}
sleep(5000).then(() => { sleep(5000).then(() => {
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"));

26
src/preload/tabs.ts Normal file
View file

@ -0,0 +1,26 @@
import {addStyle} from "../utils";
import * as fs from "fs";
import * as path from "path";
export function injectTabs() {
document.addEventListener("DOMContentLoaded", function (event) {
var elem = document.createElement("div");
elem.innerHTML = `<nav class="tabs">
<div id="tabs-controls-container">
<button class="tabs-buttons" onclick="armcord.openTab(1)">1</button>
<button class="tabs-buttons" onclick="armcord.openTab(2)">2</button>
<button class="tabs-buttons" onclick="armcord.openTab(3)">3</button>
<button class="tabs-buttons" onclick="armcord.openTab(4)">4</button>
<button class="tabs-buttons" onclick="armcord.openTab(5)">5</button>
<p class="experimental">Experimental</p>
</div>
</nav>`;
elem.classList.add("withFrame-haYltI");
if (document.getElementById("app-mount") == null) {
document.body.appendChild(elem);
} else {
document.getElementById("app-mount")!.prepend(elem);
}
const cssPath = path.join(__dirname, "../", "/content/css/tabs.css");
addStyle(fs.readFileSync(cssPath, "utf8"));
});
}

View file

@ -1,5 +1,5 @@
import { ipcRenderer } from "electron"; import {ipcRenderer} from "electron";
import { addStyle } from "../utils"; import {addStyle} from "../utils";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
export function injectTitlebar() { export function injectTitlebar() {
@ -39,7 +39,11 @@ export function injectTitlebar() {
}); });
quit!.addEventListener("click", () => { quit!.addEventListener("click", () => {
if (ipcRenderer.sendSync("minimizeToTray") === true) {
ipcRenderer.send("win-hide"); ipcRenderer.send("win-hide");
} else if (ipcRenderer.sendSync("minimizeToTray") === false) {
ipcRenderer.send("win-quit");
}
}); });
}); });
} }

View file

@ -1,25 +1,46 @@
import { BrowserWindow, shell } from "electron"; import {BrowserWindow, shell, ipcMain} from "electron";
import * as storage from "electron-json-storage";
import {getConfigUnsafe, saveSettings, Settings} from "../utils";
import path from "path"; import path from "path";
var settings: any;
var settingsWindow; var isAlreadyCreated: boolean = false;
storage.get("settings", function (error, data: any) {
if (error) throw error;
console.log(data);
settings = data;
});
var settingsWindow: BrowserWindow;
export function createSettingsWindow() { export function createSettingsWindow() {
if (isAlreadyCreated) {
settingsWindow.show();
} else {
settingsWindow = new BrowserWindow({ settingsWindow = new BrowserWindow({
width: 500, width: 500,
height: 600, height: 500,
title: "ArmCord Settings", title: "ArmCord Settings",
darkTheme: true, darkTheme: true,
icon: path.join(__dirname, "/assets/icon_transparent.png"), frame: true,
frame: false,
autoHideMenuBar: true, autoHideMenuBar: true,
webPreferences: { webPreferences: {
preload: path.join(__dirname, "preload/preload.js"), preload: path.join(__dirname, "preload.js")
}, }
}); });
ipcMain.on("saveSettings", (event, args: Settings) => {
settingsWindow.webContents.setWindowOpenHandler(({ url }) => { console.log(args);
saveSettings(args);
});
ipcMain.handle("getSetting", (event, toGet: string) => {
return getConfigUnsafe(toGet);
});
settingsWindow.webContents.setWindowOpenHandler(({url}) => {
shell.openExternal(url); shell.openExternal(url);
return { action: "deny" }; return {action: "deny"};
}); });
settingsWindow.loadURL(`file://${__dirname}/settings.html`);
settingsWindow.loadFile("settings.html"); settingsWindow.on("close", async (e) => {
e.preventDefault();
settingsWindow.hide();
});
isAlreadyCreated = true;
}
} }

View file

@ -1 +1,9 @@
console.log("test") import {contextBridge, ipcRenderer} from "electron";
console.log("ArmCord Settings");
contextBridge.exposeInMainWorld("settings", {
save: (...args: any) => ipcRenderer.send("saveSettings", ...args),
get: (toGet: string) =>
ipcRenderer.invoke("getSetting", toGet).then((result) => {
return result;
}) //jank but works
});

176
src/settings/settings.css Normal file
View file

@ -0,0 +1,176 @@
/*MIT License
Copyright (c) 2021 GooseMod
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.*/
:root {
--background-primary: #282b30;
--background-secondary: rgba(255, 255, 255, 0.1);
--brand-experiment: #5865f2;
--header-primary: #fff;
--text-muted: #72767d;
}
@font-face {
font-family: Whitney;
font-weight: 400;
font-style: normal;
src: url(https://armcord.smartfridge.space/whitney_400.woff) format("woff");
}
html,
body {
overflow: hidden;
margin: 0;
padding: 0;
margin: 2%;
background: var(--background-primary);
}
* {
font-family: "Whitney", sans-serif;
box-sizing: border-box;
cursor: default;
}
.left {
float: right;
vertical-align: right !important;
}
.switch {
vertical-align: middle;
}
.header {
color: white;
font-size: 1.5em;
}
.center {
text-align: center;
}
/*buttons*/
button {
background-color: #7289da;
font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #ffffff;
padding: 4px;
border-radius: 5px;
margin-top: 5px;
text-align: center;
border-style: none;
outline: none;
}
button:hover {
background-color: #687dc6;
border-style: none;
outline: none;
cursor: pointer;
}
.tgl {
display: none;
}
.tgl,
.tgl:after,
.tgl:before,
.tgl *,
.tgl *:after,
.tgl *:before,
.tgl + .tgl-btn {
box-sizing: border-box;
}
.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: 4em;
height: 2em;
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: 0;
}
.tgl + .tgl-btn:before {
display: none;
}
.tgl:checked + .tgl-btn:after {
left: 50%;
}
.tgl-light + .tgl-btn {
background: #5c5757;
border-radius: 2em;
padding: 2px;
transition: all 0.4s ease;
}
.tgl-light + .tgl-btn:after {
border-radius: 50%;
background: rgb(255, 255, 255);
transition: all 0.2s ease;
}
.tgl-light:checked + .tgl-btn {
background: #47ca5a;
}
select {
-webkit-appearance: button;
-moz-appearance: button;
background-color: #2c2f33;
background-position: center right;
background-repeat: no-repeat;
border: 1px solid #aaa;
border-radius: 2px;
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
color: #fff;
font-size: 1.2em;
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View file

@ -0,0 +1,92 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>ArmCord Settings</title>
<style>
@import url("settings.css");
</style>
</head>
<body>
<div class="switch">
<select name="theme" id="theme" class="left">
<option value="default">Default</option>
<option value="native">Native</option>
<option value="glasstron">Glasstron (experimental)</option>
<option value="tabs">Tabs (experimental)</option>
</select>
<p class="header">ArmCord theme:</p>
</div>
<br />
<div class="switch">
<label class="header">ArmCord CSP</label>
<input class="tgl tgl-light left" id="csp" type="checkbox" />
<label class="tgl-btn left" for="csp"></label>
</div>
<br />
<div class="switch">
<label class="header">Minimize to tray</label>
<input class="tgl tgl-light left" id="tray" type="checkbox" />
<label class="tgl-btn left" for="tray"></label>
</div>
<br />
<div class="switch">
<label class="header">Automatic Patches</label>
<input class="tgl tgl-light left" id="patches" type="checkbox" />
<label class="tgl-btn left" for="patches"></label>
</div>
<div class="switch">
<select name="channel" id="channel" class="left">
<option value="stable">Stable</option>
<option value="canary">Canary</option>
<option value="ptb">PTB</option>
<option value="foss">Fosscord</option>
</select>
<p class="header">Discord channel:</p>
</div>
<div class="switch">
<select name="mod" id="mod" class="left">
<option value="cumcord">Cumcord</option>
<option value="goosemod">GooseMod</option>
<option value="flicker">Flicker</option>
</select>
<p class="header">Client mod:</p>
</div>
<div class="switch">
<select name="blurType" id="blurType" class="left">
<option value="acrylic">Acrylic</option>
<option value="blurbehind">Blur Behind</option>
<option value="transparent">Transparent</option>
<option value="none">None</option>
</select>
<p class="header">Glasstron blur type:</p>
</div>
<button id="save" class="center">Save settings</button>
</body>
<script>
async function loadSettings() {
document.getElementById("csp").checked = await settings.get("armcordCSP");
document.getElementById("tray").checked = await settings.get("minimizeToTray");
document.getElementById("patches").value = await settings.get("automaticPatches");
document.getElementById("mod").value = await settings.get("mods");
document.getElementById("channel").value = await settings.get("channel");
document.getElementById("theme").value = await settings.get("windowStyle");
document.getElementById("blurType").value = await settings.get("blurType");
}
loadSettings();
document.getElementById("save").addEventListener("click", function () {
//function saveSettings(windowStyle: string, channelSetting: string, armcordCSPSetting: boolean, minimizeToTray: boolean, automaticPatches: boolean,modsSetting: string, blurType: string)
settings.save(
document.getElementById("theme").value,
document.getElementById("channel").value,
document.getElementById("csp").checked,
document.getElementById("tray").checked,
document.getElementById("patches").checked,
document.getElementById("mod").value,
document.getElementById("blurType").value
);
});
</script>
</html>

View file

@ -1,11 +1,11 @@
import { app } from "electron"; import {app} from "electron";
import {mainWindow} from './window'; import {mainWindow} from "./window";
//https://github.com/electron/electron/issues/1334#issuecomment-716080005 //https://github.com/electron/electron/issues/1334#issuecomment-716080005
// TO-DO add more // TO-DO add more
app.on("web-contents-created", (webContentsCreatedEvent, webContents) => { app.on("web-contents-created", (webContentsCreatedEvent, webContents) => {
webContents.on("before-input-event", (beforeInputEvent, input) => { webContents.on("before-input-event", (beforeInputEvent, input) => {
// console.log('Main console::', input) // console.log('Main console::', input)
const { code, alt, control, shift, meta } = input; const {code, alt, control, shift, meta} = input;
// Shortcut: toggle devTools // Shortcut: toggle devTools
if (shift && control && !alt && !meta && code === "KeyI") { if (shift && control && !alt && !meta && code === "KeyI") {
mainWindow.webContents.toggleDevTools(); mainWindow.webContents.toggleDevTools();

View file

@ -1,31 +1,38 @@
import { app, Menu, Tray } from 'electron'; import {app, Menu, Tray} from "electron";
import {mainWindow} from './window'; import {mainWindow} from "./window";
import * as path from 'path' import * as path from "path";
let tray = null import {createSettingsWindow} from "./settings/main";
let tray = null;
app.whenReady().then(() => { app.whenReady().then(() => {
tray = new Tray(path.join(__dirname, "../", "/assets/ac_plug.png")) tray = new Tray(path.join(__dirname, "../", "/assets/ac_plug.png"));
const contextMenu = Menu.buildFromTemplate([ const contextMenu = Menu.buildFromTemplate([
{ {
label: "Open ArmCord", label: "Open ArmCord",
click: function () { click: function () {
mainWindow.show(); mainWindow.show();
}
}, },
{
label: "Open Settings",
click: function () {
createSettingsWindow();
}
}, },
{ {
label: "Support Discord Server", label: "Support Discord Server",
click: function () { click: function () {
mainWindow.show(); mainWindow.show();
mainWindow.loadURL("https://discord.gg/F25bc4RYDt"); mainWindow.loadURL("https://discord.gg/TnhxcqynZ2");
}, }
}, },
{ {
label: "Quit ArmCord", label: "Quit ArmCord",
click: function () { click: function () {
app.quit(); app.quit();
}, }
}, }
]); ]);
tray.setToolTip('ArmCord ' + process.env.npm_package_version) tray.setToolTip("ArmCord " + app.getVersion());
tray.setContextMenu(contextMenu) tray.setContextMenu(contextMenu);
}) });

70
src/types/glasstron.d.ts vendored Normal file
View file

@ -0,0 +1,70 @@
declare module "glasstron" {
export class BrowserWindow extends Electron.BrowserWindow {
getBlur(): Promise<boolean>;
setBlur(value: boolean): Promise<boolean>;
blurType: WindowsBlurType;
setVibrancy(vibrancy: MacOSVibrancy): void;
}
/**
* @deprecated
*/
export function init(): void;
/**
* @deprecated
*/
export function update(
window: Electron.BrowserWindow,
values: {
windows?: {
blurType: WindowsBlurType;
};
macos?: {
vibrancy: MacOSVibrancy;
};
linux?: {
requestBlur: boolean;
};
}
): void;
export class Hacks {
static injectOnElectron(): void;
static delayReadyEvent(): void;
}
export type WindowsBlurType = "acrylic" | "blurbehind" | "transparent" | "none";
export type MacOSVibrancy =
| (
| "appearance-based"
| "light"
| "dark"
| "titlebar"
| "selection"
| "menu"
| "popover"
| "sidebar"
| "medium-light"
| "ultra-dark"
| "header"
| "sheet"
| "window"
| "hud"
| "fullscreen-ui"
| "tooltip"
| "content"
| "under-window"
| "under-page"
)
| null;
}
declare module "glasstron/src/utils" {
class Utils {
static getSavePath(): string;
static copyToPath(innerFile: string, outerFilename?: string, flags?: number): void;
static removeFromPath(filename: string): void;
static isInPath(filename: string): boolean;
static getPlatform(): any;
static parseKeyValString(string: string, keyvalSeparator?: string, pairSeparator?: string): any;
static makeKeyValString(object: any, keyvalSeparator?: string, pairSeparator?: string): string;
}
export = Utils;
}

View file

@ -1,6 +1,6 @@
import * as storage from "electron-json-storage"; import * as storage from "electron-json-storage";
import * as fs from "fs"; import * as fs from "fs";
import { app } from "electron"; import {app} from "electron";
import path from "path"; import path from "path";
export var firstRun: boolean; export var firstRun: boolean;
@ -16,40 +16,56 @@ export function addScript(scriptString: string) {
script.textContent = scriptString; script.textContent = scriptString;
document.body.append(script); document.body.append(script);
} }
export async function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export async function checkIfConfigIsNew() {
if ((await getConfigUnsafe("automaticPatches")) == undefined) {
firstRun = true;
}
}
export interface Settings {
windowStyle: string;
channel: string;
armcordCSP: boolean;
minimizeToTray: boolean;
automaticPatches: boolean;
mods: string;
blurType: string;
}
export function setup() { export function setup() {
console.log("Setting up ArmCord settings."); console.log("Setting up temporary ArmCord settings.");
const defaults: Settings = {
windowStyle: "default",
channel: "stable",
armcordCSP: true,
minimizeToTray: true,
automaticPatches: false,
mods: "cumcord",
blurType: "acrylic"
};
storage.set( storage.set(
"settings", "settings",
{ {
customTitlebar: true, ...defaults,
channel: "stable", doneSetup: false
doneSetup: true,
armcordCSP: true,
mods: "cumcord",
}, },
function (error) { function (error) {
if (error) throw error; if (error) throw error;
} }
); );
} }
export async function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms)); export function saveSettings(settings: Settings) {
}
export function saveSettings(
customTitlebarSetting: boolean,
channelSetting: string,
armcordCSPSetting: boolean,
modsSetting: string
) {
console.log("Setting up ArmCord settings."); console.log("Setting up ArmCord settings.");
storage.set( storage.set(
"settings", "settings",
{ {
customTitlebar: customTitlebarSetting, ...settings,
channel: channelSetting, doneSetup: true
doneSetup: true,
armcordCSP: armcordCSPSetting,
mods: modsSetting,
}, },
function (error) { function (error) {
if (error) throw error; if (error) throw error;
@ -74,3 +90,12 @@ export function getVersion() {
//to-do better way of doing this //to-do better way of doing this
return "3.1.0"; return "3.1.0";
} }
export async function injectJS(inject: string) {
const js = await (await fetch(`${inject}`)).text();
const el = document.createElement("script");
el.appendChild(document.createTextNode(js));
document.body.appendChild(el);
}

View file

@ -2,22 +2,45 @@
// I had to add most of the window creation code here to split both into seperete functions // I had to add most of the window creation code here to split both into seperete functions
// WHY? Because I can't use the same code for both due to annoying bug with value `frame` not responding to variables // WHY? Because I can't use the same code for both due to annoying bug with value `frame` not responding to variables
// 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, shell } from "electron"; import {BrowserWindow, shell, app, ipcMain} from "electron";
import path from "path"; import path from "path";
import { contentPath } from "./main"; import {contentPath} from "./main";
import { firstRun } from "./utils"; import {checkIfConfigIsNew, firstRun, getConfigUnsafe} from "./utils";
import { registerIpc } from "./ipc"; import {registerIpc} from "./ipc";
import contextMenu from "electron-context-menu";
export let mainWindow: BrowserWindow; export let mainWindow: BrowserWindow;
import * as glasstron from "glasstron";
let guestWindows: BrowserWindow[] = [];
contextMenu({
showSaveImageAs: true,
showCopyImageAddress: true,
showSearchWithGoogle: true
});
function doAfterDefiningTheWindow() { function doAfterDefiningTheWindow() {
checkIfConfigIsNew();
registerIpc(); registerIpc();
mainWindow.webContents.userAgent = mainWindow.webContents.userAgent =
"Mozilla/5.0 (X11; Linux x86) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"; //fake useragent for screenshare to work "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"; //fake useragent for screenshare to work
mainWindow.webContents.setWindowOpenHandler(({ url }) => { mainWindow.webContents.setWindowOpenHandler(({url}) => {
shell.openExternal(url); shell.openExternal(url);
return { action: "deny" }; return {action: "deny"};
});
mainWindow.webContents.session.webRequest.onBeforeRequest((details, callback) => {
if (/api\/v\d\/science$/g.test(details.url)) return callback({cancel: true});
return callback({});
});
mainWindow.on("close", async (e) => {
if (await getConfigUnsafe("minimizeToTray")) {
e.preventDefault();
mainWindow.hide();
} else if (!(await getConfigUnsafe("minimizeToTray"))) {
e.preventDefault();
app.exit();
app.quit();
}
}); });
console.log(contentPath); console.log(contentPath);
try { try {
mainWindow.loadFile(contentPath); mainWindow.loadFile(contentPath);
@ -52,7 +75,8 @@ export function createCustomWindow() {
autoHideMenuBar: true, autoHideMenuBar: true,
webPreferences: { webPreferences: {
preload: path.join(__dirname, "preload/preload.js"), preload: path.join(__dirname, "preload/preload.js"),
}, spellcheck: true
}
}); });
doAfterDefiningTheWindow(); doAfterDefiningTheWindow();
} }
@ -67,7 +91,96 @@ export function createNativeWindow() {
autoHideMenuBar: true, autoHideMenuBar: true,
webPreferences: { webPreferences: {
preload: path.join(__dirname, "preload/preload.js"), preload: path.join(__dirname, "preload/preload.js"),
}, spellcheck: true
}
}); });
doAfterDefiningTheWindow(); doAfterDefiningTheWindow();
} }
export function createGlasstronWindow() {
mainWindow = new glasstron.BrowserWindow({
width: 300,
height: 350,
title: "ArmCord",
darkTheme: true,
icon: path.join(__dirname, "/assets/icon_transparent.png"),
frame: true,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, "preload/preload.js"),
spellcheck: true
}
});
//@ts-expect-error
mainWindow.blurType = getConfigUnsafe("blurType");
//@ts-expect-error
mainWindow.setBlur(true);
doAfterDefiningTheWindow();
}
export function createTabsHost() {
guestWindows[1] = mainWindow;
mainWindow = new BrowserWindow({
width: 300,
height: 350,
title: "ArmCord",
darkTheme: true,
icon: path.join(__dirname, "/assets/icon_transparent.png"),
frame: true,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, "preload/preload.js")
}
});
doAfterDefiningTheWindow();
}
export function createTabsGuest(number: number) {
console.log(guestWindows);
if (guestWindows[number] !== undefined || null) {
try {
console.log("Showing Guest Window " + number);
mainWindow.hide();
guestWindows[number].show();
mainWindow = guestWindows[number];
} catch (e) {
console.error(e);
}
} else {
console.log("Creating Guest Window " + number);
mainWindow.hide();
guestWindows[number] = new BrowserWindow({
width: 800,
height: 600,
title: "ArmCord Guest Window " + number,
darkTheme: true,
icon: path.join(__dirname, "/assets/icon_transparent.png"),
frame: true,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, "preload/preload.js")
}
});
mainWindow = guestWindows[number];
ipcMain.on("tab" + number, (event) => {
event.returnValue = true; //return true so we know the tab exists
});
guestWindows[number].webContents.userAgent =
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"; //fake useragent for screenshare to work
guestWindows[number].webContents.setWindowOpenHandler(({url}) => {
shell.openExternal(url);
return {action: "deny"};
});
guestWindows[number].webContents.session.webRequest.onBeforeRequest(
(details: {url: string}, callback: (arg0: {cancel?: boolean}) => any) => {
if (/api\/v\d\/science$/g.test(details.url)) return callback({cancel: true});
return callback({});
}
);
guestWindows[number].loadURL("https://discord.com/app");
}
}

View file

@ -26,7 +26,6 @@
"declaration": false, // Exports declaration files in addition, used for exporting a module. "declaration": false, // Exports declaration files in addition, used for exporting a module.
"declarationMap": false, // Allows the user to go to the source file when hitting a go-to-implementation key like F12 in VSCode for example. "declarationMap": false, // Allows the user to go to the source file when hitting a go-to-implementation key like F12 in VSCode for example.
//"declarationDir": "typings", // declarationDir allows you to separate the compiled code from the declaration files, used in conjunction with package.json's "types" property. //"declarationDir": "typings", // declarationDir allows you to separate the compiled code from the declaration files, used in conjunction with package.json's "types" property.
// Web Compatibility // // Web Compatibility //
"target": "ES2020", // ES2017 supports async/await, reducing the amount of compiled code, especially for async-heavy projects. ES2020 is from the Node 14 base (https://github.com/tsconfig/bases/blob/master/bases/node14.json) "target": "ES2020", // ES2017 supports async/await, reducing the amount of compiled code, especially for async-heavy projects. ES2020 is from the Node 14 base (https://github.com/tsconfig/bases/blob/master/bases/node14.json)
"downlevelIteration": false, // This flag adds extra support when targeting ES3, but adds extra bloat otherwise. "downlevelIteration": false, // This flag adds extra support when targeting ES3, but adds extra bloat otherwise.