Updates and Linting (#578)
* Updates and Linting * fix lint task * [ImgBot] Optimize images *Total -- 404.98kb -> 304.38kb (24.84%) /assets/screenshot-1920x1080.png -- 205.00kb -> 123.21kb (39.9%) /build/appx/Square150x150Logo.png -- 9.63kb -> 7.71kb (20%) /assets/ac_icon.png -- 40.15kb -> 34.98kb (12.88%) /assets/StoreLogo.png -- 40.15kb -> 34.98kb (12.88%) /assets/Square150x150Logo.png -- 7.24kb -> 6.53kb (9.83%) /assets/ac_icon_transparent.png -- 45.54kb -> 42.00kb (7.76%) /assets/ac_plug_colored.png -- 17.98kb -> 16.72kb (7%) /assets/ac_black_plug.png -- 8.49kb -> 8.06kb (5.06%) /assets/ac_black_plug_hollow.png -- 10.30kb -> 9.95kb (3.4%) /build/appx/Square44x44Logo.png -- 1.69kb -> 1.64kb (2.89%) /assets/Square44x44Logo.png -- 1.90kb -> 1.85kb (2.83%) /assets/Wide310x150Logo.png -- 4.21kb -> 4.17kb (0.97%) /build/appx/Wide310x150Logo.png -- 4.21kb -> 4.17kb (0.97%) /assets/ac_white_plug.png -- 8.49kb -> 8.42kb (0.83%) Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com> * Asyncification!!! Check `REVIEW` comments * More async for `src/discord` * update packages to latest minor version * Void some promises * Add some types - 93 problems left! * make DeepScan Happy * DeepScan part 2 * I am the Constant * preload must be `.mts` * Migrate electron context menu official package * fix bad preload on setup window * fix minor import oversights * fix modloader * Stop main window from continuing during setup * update packages, slow dependabot * Remove paste override, it seems to work without now * IPC typing * Package updates & a few more typings * fix linting errors in screenshare * use pnpm in actions * fix dev releaser? * update node build, fix dev one more time * release action is broke * Fix Release * update actions * actions are so finicky * remove delete-tag-and-release * add github token env * Hopefully this fixes the release workflow * [debug] * this should actually fix it * Fix typo in dev action * put everything in a dir and then get it * use a different releaser * correct release file location * action places it in a folder named x.zip, recurse into that and grab the actual files * Cleanup actions a bit * release is dependent on mac build * remove mac build * split linux arm and x86 * rely on linux arm * remove deprecated action * attempt to fix weird recursive zip * fix env * use pnpm in actions fix dev releaser? update node build, fix dev one more time release action is broke Fix Release update actions actions are so finicky remove delete-tag-and-release add github token env Hopefully this fixes the release workflow [debug] this should actually fix it Fix typo in dev action put everything in a dir and then get it use a different releaser correct release file location action places it in a folder named x.zip, recurse into that and grab the actual files Cleanup actions a bit release is dependent on mac build remove mac build split linux arm and x86 rely on linux arm remove deprecated action attempt to fix weird recursive zip fix env * don't globally install pnpm packages (I don't think the cache checks global) * Type the armcord window * Finalize typings * fix deepscan issues * fix screenshare preload * fix app quitting --------- Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com> Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com> Co-authored-by: smartfrigde <37928912+smartfrigde@users.noreply.github.com>
|
@ -1,35 +0,0 @@
|
|||
extends: eslint-config-dmitmel/presets/node
|
||||
env:
|
||||
browser: true
|
||||
plugins: ["prettier"]
|
||||
|
||||
settings:
|
||||
node:
|
||||
tryExtensions: [".tsx", ".ts", ".jsx", ".js", ".json", ".node"]
|
||||
|
||||
ignorePatterns: ["src/arrpc/**"]
|
||||
|
||||
rules:
|
||||
prettier/prettier:
|
||||
- error
|
||||
node/no-unsupported-features/es-syntax:
|
||||
- error
|
||||
- ignores:
|
||||
- modules
|
||||
|
||||
overrides:
|
||||
- files: "**/*.ts*"
|
||||
extends:
|
||||
- eslint-config-dmitmel/presets/typescript-addon
|
||||
parserOptions:
|
||||
project: "tsconfig.json"
|
||||
sourceType: module
|
||||
rules:
|
||||
eqeqeq: 0
|
||||
require-await: 0
|
||||
no-undefined: 0
|
||||
node/no-unsupported-features/es-syntax: 0
|
||||
"@typescript-eslint/no-dynamic-delete": 0
|
||||
"@typescript-eslint/no-explicit-any": 0
|
||||
"@typescript-eslint/no-non-null-asserted-optional-chain": 0
|
||||
"@typescript-eslint/naming-convention": 0
|
2
.github/dependabot.yml
vendored
|
@ -3,7 +3,7 @@ updates:
|
|||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "weekly"
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
|
|
1
.github/release.md
vendored
|
@ -1,3 +1,4 @@
|
|||
# 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!
|
||||
|
|
262
.github/workflows/dev.yml
vendored
|
@ -1,185 +1,147 @@
|
|||
name: Dev build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
env:
|
||||
FORCE_COLOR: true
|
||||
FORCE_COLOR: true
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
||||
- name: Install Node dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Install Electron-Builder
|
||||
run: pnpm install -g electron-builder
|
||||
- name: Build
|
||||
run: pnpm run build && pnpm electron-builder --linux zip
|
||||
|
||||
- name: Build
|
||||
run: npm run build && electron-builder --linux zip && electron-builder --arm64 --linux zip
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ArmCordLinux
|
||||
path: dist/armcord-*.zip
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ArmCordLinux.zip
|
||||
path: dist/ArmCord-3.3.0.zip
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ArmCordLinuxArm64.zip
|
||||
path: dist/ArmCord-3.3.0-arm64.zip
|
||||
build-snap:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
build-linux-arm:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
||||
- name: Install Node dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Install Electron-Builder
|
||||
run: pnpm install -g electron-builder
|
||||
- name: Build
|
||||
run: pnpm run build && pnpm electron-builder --arm64 --linux zip
|
||||
|
||||
- name: Build
|
||||
run: npm run build && electron-builder --linux snap --config.snap.grade=devel
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ArmCordLinuxArm64
|
||||
path: dist/armcord-*-arm64.zip
|
||||
|
||||
- uses: snapcore/action-publish@v1
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}
|
||||
with:
|
||||
snap: dist/ArmCord_3.3.0_amd64.snap
|
||||
release: edge
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
||||
- name: Install Node dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Install Electron-Builder
|
||||
run: pnpm install -g electron-builder
|
||||
- name: Build
|
||||
run: pnpm run build && pnpm electron-builder --windows zip
|
||||
|
||||
- name: Build
|
||||
run: npm run build && electron-builder --windows zip
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ArmCordWindows
|
||||
path: dist/armcord-3.3.0-win.zip
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ArmCordWindows.zip
|
||||
path: dist/ArmCord-3.3.0-win.zip
|
||||
build-windowsOnARM:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
build-windows-arm:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Set architecture
|
||||
run: set npm_config_arch=arm64
|
||||
- name: Set architecture
|
||||
run: set npm_config_arch=arm64
|
||||
|
||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||
- uses: pnpm/action-setup@v4 # Install pnpm using packageManager key in package.json
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
||||
- name: Install Node dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Install Electron-Builder
|
||||
run: pnpm install -g electron-builder
|
||||
- name: Build
|
||||
run: pnpm run build && pnpm electron-builder --windows zip --arm64
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build
|
||||
run: npm run build && electron-builder --windows zip --arm64
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ArmCordWindowsArm64.zip
|
||||
path: dist\ArmCord-3.3.0-arm64-win.zip
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ArmCordWindowsArm64
|
||||
path: dist\armcord-3.3.0-arm64-win.zip
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-linux, build-windows, build-windowsOnARM]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-linux, build-windows, build-windows-arm, build-linux-arm]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: ArmCordWindows.zip
|
||||
path: windows
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: release-files
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: ArmCordLinux.zip
|
||||
path: linux
|
||||
- name: Get short commit hash
|
||||
id: vars
|
||||
run: echo "sha_short=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: ArmCordLinuxArm64.zip
|
||||
path: linux
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: ArmCordWindowsArm64.zip
|
||||
path: windows
|
||||
- run: gh release delete devbuild -y --cleanup-tag
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- 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.1
|
||||
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.3.0.zip
|
||||
linux/ArmCord-3.3.0-arm64.zip
|
||||
windows/ArmCord-3.3.0-win.zip
|
||||
windows/ArmCord-3.3.0-arm64-win.zip
|
||||
- name: Create Release
|
||||
uses: ncipollo/release-action@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
bodyFile: .github/release.md
|
||||
generateReleaseNotes: true
|
||||
name: Dev Build ${{ steps.vars.outputs.sha_short }}
|
||||
prerelease: true
|
||||
tag: devbuild
|
||||
artifacts: "release-files/**/*.zip"
|
||||
|
|
32
.github/workflows/eslint.yml
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*"
|
||||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
run-linters:
|
||||
name: Run linters
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Run linters
|
||||
run: pnpm run lint
|
219
.github/workflows/stable.yml
vendored
|
@ -1,11 +1,11 @@
|
|||
name: Release build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- stable
|
||||
push:
|
||||
branches:
|
||||
- stable
|
||||
|
||||
env:
|
||||
FORCE_COLOR: true
|
||||
FORCE_COLOR: true
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
|
@ -13,69 +13,67 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v2
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
||||
|
||||
- name: Install Electron-Builder
|
||||
run: pnpm install -g electron-builder
|
||||
run: pnpm i -g cargo-cp-artifact electron-builder && pnpm i
|
||||
|
||||
- name: Build
|
||||
run: npm run build && electron-builder --linux && electron-builder --arm64 --linux && electron-builder --armv7l --linux
|
||||
run: pnpm run build && electron-builder --linux && electron-builder --arm64 --linux && electron-builder --armv7l --linux
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: List all files in the dist directory
|
||||
run: ls -l dist
|
||||
|
||||
- name: Delete unpacked builds
|
||||
run: rm -rf dist/linux-unpacked && rm -rf dist/linux-arm64-unpacked && rm -rf dist/linux-armv7l-unpacked
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ArmCordLinux
|
||||
path: dist/
|
||||
|
||||
|
||||
build-mac:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v2
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
||||
|
||||
- name: Install Electron-Builder
|
||||
run: pnpm install -g electron-builder
|
||||
run: pnpm i -g cargo-cp-artifact electron-builder && pnpm i
|
||||
|
||||
- name: Build
|
||||
run: npm run build && electron-builder --macos --x64 --arm64
|
||||
run: pnpm run build && electron-builder --macos --x64 --arm64
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: List all files in the dist directory
|
||||
run: ls -l dist
|
||||
|
||||
- name: Delete unpacked builds
|
||||
run: rm -rf dist/macos-unpacked
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ArmCordMac
|
||||
path: dist/
|
||||
|
@ -84,62 +82,62 @@ jobs:
|
|||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v2
|
||||
- name: Use Node.js 22
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
||||
|
||||
- name: Install Electron-Builder
|
||||
run: pnpm install -g electron-builder
|
||||
run: pnpm i -g cargo-cp-artifact electron-builder && pnpm i
|
||||
|
||||
- name: Build
|
||||
run: npm run build && electron-builder --windows
|
||||
run: pnpm run build && electron-builder --windows
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Delete unpacked builds
|
||||
run: Remove-Item -LiteralPath ".\dist\win-unpacked" -Force -Recurse
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ArmCordWindows
|
||||
path: dist/
|
||||
|
||||
build-windowsOnARM:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Set architecture
|
||||
run: set npm_config_arch=arm64
|
||||
|
||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: pnpm install -g cargo-cp-artifact && pnpm install
|
||||
|
||||
- name: Install Electron-Builder
|
||||
run: pnpm install -g electron-builder
|
||||
run: pnpm install -g cargo-cp-artifact electron-builder && pnpm install
|
||||
|
||||
- name: Build
|
||||
run: npm run build && electron-builder --windows --arm64
|
||||
run: pnpm run build && electron-builder --windows --arm64
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Delete unpacked builds
|
||||
run: Remove-Item -LiteralPath ".\dist\win-arm64-unpacked" -Force -Recurse
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ArmCordWindowsArm64
|
||||
path: dist/
|
||||
|
@ -148,86 +146,93 @@ jobs:
|
|||
needs: [build-linux, build-mac, build-windows, build-windowsOnARM]
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v2
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ArmCordMac
|
||||
path: macos
|
||||
- uses: actions/download-artifact@v2
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ArmCordWindows
|
||||
path: windows
|
||||
- uses: actions/download-artifact@v2
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ArmCordWindowsArm64
|
||||
path: windows
|
||||
- uses: actions/download-artifact@v2
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ArmCordLinux
|
||||
path: linux
|
||||
|
||||
- name: ls
|
||||
run: ls
|
||||
|
||||
- name: Delete unwanted directories
|
||||
run: rm -rf {linux,macos,windows}/*/
|
||||
rm -rf {linux,macos,windows}/.icon*
|
||||
rm -rf {linux,macos,windows}/builder-debug.yml
|
||||
rm -rf {linux,macos,windows}/.icon*
|
||||
rm -rf {linux,macos,windows}/builder-debug.yml
|
||||
|
||||
- name: ls dirs
|
||||
run: ls linux && ls macos && ls windows
|
||||
|
||||
- name: Get some values needed for the release
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::set-output name=releaseTag::$(git describe --tags --abbrev=0)"
|
||||
echo "::set-output name=releaseTag::$(git describe --tags --abbrev=0)"
|
||||
|
||||
- name: Create Release
|
||||
uses: actions/github-script@v2
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
console.log('environment', process.versions);
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
console.log('environment', process.versions);
|
||||
|
||||
const fs = require('fs').promises;
|
||||
const fs = require('fs').promises;
|
||||
|
||||
const { repo: { owner, repo }, sha } = context;
|
||||
console.log({ owner, repo, sha });
|
||||
const { repo: { owner, repo }, sha } = context;
|
||||
console.log({ owner, repo, sha });
|
||||
|
||||
const release = await github.repos.createRelease({
|
||||
owner, repo,
|
||||
tag_name: process.env.releaseTag,
|
||||
draft: true,
|
||||
target_commitish: sha
|
||||
});
|
||||
const release = await github.repos.createRelease({
|
||||
owner, repo,
|
||||
tag_name: process.env.releaseTag,
|
||||
draft: true,
|
||||
target_commitish: sha
|
||||
});
|
||||
|
||||
console.log('created release', { release });
|
||||
console.log('created release', { release });
|
||||
|
||||
for (let file of await fs.readdir('linux')) {
|
||||
// do whatever filtering you want here, I'm just uploading all the files
|
||||
console.log('uploading', file);
|
||||
await github.repos.uploadReleaseAsset({
|
||||
owner, repo,
|
||||
release_id: release.data.id,
|
||||
name: file,
|
||||
data: await fs.readFile(`./linux/${file}`)
|
||||
});
|
||||
}
|
||||
for (let file of await fs.readdir('windows')) {
|
||||
// do whatever filtering you want here, I'm just uploading all the files
|
||||
console.log('uploading', file);
|
||||
await github.repos.uploadReleaseAsset({
|
||||
owner, repo,
|
||||
release_id: release.data.id,
|
||||
name: file,
|
||||
data: await fs.readFile(`./windows/${file}`)
|
||||
});
|
||||
}
|
||||
for (let file of await fs.readdir('macos')) {
|
||||
// do whatever filtering you want here, I'm just uploading all the files
|
||||
console.log('uploading', file);
|
||||
await github.repos.uploadReleaseAsset({
|
||||
owner, repo,
|
||||
release_id: release.data.id,
|
||||
name: file,
|
||||
data: await fs.readFile(`./macos/${file}`)
|
||||
});
|
||||
}
|
||||
for (let file of await fs.readdir('linux')) {
|
||||
// do whatever filtering you want here, I'm just uploading all the files
|
||||
console.log('uploading', file);
|
||||
await github.repos.uploadReleaseAsset({
|
||||
owner, repo,
|
||||
release_id: release.data.id,
|
||||
name: file,
|
||||
data: await fs.readFile(`./linux/${file}`)
|
||||
});
|
||||
}
|
||||
for (let file of await fs.readdir('windows')) {
|
||||
// do whatever filtering you want here, I'm just uploading all the files
|
||||
console.log('uploading', file);
|
||||
await github.repos.uploadReleaseAsset({
|
||||
owner, repo,
|
||||
release_id: release.data.id,
|
||||
name: file,
|
||||
data: await fs.readFile(`./windows/${file}`)
|
||||
});
|
||||
}
|
||||
for (let file of await fs.readdir('macos')) {
|
||||
// do whatever filtering you want here, I'm just uploading all the files
|
||||
console.log('uploading', file);
|
||||
await github.repos.uploadReleaseAsset({
|
||||
owner, repo,
|
||||
release_id: release.data.id,
|
||||
name: file,
|
||||
data: await fs.readFile(`./macos/${file}`)
|
||||
});
|
||||
}
|
||||
env:
|
||||
releaseTag: ${{ steps.vars.outputs.releaseTag }}
|
||||
releaseTag: ${{ steps.vars.outputs.releaseTag }}
|
||||
|
|
18
.github/workflows/winget.yml
vendored
|
@ -1,13 +1,13 @@
|
|||
name: Publish to WinGet
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||
with:
|
||||
identifier: ArmCord.ArmCord
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||
with:
|
||||
identifier: ArmCord.ArmCord
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
|
|
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ dist
|
|||
ts-out/
|
||||
ts-out
|
||||
package-lock.json
|
||||
.pnpm-store
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
npm run format
|
||||
pnpm run format
|
||||
git add -A
|
||||
|
|
5
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"ExodiusStudios.comment-anchors"
|
||||
]
|
||||
}
|
19
.vscode/settings.json
vendored
|
@ -1,2 +1,21 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"armcord",
|
||||
"armcordinternal",
|
||||
"arrpc",
|
||||
"Autogain",
|
||||
"clientmod",
|
||||
"copyfiles",
|
||||
"Ducko",
|
||||
"modloader",
|
||||
"nsis",
|
||||
"smartfridge",
|
||||
"smartfrigde",
|
||||
"togglefullscreen",
|
||||
"unmaximize",
|
||||
"vaapi"
|
||||
],
|
||||
"cSpell.ignorePaths": [
|
||||
"assets/lang"
|
||||
]
|
||||
}
|
63
README.md
|
@ -34,7 +34,6 @@
|
|||
|
||||
ArmCord is using a newer build of Electron than the stock Discord app. This means you can have a much more stable and secure experience, along with slightly better performance.
|
||||
|
||||
|
||||
- **Cross-platform support!**
|
||||
|
||||
ArmCord was originally created for ARM64 Linux devices since Discord doesn't support them. We soon decided to support every platform that [Electron supports](https://github.com/electron/electron#platform-support)!
|
||||
|
@ -42,9 +41,11 @@
|
|||
# How to run/install it?
|
||||
|
||||
## Packaging status
|
||||
|
||||
[![Packaging status](https://repology.org/badge/vertical-allrepos/armcord.svg)](https://repology.org/project/armcord/versions)
|
||||
|
||||
### Windows
|
||||
|
||||
<a href="https://microsoft.com/store/apps/9PFHLJFD7KJT">
|
||||
<img src="https://get.microsoft.com/images/en-us%20dark.svg" alt="Download ArmCord" />
|
||||
</a>
|
||||
|
@ -52,106 +53,146 @@
|
|||
If you're using an older version of Windows, you need to use [pre-built installers](https://www.armcord.app/download).
|
||||
|
||||
### Flatpak
|
||||
|
||||
<a href='https://flathub.org/apps/details/xyz.armcord.ArmCord'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.svg'/></a>
|
||||
|
||||
### Debian, Ubuntu and Raspbian repository
|
||||
|
||||
ArmCord is available on our official repositories for `apt` package manager. By using this method you'll receive automatic updates and get all the dependencies. Run the following commands to install ArmCord from them:
|
||||
|
||||
```sh
|
||||
curl -fsSL https://apt.armcord.app/public.gpg | sudo gpg --dearmor -o /usr/share/keyrings/armcord.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/armcord.gpg] https://apt.armcord.app/ stable main" | sudo tee /etc/apt/sources.list.d/armcord.list
|
||||
sudo apt update
|
||||
sudo apt install armcord
|
||||
```
|
||||
|
||||
If you previously used old ArmCord apt repo, here's how you can remove it:
|
||||
|
||||
```sh
|
||||
sudo rm /etc/apt/sources.list.d/armcord.list
|
||||
sudo rm /usr/share/keyrings/armcord.gpg
|
||||
sudo apt update
|
||||
```
|
||||
|
||||
### Snap package
|
||||
|
||||
ArmCord is also available on the Snap store [here](https://snapcraft.io/armcord).
|
||||
<a href="https://snapcraft.io/armcord">
|
||||
<img alt="Get it from the Snap Store" src="https://snapcraft.io/static/images/badges/en/snap-store-black.svg" />
|
||||
</a>
|
||||
|
||||
Similar to `armcord-git` on AUR, you can install the latest dev builds through snaps by running this command:
|
||||
|
||||
```shell
|
||||
sudo snap install armcord --channel=latest/edge
|
||||
```
|
||||
|
||||
Snapd will automatically update the app including developer builds.
|
||||
|
||||
### Winget Package
|
||||
|
||||
ArmCord is also available on the [winget-pkgs](https://github.com/microsoft/winget-pkgs) repository:
|
||||
```
|
||||
|
||||
```ps1
|
||||
winget install ArmCord.ArmCord
|
||||
```
|
||||
|
||||
### Scoop package
|
||||
|
||||
ArmCord is also available on [Scoop extras](https://github.com/ScoopInstaller/Extras) repo:
|
||||
```
|
||||
|
||||
```ps1
|
||||
scoop bucket add extras
|
||||
```
|
||||
```
|
||||
|
||||
```ps1
|
||||
scoop install armcord
|
||||
```
|
||||
|
||||
### AUR Package
|
||||
|
||||
ArmCord is also available on the Arch User Repository (AUR):
|
||||
|
||||
- [armcord-bin](https://aur.archlinux.org/packages/armcord-bin) - ArmCord Release ~ Static binary from release, stable release only
|
||||
|
||||
- [armcord-git](https://aur.archlinux.org/packages/armcord-git) - ArmCord Dev ~ Latest devbuild built from source (takes ~1 minute) using the system electron
|
||||
|
||||
Install it via an AUR helper tool like `yay`.
|
||||
|
||||
**Example:** `yay -S armcord-bin`
|
||||
|
||||
### Homebrew repository
|
||||
ArmcCord also has a homebrew repository
|
||||
```
|
||||
|
||||
ArmCord also has a homebrew repository
|
||||
|
||||
```zsh
|
||||
brew tap armcord/armcord
|
||||
```
|
||||
```
|
||||
|
||||
```zsh
|
||||
brew install --cask armcord
|
||||
```
|
||||
|
||||
### FreeBSD
|
||||
|
||||
You can also get ArmCord running on FreeBSD by following [these instructions](https://gist.github.com/axyiee/4d29c982ac85d5d26f98a51040b5de37).
|
||||
|
||||
### Pi-Apps
|
||||
|
||||
ArmCord is also available in [Pi-Apps](https://github.com/Botspot/pi-apps).
|
||||
[![badge](https://github.com/Botspot/pi-apps/blob/master/icons/badge.png?raw=true)](https://github.com/Botspot/pi-apps)
|
||||
|
||||
### Pre-built binaries:
|
||||
|
||||
Check the **releases tab** for precompiled packages for Linux, Windows, and Mac OS. 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>
|
||||
|
||||
### Compiling:
|
||||
|
||||
Alternatively, you can run ArmCord from source ([NodeJS](https://nodejs.dev), [pnpm](https://pnpm.io/installation#using-npm), and [rust toolchain](https://www.rust-lang.org/tools/install) are required):
|
||||
|
||||
1. Clone ArmCord repo: `git clone https://github.com/ArmCord/ArmCord.git`
|
||||
2. Run `pnpm install` to install dependencies
|
||||
3. Build with `npm run build`
|
||||
4. Compile/Package with `npm run package`
|
||||
|
||||
3. Build with `pnpm run build`
|
||||
4. Compile/Package with `pnpm run package`
|
||||
|
||||
# FAQ
|
||||
|
||||
## Do you have a support Discord?
|
||||
|
||||
[![](https://dcbadge.vercel.app/api/server/TnhxcqynZ2)](https://discord.gg/TnhxcqynZ2)
|
||||
|
||||
## Will I get banned for using this?
|
||||
|
||||
- You are breaking [Discord ToS](https://discord.com/terms#software-in-discord%E2%80%99s-services) by using ArmCord, but no one has been banned from using it or any of the client mods included.
|
||||
|
||||
## Can I use this on anything other than ARM?
|
||||
|
||||
- Yes! ArmCord should work normally under Windows, MacOS, and Linux as long as it has Electron support.
|
||||
|
||||
## How can I access the settings?
|
||||
|
||||
- Open Discord settings and there should be a button `ArmCord Settings` button with a white Discord icon, you can also right click on the tray icon and click `Open Settings`
|
||||
|
||||
## How does this work?
|
||||
|
||||
- We are using the official web app and wrapping it up in Electron. While you may think this is lame and done like thousands of times before, what makes us unique is that we actually strive for creating a customized experience. You can very easily load themes and mods with no installers/injectors. You can even make the client have transparency effects and follow the fluent design of Windows! At its core, it's just a simple web wrapper, however, we applied many patches to make this work well for you <3
|
||||
|
||||
## Why is macOS support lacking?
|
||||
## Why is MacOS support lacking?
|
||||
|
||||
- Due to me not owning any macOS device, I can't easily debug/test or do anything related to it. Of course, VMs and Hackintosh machines exist but from my experience, these are unreliable or very time-consuming to set up and maintain. While ArmCord "works" on macOS you may encounter weird issues or inconsistencies with other apps in terms of how they behave (for example macOS lack of tray).
|
||||
|
||||
## Where can I find the source code?
|
||||
|
||||
- The source code is on [GitHub](https://github.com/ArmCord/ArmCord/).
|
||||
|
||||
## Where can I translate this?
|
||||
|
||||
- Translations are done using our [Weblate page](https://hosted.weblate.org/projects/armcord/armcord/).
|
||||
|
||||
# Credits
|
||||
|
||||
- [ArmCord UI design, branding, and a few features](https://github.com/kckarnige)
|
||||
- [OpenAsar](https://github.com/GooseMod/OpenAsar)
|
||||
- [arRPC (for Rich Presence)](https://github.com/OpenAsar/arrpc)
|
||||
|
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 123 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
48
eslint.config.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
/* eslint-disable n/no-unpublished-import */
|
||||
// @ts-check
|
||||
|
||||
import eslint from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
import prettier from "eslint-plugin-prettier";
|
||||
import n from "eslint-plugin-n";
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
{ignores: ["ts-out", "src/discord/content/js"]}, // REVIEW - investigate discord files a bit before finalizing this - I think these are meant to be run in the app console, and this would be difficult to type
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
n.configs["flat/recommended"],
|
||||
{
|
||||
settings: {
|
||||
n: {
|
||||
allowModules: ["electron"],
|
||||
tryExtensions: [".tsx", ".ts", ".jsx", ".js", ".json", ".node", ".d.ts"]
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
prettier,
|
||||
n
|
||||
},
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: import.meta.dirname
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
rules: {
|
||||
"no-constant-binary-expression": 0,
|
||||
"n/no-unsupported-features/node-builtins": 1,
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
2,
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_"
|
||||
}
|
||||
],
|
||||
// @ts-expect-error - Don't worry about it
|
||||
...prettier.configs.recommended.rules
|
||||
}
|
||||
}
|
||||
);
|
52
package.json
|
@ -1,23 +1,23 @@
|
|||
{
|
||||
"name": "ArmCord",
|
||||
"name": "armcord",
|
||||
"version": "3.3.0",
|
||||
"description": "ArmCord is a custom client designed to enhance your Discord experience while keeping everything lightweight.",
|
||||
"main": "ts-out/main.js",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"node": ">=22"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && copyfiles -u 1 src/**/*.html src/**/**/*.css src/**/**/*.js ts-out/ && copyfiles package.json ts-out/ && copyfiles assets/**/** ts-out/",
|
||||
"watch": "tsc -w",
|
||||
"start": "npm run build && electron ./ts-out/main.js",
|
||||
"startThemeManager": "npm run build && electron ./ts-out/main.js themes",
|
||||
"startKeybindManager": "npm run build && electron ./ts-out/main.js keybinds",
|
||||
"startWayland": "npm run build && electron ./ts-out/main.js --ozone-platform-hint=auto --enable-features=WebRTCPipeWireCapturer,WaylandWindowDecorations --disable-gpu",
|
||||
"package": "npm run build && electron-builder",
|
||||
"packageQuick": "npm run build && electron-builder --dir",
|
||||
"start": "pnpm run build && electron ./ts-out/main.js",
|
||||
"startThemeManager": "pnpm run build && electron ./ts-out/main.js themes",
|
||||
"startKeybindManager": "pnpm run build && electron ./ts-out/main.js keybinds",
|
||||
"startWayland": "pnpm run build && electron ./ts-out/main.js --ozone-platform-hint=auto --enable-features=WebRTCPipeWireCapturer,WaylandWindowDecorations --disable-gpu",
|
||||
"package": "pnpm run build && electron-builder",
|
||||
"packageQuick": "pnpm run build && electron-builder --dir",
|
||||
"format": "prettier --write src *.json",
|
||||
"lint": "eslint src --ext .js,.jsx,.ts,.tsx --ignore-path .gitignore",
|
||||
"CIbuild": "npm run build && electron-builder --linux zip && electron-builder --windows zip && electron-builder --macos zip",
|
||||
"lint": "eslint \"**/*.{ts,tsx,js,jsx}\" .",
|
||||
"CIbuild": "pnpm run build && electron-builder --linux zip && electron-builder --windows zip && electron-builder --macos zip",
|
||||
"prepare": "git config --local core.hooksPath .hooks/"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -32,27 +32,27 @@
|
|||
},
|
||||
"homepage": "https://github.com/armcord/armcord#readme",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.1",
|
||||
"@types/ws": "^8.5.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
||||
"@typescript-eslint/parser": "^5.59.2",
|
||||
"@eslint/js": "^9.4.0",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/node": "^20.14.2",
|
||||
"@types/ws": "^8.5.10",
|
||||
"copyfiles": "^2.4.1",
|
||||
"electron": "30.0.5",
|
||||
"electron-builder": "^24.13.3",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint-config-dmitmel": "github:dmitmel/eslint-config-dmitmel",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.9.3"
|
||||
"electron": "30.1.0",
|
||||
"electron-builder": "25.0.0-alpha.9",
|
||||
"eslint": "^9.4.0",
|
||||
"eslint-plugin-n": "^17.8.1",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"prettier": "^3.3.1",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-eslint": "^7.12.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"arrpc": "github:OpenAsar/arrpc",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"cross-fetch": "^4.0.0",
|
||||
"electron-context-menu": "^4.0.0",
|
||||
"extract-zip": "^2.0.1",
|
||||
"v8-compile-cache": "^2.3.0",
|
||||
"ws": "^8.11.0"
|
||||
"v8-compile-cache": "^2.4.0",
|
||||
"ws": "^8.17.0"
|
||||
},
|
||||
"build": {
|
||||
"snap": {
|
||||
|
@ -95,6 +95,6 @@
|
|||
"applicationId": "smartfrigde.ArmCord"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@9.1.1",
|
||||
"packageManager": "pnpm@9.2.0",
|
||||
"package-manager-strict": false
|
||||
}
|
||||
|
|
2218
pnpm-lock.yaml
|
@ -1,4 +1,5 @@
|
|||
module.exports = {
|
||||
/** @type {import("prettier").Config} */
|
||||
const config = {
|
||||
printWidth: 120,
|
||||
tabWidth: 4,
|
||||
useTabs: false,
|
||||
|
@ -12,3 +13,5 @@ module.exports = {
|
|||
arrowParens: "always",
|
||||
endOfLine: "auto"
|
||||
};
|
||||
|
||||
export default config;
|
|
@ -1,6 +1,7 @@
|
|||
import {app, dialog} from "electron";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import type {Settings} from "../types/settings.d.js";
|
||||
import {getWindowStateLocation} from "./windowState.js";
|
||||
export let firstRun: boolean;
|
||||
export function checkForDataFolder(): void {
|
||||
|
@ -11,64 +12,32 @@ export function checkForDataFolder(): void {
|
|||
}
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
// Referenced for detecting a broken config.
|
||||
"0"?: string;
|
||||
// Referenced once for disabling mod updating.
|
||||
noBundleUpdates?: boolean;
|
||||
// Only used for external url warning dialog.
|
||||
ignoreProtocolWarning?: boolean;
|
||||
customIcon: string;
|
||||
windowStyle: string;
|
||||
channel: string;
|
||||
armcordCSP: boolean;
|
||||
minimizeToTray: boolean;
|
||||
multiInstance: boolean;
|
||||
spellcheck: boolean;
|
||||
mods: string;
|
||||
dynamicIcon: boolean;
|
||||
mobileMode: boolean;
|
||||
skipSplash: boolean;
|
||||
performanceMode: string;
|
||||
customJsBundle: RequestInfo | URL;
|
||||
customCssBundle: RequestInfo | URL;
|
||||
startMinimized: boolean;
|
||||
useLegacyCapturer: boolean;
|
||||
tray: boolean;
|
||||
keybinds: Array<string>;
|
||||
inviteWebsocket: boolean;
|
||||
disableAutogain: boolean;
|
||||
trayIcon: string;
|
||||
doneSetup: boolean;
|
||||
clientName: string;
|
||||
}
|
||||
export function getConfigLocation(): string {
|
||||
const userDataPath = app.getPath("userData");
|
||||
const storagePath = path.join(userDataPath, "/storage/");
|
||||
return `${storagePath}settings.json`;
|
||||
}
|
||||
export async function getConfig<K extends keyof Settings>(object: K): Promise<Settings[K]> {
|
||||
let rawdata = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||
let returndata = JSON.parse(rawdata);
|
||||
return returndata[object];
|
||||
// REVIEW - If I remember correctly fs doesn't need async. I have adjusted the Promise<Settings[K]> to reflect so.
|
||||
// Why touch it when it worked fine? The Async-ness of this function caused headaches in a lot of other places.
|
||||
// Tested with src/tray.ts - Seems to work great!
|
||||
// NOTE - Removed getConfigSync<K extends keyof Settings>(object: K) - Redundant now.
|
||||
export function getConfig<K extends keyof Settings>(object: K): Settings[K] {
|
||||
const rawData = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||
const returnData = JSON.parse(rawData) as Settings;
|
||||
return returnData[object];
|
||||
}
|
||||
export function getConfigSync<K extends keyof Settings>(object: K) {
|
||||
let rawdata = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||
let returndata = JSON.parse(rawdata);
|
||||
return returndata[object];
|
||||
}
|
||||
export async function setConfig<K extends keyof Settings>(object: K, toSet: Settings[K]): Promise<void> {
|
||||
let rawdata = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||
let parsed = JSON.parse(rawdata);
|
||||
export function setConfig<K extends keyof Settings>(object: K, toSet: Settings[K]): void {
|
||||
const rawData = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||
const parsed = JSON.parse(rawData) as Settings;
|
||||
parsed[object] = toSet;
|
||||
let toSave = JSON.stringify(parsed, null, 4);
|
||||
const toSave = JSON.stringify(parsed, null, 4);
|
||||
fs.writeFileSync(getConfigLocation(), toSave, "utf-8");
|
||||
}
|
||||
export async function setConfigBulk(object: Settings): Promise<void> {
|
||||
export function setConfigBulk(object: Settings): void {
|
||||
let existingData = {};
|
||||
try {
|
||||
const existingDataBuffer = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||
existingData = JSON.parse(existingDataBuffer.toString());
|
||||
existingData = JSON.parse(existingDataBuffer.toString()) as Settings;
|
||||
} catch (error) {
|
||||
// Ignore errors when the file doesn't exist or parsing fails
|
||||
}
|
||||
|
@ -78,7 +47,7 @@ export async function setConfigBulk(object: Settings): Promise<void> {
|
|||
const toSave = JSON.stringify(mergedData, null, 4);
|
||||
fs.writeFileSync(getConfigLocation(), toSave, "utf-8");
|
||||
}
|
||||
export async function checkIfConfigExists(): Promise<void> {
|
||||
export function checkIfConfigExists(): void {
|
||||
const userDataPath = app.getPath("userData");
|
||||
const storagePath = path.join(userDataPath, "/storage/");
|
||||
const settingsFile = `${storagePath}settings.json`;
|
||||
|
@ -91,7 +60,7 @@ export async function checkIfConfigExists(): Promise<void> {
|
|||
console.log("First run of the ArmCord. Starting setup.");
|
||||
setup();
|
||||
firstRun = true;
|
||||
} else if ((await getConfig("doneSetup")) == false) {
|
||||
} else if (getConfig("doneSetup") == false) {
|
||||
console.log("First run of the ArmCord. Starting setup.");
|
||||
setup();
|
||||
firstRun = true;
|
||||
|
@ -99,9 +68,9 @@ export async function checkIfConfigExists(): Promise<void> {
|
|||
console.log("ArmCord has been run before. Skipping setup.");
|
||||
}
|
||||
}
|
||||
export async function checkIfConfigIsBroken(): Promise<void> {
|
||||
export function checkIfConfigIsBroken(): void {
|
||||
try {
|
||||
let settingsData = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||
const settingsData = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||
JSON.parse(settingsData);
|
||||
console.log("Config is fine");
|
||||
} catch (e) {
|
||||
|
@ -114,7 +83,7 @@ export async function checkIfConfigIsBroken(): Promise<void> {
|
|||
);
|
||||
}
|
||||
try {
|
||||
let windowData = fs.readFileSync(getWindowStateLocation(), "utf-8");
|
||||
const windowData = fs.readFileSync(getWindowStateLocation(), "utf-8");
|
||||
JSON.parse(windowData);
|
||||
console.log("Window config is fine");
|
||||
} catch (e) {
|
||||
|
|
|
@ -5,7 +5,7 @@ export function addStyle(styleString: string): void {
|
|||
}
|
||||
|
||||
export function addScript(scriptString: string): void {
|
||||
let script = document.createElement("script");
|
||||
const script = document.createElement("script");
|
||||
script.textContent = scriptString;
|
||||
document.body.append(script);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import {app} from "electron";
|
|||
import {getConfig} from "./config.js";
|
||||
|
||||
export let transparency: boolean;
|
||||
export async function injectElectronFlags(): Promise<void> {
|
||||
export function injectElectronFlags(): void {
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 GooseNest
|
||||
|
@ -29,7 +29,7 @@ export async function injectElectronFlags(): Promise<void> {
|
|||
battery: "--enable-features=TurnOffStreamingMediaCachingOnBattery --force_low_power_gpu", // Known to have better battery life for Chromium?
|
||||
vaapi: "--ignore-gpu-blocklist --enable-features=VaapiVideoDecoder --enable-gpu-rasterization --enable-zero-copy --force_high_performance_gpu --use-gl=desktop --disable-features=UseChromeOSDirectVideoDecoder"
|
||||
};
|
||||
switch (await getConfig("performanceMode")) {
|
||||
switch (getConfig("performanceMode")) {
|
||||
case "performance":
|
||||
console.log("Performance mode enabled");
|
||||
app.commandLine.appendArgument(presets.performance);
|
||||
|
@ -41,7 +41,7 @@ export async function injectElectronFlags(): Promise<void> {
|
|||
default:
|
||||
console.log("No performance modes set");
|
||||
}
|
||||
if ((await getConfig("windowStyle")) == "transparent" && process.platform === "win32") {
|
||||
if (getConfig("windowStyle") == "transparent" && process.platform === "win32") {
|
||||
transparency = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,27 @@
|
|||
import {app} from "electron";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
export async function setLang(language: string): Promise<void> {
|
||||
import {i18nStrings} from "../types/i18nStrings";
|
||||
export function setLang(language: string): void {
|
||||
const langConfigFile = `${path.join(app.getPath("userData"), "/storage/")}lang.json`;
|
||||
if (!fs.existsSync(langConfigFile)) {
|
||||
fs.writeFileSync(langConfigFile, "{}", "utf-8");
|
||||
}
|
||||
let rawdata = fs.readFileSync(langConfigFile, "utf-8");
|
||||
let parsed = JSON.parse(rawdata);
|
||||
const rawData = fs.readFileSync(langConfigFile, "utf-8");
|
||||
const parsed = JSON.parse(rawData) as i18nStrings;
|
||||
parsed.lang = language;
|
||||
let toSave = JSON.stringify(parsed, null, 4);
|
||||
const toSave = JSON.stringify(parsed, null, 4);
|
||||
fs.writeFileSync(langConfigFile, toSave, "utf-8");
|
||||
}
|
||||
let language: string;
|
||||
export async function getLang(object: string): Promise<string> {
|
||||
export function getLang(object: string): string {
|
||||
if (language == undefined) {
|
||||
try {
|
||||
const userDataPath = app.getPath("userData");
|
||||
const storagePath = path.join(userDataPath, "/storage/");
|
||||
const langConfigFile = `${storagePath}lang.json`;
|
||||
let rawdata = fs.readFileSync(langConfigFile, "utf-8");
|
||||
let parsed = JSON.parse(rawdata);
|
||||
const rawData = fs.readFileSync(langConfigFile, "utf-8");
|
||||
const parsed = JSON.parse(rawData) as i18nStrings;
|
||||
language = parsed.lang;
|
||||
} catch (_e) {
|
||||
console.log("Language config file doesn't exist. Fallback to English.");
|
||||
|
@ -34,26 +35,26 @@ export async function getLang(object: string): Promise<string> {
|
|||
if (!fs.existsSync(langPath)) {
|
||||
langPath = path.join(import.meta.dirname, "../", "/assets/lang/en-US.json");
|
||||
}
|
||||
let rawdata = fs.readFileSync(langPath, "utf-8");
|
||||
let parsed = JSON.parse(rawdata);
|
||||
let rawData = fs.readFileSync(langPath, "utf-8");
|
||||
let parsed = JSON.parse(rawData) as i18nStrings;
|
||||
if (parsed[object] == undefined) {
|
||||
console.log(`${object} is undefined in ${language}`);
|
||||
langPath = path.join(import.meta.dirname, "../", "/assets/lang/en-US.json");
|
||||
rawdata = fs.readFileSync(langPath, "utf-8");
|
||||
parsed = JSON.parse(rawdata);
|
||||
rawData = fs.readFileSync(langPath, "utf-8");
|
||||
parsed = JSON.parse(rawData) as i18nStrings;
|
||||
return parsed[object];
|
||||
} else {
|
||||
return parsed[object];
|
||||
}
|
||||
}
|
||||
export async function getLangName(): Promise<string> {
|
||||
export function getLangName(): string {
|
||||
if (language == undefined) {
|
||||
try {
|
||||
const userDataPath = app.getPath("userData");
|
||||
const storagePath = path.join(userDataPath, "/storage/");
|
||||
const langConfigFile = `${storagePath}lang.json`;
|
||||
let rawdata = fs.readFileSync(langConfigFile, "utf-8");
|
||||
let parsed = JSON.parse(rawdata);
|
||||
const rawData = fs.readFileSync(langConfigFile, "utf-8");
|
||||
const parsed = JSON.parse(rawData) as i18nStrings;
|
||||
language = parsed.lang;
|
||||
} catch (_e) {
|
||||
console.log("Language config file doesn't exist. Fallback to English.");
|
||||
|
|
|
@ -1,34 +1,31 @@
|
|||
import {app} from "electron";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
export interface WindowState {
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;
|
||||
isMaximized: boolean;
|
||||
}
|
||||
import {WindowState} from "../types/windowState";
|
||||
export function getWindowStateLocation() {
|
||||
const userDataPath = app.getPath("userData");
|
||||
const storagePath = path.join(userDataPath, "/storage/");
|
||||
return `${storagePath}window.json`;
|
||||
}
|
||||
export async function setWindowState(object: WindowState): Promise<void> {
|
||||
export function setWindowState(object: WindowState): void {
|
||||
const userDataPath = app.getPath("userData");
|
||||
const storagePath = path.join(userDataPath, "/storage/");
|
||||
const saveFile = `${storagePath}window.json`;
|
||||
let toSave = JSON.stringify(object, null, 4);
|
||||
const toSave = JSON.stringify(object, null, 4);
|
||||
fs.writeFileSync(saveFile, toSave, "utf-8");
|
||||
}
|
||||
export async function getWindowState<K extends keyof WindowState>(object: K): Promise<WindowState[K]> {
|
||||
|
||||
// REVIEW - Similar to getConfig, this seems to return a promise when it has no async. Originally Promise<WindowState[K]>
|
||||
|
||||
export function getWindowState<K extends keyof WindowState>(object: K): WindowState[K] {
|
||||
const userDataPath = app.getPath("userData");
|
||||
const storagePath = path.join(userDataPath, "/storage/");
|
||||
const settingsFile = `${storagePath}window.json`;
|
||||
if (!fs.existsSync(settingsFile)) {
|
||||
fs.writeFileSync(settingsFile, "{}", "utf-8");
|
||||
}
|
||||
let rawdata = fs.readFileSync(settingsFile, "utf-8");
|
||||
let returndata = JSON.parse(rawdata);
|
||||
console.log(`[Window state manager] ${returndata}`);
|
||||
return returndata[object];
|
||||
const rawData = fs.readFileSync(settingsFile, "utf-8");
|
||||
const returnData = JSON.parse(rawData) as WindowState;
|
||||
console.log(`[Window state manager] ${JSON.stringify(returnData)}`);
|
||||
return returnData[object];
|
||||
}
|
||||
|
|
|
@ -34,7 +34,9 @@
|
|||
border-radius: 10px;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
box-shadow:
|
||||
0 4px 8px 0 rgba(0, 0, 0, 0.2),
|
||||
0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
-webkit-animation-name: animatetop;
|
||||
-webkit-animation-duration: 0.4s;
|
||||
animation-name: animatetop;
|
||||
|
|
|
@ -47,7 +47,9 @@
|
|||
.desktop-capturer-selection__btn:hover,
|
||||
.desktop-capturer-selection__btn:focus {
|
||||
background: #7289da;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.45), 0 0 2px rgba(0, 0, 0, 0.25);
|
||||
box-shadow:
|
||||
0 0 4px rgba(0, 0, 0, 0.45),
|
||||
0 0 2px rgba(0, 0, 0, 0.25);
|
||||
color: #fff;
|
||||
}
|
||||
.desktop-capturer-selection__thumbnail {
|
||||
|
|
|
@ -50,7 +50,7 @@ function setConstraint(constraint, name, value) {
|
|||
}
|
||||
function disableAutogain(constraints) {
|
||||
console.log("Automatically unsetting gain!", constraints);
|
||||
if (constraints && constraints.audio) {
|
||||
if (constraints?.audio) {
|
||||
if (typeof constraints.audio !== "object") {
|
||||
constraints.audio = {};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import electron from "electron";
|
||||
import {getConfig} from "../../common/config.js";
|
||||
|
||||
const unstrictCSP = (): void => {
|
||||
console.log("Setting up CSP unstricter...");
|
||||
const unrestrictCSP = (): void => {
|
||||
console.log("Setting up CSP unrestricter...");
|
||||
|
||||
electron.session.defaultSession.webRequest.onHeadersReceived(({responseHeaders, resourceType}, done) => {
|
||||
if (!responseHeaders) return done({});
|
||||
|
@ -19,9 +19,10 @@ const unstrictCSP = (): void => {
|
|||
});
|
||||
};
|
||||
|
||||
electron.app.whenReady().then(async () => {
|
||||
if (await getConfig("armcordCSP")) {
|
||||
unstrictCSP();
|
||||
void electron.app.whenReady().then(() => {
|
||||
// REVIEW - Awaiting the line above will hang the app.
|
||||
if (getConfig("armcordCSP")) {
|
||||
unrestrictCSP();
|
||||
} else {
|
||||
console.log("ArmCord CSP is disabled. The CSP should be managed by a third-party plugin(s).");
|
||||
}
|
||||
|
|
|
@ -3,23 +3,23 @@ import extract from "extract-zip";
|
|||
import path from "path";
|
||||
import {getConfig} from "../../common/config.js";
|
||||
import fs from "fs";
|
||||
import {promisify} from "node:util";
|
||||
import {pipeline} from "stream";
|
||||
const streamPipeline = promisify(pipeline);
|
||||
import {Readable} from "stream";
|
||||
import type {ReadableStream} from "stream/web";
|
||||
import {finished} from "stream/promises";
|
||||
async function updateModBundle(): Promise<void> {
|
||||
if ((await getConfig("noBundleUpdates")) == undefined ?? false) {
|
||||
if (getConfig("noBundleUpdates") == undefined || false) {
|
||||
try {
|
||||
console.log("Downloading mod bundle");
|
||||
const distFolder = `${app.getPath("userData")}/plugins/loader/dist/`;
|
||||
while (!fs.existsSync(distFolder)) {
|
||||
//waiting
|
||||
}
|
||||
let name: string = await getConfig("mods");
|
||||
const name: string = getConfig("mods");
|
||||
if (name == "custom") {
|
||||
// aspy fix
|
||||
let bundle: string = await (await fetch(await getConfig("customJsBundle"))).text();
|
||||
const bundle: string = await (await fetch(getConfig("customJsBundle"))).text();
|
||||
fs.writeFileSync(`${distFolder}bundle.js`, bundle, "utf-8");
|
||||
let css: string = await (await fetch(await getConfig("customCssBundle"))).text();
|
||||
const css: string = await (await fetch(getConfig("customCssBundle"))).text();
|
||||
fs.writeFileSync(`${distFolder}bundle.css`, css, "utf-8");
|
||||
} else {
|
||||
const clientMods = {
|
||||
|
@ -31,9 +31,9 @@ async function updateModBundle(): Promise<void> {
|
|||
shelter: "https://armcord.app/placeholder.css"
|
||||
};
|
||||
console.log(clientMods[name as keyof typeof clientMods]);
|
||||
let bundle: string = await (await fetch(clientMods[name as keyof typeof clientMods])).text();
|
||||
const bundle: string = await (await fetch(clientMods[name as keyof typeof clientMods])).text();
|
||||
fs.writeFileSync(`${distFolder}bundle.js`, bundle, "utf-8");
|
||||
let css: string = await (await fetch(clientModsCss[name as keyof typeof clientModsCss])).text();
|
||||
const css: string = await (await fetch(clientModsCss[name as keyof typeof clientModsCss])).text();
|
||||
fs.writeFileSync(`${distFolder}bundle.css`, css, "utf-8");
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -53,14 +53,14 @@ export let modInstallState: string;
|
|||
export function updateModInstallState() {
|
||||
modInstallState = "modDownload";
|
||||
|
||||
updateModBundle();
|
||||
void updateModBundle(); // REVIEW - Awaiting this will hang the app on the splash
|
||||
import("./plugin.js");
|
||||
|
||||
modInstallState = "done";
|
||||
}
|
||||
|
||||
export async function installModLoader(): Promise<void> {
|
||||
if ((await getConfig("mods")) == "none") {
|
||||
if (getConfig("mods") == "none") {
|
||||
modInstallState = "none";
|
||||
fs.rmSync(`${app.getPath("userData")}/plugins/loader`, {recursive: true, force: true});
|
||||
|
||||
|
@ -80,7 +80,7 @@ export async function installModLoader(): Promise<void> {
|
|||
fs.rmSync(`${app.getPath("userData")}/plugins/loader`, {recursive: true, force: true});
|
||||
modInstallState = "installing";
|
||||
|
||||
let zipPath = `${app.getPath("temp")}/loader.zip`;
|
||||
const zipPath = `${app.getPath("temp")}/loader.zip`;
|
||||
|
||||
if (!fs.existsSync(pluginFolder)) {
|
||||
fs.mkdirSync(pluginFolder);
|
||||
|
@ -88,31 +88,35 @@ export async function installModLoader(): Promise<void> {
|
|||
}
|
||||
|
||||
// Add more of these later if needed!
|
||||
let URLs = [
|
||||
const URLs = [
|
||||
"https://armcord.app/loader.zip",
|
||||
"https://armcord.vercel.app/loader.zip",
|
||||
"https://raw.githubusercontent.com/ArmCord/website/new/public/loader.zip"
|
||||
];
|
||||
let loaderZip: any;
|
||||
|
||||
// REVIEW - Rewrote this
|
||||
while (true) {
|
||||
if (URLs.length <= 0) throw new Error(`unexpected response ${loaderZip.statusText}`);
|
||||
|
||||
try {
|
||||
loaderZip = await fetch(URLs[0]);
|
||||
} catch (err) {
|
||||
console.log("[Mod loader] Failed to download. Links left to try: " + (URLs.length - 1));
|
||||
URLs.splice(0, 1);
|
||||
|
||||
continue;
|
||||
let completed = false;
|
||||
await fetch(URLs[0])
|
||||
.then(async (loaderZip) => {
|
||||
const fileStream = fs.createWriteStream(zipPath);
|
||||
await finished(Readable.fromWeb(loaderZip.body as ReadableStream).pipe(fileStream)).then(
|
||||
async () => {
|
||||
await extract(zipPath, {dir: path.join(app.getPath("userData"), "plugins")}).then(() => {
|
||||
updateModInstallState();
|
||||
completed = true;
|
||||
});
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
console.warn(`[Mod loader] Failed to download. Links left to try: ${URLs.length - 1}`);
|
||||
URLs.splice(0, 1);
|
||||
});
|
||||
if (completed || URLs.length == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
await streamPipeline(loaderZip.body, fs.createWriteStream(zipPath));
|
||||
await extract(zipPath, {dir: path.join(app.getPath("userData"), "plugins")});
|
||||
|
||||
updateModInstallState();
|
||||
} catch (e) {
|
||||
console.log("[Mod loader] Failed to install modloader");
|
||||
console.error(e);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as fs from "fs";
|
||||
import fs from "fs";
|
||||
import {app, session} from "electron";
|
||||
const userDataPath = app.getPath("userData");
|
||||
const pluginFolder = `${userDataPath}/plugins`;
|
||||
|
@ -6,12 +6,13 @@ if (!fs.existsSync(pluginFolder)) {
|
|||
fs.mkdirSync(pluginFolder);
|
||||
console.log("Created missing plugin folder");
|
||||
}
|
||||
app.whenReady().then(() => {
|
||||
await app.whenReady().then(() => {
|
||||
fs.readdirSync(pluginFolder).forEach((file) => {
|
||||
try {
|
||||
const manifest = fs.readFileSync(`${pluginFolder}/${file}/manifest.json`, "utf8");
|
||||
const pluginFile = JSON.parse(manifest);
|
||||
session.defaultSession.loadExtension(`${pluginFolder}/${file}`);
|
||||
// NOTE - The below type assertion is just what we need from the chrome manifest
|
||||
const pluginFile = JSON.parse(manifest) as {name: string; author: string};
|
||||
void session.defaultSession.loadExtension(`${pluginFolder}/${file}`); // REVIEW - Awaiting this will cause plugins to not inject
|
||||
console.log(`[Mod loader] Loaded ${pluginFile.name} made by ${pluginFile.author}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
//ipc stuff
|
||||
import {app, clipboard, desktopCapturer, ipcMain, nativeImage, shell} from "electron";
|
||||
import {app, clipboard, desktopCapturer, ipcMain, nativeImage, shell, SourcesOptions} from "electron";
|
||||
import {mainWindow} from "./window.js";
|
||||
|
||||
import os from "os";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import {getConfig, setConfigBulk, getConfigLocation, Settings} from "../common/config.js";
|
||||
import {getConfig, setConfigBulk, getConfigLocation} from "../common/config.js";
|
||||
import {setLang, getLang, getLangName} from "../common/lang.js";
|
||||
import {sleep} from "../common/sleep.js";
|
||||
import {getVersion, getDisplayVersion} from "../common/version.js";
|
||||
import {customTitlebar} from "../main.js";
|
||||
import {createSettingsWindow} from "../settings/main.js";
|
||||
import {splashWindow} from "../splash/main.js";
|
||||
import {createTManagerWindow} from "../themeManager/main.js";
|
||||
import {modInstallState} from "./extensions/mods.js";
|
||||
import {Settings} from "../types/settings.d.js";
|
||||
|
||||
const userDataPath = app.getPath("userData");
|
||||
const storagePath = path.join(userDataPath, "/storage/");
|
||||
|
@ -30,7 +30,7 @@ export function registerIpc(): void {
|
|||
return getLang(toGet);
|
||||
});
|
||||
ipcMain.on("open-external-link", (_event, href: string) => {
|
||||
shell.openExternal(href);
|
||||
void shell.openExternal(href);
|
||||
});
|
||||
ipcMain.on("setPing", (_event, pingCount: number) => {
|
||||
switch (os.platform()) {
|
||||
|
@ -39,7 +39,7 @@ export function registerIpc(): void {
|
|||
break;
|
||||
case "win32":
|
||||
if (pingCount > 0) {
|
||||
let image = nativeImage.createFromPath(path.join(import.meta.dirname, "../", `/assets/ping.png`));
|
||||
const image = nativeImage.createFromPath(path.join(import.meta.dirname, "../", `/assets/ping.png`));
|
||||
mainWindow.setOverlayIcon(image, "badgeCount");
|
||||
} else {
|
||||
mainWindow.setOverlayIcon(null, "badgeCount");
|
||||
|
@ -80,9 +80,9 @@ export function registerIpc(): void {
|
|||
ipcMain.on("modInstallState", (event) => {
|
||||
event.returnValue = modInstallState;
|
||||
});
|
||||
ipcMain.on("splashEnd", async () => {
|
||||
ipcMain.on("splashEnd", () => {
|
||||
splashWindow.close();
|
||||
if (await getConfig("startMinimized")) {
|
||||
if (getConfig("startMinimized")) {
|
||||
mainWindow.hide();
|
||||
} else {
|
||||
mainWindow.show();
|
||||
|
@ -92,75 +92,77 @@ export function registerIpc(): void {
|
|||
app.relaunch();
|
||||
app.exit();
|
||||
});
|
||||
ipcMain.on("minimizeToTray", async (event) => {
|
||||
event.returnValue = await getConfig("minimizeToTray");
|
||||
ipcMain.on("saveSettings", (_event, args: Settings) => {
|
||||
setConfigBulk(args);
|
||||
});
|
||||
ipcMain.on("channel", async (event) => {
|
||||
event.returnValue = await getConfig("channel");
|
||||
ipcMain.on("minimizeToTray", (event) => {
|
||||
event.returnValue = getConfig("minimizeToTray");
|
||||
});
|
||||
ipcMain.on("clientmod", async (event) => {
|
||||
event.returnValue = await getConfig("mods");
|
||||
ipcMain.on("channel", (event) => {
|
||||
event.returnValue = getConfig("channel");
|
||||
});
|
||||
ipcMain.on("legacyCapturer", async (event) => {
|
||||
event.returnValue = await getConfig("useLegacyCapturer");
|
||||
ipcMain.on("clientmod", (event) => {
|
||||
event.returnValue = getConfig("mods");
|
||||
});
|
||||
ipcMain.on("trayIcon", async (event) => {
|
||||
event.returnValue = await getConfig("trayIcon");
|
||||
ipcMain.on("legacyCapturer", (event) => {
|
||||
event.returnValue = getConfig("useLegacyCapturer");
|
||||
});
|
||||
ipcMain.on("disableAutogain", async (event) => {
|
||||
event.returnValue = await getConfig("disableAutogain");
|
||||
ipcMain.on("trayIcon", (event) => {
|
||||
event.returnValue = getConfig("trayIcon");
|
||||
});
|
||||
ipcMain.on("disableAutogain", (event) => {
|
||||
event.returnValue = getConfig("disableAutogain");
|
||||
});
|
||||
ipcMain.on("titlebar", (event) => {
|
||||
event.returnValue = customTitlebar;
|
||||
});
|
||||
ipcMain.on("mobileMode", async (event) => {
|
||||
event.returnValue = await getConfig("mobileMode");
|
||||
ipcMain.on("mobileMode", (event) => {
|
||||
event.returnValue = getConfig("mobileMode");
|
||||
});
|
||||
// REVIEW - I don't see a reason to await the actual action of running the settings window. The user cannot open more than one anyway, as defined in the function.
|
||||
ipcMain.on("openSettingsWindow", () => {
|
||||
createSettingsWindow();
|
||||
void createSettingsWindow();
|
||||
});
|
||||
ipcMain.on("openManagerWindow", () => {
|
||||
createTManagerWindow();
|
||||
void createTManagerWindow();
|
||||
});
|
||||
ipcMain.on("setting-armcordCSP", async (event) => {
|
||||
if (await getConfig("armcordCSP")) {
|
||||
ipcMain.on("setting-armcordCSP", (event) => {
|
||||
if (getConfig("armcordCSP")) {
|
||||
event.returnValue = true;
|
||||
} else {
|
||||
event.returnValue = false;
|
||||
}
|
||||
});
|
||||
ipcMain.handle("DESKTOP_CAPTURER_GET_SOURCES", (_event, opts) => desktopCapturer.getSources(opts));
|
||||
// NOTE - I assume this would return sources based on the fact that the function only ingests sources
|
||||
ipcMain.handle("DESKTOP_CAPTURER_GET_SOURCES", (_event, opts: SourcesOptions) => desktopCapturer.getSources(opts));
|
||||
ipcMain.on("saveSettings", (_event, args: Settings) => {
|
||||
console.log(args);
|
||||
setConfigBulk(args);
|
||||
});
|
||||
ipcMain.on("openStorageFolder", async () => {
|
||||
// REVIEW - The lower 4 functions had await sleep(1000), I'm not sure why. Behavior is same regardless
|
||||
ipcMain.on("openStorageFolder", () => {
|
||||
shell.showItemInFolder(storagePath);
|
||||
await sleep(1000);
|
||||
});
|
||||
ipcMain.on("openThemesFolder", async () => {
|
||||
ipcMain.on("openThemesFolder", () => {
|
||||
shell.showItemInFolder(themesPath);
|
||||
await sleep(1000);
|
||||
});
|
||||
ipcMain.on("openPluginsFolder", async () => {
|
||||
ipcMain.on("openPluginsFolder", () => {
|
||||
shell.showItemInFolder(pluginsPath);
|
||||
await sleep(1000);
|
||||
});
|
||||
ipcMain.on("openCrashesFolder", async () => {
|
||||
ipcMain.on("openCrashesFolder", () => {
|
||||
shell.showItemInFolder(path.join(app.getPath("temp"), `${app.getName()} Crashes`));
|
||||
await sleep(1000);
|
||||
});
|
||||
ipcMain.on("getLangName", async (event) => {
|
||||
event.returnValue = await getLangName();
|
||||
ipcMain.on("getLangName", (event) => {
|
||||
event.returnValue = getLangName();
|
||||
});
|
||||
ipcMain.on("crash", async () => {
|
||||
ipcMain.on("crash", () => {
|
||||
process.crash();
|
||||
});
|
||||
ipcMain.handle("getSetting", (_event, toGet: keyof Settings) => {
|
||||
return getConfig(toGet);
|
||||
});
|
||||
ipcMain.on("copyDebugInfo", () => {
|
||||
let settingsFileContent = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||
const settingsFileContent = fs.readFileSync(getConfigLocation(), "utf-8");
|
||||
clipboard.writeText(
|
||||
`**OS:** ${os.platform()} ${os.version()}\n**Architecture:** ${os.arch()}\n**ArmCord version:** ${getVersion()}\n**Electron version:** ${
|
||||
process.versions.electron
|
||||
|
|
|
@ -1,17 +1,9 @@
|
|||
import {BrowserWindow, Menu, app, clipboard} from "electron";
|
||||
import {BrowserWindow, Menu, app} from "electron";
|
||||
import {mainWindow} from "./window.js";
|
||||
import {createSettingsWindow} from "../settings/main.js";
|
||||
|
||||
function paste(contents: any): void {
|
||||
const contentTypes = clipboard.availableFormats().toString();
|
||||
//Workaround: fix pasting the images.
|
||||
if (contentTypes.includes("image/") && contentTypes.includes("text/html")) {
|
||||
clipboard.writeImage(clipboard.readImage());
|
||||
}
|
||||
contents.paste();
|
||||
}
|
||||
export async function setMenu(): Promise<void> {
|
||||
let template: Electron.MenuItemConstructorOptions[] = [
|
||||
export function setMenu(): void {
|
||||
const template: Electron.MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: "ArmCord",
|
||||
submenu: [
|
||||
|
@ -28,7 +20,7 @@ export async function setMenu(): Promise<void> {
|
|||
label: "Open settings",
|
||||
accelerator: "CmdOrCtrl+Shift+'",
|
||||
click() {
|
||||
createSettingsWindow();
|
||||
void createSettingsWindow();
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -67,13 +59,6 @@ export async function setMenu(): Promise<void> {
|
|||
{type: "separator"},
|
||||
{label: "Cut", accelerator: "CmdOrCtrl+X", role: "cut"},
|
||||
{label: "Copy", accelerator: "CmdOrCtrl+C", role: "copy"},
|
||||
{
|
||||
label: "Paste",
|
||||
accelerator: "CmdOrCtrl+V",
|
||||
click() {
|
||||
paste(mainWindow.webContents);
|
||||
}
|
||||
},
|
||||
{label: "Select All", accelerator: "CmdOrCtrl+A", role: "selectAll"}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import {contextBridge, ipcRenderer} from "electron";
|
||||
import {contextBridge, ipcRenderer, type SourcesOptions} from "electron";
|
||||
import {injectTitlebar} from "./titlebar.mjs";
|
||||
import type {ArmCordWindow} from "../../types/armcordWindow.d.js";
|
||||
const CANCEL_ID = "desktop-capturer-selection__cancel";
|
||||
const desktopCapturer = {
|
||||
getSources: (opts: any) => ipcRenderer.invoke("DESKTOP_CAPTURER_GET_SOURCES", opts)
|
||||
getSources: (opts: SourcesOptions) => ipcRenderer.invoke("DESKTOP_CAPTURER_GET_SOURCES", opts)
|
||||
};
|
||||
interface IPCSources {
|
||||
id: string;
|
||||
|
@ -10,9 +11,9 @@ interface IPCSources {
|
|||
thumbnail: HTMLCanvasElement;
|
||||
}
|
||||
async function getDisplayMediaSelector(): Promise<string> {
|
||||
const sources: IPCSources[] = await desktopCapturer.getSources({
|
||||
const sources = (await desktopCapturer.getSources({
|
||||
types: ["screen", "window"]
|
||||
});
|
||||
})) as IPCSources[];
|
||||
return `<div class="desktop-capturer-selection__scroller">
|
||||
<ul class="desktop-capturer-selection__list">
|
||||
${sources
|
||||
|
@ -44,24 +45,25 @@ contextBridge.exposeInMainWorld("armcord", {
|
|||
},
|
||||
titlebar: {
|
||||
injectTitlebar: () => injectTitlebar(),
|
||||
isTitlebar: ipcRenderer.sendSync("titlebar")
|
||||
isTitlebar: ipcRenderer.sendSync("titlebar") as boolean
|
||||
},
|
||||
electron: process.versions.electron,
|
||||
channel: ipcRenderer.sendSync("channel"),
|
||||
channel: ipcRenderer.sendSync("channel") as string,
|
||||
setPingCount: (pingCount: number) => ipcRenderer.send("setPing", pingCount),
|
||||
setTrayIcon: (favicon: string) => ipcRenderer.send("sendTrayIcon", favicon),
|
||||
getLang: (toGet: string) =>
|
||||
ipcRenderer.invoke("getLang", toGet).then((result) => {
|
||||
return result;
|
||||
return result as string;
|
||||
}),
|
||||
getDisplayMediaSelector,
|
||||
version: ipcRenderer.sendSync("get-app-version", "app-version"),
|
||||
mods: ipcRenderer.sendSync("clientmod"),
|
||||
version: ipcRenderer.sendSync("get-app-version", "app-version") as string,
|
||||
mods: ipcRenderer.sendSync("clientmod") as string,
|
||||
openSettingsWindow: () => ipcRenderer.send("openSettingsWindow")
|
||||
});
|
||||
} as ArmCordWindow);
|
||||
let windowCallback: (arg0: object) => void;
|
||||
contextBridge.exposeInMainWorld("ArmCordRPC", {
|
||||
listen: (callback: any) => {
|
||||
// REVIEW - I don't think this is right
|
||||
listen: (callback: () => void) => {
|
||||
windowCallback = callback;
|
||||
}
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
import {addStyle} from "../../common/dom.js";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
export function injectMobileStuff(): void {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const mobileCSS = path.join(import.meta.dirname, "../", "/content/css/mobile.css");
|
|
@ -1,9 +0,0 @@
|
|||
const optimize = (orig: Function) =>
|
||||
function (this: any, ...args: any[]) {
|
||||
if (typeof args[0]?.className === "string" && args[0].className.indexOf("activity") !== -1)
|
||||
return setTimeout(() => orig.apply(this, args), 100);
|
||||
|
||||
return orig.apply(this, args);
|
||||
};
|
||||
|
||||
Element.prototype.removeChild = optimize(Element.prototype.removeChild);
|
18
src/discord/preload/optimizer.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
type OptimizableFunction<T extends Node> = (child: T) => T;
|
||||
|
||||
const optimize = <T extends Node>(orig: OptimizableFunction<T>) => {
|
||||
return function (this: Element, ...args: [Element]) {
|
||||
if (typeof args[0]?.className === "string" && args[0].className.indexOf("activity") !== -1) {
|
||||
// @ts-expect-error - // FIXME
|
||||
return setTimeout(() => orig.apply(this, args), 100);
|
||||
}
|
||||
// @ts-expect-error - // FIXME
|
||||
return orig.apply(this, args);
|
||||
} as unknown as OptimizableFunction<T>;
|
||||
};
|
||||
|
||||
// We are taking in the function itself
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
Element.prototype.removeChild = optimize(Element.prototype.removeChild);
|
||||
|
||||
// Thanks Ari - <@1249446413952225452>
|
|
@ -1,36 +1,39 @@
|
|||
import "./bridge.mjs";
|
||||
import "./optimizer.mjs";
|
||||
import "./settings.mjs";
|
||||
import "./bridge.js";
|
||||
import "./optimizer.js";
|
||||
import "./settings.js";
|
||||
import {ipcRenderer} from "electron";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import {injectMobileStuff} from "./mobile.mjs";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import {injectMobileStuff} from "./mobile.js";
|
||||
import {fixTitlebar, injectTitlebar} from "./titlebar.mjs";
|
||||
import {injectSettings} from "./settings.mjs";
|
||||
import {injectSettings} from "./settings.js";
|
||||
import {addStyle, addScript} from "../../common/dom.js";
|
||||
import {sleep} from "../../common/sleep.js";
|
||||
import type {ArmCordWindow} from "../../types/armcordWindow.d.js";
|
||||
|
||||
window.localStorage.setItem("hideNag", "true");
|
||||
|
||||
if (ipcRenderer.sendSync("legacyCapturer")) {
|
||||
console.warn("Using legacy capturer module");
|
||||
import("./capturer.mjs");
|
||||
import("./capturer.js");
|
||||
}
|
||||
|
||||
const version = ipcRenderer.sendSync("displayVersion");
|
||||
async function updateLang(): Promise<void> {
|
||||
const version = ipcRenderer.sendSync("displayVersion") as string;
|
||||
function updateLang(): void {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts: any = value.split(`; locale=`);
|
||||
if (parts.length === 2) ipcRenderer.send("setLang", parts.pop().split(";").shift());
|
||||
const parts = value.split(`; locale=`);
|
||||
if (parts.length === 2) ipcRenderer.send("setLang", parts.pop()?.split(";").shift());
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
armcord: any;
|
||||
// REVIEW - Assumption, this was previously any
|
||||
armcord: ArmCordWindow;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`ArmCord ${version}`);
|
||||
ipcRenderer.on("themeLoader", (_event, message) => {
|
||||
ipcRenderer.on("themeLoader", (_event, message: string) => {
|
||||
addStyle(message);
|
||||
});
|
||||
|
||||
|
@ -40,7 +43,7 @@ if (ipcRenderer.sendSync("titlebar")) {
|
|||
if (ipcRenderer.sendSync("mobileMode")) {
|
||||
injectMobileStuff();
|
||||
}
|
||||
sleep(5000).then(async () => {
|
||||
await sleep(5000).then(() => {
|
||||
// dirty hack to make clicking notifications focus ArmCord
|
||||
addScript(`
|
||||
(() => {
|
||||
|
@ -62,7 +65,7 @@ sleep(5000).then(async () => {
|
|||
addScript(fs.readFileSync(path.join(import.meta.dirname, "../", "/content/js/rpc.js"), "utf8"));
|
||||
const cssPath = path.join(import.meta.dirname, "../", "/content/css/discord.css");
|
||||
addStyle(fs.readFileSync(cssPath, "utf8"));
|
||||
await updateLang();
|
||||
updateLang();
|
||||
});
|
||||
|
||||
// Settings info version injection
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import {addStyle} from "../../common/dom.js";
|
||||
import {WebviewTag} from "electron";
|
||||
|
||||
var webview = `<webview src="${path.join(
|
||||
const webview = `<webview src="${path.join(
|
||||
"file://",
|
||||
import.meta.dirname,
|
||||
"../",
|
||||
|
@ -25,8 +24,8 @@ export function injectSettings() {
|
|||
document.addEventListener("DOMContentLoaded", function (_event) {
|
||||
const settingsCssPath = path.join(import.meta.dirname, "../", "/content/css/inAppSettings.css");
|
||||
addStyle(fs.readFileSync(settingsCssPath, "utf8"));
|
||||
const webview = document.querySelector("webview") as WebviewTag;
|
||||
const webview = document.querySelector("webview")!;
|
||||
webview.addEventListener("console-message", (e) => {
|
||||
console.log("Settings page logged a message:", e.message);
|
||||
console.log("Settings page logged a message:", e as Electron.ConsoleMessageEvent);
|
||||
});
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
import {ipcRenderer} from "electron";
|
||||
import {addStyle} from "../../common/dom.js";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
export function injectTitlebar(): void {
|
||||
document.addEventListener("DOMContentLoaded", function (_event) {
|
||||
|
|
|
@ -12,7 +12,7 @@ function showAudioDialog(): boolean {
|
|||
detail: "Selecting yes will make viewers of your stream hear your entire system audio."
|
||||
};
|
||||
|
||||
dialog.showMessageBox(capturerWindow, options).then(({response}) => {
|
||||
void dialog.showMessageBox(capturerWindow, options).then(({response}) => {
|
||||
if (response == 0) {
|
||||
return true;
|
||||
} else {
|
||||
|
@ -23,48 +23,51 @@ function showAudioDialog(): boolean {
|
|||
}
|
||||
|
||||
function registerCustomHandler(): void {
|
||||
session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => {
|
||||
session.defaultSession.setDisplayMediaRequestHandler((request, callback) => {
|
||||
console.log(request);
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ["screen", "window"]
|
||||
});
|
||||
console.log(sources);
|
||||
if (process.platform === "linux" && process.env.XDG_SESSION_TYPE?.toLowerCase() === "wayland") {
|
||||
console.log("WebRTC Capturer detected, skipping window creation."); //assume webrtc capturer is used
|
||||
var options: Electron.Streams = {video: sources[0]};
|
||||
if (showAudioDialog() == true) options = {video: sources[0], audio: "loopbackWithMute"};
|
||||
callback(options);
|
||||
} else {
|
||||
capturerWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
title: "ArmCord Screenshare",
|
||||
darkTheme: true,
|
||||
icon: iconPath,
|
||||
frame: true,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
spellcheck: false,
|
||||
preload: path.join(import.meta.dirname, "preload.js")
|
||||
}
|
||||
});
|
||||
ipcMain.once("selectScreenshareSource", (_event, id, name) => {
|
||||
//console.log(sources[id]);
|
||||
//console.log(id);
|
||||
capturerWindow.close();
|
||||
let result = {id, name};
|
||||
if (process.platform === "linux" || process.platform === "win32") {
|
||||
var options: Electron.Streams = {video: sources[0]};
|
||||
void desktopCapturer
|
||||
.getSources({
|
||||
types: ["screen", "window"]
|
||||
})
|
||||
.then((sources) => {
|
||||
console.log(sources);
|
||||
if (process.platform === "linux" && process.env.XDG_SESSION_TYPE?.toLowerCase() === "wayland") {
|
||||
console.log("WebRTC Capturer detected, skipping window creation."); //assume webrtc capturer is used
|
||||
let options: Electron.Streams = {video: sources[0]};
|
||||
if (showAudioDialog() == true) options = {video: sources[0], audio: "loopbackWithMute"};
|
||||
callback(options);
|
||||
} else {
|
||||
callback({video: result});
|
||||
capturerWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
title: "ArmCord Screenshare",
|
||||
darkTheme: true,
|
||||
icon: iconPath,
|
||||
frame: true,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
spellcheck: false,
|
||||
preload: path.join(import.meta.dirname, "preload.mjs")
|
||||
}
|
||||
});
|
||||
ipcMain.once("selectScreenshareSource", (_event, id: string, name: string) => {
|
||||
//console.log(sources[id]);
|
||||
//console.log(id);
|
||||
capturerWindow.close();
|
||||
const result = {id, name};
|
||||
if (process.platform === "linux" || process.platform === "win32") {
|
||||
let options: Electron.Streams = {video: sources[0]};
|
||||
if (showAudioDialog() == true) options = {video: sources[0], audio: "loopbackWithMute"};
|
||||
callback(options);
|
||||
} else {
|
||||
callback({video: result});
|
||||
}
|
||||
});
|
||||
void capturerWindow.loadURL(`file://${import.meta.dirname}/picker.html`);
|
||||
capturerWindow.webContents.send("getSources", sources);
|
||||
}
|
||||
});
|
||||
capturerWindow.loadURL(`file://${import.meta.dirname}/picker.html`);
|
||||
capturerWindow.webContents.send("getSources", sources);
|
||||
}
|
||||
});
|
||||
}
|
||||
registerCustomHandler();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
|
|
@ -4,9 +4,9 @@ interface IPCSources {
|
|||
name: string;
|
||||
thumbnail: HTMLCanvasElement;
|
||||
}
|
||||
async function addDisplays(): Promise<void> {
|
||||
ipcRenderer.once("getSources", (_event, arg) => {
|
||||
let sources: IPCSources[] = arg;
|
||||
function addDisplays(): void {
|
||||
ipcRenderer.once("getSources", (_event, arg: IPCSources[]) => {
|
||||
const sources = arg;
|
||||
console.log(sources);
|
||||
const selectionElem = document.createElement("div");
|
||||
selectionElem.classList.add("desktop-capturer-selection");
|
||||
|
@ -33,7 +33,7 @@ async function addDisplays(): Promise<void> {
|
|||
</div>`;
|
||||
document.body.appendChild(selectionElem);
|
||||
document.querySelectorAll(".desktop-capturer-selection__btn").forEach((button) => {
|
||||
button.addEventListener("click", async () => {
|
||||
button.addEventListener("click", () => {
|
||||
try {
|
||||
const id = button.getAttribute("data-id");
|
||||
const title = button.getAttribute("title");
|
|
@ -1,9 +1,11 @@
|
|||
// To allow seamless switching between custom titlebar and native os titlebar,
|
||||
// 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 separate functions
|
||||
// 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.
|
||||
import {BrowserWindow, MessageBoxOptions, app, dialog, nativeImage, shell} from "electron";
|
||||
import path from "path";
|
||||
import type EventEmitter from "events";
|
||||
import {ThemeManifest} from "../types/themeManifest.d.js";
|
||||
import {registerIpc} from "./ipc.js";
|
||||
import {setMenu} from "./menu.js";
|
||||
import * as fs from "fs";
|
||||
|
@ -28,7 +30,7 @@ contextMenu({
|
|||
// Only show it when right-clicking text
|
||||
visible: parameters.selectionText.trim().length > 0,
|
||||
click: () => {
|
||||
shell.openExternal(`https://google.com/search?q=${encodeURIComponent(parameters.selectionText)}`);
|
||||
void shell.openExternal(`https://google.com/search?q=${encodeURIComponent(parameters.selectionText)}`);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -36,27 +38,30 @@ contextMenu({
|
|||
// Only show it when right-clicking text
|
||||
visible: parameters.selectionText.trim().length > 0,
|
||||
click: () => {
|
||||
shell.openExternal(`https://duckduckgo.com/?q=${encodeURIComponent(parameters.selectionText)}`);
|
||||
void shell.openExternal(`https://duckduckgo.com/?q=${encodeURIComponent(parameters.selectionText)}`);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
async function doAfterDefiningTheWindow(): Promise<void> {
|
||||
if ((await getWindowState("isMaximized")) ?? false) {
|
||||
function doAfterDefiningTheWindow(): void {
|
||||
if (getWindowState("isMaximized") ?? false) {
|
||||
mainWindow.setSize(835, 600); //just so the whole thing doesn't cover whole screen
|
||||
mainWindow.maximize();
|
||||
mainWindow.webContents.executeJavaScript(`document.body.setAttribute("isMaximized", "");`);
|
||||
void mainWindow.webContents.executeJavaScript(`document.body.setAttribute("isMaximized", "");`);
|
||||
mainWindow.hide(); // please don't flashbang the user
|
||||
}
|
||||
if ((await getConfig("windowStyle")) == "transparency" && process.platform === "win32") {
|
||||
if (getConfig("windowStyle") == "transparency" && process.platform === "win32") {
|
||||
mainWindow.setBackgroundMaterial("mica");
|
||||
if ((await getConfig("startMinimized")) == false) {
|
||||
if (getConfig("startMinimized") == false) {
|
||||
mainWindow.show();
|
||||
}
|
||||
}
|
||||
let ignoreProtocolWarning = await getConfig("ignoreProtocolWarning");
|
||||
|
||||
// REVIEW - Test the protocol warning. I was not sure how to get it to pop up. For now I've voided the promises.
|
||||
|
||||
const ignoreProtocolWarning = getConfig("ignoreProtocolWarning");
|
||||
registerIpc();
|
||||
if (await getConfig("mobileMode")) {
|
||||
if (getConfig("mobileMode")) {
|
||||
mainWindow.webContents.userAgent =
|
||||
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.149 Mobile Safari/537.36";
|
||||
} else {
|
||||
|
@ -96,9 +101,9 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
|||
}
|
||||
};
|
||||
if (url.startsWith("https:") || url.startsWith("http:") || url.startsWith("mailto:")) {
|
||||
shell.openExternal(url);
|
||||
void shell.openExternal(url);
|
||||
} else if (ignoreProtocolWarning) {
|
||||
shell.openExternal(url);
|
||||
void shell.openExternal(url);
|
||||
} else {
|
||||
const options: MessageBoxOptions = {
|
||||
type: "question",
|
||||
|
@ -111,7 +116,7 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
|||
checkboxChecked: false
|
||||
};
|
||||
|
||||
dialog.showMessageBox(mainWindow, options).then(({response, checkboxChecked}) => {
|
||||
void dialog.showMessageBox(mainWindow, options).then(({response, checkboxChecked}) => {
|
||||
console.log(response, checkboxChecked);
|
||||
if (checkboxChecked) {
|
||||
if (response == 0) {
|
||||
|
@ -121,13 +126,13 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
|||
}
|
||||
}
|
||||
if (response == 0) {
|
||||
shell.openExternal(url);
|
||||
void shell.openExternal(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
return {action: "deny"};
|
||||
});
|
||||
if ((await getConfig("useLegacyCapturer")) == false) {
|
||||
if (getConfig("useLegacyCapturer") == false) {
|
||||
console.log("Starting screenshare module...");
|
||||
import("./screenshare/main.js");
|
||||
}
|
||||
|
@ -137,9 +142,12 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
|||
(_, callback) => callback({cancel: true})
|
||||
);
|
||||
|
||||
if ((await getConfig("trayIcon")) == "default" || (await getConfig("dynamicIcon"))) {
|
||||
mainWindow.webContents.on("page-favicon-updated", async () => {
|
||||
let faviconBase64 = await mainWindow.webContents.executeJavaScript(`
|
||||
if (getConfig("trayIcon") == "default" || getConfig("dynamicIcon")) {
|
||||
mainWindow.webContents.on("page-favicon-updated", () => {
|
||||
// REVIEW - no need to await if we just .then() - This works!
|
||||
void mainWindow.webContents
|
||||
.executeJavaScript(
|
||||
`
|
||||
var getFavicon = function(){
|
||||
var favicon = undefined;
|
||||
var nodeList = document.getElementsByTagName("link");
|
||||
|
@ -153,29 +161,33 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
|||
return favicon;
|
||||
}
|
||||
getFavicon()
|
||||
`);
|
||||
let buf = Buffer.from(faviconBase64.replace(/^data:image\/\w+;base64,/, ""), "base64");
|
||||
fs.writeFileSync(path.join(app.getPath("temp"), "/", "tray.png"), buf, "utf-8");
|
||||
let trayPath = nativeImage.createFromPath(path.join(app.getPath("temp"), "/", "tray.png"));
|
||||
if (process.platform === "darwin" && trayPath.getSize().height > 22)
|
||||
trayPath = trayPath.resize({height: 22});
|
||||
if (process.platform === "win32" && trayPath.getSize().height > 32)
|
||||
trayPath = trayPath.resize({height: 32});
|
||||
if (await getConfig("tray")) {
|
||||
if ((await getConfig("trayIcon")) == "default") {
|
||||
tray.setImage(trayPath);
|
||||
}
|
||||
}
|
||||
if (await getConfig("dynamicIcon")) {
|
||||
mainWindow.setIcon(trayPath);
|
||||
}
|
||||
`
|
||||
)
|
||||
.then((faviconBase64: string) => {
|
||||
const buf = Buffer.from(faviconBase64.replace(/^data:image\/\w+;base64,/, ""), "base64");
|
||||
fs.writeFileSync(path.join(app.getPath("temp"), "/", "tray.png"), buf, "utf-8");
|
||||
let trayPath = nativeImage.createFromPath(path.join(app.getPath("temp"), "/", "tray.png"));
|
||||
if (process.platform === "darwin" && trayPath.getSize().height > 22)
|
||||
trayPath = trayPath.resize({height: 22});
|
||||
if (process.platform === "win32" && trayPath.getSize().height > 32)
|
||||
trayPath = trayPath.resize({height: 32});
|
||||
if (getConfig("tray")) {
|
||||
if (getConfig("trayIcon") == "default") {
|
||||
tray.setImage(trayPath);
|
||||
}
|
||||
}
|
||||
if (getConfig("dynamicIcon")) {
|
||||
mainWindow.setIcon(trayPath);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
mainWindow.webContents.on("page-title-updated", async (e, title) => {
|
||||
mainWindow.webContents.on("page-title-updated", (e, title) => {
|
||||
const armCordSuffix = " - ArmCord"; /* identify */
|
||||
if (!title.endsWith(armCordSuffix)) {
|
||||
e.preventDefault();
|
||||
await mainWindow.webContents.executeJavaScript(
|
||||
// REVIEW - I don't see a reason to wait for the titlebar to update
|
||||
void mainWindow.webContents.executeJavaScript(
|
||||
`document.title = '${title.replace("Discord |", "") + armCordSuffix}'`
|
||||
);
|
||||
}
|
||||
|
@ -193,7 +205,7 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
|||
fs.readdirSync(themesFolder).forEach((file) => {
|
||||
try {
|
||||
const manifest = fs.readFileSync(`${themesFolder}/${file}/manifest.json`, "utf8");
|
||||
let themeFile = JSON.parse(manifest);
|
||||
const themeFile = JSON.parse(manifest) as ThemeManifest;
|
||||
if (
|
||||
fs
|
||||
.readFileSync(path.join(userDataPath, "/disabled.txt"))
|
||||
|
@ -213,23 +225,23 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
|||
}
|
||||
});
|
||||
});
|
||||
await setMenu();
|
||||
mainWindow.on("close", async (e) => {
|
||||
setMenu();
|
||||
mainWindow.on("close", (e) => {
|
||||
if (process.platform === "darwin" && forceQuit) {
|
||||
mainWindow.close();
|
||||
} else {
|
||||
let [width, height] = mainWindow.getSize();
|
||||
await setWindowState({
|
||||
const [width, height] = mainWindow.getSize();
|
||||
setWindowState({
|
||||
width,
|
||||
height,
|
||||
isMaximized: mainWindow.isMaximized(),
|
||||
x: mainWindow.getPosition()[0],
|
||||
y: mainWindow.getPosition()[1]
|
||||
});
|
||||
if (await getConfig("minimizeToTray")) {
|
||||
if (getConfig("minimizeToTray")) {
|
||||
e.preventDefault();
|
||||
mainWindow.hide();
|
||||
} else if (!(await getConfig("minimizeToTray"))) {
|
||||
} else if (!getConfig("minimizeToTray")) {
|
||||
e.preventDefault();
|
||||
app.quit();
|
||||
}
|
||||
|
@ -244,43 +256,49 @@ async function doAfterDefiningTheWindow(): Promise<void> {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
// REVIEW - Awaiting javascript execution is silly
|
||||
mainWindow.on("focus", () => {
|
||||
mainWindow.webContents.executeJavaScript(`document.body.removeAttribute("unFocused");`);
|
||||
void mainWindow.webContents.executeJavaScript(`document.body.removeAttribute("unFocused");`);
|
||||
});
|
||||
mainWindow.on("blur", () => {
|
||||
mainWindow.webContents.executeJavaScript(`document.body.setAttribute("unFocused", "");`);
|
||||
void mainWindow.webContents.executeJavaScript(`document.body.setAttribute("unFocused", "");`);
|
||||
});
|
||||
|
||||
mainWindow.on("maximize", () => {
|
||||
mainWindow.webContents.executeJavaScript(`document.body.setAttribute("isMaximized", "");`);
|
||||
void mainWindow.webContents.executeJavaScript(`document.body.setAttribute("isMaximized", "");`);
|
||||
});
|
||||
mainWindow.on("unmaximize", () => {
|
||||
mainWindow.webContents.executeJavaScript(`document.body.removeAttribute("isMaximized");`);
|
||||
void mainWindow.webContents.executeJavaScript(`document.body.removeAttribute("isMaximized");`);
|
||||
});
|
||||
if ((await getConfig("inviteWebsocket")) == true) {
|
||||
const server = await new RPCServer();
|
||||
server.on("activity", (data: string) => mainWindow.webContents.send("rpc", data));
|
||||
server.on("invite", (code: string) => {
|
||||
console.log(code);
|
||||
createInviteWindow(code);
|
||||
if (getConfig("inviteWebsocket")) {
|
||||
// NOTE - RPCServer appears to be untyped. cool.
|
||||
// REVIEW - Whatever Ducko has done here to make an async constructor is awful.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||
new RPCServer().then((server: EventEmitter) => {
|
||||
server.on("activity", (data: string) => mainWindow.webContents.send("rpc", data));
|
||||
server.on("invite", (code: string) => {
|
||||
console.log(code);
|
||||
createInviteWindow(code);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (firstRun) {
|
||||
mainWindow.close();
|
||||
}
|
||||
//loadURL broke for no good reason after E28
|
||||
mainWindow.loadFile(`${import.meta.dirname}/../splash/redirect.html`);
|
||||
void mainWindow.loadFile(`${import.meta.dirname}/../splash/redirect.html`);
|
||||
|
||||
if (await getConfig("skipSplash")) {
|
||||
if (getConfig("skipSplash")) {
|
||||
mainWindow.show();
|
||||
}
|
||||
}
|
||||
export async function createCustomWindow(): Promise<void> {
|
||||
export function createCustomWindow(): void {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: (await getWindowState("width")) ?? 835,
|
||||
height: (await getWindowState("height")) ?? 600,
|
||||
x: await getWindowState("x"),
|
||||
y: await getWindowState("y"),
|
||||
width: getWindowState("width") ?? 835,
|
||||
height: getWindowState("height") ?? 600,
|
||||
x: getWindowState("x"),
|
||||
y: getWindowState("y"),
|
||||
title: "ArmCord",
|
||||
show: false,
|
||||
darkTheme: true,
|
||||
|
@ -292,17 +310,17 @@ export async function createCustomWindow(): Promise<void> {
|
|||
webviewTag: true,
|
||||
sandbox: false,
|
||||
preload: path.join(import.meta.dirname, "preload/preload.mjs"),
|
||||
spellcheck: await getConfig("spellcheck")
|
||||
spellcheck: getConfig("spellcheck")
|
||||
}
|
||||
});
|
||||
doAfterDefiningTheWindow();
|
||||
}
|
||||
export async function createNativeWindow(): Promise<void> {
|
||||
export function createNativeWindow(): void {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: (await getWindowState("width")) ?? 835,
|
||||
height: (await getWindowState("height")) ?? 600,
|
||||
x: await getWindowState("x"),
|
||||
y: await getWindowState("y"),
|
||||
width: getWindowState("width") ?? 835,
|
||||
height: getWindowState("height") ?? 600,
|
||||
x: getWindowState("x"),
|
||||
y: getWindowState("y"),
|
||||
title: "ArmCord",
|
||||
darkTheme: true,
|
||||
icon: iconPath,
|
||||
|
@ -314,17 +332,17 @@ export async function createNativeWindow(): Promise<void> {
|
|||
webviewTag: true,
|
||||
sandbox: false,
|
||||
preload: path.join(import.meta.dirname, "preload/preload.mjs"),
|
||||
spellcheck: await getConfig("spellcheck")
|
||||
spellcheck: getConfig("spellcheck")
|
||||
}
|
||||
});
|
||||
doAfterDefiningTheWindow();
|
||||
}
|
||||
export async function createTransparentWindow(): Promise<void> {
|
||||
export function createTransparentWindow(): void {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: (await getWindowState("width")) ?? 835,
|
||||
height: (await getWindowState("height")) ?? 600,
|
||||
x: await getWindowState("x"),
|
||||
y: await getWindowState("y"),
|
||||
width: getWindowState("width") ?? 835,
|
||||
height: getWindowState("height") ?? 600,
|
||||
x: getWindowState("x"),
|
||||
y: getWindowState("y"),
|
||||
title: "ArmCord",
|
||||
darkTheme: true,
|
||||
icon: iconPath,
|
||||
|
@ -336,12 +354,12 @@ export async function createTransparentWindow(): Promise<void> {
|
|||
sandbox: false,
|
||||
webviewTag: true,
|
||||
preload: path.join(import.meta.dirname, "preload/preload.mjs"),
|
||||
spellcheck: await getConfig("spellcheck")
|
||||
spellcheck: getConfig("spellcheck")
|
||||
}
|
||||
});
|
||||
doAfterDefiningTheWindow();
|
||||
}
|
||||
export async function createInviteWindow(code: string): Promise<void> {
|
||||
export function createInviteWindow(code: string): void {
|
||||
inviteWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
|
@ -352,15 +370,16 @@ export async function createInviteWindow(code: string): Promise<void> {
|
|||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
spellcheck: await getConfig("spellcheck")
|
||||
spellcheck: getConfig("spellcheck")
|
||||
}
|
||||
});
|
||||
let formInviteURL = `https://discord.com/invite/${code}`;
|
||||
const formInviteURL = `https://discord.com/invite/${code}`;
|
||||
inviteWindow.webContents.session.webRequest.onBeforeRequest((details, callback) => {
|
||||
if (details.url.includes("ws://")) return callback({cancel: true});
|
||||
return callback({});
|
||||
});
|
||||
inviteWindow.loadURL(formInviteURL);
|
||||
// REVIEW - This shouldn't matter, since below we have an event on it
|
||||
void inviteWindow.loadURL(formInviteURL);
|
||||
inviteWindow.webContents.once("did-finish-load", () => {
|
||||
if (!mainWindow.webContents.isLoading()) {
|
||||
inviteWindow.show();
|
||||
|
|
43
src/main.ts
|
@ -11,23 +11,22 @@ import {createSplashWindow} from "./splash/main.js";
|
|||
import {createSetupWindow} from "./setup/main.js";
|
||||
import {
|
||||
setConfig,
|
||||
getConfigSync,
|
||||
checkForDataFolder,
|
||||
checkIfConfigExists,
|
||||
checkIfConfigIsBroken,
|
||||
getConfig,
|
||||
firstRun,
|
||||
Settings,
|
||||
getConfigLocation
|
||||
} from "./common/config.js";
|
||||
import {injectElectronFlags} from "./common/flags.js";
|
||||
import {setLang} from "./common/lang.js";
|
||||
import {installModLoader} from "./discord/extensions/mods.js";
|
||||
export let iconPath: string;
|
||||
export let settings: any;
|
||||
import type {Settings} from "./types/settings";
|
||||
export let settings: Settings;
|
||||
export let customTitlebar: boolean;
|
||||
|
||||
app.on("render-process-gone", (event, webContents, details) => {
|
||||
app.on("render-process-gone", (_event, _webContents, details) => {
|
||||
if (details.reason == "crashed") {
|
||||
app.relaunch();
|
||||
}
|
||||
|
@ -35,23 +34,23 @@ app.on("render-process-gone", (event, webContents, details) => {
|
|||
async function args(): Promise<void> {
|
||||
let argNum = 2;
|
||||
if (process.argv[0] == "electron") argNum++;
|
||||
let args = process.argv[argNum];
|
||||
const args = process.argv[argNum];
|
||||
if (args == undefined) return;
|
||||
if (args.startsWith("--")) return; //electron flag
|
||||
if (args.includes("=")) {
|
||||
let e = args.split("=");
|
||||
await setConfig(e[0] as keyof Settings, e[1]);
|
||||
const e = args.split("=");
|
||||
setConfig(e[0] as keyof Settings, e[1]);
|
||||
console.log(`Setting ${e[0]} to ${e[1]}`);
|
||||
app.relaunch();
|
||||
app.exit();
|
||||
} else if (args == "themes") {
|
||||
app.whenReady().then(async () => {
|
||||
createTManagerWindow();
|
||||
await app.whenReady().then(async () => {
|
||||
await createTManagerWindow();
|
||||
});
|
||||
}
|
||||
}
|
||||
args(); // i want my top level awaits
|
||||
if (!app.requestSingleInstanceLock() && getConfigSync("multiInstance") == (false ?? undefined)) {
|
||||
await args(); // i want my top level awaits - IMPLEMENTED :)
|
||||
if (!app.requestSingleInstanceLock() && getConfig("multiInstance") == (false ?? undefined)) {
|
||||
// if value isn't set after 3.2.4
|
||||
// kill if 2nd instance
|
||||
app.quit();
|
||||
|
@ -82,21 +81,22 @@ if (!app.requestSingleInstanceLock() && getConfigSync("multiInstance") == (false
|
|||
checkIfConfigIsBroken();
|
||||
injectElectronFlags();
|
||||
console.log("[Config Manager] Current config: " + fs.readFileSync(getConfigLocation(), "utf-8"));
|
||||
app.whenReady().then(async () => {
|
||||
if ((await getConfig("customIcon")) !== undefined ?? null) {
|
||||
iconPath = await getConfig("customIcon");
|
||||
void app.whenReady().then(async () => {
|
||||
// REVIEW - Awaiting the line above will cause a hang at startup
|
||||
if (getConfig("customIcon") !== null) {
|
||||
iconPath = getConfig("customIcon");
|
||||
} else {
|
||||
iconPath = path.join(import.meta.dirname, "../", "/assets/desktop.png");
|
||||
}
|
||||
async function init(): Promise<void> {
|
||||
if ((await getConfig("skipSplash")) == false) {
|
||||
createSplashWindow();
|
||||
if (getConfig("skipSplash") == false) {
|
||||
void createSplashWindow(); // REVIEW - Awaiting will hang at start
|
||||
}
|
||||
if (firstRun == true) {
|
||||
await setLang(new Intl.DateTimeFormat().resolvedOptions().locale);
|
||||
createSetupWindow();
|
||||
setLang(new Intl.DateTimeFormat().resolvedOptions().locale);
|
||||
await createSetupWindow();
|
||||
}
|
||||
switch (await getConfig("windowStyle")) {
|
||||
switch (getConfig("windowStyle")) {
|
||||
case "default":
|
||||
createCustomWindow();
|
||||
customTitlebar = true;
|
||||
|
@ -125,8 +125,9 @@ if (!app.requestSingleInstanceLock() && getConfigSync("multiInstance") == (false
|
|||
callback(true);
|
||||
}
|
||||
});
|
||||
app.on("activate", async function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) await init();
|
||||
app.on("activate", function () {
|
||||
// REVIEW - I don't think it really matters if this promise is voided
|
||||
if (BrowserWindow.getAllWindows().length === 0) void init();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,10 +2,11 @@ import {BrowserWindow, app, shell} from "electron";
|
|||
import path from "path";
|
||||
import fs from "fs";
|
||||
import {getDisplayVersion} from "../common/version.js";
|
||||
import type {ThemeManifest} from "../types/themeManifest.d.js";
|
||||
let settingsWindow: BrowserWindow;
|
||||
let instance = 0;
|
||||
|
||||
export function createSettingsWindow(): void {
|
||||
export async function createSettingsWindow(): Promise<void> {
|
||||
console.log("Creating a settings window.");
|
||||
instance += 1;
|
||||
if (instance > 1) {
|
||||
|
@ -28,7 +29,7 @@ export function createSettingsWindow(): void {
|
|||
}
|
||||
});
|
||||
async function settingsLoadPage(): Promise<void> {
|
||||
settingsWindow.loadURL(`file://${import.meta.dirname}/settings.html`);
|
||||
await settingsWindow.loadURL(`file://${import.meta.dirname}/settings.html`);
|
||||
}
|
||||
const userDataPath = app.getPath("userData");
|
||||
const themesFolder = `${userDataPath}/themes/`;
|
||||
|
@ -43,7 +44,7 @@ export function createSettingsWindow(): void {
|
|||
fs.readdirSync(themesFolder).forEach((file) => {
|
||||
try {
|
||||
const manifest = fs.readFileSync(`${themesFolder}/${file}/manifest.json`, "utf8");
|
||||
let themeFile = JSON.parse(manifest);
|
||||
const themeFile = JSON.parse(manifest) as ThemeManifest;
|
||||
if (
|
||||
fs
|
||||
.readFileSync(path.join(userDataPath, "/disabled.txt"))
|
||||
|
@ -64,10 +65,10 @@ export function createSettingsWindow(): void {
|
|||
});
|
||||
});
|
||||
settingsWindow.webContents.setWindowOpenHandler(({url}) => {
|
||||
shell.openExternal(url);
|
||||
void shell.openExternal(url);
|
||||
return {action: "deny"};
|
||||
});
|
||||
settingsLoadPage();
|
||||
await settingsLoadPage();
|
||||
settingsWindow.on("close", () => {
|
||||
instance = 0;
|
||||
});
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import {contextBridge, ipcRenderer} from "electron";
|
||||
import {Settings} from "../types/settings";
|
||||
//import {addStyle} from "../utils.js";
|
||||
console.log("ArmCord Settings");
|
||||
console.log(process.platform);
|
||||
contextBridge.exposeInMainWorld("settings", {
|
||||
save: (...args: any) => ipcRenderer.send("saveSettings", ...args),
|
||||
// REVIEW - this may be typed incorrectly, I'm not sure how "..." works
|
||||
save: (...args: Settings[]) => ipcRenderer.send("saveSettings", ...args),
|
||||
restart: () => ipcRenderer.send("restart"),
|
||||
saveAlert: (restartFunc: any) => ipcRenderer.send("saveAlert", restartFunc),
|
||||
// REVIEW - I couldn't find a reference to anything about the below function
|
||||
saveAlert: (restartFunc: () => void) => ipcRenderer.send("saveAlert", restartFunc),
|
||||
getLang: (toGet: string) => ipcRenderer.invoke("getLang", toGet),
|
||||
get: (toGet: string) => ipcRenderer.invoke("getSetting", toGet),
|
||||
openThemesFolder: () => ipcRenderer.send("openThemesFolder"),
|
||||
|
@ -17,7 +20,8 @@ contextBridge.exposeInMainWorld("settings", {
|
|||
crash: () => ipcRenderer.send("crash"),
|
||||
os: process.platform
|
||||
});
|
||||
|
||||
/*
|
||||
ipcRenderer.on("themeLoader", (_event, message) => {
|
||||
//addStyle(message);
|
||||
});
|
||||
*/
|
||||
|
|
|
@ -260,6 +260,7 @@ select option {
|
|||
select {
|
||||
-webkit-appearance: button;
|
||||
-moz-appearance: button;
|
||||
appearance: button;
|
||||
background-color: var(--background-secondary-alt);
|
||||
background-position: center right;
|
||||
background-repeat: no-repeat;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
|
|
@ -1,42 +1,47 @@
|
|||
import {BrowserWindow, app, ipcMain} from "electron";
|
||||
import path from "path";
|
||||
import * as fs from "fs";
|
||||
import fs from "fs";
|
||||
import {iconPath} from "../main.js";
|
||||
import {setConfigBulk, getConfigLocation, Settings} from "../common/config.js";
|
||||
import {setConfigBulk, getConfigLocation} from "../common/config.js";
|
||||
import type {Settings} from "../types/settings.d.js";
|
||||
|
||||
let setupWindow: BrowserWindow;
|
||||
export function createSetupWindow(): void {
|
||||
setupWindow = new BrowserWindow({
|
||||
width: 390,
|
||||
height: 470,
|
||||
title: "ArmCord Setup",
|
||||
darkTheme: true,
|
||||
icon: iconPath,
|
||||
frame: false,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
spellcheck: false,
|
||||
preload: path.join(import.meta.dirname, "preload.mjs")
|
||||
}
|
||||
});
|
||||
ipcMain.on("saveSettings", (_event, args: Settings) => {
|
||||
console.log(args);
|
||||
setConfigBulk(args);
|
||||
});
|
||||
ipcMain.on("setup-minimize", () => {
|
||||
setupWindow.minimize();
|
||||
});
|
||||
ipcMain.on("setup-getOS", (event) => {
|
||||
event.returnValue = process.platform;
|
||||
});
|
||||
ipcMain.on("setup-quit", async () => {
|
||||
fs.unlink(await getConfigLocation(), (err) => {
|
||||
if (err) throw err;
|
||||
|
||||
console.log('Closed during setup. "settings.json" was deleted');
|
||||
app.quit();
|
||||
export async function createSetupWindow(): Promise<void> {
|
||||
// NOTE - intentionally hang the process until setup is completed
|
||||
return new Promise((resolve) => {
|
||||
setupWindow = new BrowserWindow({
|
||||
width: 390,
|
||||
height: 470,
|
||||
title: "ArmCord Setup",
|
||||
darkTheme: true,
|
||||
icon: iconPath,
|
||||
frame: false,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
sandbox: false,
|
||||
spellcheck: false,
|
||||
preload: path.join(import.meta.dirname, "preload.mjs")
|
||||
}
|
||||
});
|
||||
ipcMain.on("saveSettings", (_event, args: Settings) => {
|
||||
console.log(args);
|
||||
setConfigBulk(args);
|
||||
resolve();
|
||||
});
|
||||
ipcMain.on("setup-minimize", () => {
|
||||
setupWindow.minimize();
|
||||
});
|
||||
ipcMain.on("setup-getOS", (event) => {
|
||||
event.returnValue = process.platform;
|
||||
});
|
||||
ipcMain.on("setup-quit", () => {
|
||||
fs.unlink(getConfigLocation(), (err) => {
|
||||
if (err) throw err;
|
||||
|
||||
console.log('Closed during setup. "settings.json" was deleted');
|
||||
app.quit();
|
||||
});
|
||||
});
|
||||
void setupWindow.loadURL(`file://${import.meta.dirname}/setup.html`);
|
||||
});
|
||||
setupWindow.loadURL(`file://${import.meta.dirname}/setup.html`);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import {contextBridge, ipcRenderer} from "electron";
|
||||
import {injectTitlebar} from "../discord/preload/titlebar.mjs";
|
||||
import {Settings} from "../types/settings";
|
||||
|
||||
injectTitlebar();
|
||||
contextBridge.exposeInMainWorld("armcordinternal", {
|
||||
restart: () => ipcRenderer.send("restart"),
|
||||
getOS: ipcRenderer.sendSync("setup-getOS"),
|
||||
saveSettings: (...args: any) => ipcRenderer.send("saveSettings", ...args),
|
||||
getOS: ipcRenderer.sendSync("setup-getOS") as string, // String as far as I care.
|
||||
saveSettings: (...args: [Settings]) => ipcRenderer.send("saveSettings", ...args),
|
||||
getLang: (toGet: string) =>
|
||||
ipcRenderer.invoke("getLang", toGet).then((result) => {
|
||||
ipcRenderer.invoke("getLang", toGet).then((result: string) => {
|
||||
return result;
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
@ -123,9 +123,8 @@
|
|||
});
|
||||
if (window.armcordinternal.getOS == "linux") {
|
||||
document.getElementById("tray").value = "false";
|
||||
document.getElementById(
|
||||
"linuxNotice"
|
||||
).innerHTML = `Linux may not work well with tray icons. Depending on your system configuration, you may not be able to see the tray icon. Enable at your own risk. Can be changed later.`;
|
||||
document.getElementById("linuxNotice").innerHTML =
|
||||
`Linux may not work well with tray icons. Depending on your system configuration, you may not be able to see the tray icon. Enable at your own risk. Can be changed later.`;
|
||||
}
|
||||
document.getElementById("next-page4").addEventListener("click", () => {
|
||||
window.armcordinternal.saveSettings({
|
||||
|
|
|
@ -19,5 +19,5 @@ export async function createSplashWindow(): Promise<void> {
|
|||
preload: path.join(import.meta.dirname, "preload.mjs")
|
||||
}
|
||||
});
|
||||
splashWindow.loadFile(path.join(import.meta.dirname, "splash.html"));
|
||||
await splashWindow.loadFile(path.join(import.meta.dirname, "splash.html"));
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@ import {contextBridge, ipcRenderer} from "electron";
|
|||
|
||||
contextBridge.exposeInMainWorld("internal", {
|
||||
restart: () => ipcRenderer.send("restart"),
|
||||
installState: ipcRenderer.sendSync("modInstallState"),
|
||||
version: ipcRenderer.sendSync("get-app-version", "app-version"),
|
||||
installState: ipcRenderer.sendSync("modInstallState") as string,
|
||||
version: ipcRenderer.sendSync("get-app-version", "app-version") as string,
|
||||
getLang: (toGet: string) =>
|
||||
ipcRenderer.invoke("getLang", toGet).then((result) => {
|
||||
ipcRenderer.invoke("getLang", toGet).then((result: string) => {
|
||||
return result;
|
||||
}),
|
||||
splashEnd: () => ipcRenderer.send("splashEnd")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Loading</title>
|
||||
|
|
|
@ -53,6 +53,7 @@ body {
|
|||
font-family: "Whitney", sans-serif;
|
||||
box-sizing: border-box;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
|
|
@ -1,36 +1,22 @@
|
|||
import {BrowserWindow, app, dialog, ipcMain, shell} from "electron";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import {sleep} from "../common/sleep.js";
|
||||
import {createInviteWindow, mainWindow} from "../discord/window.js";
|
||||
import type {ThemeManifest} from "../types/themeManifest.d.js";
|
||||
let themeWindow: BrowserWindow;
|
||||
let instance = 0;
|
||||
interface ThemeManifest {
|
||||
name?: string;
|
||||
author?: string;
|
||||
description?: string;
|
||||
version?: string;
|
||||
invite?: string;
|
||||
authorId?: string;
|
||||
theme: string;
|
||||
authorLink?: string;
|
||||
donate?: string;
|
||||
patreon?: string;
|
||||
website?: string;
|
||||
source?: string;
|
||||
updateSrc?: string;
|
||||
supportsArmCordTitlebar?: boolean;
|
||||
}
|
||||
|
||||
function parseBDManifest(content: string) {
|
||||
const metaReg = /@([^ ]*) (.*)/g;
|
||||
if (!content.startsWith("/**")) {
|
||||
throw new Error("Not a manifest.");
|
||||
}
|
||||
let manifest: ThemeManifest = {theme: "src.css"};
|
||||
const manifest: ThemeManifest = {theme: "src.css", name: "null"}; // Will be defined later
|
||||
|
||||
let match;
|
||||
while ((match = metaReg.exec(content)) !== null) {
|
||||
let [_, key, value] = match;
|
||||
const [_, key] = match;
|
||||
let [value] = match;
|
||||
if (key === "import") break;
|
||||
|
||||
value = value.trim();
|
||||
|
@ -88,7 +74,7 @@ function parseBDManifest(content: string) {
|
|||
}
|
||||
const userDataPath = app.getPath("userData");
|
||||
const themesPath = path.join(userDataPath, "/themes/");
|
||||
export function createTManagerWindow(): void {
|
||||
export async function createTManagerWindow(): Promise<void> {
|
||||
console.log("Creating theme manager window.");
|
||||
instance += 1;
|
||||
if (instance > 1) {
|
||||
|
@ -118,13 +104,13 @@ export function createTManagerWindow(): void {
|
|||
if (url.startsWith("https://discord.gg/")) {
|
||||
createInviteWindow(url.replace("https://discord.gg/", ""));
|
||||
} else {
|
||||
shell.openExternal(url);
|
||||
void shell.openExternal(url);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function managerLoadPage(): Promise<void> {
|
||||
themeWindow.loadFile(`${import.meta.dirname}/manager.html`);
|
||||
await themeWindow.loadFile(`${import.meta.dirname}/manager.html`);
|
||||
}
|
||||
const userDataPath = app.getPath("userData");
|
||||
const themesFolder = `${userDataPath}/themes/`;
|
||||
|
@ -135,27 +121,24 @@ export function createTManagerWindow(): void {
|
|||
if (!fs.existsSync(`${userDataPath}/disabled.txt`)) {
|
||||
fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), "");
|
||||
}
|
||||
ipcMain.on("openThemesFolder", async () => {
|
||||
ipcMain.on("openThemesFolder", () => {
|
||||
shell.showItemInFolder(themesPath);
|
||||
await sleep(1000);
|
||||
});
|
||||
ipcMain.on("reloadMain", async () => {
|
||||
ipcMain.on("reloadMain", () => {
|
||||
mainWindow.webContents.reload();
|
||||
});
|
||||
ipcMain.on("addToDisabled", async (_event, name: string) => {
|
||||
ipcMain.on("addToDisabled", (_event, name: string) => {
|
||||
fs.appendFileSync(path.join(userDataPath, "/disabled.txt"), `${name}\n`);
|
||||
sleep(1000);
|
||||
});
|
||||
ipcMain.on("disabled", async (e) => {
|
||||
ipcMain.on("disabled", (e) => {
|
||||
e.returnValue = fs.readFileSync(path.join(userDataPath, "/disabled.txt")).toString();
|
||||
});
|
||||
ipcMain.on("removeFromDisabled", async (_event, name: string) => {
|
||||
let e = await fs.readFileSync(path.join(userDataPath, "/disabled.txt")).toString();
|
||||
ipcMain.on("removeFromDisabled", (_event, name: string) => {
|
||||
const e = fs.readFileSync(path.join(userDataPath, "/disabled.txt")).toString();
|
||||
fs.writeFileSync(path.join(userDataPath, "/disabled.txt"), e.replace(name, ""));
|
||||
sleep(1000);
|
||||
});
|
||||
ipcMain.on("uninstallTheme", async (_event, id: string) => {
|
||||
let themePath = path.join(themesFolder, id);
|
||||
ipcMain.on("uninstallTheme", (_event, id: string) => {
|
||||
const themePath = path.join(themesFolder, id);
|
||||
if (fs.existsSync(themePath)) {
|
||||
fs.rmdirSync(themePath, {recursive: true});
|
||||
console.log(`Removed ${id} folder`);
|
||||
|
@ -166,33 +149,35 @@ export function createTManagerWindow(): void {
|
|||
themeWindow.webContents.reload();
|
||||
mainWindow.webContents.reload();
|
||||
});
|
||||
ipcMain.on("installBDTheme", async (_event, link: string) => {
|
||||
try {
|
||||
let code = await (await fetch(link)).text();
|
||||
let manifest = parseBDManifest(code);
|
||||
let themePath = path.join(themesFolder, `${manifest.name?.replace(" ", "-")}-BD`);
|
||||
if (!fs.existsSync(themePath)) {
|
||||
fs.mkdirSync(themePath);
|
||||
console.log(`Created ${manifest.name} folder`);
|
||||
ipcMain.on("installBDTheme", (_event, link: string) => {
|
||||
return async () => {
|
||||
try {
|
||||
const code = await (await fetch(link)).text();
|
||||
const manifest = parseBDManifest(code);
|
||||
const themePath = path.join(themesFolder, `${manifest.name?.replace(" ", "-")}-BD`);
|
||||
if (!fs.existsSync(themePath)) {
|
||||
fs.mkdirSync(themePath);
|
||||
console.log(`Created ${manifest.name} folder`);
|
||||
}
|
||||
manifest.updateSrc = link;
|
||||
if (code.includes(".titlebar")) manifest.supportsArmCordTitlebar = true;
|
||||
else manifest.supportsArmCordTitlebar = false;
|
||||
fs.writeFileSync(path.join(themePath, "manifest.json"), JSON.stringify(manifest));
|
||||
fs.writeFileSync(path.join(themePath, "src.css"), code);
|
||||
dialog.showMessageBoxSync({
|
||||
type: "info",
|
||||
title: "BD Theme import success",
|
||||
message: "Successfully imported theme from link."
|
||||
});
|
||||
themeWindow.webContents.reload();
|
||||
mainWindow.webContents.reload();
|
||||
} catch (e) {
|
||||
dialog.showErrorBox(
|
||||
"BD Theme import fail",
|
||||
"Failed to import theme from link. Please make sure that it's a valid BetterDiscord Theme."
|
||||
);
|
||||
}
|
||||
manifest.updateSrc = link;
|
||||
if (code.includes(".titlebar")) manifest.supportsArmCordTitlebar = true;
|
||||
else manifest.supportsArmCordTitlebar = false;
|
||||
fs.writeFileSync(path.join(themePath, "manifest.json"), JSON.stringify(manifest));
|
||||
fs.writeFileSync(path.join(themePath, "src.css"), code);
|
||||
dialog.showMessageBoxSync({
|
||||
type: "info",
|
||||
title: "BD Theme import success",
|
||||
message: "Successfully imported theme from link."
|
||||
});
|
||||
themeWindow.webContents.reload();
|
||||
mainWindow.webContents.reload();
|
||||
} catch (e) {
|
||||
dialog.showErrorBox(
|
||||
"BD Theme import fail",
|
||||
"Failed to import theme from link. Please make sure that it's a valid BetterDiscord Theme."
|
||||
);
|
||||
}
|
||||
};
|
||||
});
|
||||
themeWindow.webContents.on("did-finish-load", () => {
|
||||
fs.readdirSync(themesFolder).forEach((file) => {
|
||||
|
@ -206,7 +191,7 @@ export function createTManagerWindow(): void {
|
|||
});
|
||||
});
|
||||
|
||||
managerLoadPage();
|
||||
await managerLoadPage();
|
||||
themeWindow.on("close", () => {
|
||||
instance = 0;
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
|
@ -213,7 +213,9 @@
|
|||
border-style: solid;
|
||||
border-radius: 10px;
|
||||
width: 80%;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
box-shadow:
|
||||
0 4px 8px 0 rgba(0, 0, 0, 0.2),
|
||||
0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
-webkit-animation-name: animatetop;
|
||||
-webkit-animation-duration: 0.4s;
|
||||
animation-name: animatetop;
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import {ipcRenderer, contextBridge} from "electron";
|
||||
import {sleep} from "../common/sleep.js";
|
||||
import {ThemeManifest} from "../types/themeManifest";
|
||||
contextBridge.exposeInMainWorld("themes", {
|
||||
install: (url: string) => ipcRenderer.send("installBDTheme", url),
|
||||
uninstall: (id: string) => ipcRenderer.send("uninstallTheme", id)
|
||||
});
|
||||
ipcRenderer.on("themeManifest", (_event, json) => {
|
||||
let manifest = JSON.parse(json);
|
||||
console.log(manifest);
|
||||
sleep(1000);
|
||||
let e = document.getElementById("cardBox");
|
||||
let id = manifest.name.replace(" ", "-");
|
||||
e?.insertAdjacentHTML(
|
||||
"beforeend",
|
||||
`
|
||||
ipcRenderer.on("themeManifest", (_event, json: string) => {
|
||||
async () => {
|
||||
const manifest = JSON.parse(json) as ThemeManifest;
|
||||
console.log(manifest);
|
||||
await sleep(1000); // REVIEW - This is all that requires async, would be nice if it could be removed.
|
||||
const e = document.getElementById("cardBox");
|
||||
const id = manifest.name.replace(" ", "-");
|
||||
e?.insertAdjacentHTML(
|
||||
"beforeend",
|
||||
`
|
||||
<div class="card">
|
||||
<div class="flex-box">
|
||||
<h3 id="${`${id}header`}">${manifest.name}</h3>
|
||||
|
@ -22,50 +24,47 @@ ipcRenderer.on("themeManifest", (_event, json) => {
|
|||
<p>${manifest.description}</p>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
document.getElementById(`${id}header`)!.addEventListener("click", () => {
|
||||
document.getElementById("themeInfoModal")!.style.display = "block";
|
||||
document.getElementById("themeInfoName")!.textContent = `${manifest.name} by ${manifest.author}`;
|
||||
document.getElementById("themeInfoDesc")!.textContent = `${manifest.description}\n\n${manifest.version}`;
|
||||
if (manifest.supportsArmCordTitlebar !== undefined) {
|
||||
document.getElementById(
|
||||
"themeInfoButtons"
|
||||
)!.innerHTML += `<img class="themeInfoIcon" id="removeTheme" onclick="themes.uninstall('${id}')" title="Remove the theme" src="https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/Trash.png"></img>
|
||||
);
|
||||
document.getElementById(`${id}header`)!.addEventListener("click", () => {
|
||||
document.getElementById("themeInfoModal")!.style.display = "block";
|
||||
document.getElementById("themeInfoName")!.textContent = `${manifest.name} by ${manifest.author}`;
|
||||
document.getElementById("themeInfoDesc")!.textContent = `${manifest.description}\n\n${manifest.version}`;
|
||||
if (manifest.supportsArmCordTitlebar !== undefined) {
|
||||
document.getElementById("themeInfoButtons")!.innerHTML +=
|
||||
`<img class="themeInfoIcon" id="removeTheme" onclick="themes.uninstall('${id}')" title="Remove the theme" src="https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/Trash.png"></img>
|
||||
<img class="themeInfoIcon" id="updateTheme" onclick="themes.install('${manifest.updateSrc}')" title="Update your theme" src="https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/UpgradeArrow.png"></img>
|
||||
<img class="themeInfoIcon" id="compatibility" title="Supports ArmCord Titlebar" src=""></img>`;
|
||||
console.log("e");
|
||||
if (manifest.supportsArmCordTitlebar == true) {
|
||||
(document.getElementById(`compatibility`) as HTMLImageElement).src =
|
||||
"https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/Window.png";
|
||||
} else {
|
||||
(document.getElementById(`compatibility`) as HTMLImageElement).src =
|
||||
"https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/WindowUnsupported.png";
|
||||
console.log("e");
|
||||
if (manifest.supportsArmCordTitlebar == true) {
|
||||
(document.getElementById(`compatibility`) as HTMLImageElement).src =
|
||||
"https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/Window.png";
|
||||
} else {
|
||||
(document.getElementById(`compatibility`) as HTMLImageElement).src =
|
||||
"https://raw.githubusercontent.com/ArmCord/BrandingStuff/main/WindowUnsupported.png";
|
||||
}
|
||||
}
|
||||
if (manifest.source != undefined)
|
||||
document.getElementById("themeInfoButtons")!.innerHTML +=
|
||||
`<a href="${manifest.source}" class="button">Source code</a>`;
|
||||
if (manifest.website != undefined)
|
||||
document.getElementById("themeInfoButtons")!.innerHTML +=
|
||||
`<a href="${manifest.website}" class="button">Website</a>`;
|
||||
if (manifest.invite != undefined)
|
||||
document.getElementById("themeInfoButtons")!.innerHTML +=
|
||||
`<a href="${`https://discord.gg/${manifest.invite}`}" class="button">Support Discord</a>`;
|
||||
});
|
||||
if (!(ipcRenderer.sendSync("disabled") as string[]).includes(id)) {
|
||||
(document.getElementById(id) as HTMLInputElement).checked = true;
|
||||
}
|
||||
if (manifest.source != undefined)
|
||||
document.getElementById(
|
||||
"themeInfoButtons"
|
||||
)!.innerHTML += `<a href="${manifest.source}" class="button">Source code</a>`;
|
||||
if (manifest.website != undefined)
|
||||
document.getElementById(
|
||||
"themeInfoButtons"
|
||||
)!.innerHTML += `<a href="${manifest.website}" class="button">Website</a>`;
|
||||
if (manifest.invite != undefined)
|
||||
document.getElementById(
|
||||
"themeInfoButtons"
|
||||
)!.innerHTML += `<a href="${`https://discord.gg/${manifest.invite}`}" class="button">Support Discord</a>`;
|
||||
});
|
||||
if (!ipcRenderer.sendSync("disabled").includes(id)) {
|
||||
(document.getElementById(id) as HTMLInputElement).checked = true;
|
||||
}
|
||||
(document.getElementById(id) as HTMLInputElement)!.addEventListener("input", function (evt) {
|
||||
ipcRenderer.send("reloadMain");
|
||||
if (this.checked) {
|
||||
ipcRenderer.send("removeFromDisabled", id);
|
||||
} else {
|
||||
ipcRenderer.send("addToDisabled", id);
|
||||
}
|
||||
});
|
||||
(document.getElementById(id) as HTMLInputElement).addEventListener("input", function () {
|
||||
ipcRenderer.send("reloadMain");
|
||||
if (this.checked) {
|
||||
ipcRenderer.send("removeFromDisabled", id);
|
||||
} else {
|
||||
ipcRenderer.send("addToDisabled", id);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.getElementsByClassName("close")[0].addEventListener("click", () => {
|
||||
|
@ -73,6 +72,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
document.getElementById("themeInfoButtons")!.innerHTML = "";
|
||||
});
|
||||
document.getElementById("download")!.addEventListener("click", () => {
|
||||
ipcRenderer.send("installBDTheme", (document.getElementById("themeLink") as HTMLInputElement)!.value);
|
||||
ipcRenderer.send("installBDTheme", (document.getElementById("themeLink") as HTMLInputElement).value);
|
||||
});
|
||||
});
|
||||
|
|
38
src/tray.ts
|
@ -1,20 +1,20 @@
|
|||
import * as fs from "fs";
|
||||
import fs from "fs";
|
||||
import {Menu, MessageBoxOptions, Tray, app, dialog, nativeImage} from "electron";
|
||||
import {createInviteWindow, mainWindow} from "./discord/window.js";
|
||||
import * as path from "path";
|
||||
import path from "path";
|
||||
import {createSettingsWindow} from "./settings/main.js";
|
||||
import {getConfig, getConfigLocation, setConfig} from "./common/config.js";
|
||||
import {getDisplayVersion} from "./common/version.js";
|
||||
export let tray: any = null;
|
||||
export let tray: Tray;
|
||||
let trayIcon = "ac_plug_colored";
|
||||
app.whenReady().then(async () => {
|
||||
let finishedSetup = await getConfig("doneSetup");
|
||||
if ((await getConfig("trayIcon")) != "default") {
|
||||
trayIcon = await getConfig("trayIcon");
|
||||
void app.whenReady().then(async () => {
|
||||
// REVIEW - app will hang at startup if line above is awaited.
|
||||
const finishedSetup = getConfig("doneSetup");
|
||||
if (getConfig("trayIcon") != "default") {
|
||||
trayIcon = getConfig("trayIcon");
|
||||
}
|
||||
let trayPath = nativeImage.createFromPath(path.join(import.meta.dirname, "../", `/assets/${trayIcon}.png`));
|
||||
let trayVerIcon;
|
||||
trayVerIcon = function () {
|
||||
const trayVerIcon = function () {
|
||||
if (process.platform == "win32") {
|
||||
return trayPath.resize({height: 16});
|
||||
} else if (process.platform == "darwin") {
|
||||
|
@ -26,8 +26,8 @@ app.whenReady().then(async () => {
|
|||
};
|
||||
|
||||
if (process.platform == "darwin" && trayPath.getSize().height > 22) trayPath = trayPath.resize({height: 22});
|
||||
if (await getConfig("tray")) {
|
||||
let clientName = (await getConfig("clientName")) ?? "ArmCord";
|
||||
if (getConfig("tray")) {
|
||||
const clientName = getConfig("clientName") ?? "ArmCord";
|
||||
tray = new Tray(trayPath);
|
||||
if (finishedSetup == false) {
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
|
@ -37,8 +37,8 @@ app.whenReady().then(async () => {
|
|||
},
|
||||
{
|
||||
label: `Quit ${clientName}`,
|
||||
async click() {
|
||||
fs.unlink(await getConfigLocation(), (err) => {
|
||||
click() {
|
||||
fs.unlink(getConfigLocation(), (err) => {
|
||||
if (err) throw err;
|
||||
|
||||
console.log('Closed during setup. "settings.json" was deleted');
|
||||
|
@ -50,6 +50,7 @@ app.whenReady().then(async () => {
|
|||
tray.setContextMenu(contextMenu);
|
||||
} else {
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
// REVIEW - Awaiting any window creation will fail silently
|
||||
{
|
||||
label: `${clientName} ${getDisplayVersion()}`,
|
||||
icon: trayVerIcon(),
|
||||
|
@ -67,13 +68,13 @@ app.whenReady().then(async () => {
|
|||
{
|
||||
label: "Open Settings",
|
||||
click() {
|
||||
createSettingsWindow();
|
||||
void createSettingsWindow();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Support Discord Server",
|
||||
click() {
|
||||
createInviteWindow("TnhxcqynZ2");
|
||||
void createInviteWindow("TnhxcqynZ2");
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -82,7 +83,8 @@ app.whenReady().then(async () => {
|
|||
{
|
||||
label: `Quit ${clientName}`,
|
||||
click() {
|
||||
app.quit();
|
||||
// NOTE - Temporary fix for app not actually quitting
|
||||
app.exit();
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
@ -93,7 +95,7 @@ app.whenReady().then(async () => {
|
|||
mainWindow.show();
|
||||
});
|
||||
} else {
|
||||
if ((await getConfig("tray")) == undefined) {
|
||||
if (getConfig("tray") == undefined) {
|
||||
if (process.platform == "linux") {
|
||||
const options: MessageBoxOptions = {
|
||||
type: "question",
|
||||
|
@ -104,7 +106,7 @@ app.whenReady().then(async () => {
|
|||
detail: "Linux may not work well with tray icons. Depending on your system configuration, you may not be able to see the tray icon. Enable at your own risk. Can be changed later."
|
||||
};
|
||||
|
||||
dialog.showMessageBox(mainWindow, options).then(({response}) => {
|
||||
await dialog.showMessageBox(mainWindow, options).then(({response}) => {
|
||||
if (response == 0) {
|
||||
setConfig("tray", true);
|
||||
} else {
|
||||
|
|
21
src/types/armcordWindow.d.ts
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
export interface ArmCordWindow {
|
||||
window: {
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
minimize: () => void;
|
||||
maximize: () => void;
|
||||
};
|
||||
titlebar: {
|
||||
injectTitlebar: () => void;
|
||||
isTitlebar: boolean;
|
||||
};
|
||||
electron: string;
|
||||
channel: string;
|
||||
setPingCount: (pingCount: number) => void;
|
||||
setTrayIcon: (favicon: string) => void;
|
||||
getLang: (toGet: string) => Promise<string>;
|
||||
getDisplayMediaSelector: () => Promise<string>;
|
||||
version: string;
|
||||
mods: string;
|
||||
openSettingsWindow: () => void;
|
||||
}
|
1
src/types/i18nStrings.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export type i18nStrings = Record<string, string>;
|
31
src/types/settings.d.ts
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
export interface Settings {
|
||||
// Referenced for detecting a broken config.
|
||||
"0"?: string;
|
||||
// Referenced once for disabling mod updating.
|
||||
noBundleUpdates?: boolean;
|
||||
// Only used for external url warning dialog.
|
||||
ignoreProtocolWarning?: boolean;
|
||||
customIcon: string;
|
||||
windowStyle: string;
|
||||
channel: string;
|
||||
armcordCSP: boolean;
|
||||
minimizeToTray: boolean;
|
||||
multiInstance: boolean;
|
||||
spellcheck: boolean;
|
||||
mods: string;
|
||||
dynamicIcon: boolean;
|
||||
mobileMode: boolean;
|
||||
skipSplash: boolean;
|
||||
performanceMode: string;
|
||||
customJsBundle: RequestInfo | URL;
|
||||
customCssBundle: RequestInfo | URL;
|
||||
startMinimized: boolean;
|
||||
useLegacyCapturer: boolean;
|
||||
tray: boolean;
|
||||
keybinds: string[];
|
||||
inviteWebsocket: boolean;
|
||||
disableAutogain: boolean;
|
||||
trayIcon: string;
|
||||
doneSetup: boolean;
|
||||
clientName: string;
|
||||
}
|
16
src/types/themeManifest.d.ts
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
export interface ThemeManifest {
|
||||
name: string;
|
||||
author?: string;
|
||||
description?: string;
|
||||
version?: string;
|
||||
invite?: string;
|
||||
authorId?: string;
|
||||
theme: string;
|
||||
authorLink?: string;
|
||||
donate?: string;
|
||||
patreon?: string;
|
||||
website?: string;
|
||||
source?: string;
|
||||
updateSrc?: string;
|
||||
supportsArmCordTitlebar?: boolean;
|
||||
}
|
7
src/types/windowState.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
export interface WindowState {
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;
|
||||
isMaximized: boolean;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
// Reference: https://www.typescriptlang.org/tsconfig
|
||||
"include": ["src/**/*"], // This makes it so that the compiler won't compile anything outside of "src".
|
||||
"include": ["src/**/*", "eslint.config.js", "prettier.config.js"], // This makes it so that the compiler won't compile anything outside of "src".
|
||||
//"exclude": ["src/**/*.test.ts"], // Exclude .test.ts files since they're for Jest only.
|
||||
"compilerOptions": {
|
||||
// Project Structure //
|
||||
|
@ -14,10 +14,10 @@
|
|||
"noFallthroughCasesInSwitch": true, // Prevents accidentally forgetting to break every switch case. Of course, if you know what you're doing, feel free to add a @ts-ignore, which also signals that it's not a mistake.
|
||||
"forceConsistentCasingInFileNames": true, // Make import paths case-sensitive. "./tEst" is no longer the same as "./test".
|
||||
"esModuleInterop": true, // Enables compatibility with Node.js' module system since the entire export can be whatever you want. allowSyntheticDefaultImports doesn't address runtime issues and is made redundant by this setting.
|
||||
"lib": ["ES2022", "DOM"], // Specifies what common libraries you have access to. If you're working in Node.js, you'll want to leave out the DOM library. But do make sure to include "@types/node" because otherwise, variables like "console" won't be defined.
|
||||
"lib": ["ESNext", "DOM"], // Specifies what common libraries you have access to. If you're working in Node.js, you'll want to leave out the DOM library. But do make sure to include "@types/node" because otherwise, variables like "console" won't be defined.
|
||||
"noUnusedLocals": true, // Warns you if you have unused variables.
|
||||
// Output //
|
||||
"module": "ES2022", // Compiles ES6 imports to require() syntax.
|
||||
"module": "ESNext", // Compiles ES6 imports to require() syntax.
|
||||
"removeComments": false,
|
||||
"sourceMap": true, // Used for displaying the original source when debugging in webpack. Allows you to set breakpoints directly on TypeScript code for VSCode's debugger.
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
|||
"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.
|
||||
// Web Compatibility //
|
||||
"target": "ES2022", // 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": "ESNext", // 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.
|
||||
"importHelpers": false // Reduce the amount of bloat that comes from downlevelIteration (when polyfills are redeclared).
|
||||
}
|
||||
|
|