Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9120d05efc | ||
|
|
3a820b458f | ||
|
|
47f3b7fc89 | ||
|
|
838a3c78dd | ||
|
|
8d51cd5029 | ||
|
|
bfb9af05b0 | ||
|
|
897df3a5d4 | ||
|
|
d1296d1708 | ||
|
|
c5165afe7f | ||
|
|
e16cdcf387 | ||
|
|
1c2ed4679d | ||
|
|
af42af070e | ||
|
|
07d647ba2e | ||
|
|
90b8c5fdfe | ||
|
|
c0598821ee | ||
|
|
de3aae1d95 | ||
|
|
d1acb0490b | ||
|
|
60759c142e | ||
|
|
0c77dbec92 | ||
|
|
edfeca15ce | ||
|
|
c2eaa9d35a | ||
|
|
ddebb6563a | ||
|
|
df53ea549a | ||
|
|
a4954570b6 | ||
|
|
ca376d35ab | ||
|
|
db76f111c8 | ||
|
|
5efbbe2106 | ||
|
|
c29dd6d2d7 | ||
|
|
94b80ebe75 | ||
|
|
bba8899f67 | ||
|
|
ba0e8fedd0 | ||
|
|
27b6264c79 | ||
|
|
81929f1733 | ||
|
|
d7e2dd0593 | ||
|
|
a807ae85fc | ||
|
|
cb2fb648b6 | ||
|
|
e265e70fb9 | ||
|
|
805b6fbcc4 | ||
|
|
591d380160 | ||
|
|
8f19d41d4c | ||
|
|
b4a2c41e74 | ||
|
|
7e0532444d | ||
|
|
8b68eef9a7 | ||
|
|
e3f973ff68 |
6
.env.example
Normal file
6
.env.example
Normal file
@@ -0,0 +1,6 @@
|
||||
# githubs api has a rate limit of 60/h if not authorised.
|
||||
# you may quickly hit that and get rate limited. To counteract this, you can provide a github token
|
||||
# here and it will be used. To do so, create a token at the following links and just leave
|
||||
# all permissions at the defaults (public repos read only, 0 permissions):
|
||||
# https://github.com/settings/personal-access-tokens/new
|
||||
GITHUB_TOKEN=
|
||||
63
.eslintrc.json
Normal file
63
.eslintrc.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"ignorePatterns": ["dist", "node_modules"],
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"license-header",
|
||||
"simple-import-sort",
|
||||
"unused-imports",
|
||||
"path-alias",
|
||||
"prettier"
|
||||
],
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"alias": {
|
||||
"map": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"license-header/header": ["error", "scripts/header.txt"],
|
||||
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
||||
"spaced-comment": ["error", "always", { "markers": ["!"] }],
|
||||
"yoda": "error",
|
||||
"prefer-destructuring": ["error", { "object": true, "array": false }],
|
||||
"operator-assignment": ["error", "always"],
|
||||
"no-useless-computed-key": "error",
|
||||
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
||||
"no-invalid-regexp": "error",
|
||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||
"no-duplicate-imports": "error",
|
||||
"no-extra-semi": "error",
|
||||
"dot-notation": "error",
|
||||
"no-useless-escape": "error",
|
||||
"no-fallthrough": "error",
|
||||
"for-direction": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-dupe-else-if": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-misleading-character-class": "error",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-unsafe-optional-chaining": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"use-isnan": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-spread": "error",
|
||||
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
|
||||
"path-alias/no-relative": "error",
|
||||
|
||||
"prettier/prettier": "error"
|
||||
}
|
||||
}
|
||||
54
.github/workflows/release.yml
vendored
54
.github/workflows/release.yml
vendored
@@ -1,28 +1,44 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ${{ matrix.os }}
|
||||
release:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Run Electron Builder
|
||||
uses: samuelmeuli/action-electron-builder@e4b12cd06ddf023422f1ac4e39632bd76f6e6928
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE: true
|
||||
- name: Run Electron Builder
|
||||
uses: samuelmeuli/action-electron-builder@e4b12cd06ddf023422f1ac4e39632bd76f6e6928
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE: true
|
||||
|
||||
- name: Update AUR package
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: |
|
||||
mkdir -p -m 700 ~/.ssh
|
||||
echo $SSH_KEY > ~/.ssh/aur
|
||||
echo $SSH_PUB_KEY > ~/.ssh/aur.pub
|
||||
chmod 600 ~/.ssh/*
|
||||
|
||||
export GIT_SSH_COMMAND="ssh -i ~/.ssh/aur"
|
||||
|
||||
sudo ./scripts/ci/install_makepkg.sh
|
||||
./scripts/ci/aur_bump.sh
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.AUR_SSH_KEY }}
|
||||
SSH_PUB_KEY: ${{ secrets.AUR_SSH_PUB_KEY }}
|
||||
|
||||
32
.github/workflows/test.yml
vendored
Normal file
32
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm test
|
||||
|
||||
- name: Test if it compiles
|
||||
run: |
|
||||
pnpm build
|
||||
pnpm build --dev
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
dist
|
||||
node_modules
|
||||
.env
|
||||
|
||||
@@ -5,4 +5,4 @@ trailingComma: none
|
||||
bracketSpacing: true
|
||||
arrowParens: avoid
|
||||
useTabs: false
|
||||
endOfLine: auto
|
||||
endOfLine: lf
|
||||
|
||||
18
.vscode/settings.json
vendored
Normal file
18
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@ Download and run Vencord-Desktop-VERSION.dmg from [releases](https://github.com/
|
||||
|
||||
### Linux
|
||||
|
||||
#### Arch based
|
||||
|
||||
Install [vencord-desktop-git](https://aur.archlinux.org/packages/vencord-desktop-git) from the AUR using your favourite AUR helper, for example [yay](https://github.com/Jguer/yay)
|
||||
|
||||
#### Ubuntu/Debian based
|
||||
|
||||
Download Vencord-Desktop-VERSION.deb from [releases](https://github.com/Vencord/Desktop/releases/latest)
|
||||
@@ -28,7 +32,7 @@ Download Vencord-Desktop-VERSION.rpm from [releases](https://github.com/Vencord/
|
||||
|
||||
Either download Vencord-Desktop-VERSION.AppImage and just run it directly or grab Vencord-Desktop-VERSION.tar.gz, extract it somewhere and run `vencorddesktop`.
|
||||
|
||||
An AUR package and flatpak are planned, if you want packages for other repos, feel free to create them and they can be linked as unofficial here
|
||||
A flatpak is planned, if you want packages for other repos, feel free to create them and they can be linked as unofficial here
|
||||
|
||||
## Building
|
||||
|
||||
@@ -46,6 +50,8 @@ pnpm start
|
||||
|
||||
# Or package
|
||||
pnpm package
|
||||
# Or only build the pacman target
|
||||
pnpm package --linux pacman
|
||||
# Or package to a directory only
|
||||
pnpm package:dir
|
||||
```
|
||||
|
||||
48
package.json
48
package.json
@@ -1,31 +1,58 @@
|
||||
{
|
||||
"name": "VencordDesktop",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.6",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"homepage": "https://vencord.dev/",
|
||||
"license": "GPL-3.0",
|
||||
"author": "Vendicated <vendicated@riseup.net>",
|
||||
"main": "dist/js/main.js",
|
||||
"scripts": {
|
||||
"build": "tsx scripts/build.mts",
|
||||
"build": "tsx scripts/build/build.mts",
|
||||
"build:dev": "pnpm build --dev",
|
||||
"package": "pnpm build && electron-builder",
|
||||
"package:dir": "pnpm build && electron-builder --dir",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.mts,.mjs",
|
||||
"lint:fix": "pnpm lint --fix",
|
||||
"start": "pnpm build && electron .",
|
||||
"start:watch": "tsx scripts/startWatch.mts",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start:dev": "pnpm build:dev && electron .",
|
||||
"start:watch": "pnpm build:dev && tsx scripts/startWatch.mts",
|
||||
"test": "pnpm lint && pnpm testTypes",
|
||||
"testTypes": "tsc --noEmit",
|
||||
"watch": "pnpm build --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.15.11",
|
||||
"@types/react": "^18.0.33",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"dotenv": "^16.0.3",
|
||||
"electron": "^23.2.0",
|
||||
"electron-builder": "^23.6.0",
|
||||
"esbuild": "^0.17.14",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-plugin-license-header": "^0.6.0",
|
||||
"eslint-plugin-path-alias": "^1.0.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"prettier": "^2.8.7",
|
||||
"source-map-support": "^0.5.21",
|
||||
"tsx": "^3.12.6",
|
||||
"type-fest": "^3.8.0",
|
||||
"typescript": "^5.0.2"
|
||||
},
|
||||
"packageManager": "pnpm@8.1.1",
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"pnpm": ">=8"
|
||||
},
|
||||
"build": {
|
||||
"appId": "dev.vencord.desktop",
|
||||
"productName": "Vencord Desktop",
|
||||
"files": [
|
||||
"!*",
|
||||
"dist/js",
|
||||
@@ -41,16 +68,21 @@
|
||||
"tar.gz",
|
||||
"rpm",
|
||||
"AppImage"
|
||||
]
|
||||
],
|
||||
"desktop": {
|
||||
"Name": "Vencord Desktop",
|
||||
"GenericName": "Internet Messenger",
|
||||
"Type": "Application",
|
||||
"Categories": "Network;InstantMessaging;Chat;",
|
||||
"Keywords": "discord;vencord;electron;chat;"
|
||||
}
|
||||
},
|
||||
"mac": {
|
||||
"category": "Network"
|
||||
},
|
||||
"nsis": {
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"include": "build/installer.nsh",
|
||||
"oneClick": false
|
||||
},
|
||||
"productName": "Vencord Desktop"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2460
pnpm-lock.yaml
generated
2460
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,15 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { BuildContext, BuildOptions, context } from "esbuild";
|
||||
|
||||
const isDev = process.argv.includes("--dev");
|
||||
|
||||
const CommonOpts: BuildOptions = {
|
||||
minify: true,
|
||||
minify: !isDev,
|
||||
bundle: true,
|
||||
sourcemap: "linked",
|
||||
logLevel: "info"
|
||||
@@ -13,6 +21,9 @@ const NodeCommonOpts: BuildOptions = {
|
||||
platform: "node",
|
||||
external: ["electron"],
|
||||
target: ["esnext"],
|
||||
define: {
|
||||
IS_DEV: JSON.stringify(isDev)
|
||||
}
|
||||
};
|
||||
|
||||
const contexts = [] as BuildContext[];
|
||||
@@ -31,11 +42,22 @@ await Promise.all([
|
||||
entryPoints: ["src/preload/index.ts"],
|
||||
outfile: "dist/js/preload.js"
|
||||
}),
|
||||
createContext({
|
||||
...NodeCommonOpts,
|
||||
entryPoints: ["src/updater/preload.ts"],
|
||||
outfile: "dist/js/updaterPreload.js"
|
||||
}),
|
||||
createContext({
|
||||
...CommonOpts,
|
||||
globalName: "VencordDesktop",
|
||||
entryPoints: ["src/renderer/index.ts"],
|
||||
outfile: "dist/js/renderer.js",
|
||||
format: "iife",
|
||||
inject: ["./scripts/build/injectReact.mjs"],
|
||||
jsxFactory: "VencordCreateElement",
|
||||
jsxFragment: "VencordFragment",
|
||||
// Work around https://github.com/evanw/esbuild/issues/2460
|
||||
tsconfig: "./scripts/build/tsconfig.esbuild.json"
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -44,8 +66,10 @@ const watch = process.argv.includes("--watch");
|
||||
if (watch) {
|
||||
await Promise.all(contexts.map(ctx => ctx.watch()));
|
||||
} else {
|
||||
await Promise.all(contexts.map(async ctx => {
|
||||
await ctx.rebuild();
|
||||
await ctx.dispose();
|
||||
}));
|
||||
await Promise.all(
|
||||
contexts.map(async ctx => {
|
||||
await ctx.rebuild();
|
||||
await ctx.dispose();
|
||||
})
|
||||
);
|
||||
}
|
||||
9
scripts/build/injectReact.mjs
Normal file
9
scripts/build/injectReact.mjs
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
export const VencordFragment = /* #__PURE__*/ Symbol.for("react.fragment");
|
||||
export let VencordCreateElement = (...args) =>
|
||||
(VencordCreateElement = Vencord.Webpack.Common.React.createElement)(...args);
|
||||
7
scripts/build/tsconfig.esbuild.json
Normal file
7
scripts/build/tsconfig.esbuild.json
Normal file
@@ -0,0 +1,7 @@
|
||||
// Work around https://github.com/evanw/esbuild/issues/2460
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react"
|
||||
}
|
||||
}
|
||||
20
scripts/ci/aur_bump.sh
Executable file
20
scripts/ci/aur_bump.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
VERSION=$(git describe --tags --abbrev=0 | tr -d 'v')
|
||||
SHASUM=$(sha256sum "dist/VencordDesktop-$VERSION.tar.gz" | awk '{ print $1 }')
|
||||
|
||||
git clone ssh://aur@aur.archlinux.org/vencord-desktop-bin.git aurpkg
|
||||
|
||||
cd aurpkg
|
||||
|
||||
sed -i "s/^pkgver=.*$/pkgver=$VERSION/" PKGBUILD
|
||||
sed -i "s/^sha256sums=('.*'/sha256sums=('$SHASUM'/" PKGBUILD
|
||||
makepkg --printsrcinfo > .SRCINFO
|
||||
|
||||
git commit -a -m "Bump version to $VERSION"
|
||||
git push
|
||||
|
||||
cd ..
|
||||
rm -rf aurpkg
|
||||
13
scripts/ci/install_makepkg.sh
Executable file
13
scripts/ci/install_makepkg.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
for i in \
|
||||
"makepkg_6.0.2-3_amd64.deb" \
|
||||
"libalpm13_13.0.2-3_amd64.deb" \
|
||||
"pacman-package-manager_6.0.2-3_amd64.deb"; do
|
||||
wget -O/tmp/$i https://fr.archive.ubuntu.com/ubuntu/pool/universe/p/pacman-package-manager/$i
|
||||
dpkg -i /tmp/$i || true
|
||||
done
|
||||
|
||||
apt-get -f install -oDpkg::Use-Pty=0 -qq
|
||||
5
scripts/header.txt
Normal file
5
scripts/header.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
11
scripts/start.ts
Normal file
11
scripts/start.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import "./utils/dotenv";
|
||||
|
||||
import { spawnNodeModuleBin } from "./utils/spawn.mjs";
|
||||
|
||||
spawnNodeModuleBin("electron", ["."]);
|
||||
@@ -1,15 +1,10 @@
|
||||
import { spawn as cpSpawn, SpawnOptions } from "child_process";
|
||||
import { join } from "path";
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
const EXT = process.platform === "win32" ? ".cmd" : "";
|
||||
import "./start";
|
||||
|
||||
const OPTS: SpawnOptions = {
|
||||
stdio: "inherit",
|
||||
};
|
||||
|
||||
function spawn(bin: string, args: string[]) {
|
||||
cpSpawn(join("node_modules", ".bin", bin + EXT), args, OPTS);
|
||||
}
|
||||
|
||||
spawn("tsx", ["scripts/build.mts", "--", "--watch"]);
|
||||
spawn("electron", ["."]);
|
||||
import { spawnNodeModuleBin } from "./utils/spawn.mjs";
|
||||
spawnNodeModuleBin("tsx", ["scripts/build/build.mts", "--", "--watch", "--dev"]);
|
||||
|
||||
9
scripts/utils/dotenv.ts
Normal file
9
scripts/utils/dotenv.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { config } from "dotenv";
|
||||
|
||||
config();
|
||||
18
scripts/utils/spawn.mts
Normal file
18
scripts/utils/spawn.mts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { spawn as spaaawn, SpawnOptions } from "child_process";
|
||||
import { join } from "path";
|
||||
|
||||
const EXT = process.platform === "win32" ? ".cmd" : "";
|
||||
|
||||
const OPTS: SpawnOptions = {
|
||||
stdio: "inherit"
|
||||
};
|
||||
|
||||
export function spawnNodeModuleBin(bin: string, args: string[]) {
|
||||
spaaawn(join("node_modules", ".bin", bin + EXT), args, OPTS);
|
||||
}
|
||||
13
src/globals.d.ts
vendored
13
src/globals.d.ts
vendored
@@ -1,8 +1,17 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
declare global {
|
||||
export var VencordDesktop: typeof import("./preload/VencordDesktop").VencordDesktop;
|
||||
export var VencordDesktopNative: typeof import("preload/VencordDesktopNative").VencordDesktopNative;
|
||||
export var VencordDesktop: typeof import("renderer/index");
|
||||
// TODO
|
||||
export var Vencord: any;
|
||||
export var vcdLS: typeof localStorage;
|
||||
|
||||
export var IS_DEV: boolean;
|
||||
}
|
||||
|
||||
export { };
|
||||
export {};
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { BrowserWindow } from "electron";
|
||||
import { join } from "path";
|
||||
import { ICON_PATH, STATIC_DIR } from "shared/paths";
|
||||
|
||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||
|
||||
export function createAboutWindow() {
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { join } from "path";
|
||||
|
||||
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR ?? join(app.getPath("userData"), "VencordDesktop");
|
||||
export const VENCORD_FILES_DIR = join(DATA_DIR, "vencordDist");
|
||||
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR || join(app.getPath("userData"), "VencordDesktop");
|
||||
export const VENCORD_SETTINGS_DIR = join(DATA_DIR, "settings");
|
||||
export const VENCORD_QUICKCSS_FILE = join(VENCORD_SETTINGS_DIR, "quickCss.css");
|
||||
export const VENCORD_SETTINGS_FILE = join(VENCORD_SETTINGS_DIR, "settings.json");
|
||||
|
||||
// needs to be inline require because of circular dependency
|
||||
// as otherwise "DATA_DIR" (which is used by ./settings) will be uninitialised
|
||||
export const VENCORD_FILES_DIR =
|
||||
(require("./settings") as typeof import("./settings")).Settings.store.vencordDir || join(DATA_DIR, "vencordDist");
|
||||
|
||||
export const USER_AGENT = `VencordDesktop/${app.getVersion()} (https://github.com/Vencord/Electron)`;
|
||||
|
||||
// dimensions shamelessly stolen from Discord Desktop :3
|
||||
export const MIN_WIDTH = 940;
|
||||
export const MIN_HEIGHT = 500;
|
||||
export const DEFAULT_WIDTH = 1280;
|
||||
export const DEFAULT_HEIGHT = 720;
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
import { app, BrowserWindow } from 'electron';
|
||||
import { createMainWindow } from "./mainWindow";
|
||||
import { createSplashWindow } from "./splash";
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import "./ipc";
|
||||
|
||||
import { app, BrowserWindow } from "electron";
|
||||
import { join } from "path";
|
||||
|
||||
import { DATA_DIR, VENCORD_FILES_DIR } from "./constants";
|
||||
|
||||
import { once } from "../shared/utils/once";
|
||||
import { ensureVencordFiles } from "./utils/vencordLoader";
|
||||
import { checkUpdates } from "updater/main";
|
||||
|
||||
import { ICON_PATH } from "../shared/paths";
|
||||
import "./ipc";
|
||||
import { once } from "../shared/utils/once";
|
||||
import { DATA_DIR, VENCORD_FILES_DIR } from "./constants";
|
||||
import { createMainWindow } from "./mainWindow";
|
||||
import { Settings } from "./settings";
|
||||
import { createSplashWindow } from "./splash";
|
||||
import { ensureVencordFiles } from "./utils/vencordLoader";
|
||||
if (IS_DEV) {
|
||||
require("source-map-support").install();
|
||||
}
|
||||
|
||||
// Make the Vencord files use our DATA_DIR
|
||||
process.env.VENCORD_USER_DATA_DIR = DATA_DIR;
|
||||
|
||||
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "main.js")));
|
||||
const runVencordMain = once(() => require(join(VENCORD_FILES_DIR, "vencordDesktopMain.js")));
|
||||
|
||||
let mainWin: BrowserWindow | null = null;
|
||||
|
||||
@@ -33,14 +41,13 @@ if (!app.requestSingleInstanceLock()) {
|
||||
});
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
if (process.platform === "win32")
|
||||
app.setAppUserModelId("dev.vencord.desktop");
|
||||
else if (process.platform === "darwin")
|
||||
app.dock.setIcon(ICON_PATH);
|
||||
checkUpdates();
|
||||
if (process.platform === "win32") app.setAppUserModelId("dev.vencord.desktop");
|
||||
else if (process.platform === "darwin") app.dock.setIcon(ICON_PATH);
|
||||
|
||||
createWindows();
|
||||
|
||||
app.on('activate', () => {
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindows();
|
||||
});
|
||||
});
|
||||
@@ -58,13 +65,12 @@ async function createWindows() {
|
||||
splash.destroy();
|
||||
mainWin!.show();
|
||||
|
||||
if (Settings.maximized) {
|
||||
if (Settings.store.maximized) {
|
||||
mainWin!.maximize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin")
|
||||
app.quit();
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
|
||||
@@ -1,42 +1,77 @@
|
||||
import { app, ipcMain, shell } from "electron";
|
||||
import { readFileSync, watch } from "fs";
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { app, dialog, ipcMain, shell } from "electron";
|
||||
import { existsSync, readFileSync, watch } from "fs";
|
||||
import { open, readFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import { debounce } from "shared/utils/debounce";
|
||||
import { FOCUS, GET_RENDERER_SCRIPT, GET_RENDERER_STYLES, GET_SETTINGS, GET_VENCORD_PRELOAD_FILE, RELAUNCH, SET_SETTINGS, SHOW_ITEM_IN_FOLDER } from "../shared/IpcEvents";
|
||||
|
||||
import { IpcEvents } from "../shared/IpcEvents";
|
||||
import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE } from "./constants";
|
||||
import { mainWin } from "./mainWindow";
|
||||
import { PlainSettings, setSettings } from "./settings";
|
||||
import { Settings } from "./settings";
|
||||
|
||||
ipcMain.on(GET_VENCORD_PRELOAD_FILE, e => {
|
||||
ipcMain.on(IpcEvents.GET_VENCORD_PRELOAD_FILE, e => {
|
||||
e.returnValue = join(VENCORD_FILES_DIR, "preload.js");
|
||||
});
|
||||
|
||||
ipcMain.on(GET_RENDERER_SCRIPT, e => {
|
||||
ipcMain.on(IpcEvents.GET_VENCORD_RENDERER_SCRIPT, e => {
|
||||
e.returnValue = readFileSync(join(VENCORD_FILES_DIR, "vencordDesktopRenderer.js"), "utf-8");
|
||||
});
|
||||
|
||||
ipcMain.on(IpcEvents.GET_RENDERER_SCRIPT, e => {
|
||||
e.returnValue = readFileSync(join(__dirname, "renderer.js"), "utf-8");
|
||||
});
|
||||
|
||||
ipcMain.handle(GET_RENDERER_STYLES, () => readFile(join(__dirname, "renderer.css"), "utf-8"));
|
||||
|
||||
ipcMain.on(GET_SETTINGS, e => {
|
||||
e.returnValue = PlainSettings;
|
||||
ipcMain.on(IpcEvents.GET_RENDERER_CSS_FILE, e => {
|
||||
e.returnValue = join(__dirname, "renderer.css");
|
||||
});
|
||||
|
||||
ipcMain.handle(SET_SETTINGS, (_, settings) => {
|
||||
setSettings(settings);
|
||||
ipcMain.on(IpcEvents.GET_SETTINGS, e => {
|
||||
e.returnValue = Settings.plain;
|
||||
});
|
||||
|
||||
ipcMain.handle(RELAUNCH, () => {
|
||||
ipcMain.on(IpcEvents.GET_VERSION, e => {
|
||||
e.returnValue = app.getVersion();
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, settings: typeof Settings.store, path?: string) => {
|
||||
Settings.setData(settings, path);
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.RELAUNCH, () => {
|
||||
app.relaunch();
|
||||
app.exit();
|
||||
});
|
||||
|
||||
ipcMain.handle(SHOW_ITEM_IN_FOLDER, (_, path) => {
|
||||
ipcMain.handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => {
|
||||
shell.showItemInFolder(path);
|
||||
});
|
||||
|
||||
ipcMain.handle(FOCUS, () => {
|
||||
mainWin?.focus();
|
||||
ipcMain.handle(IpcEvents.FOCUS, e => {
|
||||
e.sender.focus();
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.CLOSE, e => {
|
||||
e.sender.close();
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.SELECT_VENCORD_DIR, async () => {
|
||||
const res = await dialog.showOpenDialog(mainWin!, {
|
||||
properties: ["openDirectory"]
|
||||
});
|
||||
if (!res.filePaths.length) return "cancelled";
|
||||
|
||||
const dir = res.filePaths[0];
|
||||
for (const file of ["vencordDesktopMain.js", "preload.js", "vencordDesktopRenderer.js", "renderer.css"]) {
|
||||
if (!existsSync(join(dir, file))) return "invalid";
|
||||
}
|
||||
|
||||
return dir;
|
||||
});
|
||||
|
||||
function readCss() {
|
||||
@@ -45,7 +80,11 @@ function readCss() {
|
||||
|
||||
open(VENCORD_QUICKCSS_FILE, "a+").then(fd => {
|
||||
fd.close();
|
||||
watch(VENCORD_QUICKCSS_FILE, { persistent: false }, debounce(async () => {
|
||||
mainWin?.webContents.postMessage("VencordQuickCssUpdate", await readCss());
|
||||
}, 50));
|
||||
watch(
|
||||
VENCORD_QUICKCSS_FILE,
|
||||
{ persistent: false },
|
||||
debounce(async () => {
|
||||
mainWin?.webContents.postMessage("VencordQuickCssUpdate", await readCss());
|
||||
}, 50)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import { BrowserWindow, BrowserWindowConstructorOptions, Menu, Tray, app } from "electron";
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { app, BrowserWindow, BrowserWindowConstructorOptions, Menu, Tray } from "electron";
|
||||
import { join } from "path";
|
||||
|
||||
import { ICON_PATH } from "../shared/paths";
|
||||
import { createAboutWindow } from "./about";
|
||||
import { Settings } from "./settings";
|
||||
import { DEFAULT_HEIGHT, DEFAULT_WIDTH, MIN_HEIGHT, MIN_WIDTH } from "./constants";
|
||||
import { Settings, VencordSettings } from "./settings";
|
||||
import { makeLinksOpenExternally } from "./utils/makeLinksOpenExternally";
|
||||
import { downloadVencordFiles } from "./utils/vencordLoader";
|
||||
|
||||
let isQuitting = false;
|
||||
let tray: Tray;
|
||||
|
||||
app.on("before-quit", () => {
|
||||
isQuitting = true;
|
||||
@@ -54,7 +63,7 @@ function initTray(win: BrowserWindow) {
|
||||
}
|
||||
]);
|
||||
|
||||
const tray = new Tray(ICON_PATH);
|
||||
tray = new Tray(ICON_PATH);
|
||||
tray.setToolTip("Vencord Desktop");
|
||||
tray.setContextMenu(trayMenu);
|
||||
tray.on("click", () => win.show());
|
||||
@@ -69,7 +78,9 @@ function initTray(win: BrowserWindow) {
|
||||
}
|
||||
|
||||
function initMenuBar(win: BrowserWindow) {
|
||||
console.log(process.platform);
|
||||
const isWindows = process.platform === "win32";
|
||||
const wantCtrlQ = !isWindows || VencordSettings.store.winCtrlQ;
|
||||
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: "Vencord Desktop",
|
||||
@@ -119,18 +130,16 @@ function initMenuBar(win: BrowserWindow) {
|
||||
},
|
||||
{
|
||||
label: "Quit",
|
||||
accelerator: process.platform === "win32" ? void 0 : "CmdOrCtrl+Q",
|
||||
// TODO: Setting
|
||||
visible: process.platform !== "win32",
|
||||
accelerator: wantCtrlQ ? "CmdOrCtrl+Q" : void 0,
|
||||
visible: !isWindows,
|
||||
click() {
|
||||
app.quit();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Quit",
|
||||
accelerator: "Alt+F4",
|
||||
visible: process.platform === "win32",
|
||||
acceleratorWorksWhenHidden: false,
|
||||
accelerator: isWindows ? "Alt+F4" : void 0,
|
||||
visible: isWindows,
|
||||
click() {
|
||||
app.quit();
|
||||
}
|
||||
@@ -165,54 +174,78 @@ function initMenuBar(win: BrowserWindow) {
|
||||
Menu.setApplicationMenu(menu);
|
||||
}
|
||||
|
||||
function getWindowBoundsOptions() {
|
||||
const options = {} as BrowserWindowConstructorOptions;
|
||||
function getWindowBoundsOptions(): BrowserWindowConstructorOptions {
|
||||
const { x, y, width, height } = Settings.store.windowBounds ?? {};
|
||||
|
||||
const options = {
|
||||
width: width ?? DEFAULT_WIDTH,
|
||||
height: height ?? DEFAULT_HEIGHT
|
||||
} as BrowserWindowConstructorOptions;
|
||||
|
||||
const { x, y, width, height } = Settings.windowBounds ?? {};
|
||||
if (x != null && y != null) {
|
||||
options.x = x;
|
||||
options.y = y;
|
||||
}
|
||||
|
||||
if (width) options.width = width;
|
||||
if (height) options.height = height;
|
||||
if (!Settings.store.disableMinSize) {
|
||||
options.minWidth = MIN_WIDTH;
|
||||
options.minHeight = MIN_HEIGHT;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function initWindowBoundsListeners(win: BrowserWindow) {
|
||||
win.on("maximize", () => {
|
||||
Settings.maximized = true;
|
||||
Settings.minimized = false;
|
||||
});
|
||||
const saveState = () => {
|
||||
Settings.store.maximized = win.isMaximized();
|
||||
Settings.store.minimized = win.isMinimized();
|
||||
};
|
||||
|
||||
win.on("minimize", () => {
|
||||
Settings.minimized = true;
|
||||
});
|
||||
|
||||
win.on("unmaximize", () => {
|
||||
Settings.maximized = false;
|
||||
Settings.minimized = false;
|
||||
});
|
||||
win.on("maximize", saveState);
|
||||
win.on("minimize", saveState);
|
||||
win.on("unmaximize", saveState);
|
||||
|
||||
const saveBounds = () => {
|
||||
const [width, height] = win.getSize();
|
||||
const [x, y] = win.getPosition();
|
||||
|
||||
Settings.windowBounds = {
|
||||
width,
|
||||
height,
|
||||
x,
|
||||
y
|
||||
};
|
||||
Settings.store.windowBounds = win.getBounds();
|
||||
};
|
||||
|
||||
win.on("resize", saveBounds);
|
||||
win.on("move", saveBounds);
|
||||
}
|
||||
|
||||
function initSettingsListeners(win: BrowserWindow) {
|
||||
Settings.addChangeListener("tray", enable => {
|
||||
if (enable) initTray(win);
|
||||
else tray?.destroy();
|
||||
});
|
||||
Settings.addChangeListener("disableMinSize", disable => {
|
||||
if (disable) {
|
||||
// 0 no work
|
||||
win.setMinimumSize(1, 1);
|
||||
} else {
|
||||
win.setMinimumSize(MIN_WIDTH, MIN_HEIGHT);
|
||||
|
||||
const { width, height } = win.getBounds();
|
||||
win.setBounds({
|
||||
width: Math.max(width, MIN_WIDTH),
|
||||
height: Math.max(height, MIN_HEIGHT)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
VencordSettings.addChangeListener("macosTranslucency", enabled => {
|
||||
if (enabled) {
|
||||
win.setVibrancy("sidebar");
|
||||
win.setBackgroundColor("#ffffff00");
|
||||
} else {
|
||||
win.setVibrancy(null);
|
||||
win.setBackgroundColor("#ffffff");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function createMainWindow() {
|
||||
const win = mainWin = new BrowserWindow({
|
||||
const win = (mainWin = new BrowserWindow({
|
||||
show: false,
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
@@ -223,11 +256,18 @@ export function createMainWindow() {
|
||||
preload: join(__dirname, "preload.js")
|
||||
},
|
||||
icon: ICON_PATH,
|
||||
frame: VencordSettings.store.frameless !== true,
|
||||
...(VencordSettings.store.macosTranslucency
|
||||
? {
|
||||
vibrancy: "sidebar",
|
||||
backgroundColor: "#ffffff00"
|
||||
}
|
||||
: {}),
|
||||
...getWindowBoundsOptions()
|
||||
});
|
||||
}));
|
||||
|
||||
win.on("close", e => {
|
||||
if (isQuitting) return;
|
||||
if (isQuitting || Settings.store.minimizeToTray === false || Settings.store.tray === false) return;
|
||||
|
||||
e.preventDefault();
|
||||
win.hide();
|
||||
@@ -236,13 +276,15 @@ export function createMainWindow() {
|
||||
});
|
||||
|
||||
initWindowBoundsListeners(win);
|
||||
initTray(win);
|
||||
if (Settings.store.tray ?? true) initTray(win);
|
||||
initMenuBar(win);
|
||||
makeLinksOpenExternally(win);
|
||||
initSettingsListeners(win);
|
||||
|
||||
const subdomain = Settings.discordBranch === "canary" || Settings.discordBranch === "ptb"
|
||||
? `${Settings.discordBranch}.`
|
||||
: "";
|
||||
const subdomain =
|
||||
Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb"
|
||||
? `${Settings.store.discordBranch}.`
|
||||
: "";
|
||||
|
||||
win.loadURL(`https://${subdomain}discord.com/app`);
|
||||
|
||||
|
||||
@@ -1,49 +1,34 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { DATA_DIR } from "./constants";
|
||||
import type { Settings as TSettings } from "shared/settings";
|
||||
import { SettingsStore } from "shared/utils/SettingsStore";
|
||||
|
||||
import { DATA_DIR, VENCORD_SETTINGS_FILE } from "./constants";
|
||||
|
||||
const SETTINGS_FILE = join(DATA_DIR, "settings.json");
|
||||
|
||||
interface Settings {
|
||||
maximized?: boolean;
|
||||
minimized?: boolean;
|
||||
windowBounds?: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
discordBranch?: "stable" | "canary" | "ptb";
|
||||
openLinksWithElectron?: boolean;
|
||||
}
|
||||
|
||||
export let PlainSettings = {} as Settings;
|
||||
try {
|
||||
const content = readFileSync(SETTINGS_FILE, "utf8");
|
||||
function loadSettings<T extends object = any>(file: string, name: string) {
|
||||
let settings = {} as T;
|
||||
try {
|
||||
PlainSettings = JSON.parse(content);
|
||||
} catch (err) {
|
||||
console.error("Failed to parse settings.json:", err);
|
||||
}
|
||||
} catch { }
|
||||
|
||||
function makeSettingsProxy(settings: Settings) {
|
||||
return new Proxy(settings, {
|
||||
set(target, prop, value) {
|
||||
Reflect.set(target, prop, value);
|
||||
|
||||
writeFileSync(SETTINGS_FILE, JSON.stringify(target, null, 4));
|
||||
|
||||
return true;
|
||||
const content = readFileSync(file, "utf8");
|
||||
try {
|
||||
settings = JSON.parse(content);
|
||||
} catch (err) {
|
||||
console.error(`Failed to parse ${name} settings.json:`, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export let Settings = makeSettingsProxy(PlainSettings);
|
||||
|
||||
export function setSettings(settings: Settings) {
|
||||
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 4));
|
||||
PlainSettings = settings;
|
||||
Settings = makeSettingsProxy(settings);
|
||||
} catch {}
|
||||
|
||||
const store = new SettingsStore(settings);
|
||||
store.addGlobalChangeListener(o => writeFileSync(file, JSON.stringify(o, null, 4)));
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
export const Settings = loadSettings<TSettings>(SETTINGS_FILE, "Vencord Desktop");
|
||||
export const VencordSettings = loadSettings<any>(VENCORD_SETTINGS_FILE, "Vencord");
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { BrowserWindow } from "electron";
|
||||
import { join } from "path";
|
||||
import { SplashProps } from "shared/browserWinProperties";
|
||||
import { STATIC_DIR } from "shared/paths";
|
||||
|
||||
export function createSplashWindow() {
|
||||
const splash = new BrowserWindow({
|
||||
transparent: true,
|
||||
frame: false,
|
||||
height: 350,
|
||||
width: 300,
|
||||
center: true,
|
||||
resizable: false,
|
||||
maximizable: false
|
||||
});
|
||||
const splash = new BrowserWindow(SplashProps);
|
||||
|
||||
splash.loadFile(join(STATIC_DIR, "splash.html"));
|
||||
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { createWriteStream } from "fs";
|
||||
import type { IncomingMessage } from "http";
|
||||
import { RequestOptions, get } from "https";
|
||||
import { get, RequestOptions } from "https";
|
||||
import { finished } from "stream/promises";
|
||||
|
||||
export async function downloadFile(url: string, file: string, options: RequestOptions = {}) {
|
||||
const res = await simpleReq(url, options);
|
||||
await finished(
|
||||
res.pipe(createWriteStream(file, {
|
||||
autoClose: true
|
||||
}))
|
||||
res.pipe(
|
||||
createWriteStream(file, {
|
||||
autoClose: true
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,12 +24,8 @@ export function simpleReq(url: string, options: RequestOptions = {}) {
|
||||
return new Promise<IncomingMessage>((resolve, reject) => {
|
||||
get(url, options, res => {
|
||||
const { statusCode, statusMessage, headers } = res;
|
||||
if (statusCode! >= 400)
|
||||
return void reject(`${statusCode}: ${statusMessage} - ${url}`);
|
||||
if (statusCode! >= 300)
|
||||
return simpleReq(headers.location!, options)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
if (statusCode! >= 400) return void reject(`${statusCode}: ${statusMessage} - ${url}`);
|
||||
if (statusCode! >= 300) return simpleReq(headers.location!, options).then(resolve).catch(reject);
|
||||
|
||||
resolve(res);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { BrowserWindow, shell } from "electron";
|
||||
|
||||
import { Settings } from "../settings";
|
||||
|
||||
export function makeLinksOpenExternally(win: BrowserWindow) {
|
||||
@@ -10,7 +17,7 @@ export function makeLinksOpenExternally(win: BrowserWindow) {
|
||||
}
|
||||
|
||||
try {
|
||||
var protocol = new URL(url).protocol;
|
||||
var { protocol } = new URL(url);
|
||||
} catch {
|
||||
return { action: "deny" };
|
||||
}
|
||||
@@ -18,9 +25,10 @@ export function makeLinksOpenExternally(win: BrowserWindow) {
|
||||
switch (protocol) {
|
||||
case "http:":
|
||||
case "https:":
|
||||
if (Settings.openLinksWithElectron) {
|
||||
if (Settings.store.openLinksWithElectron) {
|
||||
return { action: "allow" };
|
||||
}
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case "mailto:":
|
||||
case "steam:":
|
||||
case "spotify:":
|
||||
|
||||
@@ -1,53 +1,57 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync } from "fs";
|
||||
import type { RequestOptions } from "https";
|
||||
import { join } from "path";
|
||||
|
||||
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
|
||||
import { downloadFile, simpleGet } from "./http";
|
||||
|
||||
// TODO: Setting to switch repo
|
||||
const API_BASE = "https://api.github.com/repos/Vendicated/Vencord";
|
||||
const API_BASE = "https://api.github.com";
|
||||
|
||||
const FILES_TO_DOWNLOAD = [
|
||||
"vencordDesktopMain.js",
|
||||
"preload.js",
|
||||
"vencordDesktopRenderer.js",
|
||||
"renderer.css"
|
||||
];
|
||||
const FILES_TO_DOWNLOAD = ["vencordDesktopMain.js", "preload.js", "vencordDesktopRenderer.js", "renderer.css"];
|
||||
|
||||
export interface ReleaseData {
|
||||
name: string;
|
||||
tag_name: string;
|
||||
html_url: string;
|
||||
assets: Array<{
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export async function githubGet(endpoint: string) {
|
||||
return simpleGet(API_BASE + endpoint, {
|
||||
const opts: RequestOptions = {
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
"User-Agent": USER_AGENT
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (process.env.GITHUB_TOKEN) opts.headers!.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
||||
|
||||
return simpleGet(API_BASE + endpoint, opts);
|
||||
}
|
||||
|
||||
export async function downloadVencordFiles() {
|
||||
const release = await githubGet("/releases/latest");
|
||||
const release = await githubGet("/repos/Vendicated/Vencord/releases/latest");
|
||||
|
||||
const data = JSON.parse(release.toString("utf-8"));
|
||||
const assets = data.assets as Array<{
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
}>;
|
||||
const { assets } = JSON.parse(release.toString("utf-8")) as ReleaseData;
|
||||
|
||||
await Promise.all(
|
||||
assets
|
||||
.filter(({ name }) => FILES_TO_DOWNLOAD.some(f => name.startsWith(f)))
|
||||
.map(({ name, browser_download_url }) =>
|
||||
downloadFile(
|
||||
browser_download_url,
|
||||
join(
|
||||
VENCORD_FILES_DIR,
|
||||
name.replace(/vencordDesktop(\w)/, (_, c) => c.toLowerCase())
|
||||
)
|
||||
)
|
||||
)
|
||||
.map(({ name, browser_download_url }) => downloadFile(browser_download_url, join(VENCORD_FILES_DIR, name)))
|
||||
);
|
||||
}
|
||||
|
||||
export async function ensureVencordFiles() {
|
||||
if (existsSync(join(VENCORD_FILES_DIR, "main.js"))) return;
|
||||
if (existsSync(join(VENCORD_FILES_DIR, "vencordDesktopMain.js"))) return;
|
||||
mkdirSync(VENCORD_FILES_DIR, { recursive: true });
|
||||
|
||||
await downloadVencordFiles();
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { ipcRenderer } from "electron";
|
||||
import type { Settings } from "../main/settings";
|
||||
import { FOCUS, GET_SETTINGS, RELAUNCH, SET_SETTINGS, SHOW_ITEM_IN_FOLDER } from "../shared/IpcEvents";
|
||||
|
||||
export const VencordDesktop = {
|
||||
app: {
|
||||
relaunch: () => ipcRenderer.invoke(RELAUNCH)
|
||||
},
|
||||
fileManager: {
|
||||
showItemInFolder: (path: string) => ipcRenderer.invoke(SHOW_ITEM_IN_FOLDER, path)
|
||||
},
|
||||
settings: {
|
||||
get: () => ipcRenderer.sendSync(GET_SETTINGS),
|
||||
set: (settings: typeof Settings) => ipcRenderer.invoke(SET_SETTINGS, settings)
|
||||
},
|
||||
win: {
|
||||
focus: () => ipcRenderer.invoke(FOCUS)
|
||||
}
|
||||
}
|
||||
|
||||
29
src/preload/VencordDesktopNative.ts
Normal file
29
src/preload/VencordDesktopNative.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import type { Settings } from "shared/settings";
|
||||
import type { LiteralUnion } from "type-fest";
|
||||
|
||||
import { IpcEvents } from "../shared/IpcEvents";
|
||||
import { invoke, sendSync } from "./typedIpcs";
|
||||
|
||||
export const VencordDesktopNative = {
|
||||
app: {
|
||||
relaunch: () => invoke<void>(IpcEvents.RELAUNCH),
|
||||
getVersion: () => sendSync<void>(IpcEvents.GET_VERSION)
|
||||
},
|
||||
fileManager: {
|
||||
showItemInFolder: (path: string) => invoke<void>(IpcEvents.SHOW_ITEM_IN_FOLDER, path),
|
||||
selectVencordDir: () => invoke<LiteralUnion<"cancelled" | "invalid", string>>(IpcEvents.SELECT_VENCORD_DIR)
|
||||
},
|
||||
settings: {
|
||||
get: () => sendSync<Settings>(IpcEvents.GET_SETTINGS),
|
||||
set: (settings: Settings, path?: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings, path)
|
||||
},
|
||||
win: {
|
||||
focus: () => invoke<void>(IpcEvents.FOCUS)
|
||||
}
|
||||
};
|
||||
@@ -1,10 +1,43 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { contextBridge, ipcRenderer, webFrame } from "electron";
|
||||
import { GET_RENDERER_SCRIPT, GET_RENDERER_STYLES, GET_VENCORD_PRELOAD_FILE } from "../shared/IpcEvents";
|
||||
import { VencordDesktop } from "./VencordDesktop";
|
||||
import { readFileSync, watch } from "fs";
|
||||
|
||||
contextBridge.exposeInMainWorld("VencordDesktop", VencordDesktop);
|
||||
import { IpcEvents } from "../shared/IpcEvents";
|
||||
import { VencordDesktopNative } from "./VencordDesktopNative";
|
||||
|
||||
require(ipcRenderer.sendSync(GET_VENCORD_PRELOAD_FILE));
|
||||
contextBridge.exposeInMainWorld("VencordDesktopNative", VencordDesktopNative);
|
||||
|
||||
webFrame.executeJavaScript(ipcRenderer.sendSync(GET_RENDERER_SCRIPT));
|
||||
ipcRenderer.invoke(GET_RENDERER_STYLES).then(s => webFrame.insertCSS(s));
|
||||
require(ipcRenderer.sendSync(IpcEvents.GET_VENCORD_PRELOAD_FILE));
|
||||
|
||||
webFrame.executeJavaScript(ipcRenderer.sendSync(IpcEvents.GET_VENCORD_RENDERER_SCRIPT));
|
||||
webFrame.executeJavaScript(ipcRenderer.sendSync(IpcEvents.GET_RENDERER_SCRIPT));
|
||||
|
||||
// #region css
|
||||
const rendererCss = ipcRenderer.sendSync(IpcEvents.GET_RENDERER_CSS_FILE);
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.id = "vcd-css-core";
|
||||
style.textContent = readFileSync(rendererCss, "utf-8");
|
||||
|
||||
if (document.readyState === "complete") {
|
||||
document.documentElement.appendChild(style);
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", () => document.documentElement.appendChild(style), {
|
||||
once: true
|
||||
});
|
||||
}
|
||||
|
||||
if (IS_DEV) {
|
||||
// persistent means keep process running if watcher is the only thing still running
|
||||
// which we obviously don't want
|
||||
watch(rendererCss, { persistent: false }, () => {
|
||||
document.getElementById("vcd-css-core")!.textContent = readFileSync(rendererCss, "utf-8");
|
||||
});
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
16
src/preload/typedIpcs.ts
Normal file
16
src/preload/typedIpcs.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { ipcRenderer } from "electron";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
|
||||
export function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
||||
return ipcRenderer.invoke(event, ...args) as Promise<T>;
|
||||
}
|
||||
|
||||
export function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
|
||||
return ipcRenderer.sendSync(event, ...args) as T;
|
||||
}
|
||||
122
src/renderer/components/Settings.tsx
Normal file
122
src/renderer/components/Settings.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import "./settings.css";
|
||||
|
||||
import { useSettings } from "renderer/settings";
|
||||
|
||||
import { Common, Util } from "../vencord";
|
||||
|
||||
const { Margins } = Util;
|
||||
|
||||
export default function SettingsUi() {
|
||||
const Settings = useSettings();
|
||||
const {
|
||||
Forms: { FormSection, FormText, FormDivider, FormSwitch, FormTitle },
|
||||
Text,
|
||||
Select,
|
||||
Button
|
||||
} = Common;
|
||||
|
||||
const switches: [keyof typeof Settings, string, string, boolean?, (() => boolean)?][] = [
|
||||
["tray", "Tray Icon", "Add a tray icon for Vencord Desktop", true],
|
||||
[
|
||||
"minimizeToTray",
|
||||
"Minimize to tray",
|
||||
"Hitting X will make Vencord Desktop minimize to the tray instead of closing",
|
||||
true,
|
||||
() => Settings.tray ?? true
|
||||
],
|
||||
[
|
||||
"disableMinSize",
|
||||
"Disable minimum window size",
|
||||
"Allows you to make the window as small as your heart desires"
|
||||
],
|
||||
[
|
||||
"openLinksWithElectron",
|
||||
"Open Links in app (experimental)",
|
||||
"Opens links in a new Vencord Desktop window instead of your web browser"
|
||||
]
|
||||
];
|
||||
|
||||
return (
|
||||
<FormSection>
|
||||
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
|
||||
Vencord Desktop Settings
|
||||
</Text>
|
||||
|
||||
<FormTitle className={Margins.top16}>Discord Branch</FormTitle>
|
||||
<Select
|
||||
placeholder="Stable"
|
||||
options={[
|
||||
{ label: "Stable", value: "stable", default: true },
|
||||
{ label: "Canary", value: "canary" },
|
||||
{ label: "PTB", value: "ptb" }
|
||||
]}
|
||||
closeOnSelect={true}
|
||||
select={v => (Settings.discordBranch = v)}
|
||||
isSelected={v => v === Settings.discordBranch}
|
||||
serialize={s => s}
|
||||
/>
|
||||
|
||||
<FormDivider className={Margins.top16 + " " + Margins.bottom16} />
|
||||
|
||||
{switches.map(([key, text, note, def, predicate]) => (
|
||||
<FormSwitch
|
||||
value={(Settings[key] ?? def ?? false) && (!predicate || predicate())}
|
||||
disabled={predicate && !predicate()}
|
||||
onChange={v => (Settings[key] = v)}
|
||||
note={note}
|
||||
key={key}
|
||||
>
|
||||
{text}
|
||||
</FormSwitch>
|
||||
))}
|
||||
|
||||
<FormTitle>Vencord Location</FormTitle>
|
||||
<FormText>
|
||||
Vencord files are loaded from{" "}
|
||||
{Settings.vencordDir ? (
|
||||
<a
|
||||
href="about:blank"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
VencordDesktopNative.fileManager.showItemInFolder(Settings.vencordDir!);
|
||||
}}
|
||||
>
|
||||
{Settings.vencordDir}
|
||||
</a>
|
||||
) : (
|
||||
"the default location"
|
||||
)}
|
||||
</FormText>
|
||||
<div className="vcd-location-btns">
|
||||
<Button
|
||||
size={Button.Sizes.SMALL}
|
||||
onClick={async () => {
|
||||
const choice = await VencordDesktopNative.fileManager.selectVencordDir();
|
||||
switch (choice) {
|
||||
case "cancelled":
|
||||
case "invalid":
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
Settings.vencordDir = choice;
|
||||
}}
|
||||
>
|
||||
Change
|
||||
</Button>
|
||||
<Button
|
||||
size={Button.Sizes.SMALL}
|
||||
color={Button.Colors.RED}
|
||||
onClick={() => (Settings.vencordDir = void 0)}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</FormSection>
|
||||
);
|
||||
}
|
||||
7
src/renderer/components/index.ts
Normal file
7
src/renderer/components/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
export { default as Settings } from "./Settings";
|
||||
6
src/renderer/components/settings.css
Normal file
6
src/renderer/components/settings.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.vcd-location-btns {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
@@ -1,4 +1,11 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import "./hideGarbage.css";
|
||||
|
||||
import { isFirstRun, localStorage } from "./utils";
|
||||
|
||||
// Make clicking Notifications focus the window
|
||||
@@ -7,7 +14,7 @@ Object.defineProperty(Notification.prototype, "onclick", {
|
||||
set(onClick) {
|
||||
originalSetOnClick.call(this, function (this: unknown) {
|
||||
onClick.apply(this, arguments);
|
||||
VencordDesktop.win.focus();
|
||||
VencordDesktopNative.win.focus();
|
||||
});
|
||||
},
|
||||
configurable: true
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import "./fixes";
|
||||
|
||||
console.log("read if cute :3");
|
||||
|
||||
export * as Components from "./components";
|
||||
export { Settings } from "./settings";
|
||||
|
||||
31
src/renderer/settings.ts
Normal file
31
src/renderer/settings.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { SettingsStore } from "shared/utils/SettingsStore";
|
||||
|
||||
import { Common } from "./vencord";
|
||||
|
||||
export const Settings = new SettingsStore(VencordDesktopNative.settings.get());
|
||||
Settings.addGlobalChangeListener((o, p) => VencordDesktopNative.settings.set(o, p));
|
||||
|
||||
export function useSettings() {
|
||||
const [, update] = Common.React.useReducer(x => x + 1, 0);
|
||||
|
||||
Common.React.useEffect(() => {
|
||||
Settings.addGlobalChangeListener(update);
|
||||
|
||||
return () => Settings.removeGlobalChangeListener(update);
|
||||
}, []);
|
||||
|
||||
return Settings.store;
|
||||
}
|
||||
|
||||
export function getValueAndOnChange(key: keyof typeof Settings.store) {
|
||||
return {
|
||||
value: Settings.store[key] as any,
|
||||
onChange: (value: any) => (Settings.store[key] = value)
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
export const localStorage = window.vcdLS = window.localStorage;
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
export const localStorage = (window.vcdLS = window.localStorage);
|
||||
|
||||
export const isFirstRun = (() => {
|
||||
const key = "VCD_FIRST_RUN";
|
||||
|
||||
13
src/renderer/vencord.ts
Normal file
13
src/renderer/vencord.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
// FIXME: this is terrible
|
||||
|
||||
const { Webpack, Plugins, Util } = Vencord;
|
||||
const { Common } = Webpack;
|
||||
const { plugins } = Plugins;
|
||||
|
||||
export { Common, Plugins, plugins, Util, Webpack };
|
||||
@@ -1,10 +1,29 @@
|
||||
export const GET_VENCORD_PRELOAD_FILE = "VCD_GET_VC_PRELOAD_FILE";
|
||||
export const GET_RENDERER_SCRIPT = "VCD_GET_RENDERER_SCRIPT";
|
||||
export const GET_RENDERER_STYLES = "VCD_GET_RENDERER_STYLES";
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
export const RELAUNCH = "VCD_RELAUNCH";
|
||||
export const FOCUS = "VC_FOCUS";
|
||||
export const enum IpcEvents {
|
||||
GET_VENCORD_PRELOAD_FILE = "VCD_GET_VC_PRELOAD_FILE",
|
||||
GET_VENCORD_RENDERER_SCRIPT = "VCD_GET_VC_RENDERER_SCRIPT",
|
||||
GET_RENDERER_SCRIPT = "VCD_GET_RENDERER_SCRIPT",
|
||||
GET_RENDERER_CSS_FILE = "VCD_GET_RENDERER_CSS_FILE",
|
||||
|
||||
export const SHOW_ITEM_IN_FOLDER = "VCD_SHOW_ITEM_IN_FOLDER";
|
||||
export const GET_SETTINGS = "VCD_GET_SETTINGS";
|
||||
export const SET_SETTINGS = "VCD_SET_SETTINGS";
|
||||
RELAUNCH = "VCD_RELAUNCH",
|
||||
FOCUS = "VCD_FOCUS",
|
||||
|
||||
GET_VERSION = "VCD_GET_VERSION",
|
||||
|
||||
SHOW_ITEM_IN_FOLDER = "VCD_SHOW_ITEM_IN_FOLDER",
|
||||
GET_SETTINGS = "VCD_GET_SETTINGS",
|
||||
SET_SETTINGS = "VCD_SET_SETTINGS",
|
||||
|
||||
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
|
||||
|
||||
UPDATER_GET_DATA = "VCD_UPDATER_GET_DATA",
|
||||
UPDATER_DOWNLOAD = "VCD_UPDATER_DOWNLOAD",
|
||||
UPDATE_IGNORE = "VCD_UPDATE_IGNORE",
|
||||
|
||||
CLOSE = "VCD_CLOSE"
|
||||
}
|
||||
|
||||
18
src/shared/browserWinProperties.ts
Normal file
18
src/shared/browserWinProperties.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import type { BrowserWindowConstructorOptions } from "electron";
|
||||
|
||||
export const SplashProps: BrowserWindowConstructorOptions = {
|
||||
transparent: true,
|
||||
frame: false,
|
||||
height: 350,
|
||||
width: 300,
|
||||
center: true,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
alwaysOnTop: true
|
||||
};
|
||||
@@ -1,4 +1,10 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { join } from "path";
|
||||
|
||||
export const STATIC_DIR = join(__dirname, "..", "..", "static");
|
||||
export const ICON_PATH = join(STATIC_DIR, "icon.png");
|
||||
export const STATIC_DIR = /* @__PURE__ */ join(__dirname, "..", "..", "static");
|
||||
export const ICON_PATH = /* @__PURE__ */ join(STATIC_DIR, "icon.png");
|
||||
|
||||
20
src/shared/settings.d.ts
vendored
Normal file
20
src/shared/settings.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import type { Rectangle } from "electron";
|
||||
|
||||
export interface Settings {
|
||||
maximized?: boolean;
|
||||
minimized?: boolean;
|
||||
windowBounds?: Rectangle;
|
||||
discordBranch?: "stable" | "canary" | "ptb";
|
||||
openLinksWithElectron?: boolean;
|
||||
vencordDir?: string;
|
||||
disableMinSize?: boolean;
|
||||
tray?: boolean;
|
||||
minimizeToTray?: boolean;
|
||||
skippedUpdate?: string;
|
||||
}
|
||||
147
src/shared/utils/SettingsStore.ts
Normal file
147
src/shared/utils/SettingsStore.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { LiteralUnion } from "type-fest";
|
||||
|
||||
// Resolves a possibly nested prop in the form of "some.nested.prop" to type of T.some.nested.prop
|
||||
type ResolvePropDeep<T, P> = P extends `${infer Pre}.${infer Suf}`
|
||||
? Pre extends keyof T
|
||||
? ResolvePropDeep<T[Pre], Suf>
|
||||
: any
|
||||
: P extends keyof T
|
||||
? T[P]
|
||||
: any;
|
||||
|
||||
/**
|
||||
* The SettingsStore allows you to easily create a mutable store that
|
||||
* has support for global and path-based change listeners.
|
||||
*/
|
||||
export class SettingsStore<T extends object> {
|
||||
private pathListeners = new Map<string, Set<(newData: any) => void>>();
|
||||
private globalListeners = new Set<(newData: T, path: string) => void>();
|
||||
|
||||
/**
|
||||
* The store object. Making changes to this object will trigger the applicable change listeners
|
||||
*/
|
||||
public declare store: T;
|
||||
/**
|
||||
* The plain data. Changes to this object will not trigger any change listeners
|
||||
*/
|
||||
public declare plain: T;
|
||||
|
||||
public constructor(plain: T) {
|
||||
this.plain = plain;
|
||||
this.store = this.makeProxy(plain);
|
||||
}
|
||||
|
||||
private makeProxy(object: any, root: T = object, path: string = "") {
|
||||
const self = this;
|
||||
|
||||
return new Proxy(object, {
|
||||
get(target, key: string) {
|
||||
const v = target[key];
|
||||
|
||||
if (typeof v === "object" && v !== null && !Array.isArray(v))
|
||||
return self.makeProxy(v, root, `${path}${path && "."}${key}`);
|
||||
|
||||
return v;
|
||||
},
|
||||
set(target, key: string, value) {
|
||||
if (target[key] === value) return true;
|
||||
|
||||
Reflect.set(target, key, value);
|
||||
const setPath = `${path}${path && "."}${key}`;
|
||||
|
||||
self.globalListeners.forEach(cb => cb(root, setPath));
|
||||
self.pathListeners.get(setPath)?.forEach(cb => cb(value));
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data of the store.
|
||||
* This will update this.store and this.plain (and old references to them will be stale! Avoid storing them in variables)
|
||||
*
|
||||
* Additionally, all global listeners (and those for pathToNotify, if specified) will be called with the new data
|
||||
* @param value New data
|
||||
* @param pathToNotify Optional path to notify instead of globally. Used to transfer path via ipc
|
||||
*/
|
||||
public setData(value: T, pathToNotify?: string) {
|
||||
this.plain = value;
|
||||
this.store = this.makeProxy(value);
|
||||
|
||||
if (pathToNotify) {
|
||||
let v = value;
|
||||
|
||||
const path = pathToNotify.split(".");
|
||||
for (const p of path) {
|
||||
if (!v) {
|
||||
console.warn(
|
||||
`Settings#setData: Path ${pathToNotify} does not exist in new data. Not dispatching update`
|
||||
);
|
||||
return;
|
||||
}
|
||||
v = v[p];
|
||||
}
|
||||
|
||||
this.pathListeners.get(pathToNotify)?.forEach(cb => cb(v));
|
||||
}
|
||||
|
||||
this.globalListeners.forEach(cb => cb(value, ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a global change listener, that will fire whenever any setting is changed
|
||||
*/
|
||||
public addGlobalChangeListener(cb: (data: T, path: string) => void) {
|
||||
this.globalListeners.add(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a scoped change listener that will fire whenever a setting matching the specified path is changed.
|
||||
*
|
||||
* For example if path is `"foo.bar"`, the listener will fire on
|
||||
* ```js
|
||||
* Setting.store.foo.bar = "hi"
|
||||
* ```
|
||||
* but not on
|
||||
* ```js
|
||||
* Setting.store.foo.baz = "hi"
|
||||
* ```
|
||||
* @param path
|
||||
* @param cb
|
||||
*/
|
||||
public addChangeListener<P extends LiteralUnion<keyof T, string>>(
|
||||
path: P,
|
||||
cb: (data: ResolvePropDeep<T, P>) => void
|
||||
) {
|
||||
const listeners = this.pathListeners.get(path as string) ?? new Set();
|
||||
listeners.add(cb);
|
||||
this.pathListeners.set(path as string, listeners);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a global listener
|
||||
* @see {@link addGlobalChangeListener}
|
||||
*/
|
||||
public removeGlobalChangeListener(cb: (data: T, path: string) => void) {
|
||||
this.globalListeners.delete(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a scoped listener
|
||||
* @see {@link addChangeListener}
|
||||
*/
|
||||
public removeChangeListener(path: LiteralUnion<keyof T, string>, cb: (data: any) => void) {
|
||||
const listeners = this.pathListeners.get(path as string);
|
||||
if (!listeners) return;
|
||||
|
||||
listeners.delete(cb);
|
||||
if (!listeners.size) this.pathListeners.delete(path as string);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,21 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns a new function that will only be called after the given delay.
|
||||
* Subsequent calls will cancel the previous timeout and start a new one from 0
|
||||
*
|
||||
* Useful for grouping multiple calls into one
|
||||
*/
|
||||
export function debounce<T extends Function>(func: T, delay = 300): T {
|
||||
let timeout: NodeJS.Timeout;
|
||||
return function (...args: any[]) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => { func(...args); }, delay);
|
||||
timeout = setTimeout(() => {
|
||||
func(...args);
|
||||
}, delay);
|
||||
} as any;
|
||||
}
|
||||
|
||||
23
src/shared/utils/monkeyPatch.ts
Normal file
23
src/shared/utils/monkeyPatch.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
type Func = (...args: any[]) => any;
|
||||
|
||||
export function monkeyPatch<O extends object>(
|
||||
object: O,
|
||||
key: keyof O,
|
||||
replacement: (original: Func, ...args: any[]) => any
|
||||
): void {
|
||||
const original = object[key] as Func;
|
||||
|
||||
const replacer = (object[key] = function (this: unknown, ...args: any[]) {
|
||||
return replacement.call(this, original, ...args);
|
||||
} as any);
|
||||
|
||||
Object.defineProperties(replacer, Object.getOwnPropertyDescriptors(original));
|
||||
replacer.toString = () => original.toString();
|
||||
replacer.$$original = original;
|
||||
}
|
||||
@@ -1,3 +1,14 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Wraps the given function so that it can only be called once
|
||||
* @param fn Function to wrap
|
||||
* @returns New function that can only be called once
|
||||
*/
|
||||
export function once<T extends Function>(fn: T): T {
|
||||
let called = false;
|
||||
return function (this: any, ...args: any[]) {
|
||||
|
||||
98
src/updater/main.ts
Normal file
98
src/updater/main.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { app, BrowserWindow, ipcMain, shell } from "electron";
|
||||
import { Settings } from "main/settings";
|
||||
import { githubGet, ReleaseData } from "main/utils/vencordLoader";
|
||||
import { join } from "path";
|
||||
import { SplashProps } from "shared/browserWinProperties";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
import { STATIC_DIR } from "shared/paths";
|
||||
|
||||
export interface UpdateData {
|
||||
currentVersion: string;
|
||||
latestVersion: string;
|
||||
release: ReleaseData;
|
||||
}
|
||||
|
||||
let updateData: UpdateData;
|
||||
|
||||
ipcMain.handle(IpcEvents.UPDATER_GET_DATA, () => updateData);
|
||||
ipcMain.handle(IpcEvents.UPDATER_DOWNLOAD, () => {
|
||||
const { assets } = updateData.release;
|
||||
const url = (() => {
|
||||
switch (process.platform) {
|
||||
case "win32":
|
||||
return assets.find(a => a.name.endsWith(".exe"))!.browser_download_url;
|
||||
case "darwin":
|
||||
return assets.find(a => a.name.endsWith(".dmg"))!.browser_download_url;
|
||||
case "linux":
|
||||
return updateData.release.html_url;
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${process.platform}`);
|
||||
}
|
||||
})();
|
||||
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcEvents.UPDATE_IGNORE, () => {
|
||||
Settings.store.skippedUpdate = updateData.latestVersion;
|
||||
});
|
||||
|
||||
function isOutdated(oldVersion: string, newVersion: string) {
|
||||
const oldParts = oldVersion.split(".");
|
||||
const newParts = newVersion.split(".");
|
||||
|
||||
if (oldParts.length !== newParts.length)
|
||||
throw new Error(`Incompatible version strings (old: ${oldVersion}, new: ${newVersion})`);
|
||||
|
||||
for (let i = 0; i < oldParts.length; i++) {
|
||||
const oldPart = Number(oldParts[i]);
|
||||
const newPart = Number(newParts[i]);
|
||||
|
||||
if (isNaN(oldPart) || isNaN(newPart))
|
||||
throw new Error(`Invalid version string (old: ${oldVersion}, new: ${newVersion})`);
|
||||
|
||||
if (oldPart < newPart) return true;
|
||||
if (oldPart > newPart) return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function checkUpdates() {
|
||||
// if (IS_DEV) return;
|
||||
try {
|
||||
const raw = await githubGet("/repos/Vencord/Desktop/releases/latest");
|
||||
const data = JSON.parse(raw.toString("utf-8")) as ReleaseData;
|
||||
|
||||
const oldVersion = app.getVersion();
|
||||
const newVersion = data.tag_name.replace(/^v/, "");
|
||||
if (Settings.store.skippedUpdate !== newVersion && isOutdated(oldVersion, newVersion)) {
|
||||
updateData = {
|
||||
currentVersion: oldVersion,
|
||||
latestVersion: newVersion,
|
||||
release: data
|
||||
};
|
||||
|
||||
openNewUpdateWindow();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("AppUpdater: Failed to check for updates\n", e);
|
||||
}
|
||||
}
|
||||
|
||||
function openNewUpdateWindow() {
|
||||
const win = new BrowserWindow({
|
||||
...SplashProps,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, "updaterPreload.js")
|
||||
}
|
||||
});
|
||||
|
||||
win.loadFile(join(STATIC_DIR, "updater.html"));
|
||||
}
|
||||
21
src/updater/preload.ts
Normal file
21
src/updater/preload.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
* Vencord Desktop, a desktop app aiming to give you a snappier Discord Experience
|
||||
* Copyright (c) 2023 Vendicated and Vencord contributors
|
||||
*/
|
||||
|
||||
import { contextBridge } from "electron";
|
||||
import { invoke } from "preload/typedIpcs";
|
||||
import { IpcEvents } from "shared/IpcEvents";
|
||||
|
||||
import type { UpdateData } from "./main";
|
||||
|
||||
contextBridge.exposeInMainWorld("Updater", {
|
||||
getData: () => invoke<UpdateData>(IpcEvents.UPDATER_GET_DATA),
|
||||
download: () => {
|
||||
invoke<void>(IpcEvents.UPDATER_DOWNLOAD);
|
||||
invoke<void>(IpcEvents.CLOSE);
|
||||
},
|
||||
ignore: () => invoke<void>(IpcEvents.UPDATE_IGNORE),
|
||||
close: () => invoke<void>(IpcEvents.CLOSE)
|
||||
});
|
||||
@@ -1,29 +1,48 @@
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
<style>
|
||||
* {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: hsl(223 6.7% 20.6%);
|
||||
border-radius: 6px;
|
||||
}
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
"Open Sans", "Helvetica Neue", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: white;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
"Open Sans", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
</style>
|
||||
.wrapper {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: hsl(223 6.7% 20.6%);
|
||||
border-radius: 8px;
|
||||
border: 1px solid hsl(220 6.5% 18%);
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgb(219, 222, 225);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 6em;
|
||||
height: 6em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<img
|
||||
draggable="false"
|
||||
src="https://cdn.discordapp.com/emojis/1024751291504791654.gif?size=512"
|
||||
alt="shiggy"
|
||||
role="presentation"
|
||||
/>
|
||||
<p>Loading Vencord Desktop...</p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
115
static/updater.html
Normal file
115
static/updater.html
Normal file
@@ -0,0 +1,115 @@
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
|
||||
"Open Sans", "Helvetica Neue", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: rgb(219, 222, 225);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
background-color: #313338;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #248046;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.5em;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
padding: 0.5em;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
font-weight: bold;
|
||||
transition: filter 0.2 ease-in-out;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
button:active {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: #248046;
|
||||
}
|
||||
|
||||
.red {
|
||||
background-color: #ed4245;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<section>
|
||||
<h1>Update Available</h1>
|
||||
<p>There's a new update for Vencord Desktop! Update now to get new fixes and features!</p>
|
||||
<p>
|
||||
Current: <span id="current"></span>
|
||||
<br />
|
||||
Latest: <span id="latest"></span>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<label id="disable-remind">
|
||||
<input type="checkbox" />
|
||||
<span>Do not remind again for </span>
|
||||
</label>
|
||||
|
||||
<div class="buttons">
|
||||
<button name="download" class="green">Download Update</button>
|
||||
<button name="close" class="red">Close</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script type="module">
|
||||
const data = await Updater.getData();
|
||||
document.getElementById("current").textContent = data.currentVersion;
|
||||
document.getElementById("latest").textContent = data.latestVersion;
|
||||
|
||||
document.querySelector("#disable-remind > span").textContent += data.latestVersion;
|
||||
|
||||
function checkDisableRemind() {
|
||||
const checkbox = document.querySelector("#disable-remind > input");
|
||||
if (checkbox.checked) {
|
||||
Updater.ignore();
|
||||
}
|
||||
}
|
||||
|
||||
const onClicks = {
|
||||
download() {
|
||||
checkDisableRemind();
|
||||
Updater.download();
|
||||
},
|
||||
close() {
|
||||
checkDisableRemind();
|
||||
Updater.close();
|
||||
}
|
||||
};
|
||||
|
||||
for (const name in onClicks) {
|
||||
document.querySelectorAll(`button[name="${name}"]`).forEach(button => {
|
||||
button.addEventListener("click", onClicks[name]);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user