Compare commits

..

4 commits

Author SHA1 Message Date
smartfrigde
7a3e6f5738 v3.0.5 2022-04-17 21:00:37 +02:00
smartfrigde
c8a6e1f726 Re-add the workflow 2022-04-17 20:59:49 +02:00
smartfrigde
03e45c272c Fix settings saving 2022-04-17 20:55:30 +02:00
smartfrigde
16531fa922 Make this mess more stable 2022-04-17 19:54:54 +02:00
35 changed files with 1529 additions and 3079 deletions

3
.github/release.md vendored
View file

@ -1,3 +0,0 @@
# Thanks for checking out ArmCord dev builds!
These builds are unstable and not ready for full release. They contain new experimental features and changes. We provide no official support for them.
Make sure to join our [Discord server](https://discord.gg/uaW5vMY3V6) to share opinions, or to chat with ArmCord developers!

View file

@ -1,9 +1,6 @@
name: Build/release name: Build/release
on: on: push
push:
branches:
- stable
jobs: jobs:
release: release:

71
.github/workflows/codeql-analysis.yml vendored Normal file
View file

@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '23 16 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View file

@ -1,161 +0,0 @@
name: Dev build
on:
push:
branches:
- dev
env:
FORCE_COLOR: true
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Checkout code
uses: actions/checkout@v2
- name: Install Node dependencies
run: npm install
- name: Install Electron-Builder
run: npm install -g electron-builder
- name: Replace the version number
run: cat src/utils.ts | sed -e 's/[[:digit:]]\.[[:digit:]]\.[[:digit:]]/DEV/g' | tee src/utils.ts > /dev/null
- name: Build
run: npm run build && electron-builder --linux zip && electron-builder --arm64 --linux zip
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: ArmCordLinux.zip
path: dist/ArmCord-3.1.0.zip
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: ArmCordLinuxArm64.zip
path: dist/ArmCord-3.1.0-arm64.zip
build-mac:
runs-on: macos-latest
steps:
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Checkout code
uses: actions/checkout@v2
- name: Install Node dependencies
run: npm install
- name: Install Electron-Builder
run: npm install -g electron-builder
- name: Replace the version number
run: cat src/utils.ts | sed -e 's/[[:digit:]]\.[[:digit:]]\.[[:digit:]]/DEV/g' | tee src/utils.ts > /dev/null
- name: Build
run: npm run build && electron-builder --macos zip
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: ArmCordMac.zip
path: dist/ArmCord-3.1.0-mac.zip
build-windows:
runs-on: windows-latest
steps:
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Checkout code
uses: actions/checkout@v2
- name: Install Node dependencies
run: npm install
- name: Install Electron-Builder
run: npm install -g electron-builder
- name: Replace the version number
run: (Get-Content src/utils.ts) -replace "\d\.\d\.\d", "DEV" | Out-File src/utils.ts
- name: Build
run: npm run build && electron-builder --windows zip
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: ArmCordWindows.zip
path: dist/ArmCord-3.1.0-win.zip
release:
runs-on: ubuntu-latest
needs: [build-linux, build-mac, build-windows]
steps:
- name: Checkout code
uses: actions/checkout@v2
- uses: actions/download-artifact@v2
with:
name: ArmCordMac.zip
path: macos
- uses: actions/download-artifact@v2
with:
name: ArmCordWindows.zip
path: windows
- uses: actions/download-artifact@v2
with:
name: ArmCordLinux.zip
path: linux
- uses: actions/download-artifact@v2
with:
name: ArmCordLinuxArm64.zip
path: linux
- name: Get some values needed for the release
id: vars
shell: bash
run: |
echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- uses: dev-drprasad/delete-tag-and-release@v0.2.0
with:
delete_release: true
tag_name: devbuild
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create the release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: devbuild
name: Dev Build ${{ steps.vars.outputs.sha_short }}
draft: false
prerelease: true
body_path: .github/release.md
files: |
linux/ArmCord-3.1.0.zip
linux/ArmCord-3.1.0-arm64.zip
macos/ArmCord-3.1.0-mac.zip
windows/ArmCord-3.1.0-win.zip

View file

@ -1,7 +1,6 @@
# Some prettier-specific files so it doesn't die. # Some prettier-specific files so it doesn't die.
**/*.png **/*.png
**/*.ico **/*.ico
**/*.woff
LICENSE LICENSE
.gitignore .gitignore

View file

@ -32,7 +32,7 @@
# How to run/install it? # How to run/install it?
### Recommended: ### Recommended:
Check releases tab for precompiled packages for Linux, Windows and Mac OS. Alternatively use our Sourceforge mirror. 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> <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/).
@ -57,9 +57,8 @@ Install it via an AUR helper tool like `yay`.
-We are using official web app and adding some magic powder to make it all work! -We are using official web app and adding some magic powder to make it all work!
## 3. Can I use this on other architectures or operating systems? ## 3. Can I use this on other architectures or operating systems?
- Yes! ArmCord should work normally under Windows, Mac OS and Linux as long as it has NodeJS, npm and Electron support. -Yes! ArmCord should work normally under Windows, ~~Mac OS~~ (Mac OS is broken see [#48](https://github.com/ArmCord/ArmCord/issues/48)) and Linux as long as it has NodeJS, npm and Electron support.
## 4. Where can I translate this?
- Translations are done using our [Weblate page](https://hosted.weblate.org/projects/armcord/armcord/). They're pushed to this [repo](https://github.com/ArmCord/i18n).
# Credits # Credits
[ArmCord UI Elements and few features](https://github.com/kckarnige) [ArmCord UI Elements and few features](https://github.com/kckarnige)
[Cumcord](https://github.com/Cumcord/Cumcord) [Cumcord](https://github.com/Cumcord/Cumcord)
@ -68,6 +67,3 @@ Install it via an AUR helper tool like `yay`.
[electron-discord-webapp](https://github.com/SpacingBat3/electron-discord-webapp) [electron-discord-webapp](https://github.com/SpacingBat3/electron-discord-webapp)
[custom-electron-titlebar (css only)](https://github.com/AlexTorresSk/custom-electron-titlebar) [custom-electron-titlebar (css only)](https://github.com/AlexTorresSk/custom-electron-titlebar)
[electron-builder](https://electron.build) [electron-builder](https://electron.build)
# Sponsors
[![JetBrains supports ArmCord with free licenses to their software to core developers](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg)](https://jb.gg/OpenSourceSupport)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Before After
Before After

BIN
assets/ac_plug.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -1,28 +0,0 @@
{
"loading_screen_start": "Starting ArmCord…",
"loading_screen_offline": "You appear to be offline. Please connect to the Internet and try again.",
"loading_screen_update": "A new version of ArmCord is available. Please update to the latest version.",
"setup_question1": "Select what kind of setup you want to perform:",
"setup_question1_answer1": "Express setup",
"setup_question1_answer2": "Full setup",
"setup_offline": "You appear to be offline. Please connect to the Internet and restart the ArmCord setup.",
"setup_question2": "Choose your Discord channel/instance:",
"setup_question3": "Should ArmCord handle client mods installation?",
"yes": "Yes",
"no": "No",
"next": "Next",
"setup_question4": "Select a client mod you want to install:",
"setup_question4_clientmodnotice": "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.",
"settings-theme": "ArmCord Theme:",
"settings-theme-glasstron": "Glasstron (experimental)",
"settings-theme-tabs": "Tabs (experimental)",
"settings-theme-default": "Default",
"settings-theme-native": "Native",
"settings-tray": "Minimize to tray",
"settings-patches": "Automatic Patches",
"settings-channel": "Discord channel:",
"settings-invitewebsocket": "discord.gg support",
"settings-mod": "Client mod:",
"settings-save": "Save settings",
"settings-updater": "Check for updates"
}

View file

@ -1,28 +0,0 @@
{
"loading_screen_start": "Démarrage d'ArmCord…",
"loading_screen_update": "Une nouvelle version de ArmCord est disponible. Veuillez mettre à jour la dernière version.",
"setup_question1": "Sélectionnez le type de configuration que vous souhaitez effectuer :",
"setup_question1_answer1": "Configuration express",
"setup_question1_answer2": "Configuration complète",
"setup_question2": "Choisissez votre canal/instance Discord :",
"setup_question3": "ArmCord doit-il s'occuper de l'installation des mods clients ?",
"yes": "Oui",
"no": "Non",
"next": "Suivant",
"setup_question4": "Sélectionnez le mod client que vous souhaitez installer :",
"setup_question4_clientmodnotice": "Pourquoi pas tous ? Le fait d'avoir plusieurs mods clients en même temps peut causer des problèmes. Si vous voulez vraiment le faire, consultez notre documentation.",
"loading_screen_offline": "Vous semblez être hors ligne. Veuillez vous connecter à internet et réessayer.",
"setup_offline": "Vous semblez être hors ligne. Veuillez vous connecter à internet et redémarrer ArmCord Setup.",
"settings-tray": "Minimize to tray",
"settings-channel": "Discord channel:",
"settings-mod": "Client mod:",
"settings-save": "Save settings",
"settings-updater": "Check for updates",
"settings-theme": "ArmCord Theme:",
"settings-theme-tabs": "Tabs (experimental)",
"settings-theme-default": "Default",
"settings-theme-glasstron": "Glasstron (experimental)",
"settings-theme-native": "Native",
"settings-patches": "Automatic Patches",
"settings-invitewebsocket": "discord.gg support"
}

View file

@ -1,28 +0,0 @@
{
"loading_screen_update": "Una nuova versione di ArmCord è disponibile. Per favore aggiorna all'ultima versione.",
"setup_question1_answer1": "Configurazione veloce",
"loading_screen_start": "Avviando Armcord…",
"loading_screen_offline": "Sembri essere offline. Per favore connettiti all'Internet e riprova.",
"setup_question1": "Seleziona quale tipo di setup vuoi eseguire:",
"setup_question2": "Seleziona il tuo canale/istanza di Discord:",
"setup_question3": "ArmCord dovrebbe gestire l'installazione di client mod?",
"yes": "Sì",
"no": "No",
"next": "Avanti",
"setup_question4": "Seleziona una client mod che vuoi installare:",
"setup_question1_answer2": "Configurazione completa",
"setup_offline": "Sembri essere offline. Per favore connettiti all'Internet e riavvia la configurazione di ArmCord.",
"setup_question4_clientmodnotice": "Perché non tutti? Avere molte client mod allo stesso tempo puo causare problemi. Se davvero vuoi farlo, vedi la nostra documentazione.",
"settings-tray": "Riduci ad icona",
"settings-channel": "Canale Discord:",
"settings-save": "Salva impostazioni",
"settings-updater": "Controlla gli aggiornamenti",
"settings-mod": "Client mod:",
"settings-theme": "ArmCord Theme:",
"settings-theme-glasstron": "Glasstron (experimental)",
"settings-theme-tabs": "Tabs (experimental)",
"settings-theme-default": "Default",
"settings-theme-native": "Native",
"settings-patches": "Automatic Patches",
"settings-invitewebsocket": "discord.gg support"
}

View file

@ -1,28 +0,0 @@
{
"loading_screen_start": "Starter ArmCord …",
"loading_screen_offline": "Koble til Internett og prøv igjen.",
"loading_screen_update": "En ny versjon av ArmCord er tilgjengelig. Oppgrader til siste versjon.",
"setup_question1": "Velg hvilket oppsett du ønsker å utføre:",
"setup_question1_answer2": "Fullt oppsett",
"setup_question2": "Velg din Discor-kanal/instans:",
"yes": "Ja",
"no": "Nei",
"setup_question1_answer1": "Hurtigoppsett",
"setup_question3": "Skal ArmCord håndtere installasjon av klient-modifikasjoner?",
"setup_offline": "Koble til Internett og start ArmCord-oppsett på ny.",
"next": "Neste",
"setup_question4": "Velg en klient-modifikasjon du ønsker å installere:",
"setup_question4_clientmodnotice": "Hvorfor ikke alle? Å ha mange klient-modifikasjoner samtidig kan forårsake problemer. Hvis du vil gjøre det likevel bør du lese dokumentasjonen vår.",
"settings-tray": "Minimize to tray",
"settings-channel": "Discord channel:",
"settings-mod": "Client mod:",
"settings-save": "Save settings",
"settings-updater": "Check for updates",
"settings-theme": "ArmCord Theme:",
"settings-theme-glasstron": "Glasstron (experimental)",
"settings-theme-tabs": "Tabs (experimental)",
"settings-theme-default": "Default",
"settings-theme-native": "Native",
"settings-patches": "Automatic Patches",
"settings-invitewebsocket": "discord.gg support"
}

View file

@ -1,28 +0,0 @@
{
"setup_question1": "Selecteer wat voor soort setup je wilt starten:",
"setup_question1_answer1": "Express setup",
"setup_question1_answer2": "Volledige setup",
"setup_question3": "Moet ArmCord client mods installeren?",
"yes": "Ja",
"no": "Nee",
"setup_offline": "Het lijkt erop alsof je offline bent. Verbind met het Internet en herstart ArmCord setup.",
"loading_screen_start": "ArmCord starten…",
"next": "Volgende",
"setup_question4": "Selecteer een client mod om te installeren:",
"setup_question4_clientmodnotice": "Waarom niet allemaal? Meerdere client mods installeren kan problemen veroorzaken. Als je dit echt wilt doen, kan je de documentatie bekijken.",
"loading_screen_offline": "Het lijkt erop alsof je offline bent. Verbind met het Internet en probeer opnieuw.",
"loading_screen_update": "Een nieuwe versie van ArmCord is beschikbaar. Update alstublieft naar de nieuwste versie.",
"setup_question2": "Kies je Discord kanaal/instantie:",
"settings-tray": "Minimaliseer naar pictogram in het systeemvak",
"settings-channel": "Discord kanaal:",
"settings-mod": "Client mod:",
"settings-save": "Instellingen opslaan",
"settings-updater": "Check voor updates",
"settings-patches": "Automatische Patches",
"settings-theme": "ArmCord Thema:",
"settings-theme-glasstron": "Glasstron (experimenteel)",
"settings-theme-tabs": "Tabs (experimenteel)",
"settings-theme-default": "Standaard",
"settings-theme-native": "Native",
"settings-invitewebsocket": "discord.gg support"
}

View file

@ -1,28 +0,0 @@
{
"loading_screen_update": "Nowa wersja ArmCord jest dostępna. Proszę zaktualizować aplikację do najnowszej wersji.",
"setup_question1_answer2": "Pełna konfiguracja",
"setup_question2": "Wybierz swój kanał/odmianę Discorda:",
"setup_question3": "Czy ArmCord powinienem zajmować sie instalacją modyfikacji klienta?",
"yes": "Tak",
"no": "Nie",
"next": "Dalej",
"setup_question4": "Wybierz modyfikację klienta którą chcesz zainstalować:",
"setup_question4_clientmodnotice": "Dlaczego nie wszystkie na raz? Posiadanie wielu modyfikacji może spowodować wiele błędów. Jeśli jednak nalegasz możesz sprawdzić naszą dokumentację.",
"loading_screen_start": "Ładowanie ArmCord…",
"loading_screen_offline": "Wydaje nam się, że nie jesteś połączony z Internetem. Połącz się z internetem i spróbuj ponownie.",
"setup_question1_answer1": "Ekspresowa konfiguracja",
"setup_question1": "Wybierz w jaki sposób chcesz skonfigurować ArmCord:",
"setup_offline": "Wydaje nam się że nie jesteś połączony z internetem. Połącz się z internetem i uruchom ponownie konfiguracje ArmCord .",
"settings-channel": "Kanał Discorda:",
"settings-updater": "Sprawdź aktualizacje",
"settings-tray": "Zminimalizuj do zasobnika zadań",
"settings-save": "Zapisz ustawienia",
"settings-mod": "Modyfikacja klienta:",
"settings-theme": "Motyw ArmCord:",
"settings-theme-glasstron": "Glasstron (eksperymentalne)",
"settings-theme-tabs": "Karty (eksperymentalne)",
"settings-theme-default": "Domyślny",
"settings-theme-native": "Natywny",
"settings-patches": "Automatyczne łatki",
"settings-invitewebsocket": "Wsparcie linków discord.gg"
}

3100
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "ArmCord", "name": "ArmCord",
"version": "3.0.6", "version": "3.0.5",
"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": {
@ -9,7 +9,6 @@
"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/**/*", "format": "prettier --write src/**/*",
"CIbuild": "npm run build && electron-builder --linux zip && electron-builder --windows zip && electron-builder --macos zip",
"postinstall": "husky install" "postinstall": "husky install"
}, },
"repository": { "repository": {
@ -23,19 +22,20 @@
}, },
"homepage": "https://github.com/armcord/armcord#readme", "homepage": "https://github.com/armcord/armcord#readme",
"devDependencies": { "devDependencies": {
"@types/node": "^17.0.33", "@types/electron-json-storage": "^4.5.0",
"@types/ws": "^8.5.3", "@types/node": "^17.0.24",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"electron": "^18.2.3", "electron": "^18.0.4",
"electron-builder": "^23.0.3", "electron-builder": "^22.5.1",
"husky": "^8.0.1", "husky": "^7.0.4",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"typescript": "^4.6.3" "typescript": "^4.5.4"
}, },
"dependencies": { "dependencies": {
"electron-context-menu": "https://github.com/ArmCord/electron-context-menu.git", "electron-context-menu": "^3.1.2",
"v8-compile-cache": "^2.3.0", "electron-json-storage": "^4.5.0",
"ws": "^8.6.0" "electron-tabs": "^0.17.0",
"v8-compile-cache": "^2.3.0"
}, },
"build": { "build": {
"appId": "com.smartfridge.armcord", "appId": "com.smartfridge.armcord",

View file

@ -1,5 +1,5 @@
.info-3pQQBb:last-child:before { .info-3pQQBb:last-child:before {
content: "ArmCord Version: 3.1.0" !important; content: "ArmCord Version: 3.0.5" !important;
height: auto; height: auto;
line-height: 16px; line-height: 16px;
text-align: center; text-align: center;
@ -10,6 +10,3 @@
.notice-2HEN-u { .notice-2HEN-u {
display: none; display: none;
} }
.sidebar-1tnWFu {
border-top-left-radius: 8px !important;
}

View file

@ -1,148 +1,91 @@
/*CSS ONLY FOR INTERNAL USE (setup and loading)*/
@import url("https://armcord.smartfridge.space/logofont.css"); @import url("https://armcord.smartfridge.space/logofont.css");
/* Meta {{{ */
:root { :root {
--background-primary: #282b30; background-color: #2c2f33 !important;
--background-secondary: rgba(255, 255, 255, 0.1); --header-secondary: #b9bbbe !important;
--brand-experiment: #7289da; --header-primary: #fff !important;
--header-primary: #fff; --background-tertiary: #202225 !important;
--text-muted: #72767d;
--font-primary: "Whitney";
} }
@font-face {
font-family: Whitney;
font-weight: 400;
font-style: normal;
src: url(https://armcord.smartfridge.space/whitney_400.woff) format("woff");
}
html,
body { body {
overflow: hidden; background-color: #2c2f33;
margin: 0;
padding-top: 30px;
width: 100%;
height: 100%;
background: var(--background-primary);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
* {
font-family: var(--font-primary), sans-serif;
box-sizing: border-box;
user-select: none;
cursor: default;
}
/* }}} */
/* Utility classes {{{ */
.hidden {
display: none !important;
}
.text-center {
text-align: center;
}
.setup-ask {
font-size: 20px;
}
.center {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
/* }}} */
#setup {
display: flex;
flex-direction: column;
align-items: center;
color: white; color: white;
} }
/* Warning {{{ */ p {
#warning {
font-size: 1.5em;
font-weight: bold;
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
max-width: 328px;
background-color: rgba(255, 0, 0, 0.1);
border: red solid 2px;
border-radius: 0.5rem;
}
#warning > p {
color: white; color: white;
font-weight: bold; text-align: center;
margin: 1rem; font-weight: 100;
font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;
text-rendering: optimizeLegibility;
} }
/* }}} */
/* Logo {{{ */ .logo {
#logo { font-size: 0px;
display: flex; text-align: center;
flex-direction: row; transform: translateY(-105%);
justify-content: center;
align-items: center;
} }
#logo p:first-child {
.logo:before {
content: "ARM";
color: #7289da; color: #7289da;
margin: 0;
font-weight: normal; font-weight: normal;
font-family: Helvetica, sans-serif; font-family: Helvetica, sans-serif;
font-size: 32px; font-size: 32px;
} }
#logo p:last-child {
color: white; .logo:after {
margin: 0; content: "Cord";
color: #ffffff;
font-weight: normal; font-weight: normal;
font-family: Discordinated; font-family: Discordinated;
font-size: 32px; font-size: 32px;
} }
/* }}} */
/* Buttons {{{ */ span {
#buttons { text-align: center;
display: flex; }
flex-direction: row;
justify-content: center;
align-items: center;
gap: 1rem;
user-select: all !important; .logo {
display: block;
margin-left: auto;
margin-right: auto;
max-height: 204px;
max-width: 204px;
transform: translateY(5%);
}
margin-top: 10px; .container {
margin-bottom: 10px; position: fixed;
top: 50%;
left: 50%;
color: #fff;
transform: translate(-50%, -50%);
}
button#express {
margin-right: 84px;
} }
button { button {
background: var(--brand-experiment); background-color: #7289da;
color: var(--header-primary); font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #ffffff;
padding: 4px;
border-radius: 5px;
margin-top: 5px;
border: none; text-align: center;
border-radius: 4px; border-style: none;
outline: none;
padding: 8px 20px; }
.setup-ask {
font-size: 20px;
} }
button:hover { button:hover {
filter: brightness(85%); background-color: #687dc6;
border-style: none;
outline: none;
cursor: pointer; cursor: pointer;
} }
/* }}} */
/* Dropdowns {{{ */
select { select {
-webkit-appearance: button; -webkit-appearance: button;
-moz-appearance: button; -moz-appearance: button;
@ -163,4 +106,6 @@ select {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
/* }}} */ .center {
text-align: center;
}

View file

@ -88,7 +88,7 @@
content: "Cord"; content: "Cord";
color: var(--cord-color) !important; color: var(--cord-color) !important;
font-weight: normal; font-weight: normal;
font-size: 14px; font-size: 15px;
font-family: Discordinated; font-family: Discordinated;
} }
.window-title:before { .window-title:before {

View file

@ -1,33 +1,60 @@
<!--- 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 rel="stylesheet" href="https://cdn.metroui.org.ua/v4/css/metro-all.min.css" />
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ArmCord Setup</title> <title>ArmCord Setup</title>
<style> <style>
@import url("css/setup.css"); @import url("css/setup.css");
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<div id="warning" class="hidden"> <h1 class="logo"></h1>
<p>You appear to be offline. Please connect to the internet and restart ArmCord Setup.</p>
</div>
<div id="setup"> <div id="setup">
<div id="logo" class="hidden"> <p>Select what kind of setup you want to perform:</p>
<p>ARM</p> <button id="express" class="center">Express setup</button>
<p>Cord</p> <button id="full" class="center">Full setup</button>
</div>
<div id="page1" class="hidden">
<p>Select the type of setup you want to perform.</p>
<div id="buttons">
<button id="express" class="center">Express</button>
<button id="full" class="center">Full</button>
</div> </div>
</div> </div>
<script>
function fade(element) {
var op = 1; // initial opacity
var timer = setInterval(function () {
if (op <= 0.1) {
clearInterval(timer);
element.style.display = "none";
}
element.style.opacity = op;
element.style.filter = "alpha(opacity=" + op * 100 + ")";
op -= op * 0.1;
}, 50);
}
<div id="page2" class="hidden"> if (window.navigator.onLine === false) {
document.getElementById("setup").innerHTML =
"You appear to be offline. Please connect to the internet and restart ArmCord Setup.";
} else {
console.log("Starting ArmCord Setup...");
document.getElementById("express").addEventListener("click", function () {
window.armcordinternal.saveSettings({
windowStyle: "default",
channel: "stable",
armcordCSP: true,
minimizeToTray: true,
automaticPatches: false,
mods: "cumcord",
blurType: "acrylic"
});
fade(document.getElementById("setup"));
setTimeout(function () {
window.armcordinternal.restart();
}, 5000);
});
document.getElementById("full").addEventListener("click", function () {
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>
<div class="center"> <div class="center">
<select name="channel" id="channel" class="dropdown-button"> <select name="channel" id="channel" class="dropdown-button">
@ -44,12 +71,13 @@
<option value="false">No</option> <option value="false">No</option>
</select> </select>
</div> </div>
<div id="buttons">
<button id="next" class="center">Next</button> <button id="next" class="center">Next</button>
</div> `;
</div> document.getElementById("next").addEventListener("click", function () {
var branch = document.getElementById("channel").value;
<div id="page3" class="hidden"> var csp = document.getElementById("csp").value;
if (csp === "true") {
document.getElementById("setup").innerHTML = `
<p class="text-center setup-ask">Select a client mod you want to install:</p> <p class="text-center setup-ask">Select a client mod you want to install:</p>
<div class="center"> <div class="center">
<select name="mod" id="mod" class="dropdown-button"> <select name="mod" id="mod" class="dropdown-button">
@ -58,99 +86,43 @@
<option value="flicker">Flicker (Heavily WIP)</option> <option value="flicker">Flicker (Heavily WIP)</option>
</select> </select>
</div> </div>
<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>
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>
<div id="buttons">
<button id="next" class="center">Next</button> <button id="next" class="center">Next</button>
</div> `;
</div> document.getElementById("next").addEventListener("click", function () {
</div> var mod = document.getElementById("mod").value;
</div>
<script>
// Accessors {{{
let options = {};
let logo = document.getElementById("logo");
logo.classList.remove("hidden");
let page1 = document.getElementById("page1");
page1.classList.remove("hidden");
page1.buttons = document.querySelectorAll("#page1 > #buttons > button");
// Connection check
let warning = document.getElementById("warning");
if (window.navigator.onLine === false) {
warning.classList.remove("hidden");
}
let page2 = document.getElementById("page2");
let page3 = document.getElementById("page3");
// }}}
// Express
page1.buttons[0].addEventListener("click", () => {
window.armcordinternal.saveSettings({ window.armcordinternal.saveSettings({
windowStyle: "default", windowStyle: "default",
channel: "stable", channel: branch,
armcordCSP: true, armcordCSP: true,
autoLaunch: true,
minimizeToTray: true, minimizeToTray: true,
automaticPatches: false, automaticPatches: false,
mods: "cumcord", mods: mod,
inviteWebsocket: true,
blurType: "acrylic" blurType: "acrylic"
}); });
setTimeout(() => window.armcordinternal.restart(), 5000); fade(document.getElementById("setup"));
}); setTimeout(function () {
window.armcordinternal.restart();
// Full }, 5000);
page1.buttons[1].addEventListener("click", () => {
page1.classList.add("hidden");
page2.classList.remove("hidden");
});
page2.buttons = document.querySelectorAll("#page2 > #buttons > button");
page2.buttons[0].addEventListener("click", () => {
options.channel = document.getElementById("channel").value;
options.csp = document.getElementById("csp").value;
page2.classList.add("hidden");
page3.buttons = document.querySelectorAll("#page3 > #buttons > button");
if (options.csp === "true") {
page3.classList.remove("hidden");
page3.buttons[0].addEventListener("click", () => {
options.mod = document.getElementById("mod").value;
window.armcordinternal.saveSettings({
windowStyle: "default",
channel: options.channel,
armcordCSP: true,
autoLaunch: true,
minimizeToTray: true,
automaticPatches: false,
mods: options.mod,
inviteWebsocket: true,
blurType: "acrylic"
});
setTimeout(() => window.armcordinternal.restart(), 500);
}); });
} else { } else {
window.armcordinternal.saveSettings({ window.armcordinternal.saveSettings({
windowStyle: "default", windowStyle: "default",
channel: options.channel, channel: branch,
armcordCSP: true, armcordCSP: true,
minimizeToTray: true, minimizeToTray: true,
automaticPatches: false, automaticPatches: false,
autoLaunch: true,
mods: "none", mods: "none",
inviteWebsocket: true,
blurType: "acrylic" blurType: "acrylic"
}); });
setTimeout(() => window.armcordinternal.restart(), 500); fade(document.getElementById("setup"));
setTimeout(function () {
window.armcordinternal.restart();
}, 5000);
} }
}); });
});
}
</script> </script>
</body> </body>
</html> </html>

View file

@ -22,9 +22,6 @@
text.innerHTML = "You appear to be offline. Please connect to the internet and try again."; text.innerHTML = "You appear to be offline. Please connect to the internet and try again.";
} else { } else {
text.innerHTML = "Starting ArmCord..."; text.innerHTML = "Starting ArmCord...";
if (window.armcord.version === "DEV") {
console.log("Running a development build of ArmCord. Skipping updater.");
} else {
fetch("https://armcord.xyz/latest.json") fetch("https://armcord.xyz/latest.json")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
@ -34,13 +31,11 @@
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(() => {
window.armcordinternal.splashEnd(); window.armcordinternal.splashEnd();
switch (window.armcord.channel) { switch (window.armcord.channel) {

View file

@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c
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. 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.
*/ */
import electron from "electron"; import electron from "electron";
import {getConfig} from "../utils"; 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)
@ -55,9 +55,9 @@ const unstrictCSP = () => {
done({responseHeaders}); done({responseHeaders});
}); });
}; };
storage.get("settings", function (error, data: any) {
electron.app.whenReady().then(async () => { if (error) throw error;
if (await getConfig("armcordCSP")) { if (data.armcordCSP) {
unstrictCSP(); unstrictCSP();
} else { } else {
console.log("ArmCord CSP is disabled. The CSP should be managed by third-party plugin."); console.log("ArmCord CSP is disabled. The CSP should be managed by third-party plugin.");

View file

@ -1,8 +1,8 @@
//ipc stuff //ipc stuff
import {app, ipcMain, shell, desktopCapturer} from "electron"; import {app, ipcMain, shell, desktopCapturer} from "electron";
import {createTabsGuest, mainWindow} from "./window"; import {createTabsGuest, mainWindow} from "./window";
import {setConfigBulk, getVersion, getConfig} from "./utils"; import {saveSettings, getVersion} from "./utils";
import {customTitlebar, tabs} 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) => {
@ -33,7 +33,7 @@ export function registerIpc() {
mainWindow.hide(); mainWindow.hide();
}); });
ipcMain.on("win-quit", (event, arg) => { ipcMain.on("win-quit", (event, arg) => {
app.exit(); app.quit();
}); });
ipcMain.on("get-app-version", (event) => { ipcMain.on("get-app-version", (event) => {
event.returnValue = getVersion(); event.returnValue = getVersion();
@ -46,16 +46,17 @@ export function registerIpc() {
app.exit(); app.exit();
}); });
ipcMain.on("saveSettings", (event, args) => { ipcMain.on("saveSettings", (event, args) => {
setConfigBulk(args); saveSettings(args);
}); });
ipcMain.on("minimizeToTray", async (event) => { ipcMain.on("minimizeToTray", (event) => {
event.returnValue = await getConfig("minimizeToTray"); console.log(settings.minimizeToTray);
event.returnValue = settings.minimizeToTray;
}); });
ipcMain.on("channel", async (event) => { ipcMain.on("channel", (event) => {
event.returnValue = await getConfig("channel"); event.returnValue = settings.channel;
}); });
ipcMain.on("clientmod", async (event, arg) => { ipcMain.on("clientmod", (event, arg) => {
event.returnValue = await getConfig("mods"); event.returnValue = settings.mods;
}); });
ipcMain.on("titlebar", (event, arg) => { ipcMain.on("titlebar", (event, arg) => {
event.returnValue = customTitlebar; event.returnValue = customTitlebar;
@ -63,14 +64,14 @@ export function registerIpc() {
ipcMain.on("tabs", (event, arg) => { ipcMain.on("tabs", (event, arg) => {
event.returnValue = tabs; event.returnValue = tabs;
}); });
ipcMain.on("shouldPatch", async (event, arg) => { ipcMain.on("shouldPatch", (event, arg) => {
event.returnValue = await getConfig("automaticPatches"); event.returnValue = settings.automaticPatches;
}); });
ipcMain.on("openSettingsWindow", (event, arg) => { ipcMain.on("openSettingsWindow", (event, arg) => {
createSettingsWindow(); createSettingsWindow();
}); });
ipcMain.on("setting-armcordCSP", async (event) => { ipcMain.on("setting-armcordCSP", (event) => {
if (await getConfig("armcordCSP")) { if (settings.armcordCSP) {
event.returnValue = true; event.returnValue = true;
} else { } else {
event.returnValue = false; event.returnValue = false;

View file

@ -1,32 +1,49 @@
// Modules to control application life and create native browser window // Modules to control application life and create native browser window
import {app, BrowserWindow, session, dialog} from "electron"; import {app, BrowserWindow, session, dialog} from "electron";
import * as path from "path";
import "v8-compile-cache"; import "v8-compile-cache";
import {getConfig, setup, checkIfConfigExists} from "./utils"; import * as storage from "electron-json-storage";
import {getConfigUnsafe, setup} from "./utils";
import "./extensions/mods"; import "./extensions/mods";
import "./extensions/plugin"; import "./extensions/plugin";
import "./tray"; import "./tray";
import {createCustomWindow, createNativeWindow, createTabsHost} from "./window"; import {createCustomWindow, createNativeWindow, createTabsHost} from "./window";
import "./shortcuts"; import "./shortcuts";
export var contentPath: string;
var channel: string;
export var settings: any; export var settings: any;
export var customTitlebar: boolean; export var customTitlebar: boolean;
export var tabs: boolean; export var tabs: boolean;
let isSingleInstance = app.requestSingleInstanceLock();
if (!isSingleInstance) {
app.quit();
}
storage.has("settings", function (error, hasKey) {
if (error) throw error;
if (process.platform == "linux") { if (!hasKey) {
if (process.env.$XDG_SESSION_TYPE == "wayland") { console.log("First run of the ArmCord. Starting setup.");
console.log("Wayland specific patches applied.") setup();
app.commandLine.appendSwitch("ozone-platform=wayland"); contentPath = path.join(__dirname, "/content/setup.html");
if (process.env.$XDG_CURRENT_DESKTOP == "GNOME") { if (!contentPath.includes("ts-out")) {
app.commandLine.appendSwitch("enable-features=UseOzonePlatform,WaylandWindowDecorations"); contentPath = path.join(__dirname, "/ts-out/content/setup.html");
}
} else { } else {
app.commandLine.appendSwitch("enable-features=UseOzonePlatform"); console.log("ArmCord has been run before. Skipping setup.");
contentPath = path.join(__dirname, "/content/splash.html");
if (!contentPath.includes("ts-out")) {
contentPath = path.join(__dirname, "/ts-out/content/splash.html");
} }
} }
} });
checkIfConfigExists(); storage.get("settings", function (error, data: any) {
if (error) throw error;
console.log(data);
channel = data.channel;
settings = data;
});
app.whenReady().then(async () => { app.whenReady().then(async () => {
switch (await getConfig("windowStyle")) { switch (await getConfigUnsafe("windowStyle")) {
case "default": case "default":
createCustomWindow(); createCustomWindow();
customTitlebar = true; customTitlebar = true;
@ -34,15 +51,9 @@ app.whenReady().then(async () => {
case "native": case "native":
createNativeWindow(); createNativeWindow();
break; break;
case "discord":
createNativeWindow();
break;
case "glasstron": case "glasstron":
dialog.showErrorBox( dialog.showErrorBox("Glasstron is unsupported.", "This build doesn't include Glasstron functionality, please edit windowStyle value in your settings.json to something different (default for example)")
"Glasstron is unsupported.", app.quit()
"This build doesn't include Glasstron functionality, please edit windowStyle value in your settings.json to something different (default for example)"
);
app.quit();
break; break;
case "tabs": case "tabs":
createTabsHost(); createTabsHost();
@ -65,24 +76,13 @@ app.whenReady().then(async () => {
}); });
app.on("activate", async function () { app.on("activate", async function () {
if (BrowserWindow.getAllWindows().length === 0) if (BrowserWindow.getAllWindows().length === 0)
switch (await getConfig("windowStyle")) { switch (await getConfigUnsafe("windowStyle")) {
case "default": case "default":
createCustomWindow(); createCustomWindow();
break; break;
case "native": case "native":
createNativeWindow(); createNativeWindow();
break; break;
case "glasstron":
dialog.showErrorBox(
"Glasstron is unsupported.",
"This build doesn't include Glasstron functionality, please edit windowStyle value in your settings.json to something different (default for example)"
);
app.quit();
break;
case "tabs":
createTabsHost();
tabs = true;
break;
default: default:
createCustomWindow(); createCustomWindow();
break; break;

View file

@ -1,5 +1,5 @@
import {BrowserWindow, shell, ipcMain} from "electron"; import {BrowserWindow, shell, ipcMain, app} from "electron";
import {getConfig, setConfigBulk, Settings} from "../utils"; import {getConfigUnsafe, saveSettings, Settings} from "../utils";
import path from "path"; import path from "path";
var settingsWindow: BrowserWindow; var settingsWindow: BrowserWindow;
var instance: number = 0; var instance: number = 0;
@ -15,7 +15,7 @@ export function createSettingsWindow() {
} else { } else {
settingsWindow = new BrowserWindow({ settingsWindow = new BrowserWindow({
width: 500, width: 500,
height: 555, height: 500,
title: "ArmCord Settings", title: "ArmCord Settings",
darkTheme: true, darkTheme: true,
frame: true, frame: true,
@ -26,10 +26,10 @@ export function createSettingsWindow() {
}); });
ipcMain.on("saveSettings", (event, args: Settings) => { ipcMain.on("saveSettings", (event, args: Settings) => {
console.log(args); console.log(args);
setConfigBulk(args); saveSettings(args);
}); });
ipcMain.handle("getSetting", (event, toGet: string) => { ipcMain.handle("getSetting", (event, toGet: string) => {
return getConfig(toGet); return getConfigUnsafe(toGet);
}); });
settingsWindow.webContents.setWindowOpenHandler(({url}) => { settingsWindow.webContents.setWindowOpenHandler(({url}) => {
shell.openExternal(url); shell.openExternal(url);

View file

@ -34,12 +34,6 @@
<input class="tgl tgl-light left" id="patches" type="checkbox" /> <input class="tgl tgl-light left" id="patches" type="checkbox" />
<label class="tgl-btn left" for="patches"></label> <label class="tgl-btn left" for="patches"></label>
</div> </div>
<br />
<div class="switch">
<label class="header">Invite Websocket</label>
<input class="tgl tgl-light left" id="websocket" type="checkbox" />
<label class="tgl-btn left" for="websocket"></label>
</div>
<div class="switch"> <div class="switch">
<select name="channel" id="channel" class="left"> <select name="channel" id="channel" class="left">
<option value="stable">Stable</option> <option value="stable">Stable</option>
@ -54,7 +48,6 @@
<option value="cumcord">Cumcord</option> <option value="cumcord">Cumcord</option>
<option value="goosemod">GooseMod</option> <option value="goosemod">GooseMod</option>
<option value="flicker">Flicker</option> <option value="flicker">Flicker</option>
<option value="none">None</option>
</select> </select>
<p class="header">Client mod:</p> <p class="header">Client mod:</p>
</div> </div>
@ -66,7 +59,6 @@
async function loadSettings() { async function loadSettings() {
document.getElementById("csp").checked = await settings.get("armcordCSP"); document.getElementById("csp").checked = await settings.get("armcordCSP");
document.getElementById("tray").checked = await settings.get("minimizeToTray"); document.getElementById("tray").checked = await settings.get("minimizeToTray");
document.getElementById("websocket").checked = await settings.get("inviteWebsocket");
document.getElementById("patches").value = await settings.get("automaticPatches"); document.getElementById("patches").value = await settings.get("automaticPatches");
document.getElementById("mod").value = await settings.get("mods"); document.getElementById("mod").value = await settings.get("mods");
document.getElementById("channel").value = await settings.get("channel"); document.getElementById("channel").value = await settings.get("channel");
@ -82,9 +74,7 @@
minimizeToTray: document.getElementById("tray").checked, minimizeToTray: document.getElementById("tray").checked,
automaticPatches: document.getElementById("patches").checked, automaticPatches: document.getElementById("patches").checked,
mods: document.getElementById("mod").value, mods: document.getElementById("mod").value,
blurType: "acrylic", blurType: "acrylic"
inviteWebsocket: document.getElementById("websocket").checked,
doneSetup: true
}); });
}); });
</script> </script>

View file

@ -1,182 +0,0 @@
// MIT License
// Copyright (c) 2020-2022 Dawid Papiewski "SpacingBat3"
// 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.
import type {Server, WebSocket} from "ws";
import {inviteWindow, createInviteWindow} from "./window";
async function wsLog(message: string, ...args: unknown[]) {
console.log("[WebSocket]" + message, ...args);
}
/** Generates an inclusive range (as `Array`) from `start` to `end`. */
function range(start: number, end: number) {
return Array.from({length: end - start + 1}, (_v, k) => start + k);
}
interface InviteResponse {
/** Response type/command. */
cmd: "INVITE_BROWSER";
/** Response arguments. */
args: {
/** An invitation code. */
code: string;
};
/** Nonce indentifying the communication. */
nonce: string;
}
function isInviteResponse(data: unknown): data is InviteResponse {
if (!(data instanceof Object)) return false;
if ((data as Partial<InviteResponse>)?.cmd !== "INVITE_BROWSER") return false;
if (typeof (data as Partial<InviteResponse>)?.args?.code !== "string") return false;
if (typeof (data as Partial<InviteResponse>)?.nonce !== "string") return false;
return true;
}
const messages = {
/**
* A fake, hard-coded Discord command to spoof the presence of
* official Discord client (which makes browser to actually start a
* communication with the ArmCord).
*/
handShake: {
/** Message command. */
cmd: "DISPATCH",
/** Message data. */
data: {
/** Message scheme version. */
v: 1,
/** Client properties. */
config: {
/** Discord CDN host (hard-coded for `dicscord.com` instance). */
cdn_host: "cdn.discordapp.com",
/** API endpoint (hard-coded for `dicscord.com` instance). */
api_endpoint: "//discord.com/api",
/** Client type. Can be (probably) `production` or `canary`. */
environment: "production"
}
},
evt: "READY",
nonce: null
}
};
/**
* Tries to reserve the server at given port.
*
* @returns `Promise`, which always resolves (either to `Server<WebSocket>` on
* success or `null` on failure).
*/
async function getServer(port: number) {
const {WebSocketServer} = await import("ws");
return new Promise<Server<WebSocket> | null>((resolve) => {
const wss = new WebSocketServer({host: "127.0.0.1", port});
wss.once("listening", () => resolve(wss));
wss.once("error", () => resolve(null));
});
}
/**
* Tries to start a WebSocket server at given port range. If it suceed, it will
* listen to the browser requests which are meant to be sent to official
* Discord client.
*
* Currently it supports only the invitation link requests.
*
*/
export default async function startServer() {
function isJsonSyntaxCorrect(string: string) {
try {
JSON.parse(string);
} catch {
return false;
}
return true;
}
/** Known Discord instances, including the official ones. */
const knownInstancesList = [
["Discord", new URL("https://discord.com/app")],
["Discord Canary", new URL("https://canary.discord.com/app")],
["Discord PTB", new URL("https://ptb.discord.com/app")],
["Fosscord", new URL("https://dev.fosscord.com/app")]
] as const;
let wss = null, wsPort = 6463;
for(const port of range(6463, 6472)) {
wss = await getServer(port);
if(wss !== null) {
void wsLog("ArmCord is listening at " + (port.toString()));
wsPort = port;
break;
}
}
if(wss === null) return;
let lock = false;
wss.on('connection', (wss, request) => {
const origin = request.headers.origin??'https://discord.com';
let known = false;
for(const instance of knownInstancesList) {
if(instance[1].origin === origin)
known = true;
}
if(!known) return;
wss.send(JSON.stringify(messages.handShake));
wss.once('message', (data, isBinary) => {
if(lock) return;
lock = true;
let parsedData:unknown = data;
if(!isBinary)
parsedData = data.toString();
if(isJsonSyntaxCorrect(parsedData as string))
parsedData = JSON.parse(parsedData as string);
if(isInviteResponse(parsedData)) {
// Replies to browser, so it finds the communication successful.
wss.send(JSON.stringify({
cmd: parsedData.cmd,
data: {
invite: null,
code: parsedData.args.code
},
evt: null,
nonce: parsedData.nonce
}));
createInviteWindow()
const child = inviteWindow;
if(child === undefined) return;
void child.loadURL(origin+'/invite/'+parsedData.args.code);
child.webContents.once("did-finish-load", () => {
child.show();
});
child.webContents.once("will-navigate", () => {
lock = false;
child.close();
})
child.on("close", (e) => {
lock = false;
})
// Blocks requests to ArmCord's WS, to prevent loops.
child.webContents.session.webRequest.onBeforeRequest({
urls: ['ws://127.0.0.1:'+wsPort.toString()+'/*']
}, (_details,callback) => callback({cancel: true}));
}
})
})
}

View file

@ -1,38 +1,11 @@
import {app, Menu, Tray} from "electron"; import {app, Menu, Tray} from "electron";
import {mainWindow} from "./window"; import {mainWindow} from "./window";
import { getConfig } from "./utils";
import * as path from "path"; import * as path from "path";
import {createSettingsWindow} from "./settings/main"; import {createSettingsWindow} from "./settings/main";
let tray: any = null; let tray = null;
app.whenReady().then(async () => { app.whenReady().then(() => {
if (await getConfig("windowStyle") == "discord") {
tray = new Tray(path.join(__dirname, "../", "/assets/dsc-tray.png"));
const contextMenu = Menu.buildFromTemplate([
{
label: "Open ArmCord",
click: function () {
mainWindow.show();
}
},
{
label: "Quit ArmCord",
click: function () {
app.quit();
}
}
]);
tray.setToolTip("Discord");
tray.setContextMenu(contextMenu);
} else {
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: "ArmCord",
},
{
type: "separator"
},
{ {
label: "Open ArmCord", label: "Open ArmCord",
click: function () { click: function () {
@ -52,9 +25,6 @@ app.whenReady().then(async () => {
mainWindow.loadURL("https://discord.gg/TnhxcqynZ2"); mainWindow.loadURL("https://discord.gg/TnhxcqynZ2");
} }
}, },
{
type: "separator"
},
{ {
label: "Quit ArmCord", label: "Quit ArmCord",
click: function () { click: function () {
@ -65,5 +35,4 @@ app.whenReady().then(async () => {
tray.setToolTip("ArmCord " + app.getVersion()); tray.setToolTip("ArmCord " + app.getVersion());
tray.setContextMenu(contextMenu); tray.setContextMenu(contextMenu);
}
}); });

View file

@ -1,9 +1,9 @@
import * as storage from "electron-json-storage";
import * as fs from "fs"; import * as fs from "fs";
import {app, dialog} from "electron"; import {app, dialog} from "electron";
import path from "path"; import path from "path";
export var firstRun: boolean; export var firstRun: boolean;
export var isSetup: boolean;
export var contentPath: string;
//utillity functions that are used all over the codebase or just too obscure to be put in the file used in //utillity 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) { export function addStyle(styleString: string) {
const style = document.createElement("style"); const style = document.createElement("style");
@ -22,16 +22,22 @@ export async function sleep(ms: number) {
} }
export async function checkIfConfigIsBroken() { export async function checkIfConfigIsBroken() {
if ((await getConfig("0")) == "d") { if (await getConfigUnsafe("0") == "d") {
console.log("Detected a corrupted config"); console.log("Detected a corrupted config")
setup(); setup()
dialog.showErrorBox( 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.")
"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."
);
} }
} }
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 temporary ArmCord settings."); console.log("Setting up temporary ArmCord settings.");
const defaults: Settings = { const defaults: Settings = {
@ -41,18 +47,50 @@ export function setup() {
minimizeToTray: true, minimizeToTray: true,
automaticPatches: false, automaticPatches: false,
mods: "cumcord", mods: "cumcord",
blurType: "acrylic", blurType: "acrylic"
inviteWebsocket: true,
doneSetup: false
}; };
setConfigBulk({ storage.set(
...defaults "settings",
}); {
...defaults,
doneSetup: false
},
function (error) {
if (error) throw error;
}
);
} }
export function saveSettings(settings: Settings) {
console.log("Setting up ArmCord settings.");
storage.set(
"settings",
{
...settings,
doneSetup: true
},
function (error) {
if (error) throw error;
}
);
}
export async function getConfigUnsafe(object: string) {
try {
const userDataPath = app.getPath("userData");
const storagePath = path.join(userDataPath, "/storage/");
let rawdata = fs.readFileSync(storagePath + "settings.json", "utf-8");
let returndata = JSON.parse(rawdata);
console.log(returndata[object]);
return returndata[object];
} catch (e) {
console.log("Config probably doesn't exist yet. Returning setup value.");
firstRun = true;
return "setup";
}
}
export function getVersion() { export function getVersion() {
//to-do better way of doing this //to-do better way of doing this
return "3.0.6"; return "3.0.5";
} }
export async function injectJS(inject: string) { export async function injectJS(inject: string) {
const js = await (await fetch(`${inject}`)).text(); const js = await (await fetch(`${inject}`)).text();
@ -63,96 +101,3 @@ export async function injectJS(inject: string) {
document.body.appendChild(el); document.body.appendChild(el);
} }
//ArmCord Settings/Storage manager
export interface Settings {
windowStyle: string;
channel: string;
armcordCSP: boolean;
minimizeToTray: boolean;
automaticPatches: boolean;
mods: string;
blurType: string;
inviteWebsocket: boolean;
doneSetup: boolean;
}
export async function getConfig(object: string) {
try {
const userDataPath = app.getPath("userData");
const storagePath = path.join(userDataPath, "/storage/");
const settingsFile = storagePath + "settings.json";
let rawdata = fs.readFileSync(settingsFile, "utf-8");
let returndata = JSON.parse(rawdata);
console.log(object + ": " + returndata[object]);
return returndata[object];
} catch (e) {
console.log("Config probably doesn't exist yet. Returning setup value.");
firstRun = true;
return "setup";
}
}
export async function setConfig(object: string, toSet: any) {
try {
const userDataPath = app.getPath("userData");
const storagePath = path.join(userDataPath, "/storage/");
const settingsFile = storagePath + "settings.json";
let rawdata = fs.readFileSync(settingsFile, "utf-8");
let parsed = JSON.parse(rawdata);
parsed[object] = toSet;
let toSave = JSON.stringify(parsed);
fs.writeFileSync(settingsFile, toSave, "utf-8");
} catch (e) {
console.log("Config probably doesn't exist yet. Returning setup value.");
firstRun = true;
return "setup";
}
}
export async function setConfigBulk(object: Settings) {
try {
const userDataPath = app.getPath("userData");
const storagePath = path.join(userDataPath, "/storage/");
const settingsFile = storagePath + "settings.json";
let toSave = JSON.stringify(object);
fs.writeFileSync(settingsFile, toSave, "utf-8");
} catch (e) {
console.log("Config probably doesn't exist yet. Returning setup value.");
firstRun = true;
return "setup";
}
}
export async function checkIfConfigExists() {
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();
isSetup = true;
contentPath = path.join(__dirname, "/content/setup.html");
if (!contentPath.includes("ts-out")) {
contentPath = path.join(__dirname, "/ts-out/content/setup.html");
}
} else {
if (await getConfig("doneSetup") == false) {
console.log("First run of the ArmCord. Starting setup.");
setup();
isSetup = true;
contentPath = path.join(__dirname, "/content/setup.html");
if (!contentPath.includes("ts-out")) {
contentPath = path.join(__dirname, "/ts-out/content/setup.html");
}
} else {
console.log("ArmCord has been run before. Skipping setup.");
contentPath = path.join(__dirname, "/content/splash.html");
if (!contentPath.includes("ts-out")) {
contentPath = path.join(__dirname, "/ts-out/content/splash.html");
}
}
}
}

View file

@ -2,29 +2,27 @@
// 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, app, ipcMain, dialog, clipboard} from "electron"; import {BrowserWindow, shell, app, ipcMain, dialog} from "electron";
import path from "path"; import path from "path";
import {checkIfConfigIsBroken, firstRun, getConfig, contentPath, isSetup} from "./utils"; import {contentPath} from "./main";
import {checkIfConfigIsBroken, firstRun, getConfigUnsafe} from "./utils";
import {registerIpc} from "./ipc"; import {registerIpc} from "./ipc";
import startServer from "./socket"
import contextMenu from "electron-context-menu"; import contextMenu from "electron-context-menu";
import os from "os";
export var icon: string;
export let mainWindow: BrowserWindow; export let mainWindow: BrowserWindow;
export let inviteWindow: BrowserWindow;
let guestWindows: BrowserWindow[] = [];
let guestWindows: BrowserWindow[] = [];
contextMenu({ contextMenu({
showSaveImageAs: true, showSaveImageAs: true,
showCopyImageAddress: true, showCopyImageAddress: true,
showSearchWithGoogle: true showSearchWithGoogle: true
}); });
async function doAfterDefiningTheWindow() { function doAfterDefiningTheWindow() {
checkIfConfigIsBroken(); checkIfConfigIsBroken()
registerIpc(); registerIpc();
mainWindow.webContents.userAgent = mainWindow.webContents.userAgent =
`Mozilla/5.0 (X11; ${os.type()} ${os.arch()}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 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"};
@ -34,24 +32,17 @@ async function doAfterDefiningTheWindow() {
return callback({}); return callback({});
}); });
mainWindow.on("close", async (e) => { mainWindow.on("close", async (e) => {
if (await getConfig("minimizeToTray")) { if (await getConfigUnsafe("minimizeToTray")) {
e.preventDefault(); e.preventDefault();
mainWindow.hide(); mainWindow.hide();
} else if (!(await getConfig("minimizeToTray"))) { } else if (!(await getConfigUnsafe("minimizeToTray"))) {
e.preventDefault(); e.preventDefault();
app.quit(); app.quit();
} }
}); });
console.log(contentPath); console.log(contentPath);
if (await getConfig("inviteWebsocket") == true) {
startServer()
}
try { try {
mainWindow.loadFile(contentPath); mainWindow.loadFile(contentPath);
if (isSetup) {
mainWindow.setSize(390, 470);
}
} catch (e) { } catch (e) {
console.log( console.log(
"Major error detected while starting up. User is most likely on Windows platform. Fallback to alternative startup." "Major error detected while starting up. User is most likely on Windows platform. Fallback to alternative startup."
@ -59,14 +50,12 @@ async function doAfterDefiningTheWindow() {
console.log(process.platform); console.log(process.platform);
if (process.platform === "win32") { if (process.platform === "win32") {
if (firstRun) { if (firstRun) {
mainWindow.setSize(390, 470);
mainWindow.loadURL(`file://${__dirname}/content/setup.html`); mainWindow.loadURL(`file://${__dirname}/content/setup.html`);
} else { } else {
mainWindow.loadURL(`file://${__dirname}/content/splash.html`); mainWindow.loadURL(`file://${__dirname}/content/splash.html`);
} }
} else { } else {
if (firstRun) { if (firstRun) {
mainWindow.setSize(390, 470);
mainWindow.loadURL(`file://${__dirname}/ts-out/content/setup.html`); mainWindow.loadURL(`file://${__dirname}/ts-out/content/setup.html`);
} else { } else {
mainWindow.loadURL(`file://${__dirname}/ts-out/content/splash.html`); mainWindow.loadURL(`file://${__dirname}/ts-out/content/splash.html`);
@ -108,10 +97,7 @@ export function createNativeWindow() {
} }
export function createTabsHost() { export function createTabsHost() {
dialog.showErrorBox( dialog.showErrorBox("READ THIS BEFORE USING THE APP", "ArmCord Tabs are highly experimental and should be only used for strict testing purposes. Please don't ask for support, however you can still report bugs!")
"READ THIS BEFORE USING THE APP",
"ArmCord Tabs are highly experimental and should be only used for strict testing purposes. Please don't ask for support, however you can still report bugs!"
);
guestWindows[1] = mainWindow; guestWindows[1] = mainWindow;
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 300, width: 300,
@ -178,18 +164,3 @@ export function createTabsGuest(number: number) {
guestWindows[number].loadURL("https://discord.com/app"); guestWindows[number].loadURL("https://discord.com/app");
} }
} }
export function createInviteWindow() {
inviteWindow = new BrowserWindow({
width: 800,
height: 600,
title: "ArmCord Invite Manager",
darkTheme: true,
icon: path.join(__dirname, "/assets/icon_transparent.png"),
frame: true,
autoHideMenuBar: true,
webPreferences: {
spellcheck: true
}
});
inviteWindow.hide()
}