21 Commits

Author SHA1 Message Date
Vendicated
b09f035ea9 Bump to v1.5.1 2024-03-12 03:39:57 +01:00
Vendicated
44627b4cd4 autostart(linux): correctly preserve command line arguments 2024-03-12 03:38:37 +01:00
Vendicated
97267ef89a Rewrite http utils; properly handle (& retry on) network errors 2024-03-12 02:31:53 +01:00
Vendicated
132adcb733 Bump dependencies; electron v29 2024-03-12 01:33:50 +01:00
Vendicated
738fa588a6 Adapt spellcheck to new context menu api 2024-03-12 01:28:37 +01:00
Lewis Crichton
e63cff7a52 chore: remove unnecessary relations [skip ci] 2024-03-08 21:32:31 +00:00
V
777a0e83fe Update bug_report.md 2024-03-07 03:21:58 +01:00
Justin Chung
612d35c96f Add categories to Vesktop settings to reduce visual clutter (#379)
Co-authored-by: Vendicated <vendicated@riseup.net>
2024-02-18 15:49:42 +00:00
Filip Komárek
0acd3e160a README: add Scoop to unofficial sources (#386) 2024-02-10 23:12:24 +01:00
V
6993b2a7d4 Update bug_report.md 2024-01-30 22:07:05 +01:00
Noah
2bd8ca96df Workaround screenshare audio using microphone on debian bug (#360)
* feat(screenshare): add workaround

* refactor: review suggestions

* chore(deps): bump venmic

* chore(deps): bump venmic
2024-01-28 16:55:46 +01:00
Noah
4d82a6f41d refactor: stop venmic on stream stop (#353) 2024-01-23 17:37:57 +01:00
V
cb33f1834b Update release.yml 2024-01-19 21:55:30 +01:00
V
fb40f4b42d Update README.md 2024-01-19 03:39:27 +01:00
V
808eb56327 Update README.md 2024-01-19 03:38:15 +01:00
Vendicated
b636b65e55 implement vencord 'transparency' option 2024-01-19 01:07:27 +01:00
V
7d30dcdb47 Make popouts respect the menu bar visibility setting 2024-01-19 00:01:30 +01:00
V
4f1615ecb3 fix window flash when clicking notification 2024-01-18 23:49:26 +01:00
V
463cd6dc46 popout: fix titlebar close button 2024-01-18 21:33:17 +01:00
V
8c007476c3 popout: fix titlebar on windows when using native titlebar 2024-01-18 21:13:52 +01:00
github-actions[bot]
b20c77734c Metainfo for v1.5.0 (#335)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-16 02:15:56 +01:00
29 changed files with 1653 additions and 882 deletions

View File

@@ -7,26 +7,52 @@ assignees: ''
---
<!--
Please do not open issues for the following things. We cannot help you with them:
- "Vesktop.app is damaged" on MacOs ~ Fake issue created by crApple. Google how to fix it https://google.it/search?q=fix+app+is+damaged
- Screenshare does not start / is black ~ This is an issue with your desktop environment, specifically its xdg-desktop-portal
- Purely graphical glitches, like flickering, scaling issues, short whitescreens, etc ~ These are most likely issues with your GPU. try to disable hardware acceleration
- Vencord related issues ~ This is the Vesktop repo, not Vencord
- Getting logged out after restart ~ If you use DevTools, make sure you have NoDevtoolsWarning enabled. Otherwise try reinstalling Vesktop
-->
**Describe the bug**
A clear and concise description of what the bug is.
<!-- A clear and concise description of what the bug is. -->
**To Reproduce**
<!--
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
-->
**Expected behavior**
A clear and concise description of what you expected to happen.
<!-- A clear and concise description of what you expected to happen. -->
**Screenshots**
If applicable, add screenshots to help explain your problem.
<!-- If applicable, add screenshots to help explain your problem. -->
**Desktop (please complete the following information):**
- OS/Distro: [e.g. Windows / Fedora Linux / MacOs]
- Desktop Environment (linux only): [e.g. gnome, kde, sway]
- Version: [e.g. 22]
**Command line output**
<!-- Run vesktop from the command line. Include the relevant command line output here: -->
```
paste inside these backticks
```
**Additional context**
Add any other context about the problem here.
<!-- Add any other context about the problem here. -->

View File

@@ -49,4 +49,4 @@ jobs:
pnpm electron-builder --${{ matrix.platform }} --publish always
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK_: ${{ secrets.APPLE_SIGNING_CERT }}
CSC_LINK: ${{ secrets.APPLE_SIGNING_CERT }}

View File

@@ -1,51 +1,64 @@
# Vesktop
Vesktop is a cross platform desktop app aiming to give you a snappier Discord experience with [Vencord](https://github.com/Vendicated/Vencord) pre-installed
Vesktop is a custom Discord desktop app
**Main features**:
- Vencord preinstalled
- Much more lightweight and faster than the official Discord app
- Linux Screenshare with sound & wayland
- Much better privacy, since Discord has no access to your system
**Not yet supported**:
- Global Keybinds
Bug reports, feature requests & contributions are highly appreciated!!
- Global Keybinds
- see the [Roadmap](https://github.com/Vencord/Vesktop/issues/324)
![](https://github.com/Vencord/Vesktop/assets/45497981/8608a899-96a9-4027-9725-2cb02ba189fd)
![grafik](https://github.com/Vencord/Vesktop/assets/45497981/8701e5de-52c4-4346-a990-719cb971642e)
![](https://github.com/Vencord/Vesktop/assets/45497981/8701e5de-52c4-4346-a990-719cb971642e)
## Installing
### Windows
Download and run Vesktop-Setup-VERSION.exe from [releases](https://github.com/Vencord/Vesktop/releases/latest)
If you don't know the difference, pick the Installer.
- [Installer](https://vencord.dev/download/vesktop/amd64/windows)
- [Portable](https://vencord.dev/download/vesktop/amd64/windows-portable)
### Mac
Download and run Vesktop-VERSION.dmg from [releases](https://github.com/Vencord/Vesktop/releases/latest)
If you don't know the difference, pick amd64
- [amd64 / x86_64](https://vencord.dev/download/vesktop/amd64/dmg)
- [arm64 / aarch64](https://vencord.dev/download/vesktop/arm64/dmg)
### Linux
[![](https://dl.flathub.org/assets/badges/flathub-badge-en.svg)](https://flathub.org/apps/dev.vencord.Vesktop)
[![Download on Flathub](https://dl.flathub.org/assets/badges/flathub-badge-en.svg)](https://flathub.org/apps/dev.vencord.Vesktop)
#### Arch based
If you don't know the difference, pick amd64.
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)
- amd64 / x86_64
- [AppImage](https://vencord.dev/download/vesktop/amd64/appimage)
- [Ubuntu/Debian (.deb)](https://vencord.dev/download/vesktop/amd64/deb)
- [Fedora/RHEL (.rpm)](https://vencord.dev/download/vesktop/amd64/rpm)
- [tarball](https://vencord.dev/download/vesktop/amd64/tar)
- arm64 / aarch64
- [AppImage](https://vencord.dev/download/vesktop/arm64/appimage)
- [Ubuntu/Debian (.deb)](https://vencord.dev/download/vesktop/arm64/deb)
- [Fedora/RHEL (.rpm)](https://vencord.dev/download/vesktop/arm64/rpm)
- [tarball](https://vencord.dev/download/vesktop/arm64/tar)
#### Ubuntu/Debian based
#### Community packages
Download Vesktop-VERSION.deb from [releases](https://github.com/Vencord/Vesktop/releases/latest)
Below you can find unofficial packages created by the community. They are not officially supported by us, so before reporting issues, please first confirm the issue also happens on official builds. When in doubt, consult with their packager first. The flatpak and AppImage should work on any distro that [supports them](https://flatpak.org/setup/), so I recommend you just use those instead!
#### Fedora/RHEL based
- Arch Linux: [Vesktop on the Arch user repository](https://aur.archlinux.org/packages?K=vesktop)
- NixOS: https://nixos.wiki/wiki/Discord#Vesktop
- Windows - Scoop: https://scoop.sh/#/apps?q=Vesktop
Download Vesktop-VERSION.rpm from [releases](https://github.com/Vencord/Vesktop/releases/latest)
## Building from Source
#### Other
Either download Vesktop-VERSION.AppImage and just run it directly or grab Vesktop-VERSION.tar.gz, extract it somewhere and run `vesktop`.
If other packages are created, feel free to open an issue and we'll link them here.
## Building
Packaging will create builds in the dist/ folder. You can then install them like mentioned above or distribute them
Packaging will create builds in the dist/ folder
```sh
git clone https://github.com/Vencord/Vesktop
@@ -64,7 +77,3 @@ pnpm package --linux pacman
# Or package to a directory only
pnpm package:dir
```
## Motivation
The official Discord Desktop app is very resource heavy compared to Discord in your Browser. There are multiple alternative Electron apps (ArmCord, WebCord, probably more) that prove how much of a performance gain you can gain by using a custom app. ArmCord already supports Vencord but makes it pretty limited for us. Making our own standalone app gives us much more control.

View File

@@ -28,6 +28,22 @@
</screenshot>
</screenshots>
<releases>
<release version="1.5.0" date="2024-01-16" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v1.5.0</url>
<description>
<p>What's Changed</p>
<ul>
<li>fully renamed to Vesktop. You will likely have to login to Discord again. You might have to re-create your vesktop shortcut</li>
<li>added option to disable smooth scrolling by @ZirixCZ</li>
<li>added setting to disable hardware acceleration by @zt64</li>
<li>fixed adding connections</li>
<li>fixed / improved discord popouts</li>
<li>you can now use the custom discord titlebar on linux/mac</li>
<li>the splash window is now draggable</li>
<li>now signed on mac</li>
</ul>
</description>
</release>
<release version="0.4.4" date="2023-12-02" type="stable">
<url>https://github.com/Vencord/Vesktop/releases/tag/v0.4.4</url>
<description>
@@ -146,11 +162,7 @@
<control>voice</control>
<display_length compare="ge">760</display_length>
<display_length compare="le">1200</display_length>
<internet>always</internet>
</recommends>
<supports>
<internet>always</internet>
</supports>
<content_rating type="oars-1.1">
<content_attribute id="social-chat">intense</content_attribute>
<content_attribute id="social-audio">intense</content_attribute>
@@ -164,4 +176,4 @@
<keyword>Privacy</keyword>
<keyword>Mod</keyword>
</keywords>
</component>
</component>

View File

@@ -1,6 +1,6 @@
{
"name": "vesktop",
"version": "1.5.0",
"version": "1.5.1",
"private": true,
"description": "",
"keywords": [],
@@ -27,33 +27,33 @@
"arrpc": "github:OpenAsar/arrpc#98879cae0565e6fce34e4cb6f544bf42c6a7e7c8"
},
"optionalDependencies": {
"@vencord/venmic": "^3.2.3"
"@vencord/venmic": "^3.3.2"
},
"devDependencies": {
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@types/node": "^20.11.2",
"@types/react": "^18.2.48",
"@typescript-eslint/eslint-plugin": "^6.19.0",
"@typescript-eslint/parser": "^6.19.0",
"@types/node": "^20.11.26",
"@types/react": "^18.2.65",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vencord/types": "^0.1.2",
"dotenv": "^16.3.1",
"electron": "^28.1.3",
"electron-builder": "^24.9.1",
"esbuild": "^0.19.11",
"eslint": "^8.56.0",
"dotenv": "^16.4.5",
"electron": "^29.1.1",
"electron-builder": "^24.13.3",
"esbuild": "^0.20.1",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.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": "^5.1.3",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^3.0.0",
"prettier": "^3.2.2",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-unused-imports": "^3.1.0",
"prettier": "^3.2.5",
"source-map-support": "^0.5.21",
"tsx": "^4.7.0",
"type-fest": "^4.9.0",
"typescript": "^5.3.3",
"xml-formatter": "^3.6.0"
"tsx": "^4.7.1",
"type-fest": "^4.12.0",
"typescript": "^5.4.2",
"xml-formatter": "^3.6.2"
},
"packageManager": "pnpm@8.11.0",
"engines": {

1539
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,4 +8,4 @@ import "./utils/dotenv";
import { spawnNodeModuleBin } from "./utils/spawn.mjs";
spawnNodeModuleBin("electron", [".", ...(process.env.ELECTRON_LAUNCH_FLAGS?.split(" ") ?? [])]);
spawnNodeModuleBin("electron", [process.cwd(), ...(process.env.ELECTRON_LAUNCH_FLAGS?.split(" ") ?? [])]);

View File

@@ -5,7 +5,7 @@
*/
import { app } from "electron";
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "fs";
import { join } from "path";
interface AutoStart {
@@ -17,7 +17,16 @@ interface AutoStart {
function makeAutoStartLinux(): AutoStart {
const configDir = process.env.XDG_CONFIG_HOME || join(process.env.HOME!, ".config");
const dir = join(configDir, "autostart");
const file = join(dir, "vencord.desktop");
const file = join(dir, "vesktop.desktop");
// IM STUPID
const legacyName = join(dir, "vencord.desktop");
if (existsSync(legacyName)) renameSync(legacyName, file);
// "Quoting must be done by enclosing the argument between double quotes and escaping the double quote character,
// backtick character ("`"), dollar sign ("$") and backslash character ("\") by preceding it with an additional backslash character"
// https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables
const commandLine = process.argv.map(arg => '"' + arg.replace(/["$`\\]/g, "\\$&") + '"').join(" ");
return {
isEnabled: () => existsSync(file),
@@ -25,12 +34,11 @@ function makeAutoStartLinux(): AutoStart {
const desktopFile = `
[Desktop Entry]
Type=Application
Version=1.0
Name=Vencord
Comment=Vencord autostart script
Exec=${process.execPath}
Terminal=false
Name=Vesktop
Comment=Vesktop autostart script
Exec=${commandLine}
StartupNotify=false
Terminal=false
`.trim();
mkdirSync(dir, { recursive: true });

View File

@@ -48,14 +48,14 @@ export const DEFAULT_HEIGHT = 720;
export const DISCORD_HOSTNAMES = ["discord.com", "canary.discord.com", "ptb.discord.com"];
const UserAgents = {
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
const BrowserUserAgents = {
darwin: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
windows:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
};
export const UserAgent = UserAgents[process.platform] || UserAgents.windows;
export const BrowserUserAgent = BrowserUserAgents[process.platform] || BrowserUserAgents.windows;
export const enum MessageBoxChoice {
Default,

View File

@@ -21,6 +21,7 @@ import { VENCORD_FILES_DIR, VENCORD_QUICKCSS_FILE, VENCORD_THEMES_DIR } from "./
import { mainWin } from "./mainWindow";
import { Settings } from "./settings";
import { handle, handleSync } from "./utils/ipcWrappers";
import { PopoutWindows } from "./utils/popout";
import { isDeckGameMode, showGamePage } from "./utils/steamOS";
import { isValidVencordInstall } from "./utils/vencordLoader";
@@ -68,14 +69,16 @@ handle(IpcEvents.SHOW_ITEM_IN_FOLDER, (_, path) => {
});
handle(IpcEvents.FOCUS, () => {
if (process.platform === "win32") mainWin.minimize(); // Windows is weird
mainWin.restore();
mainWin.show();
mainWin.setSkipTaskbar(false);
});
handle(IpcEvents.CLOSE, e => {
(BrowserWindow.fromWebContents(e.sender) ?? e.sender).close();
handle(IpcEvents.CLOSE, (e, key?: string) => {
const popout = PopoutWindows.get(key!);
if (popout) return popout.close();
const win = BrowserWindow.fromWebContents(e.sender) ?? e.sender;
win.close();
});
handle(IpcEvents.MINIMIZE, e => {

View File

@@ -25,13 +25,13 @@ import { ICON_PATH } from "../shared/paths";
import { createAboutWindow } from "./about";
import { initArRPC } from "./arrpc";
import {
BrowserUserAgent,
DATA_DIR,
DEFAULT_HEIGHT,
DEFAULT_WIDTH,
MessageBoxChoice,
MIN_HEIGHT,
MIN_WIDTH,
UserAgent,
VENCORD_FILES_DIR
} from "./constants";
import { Settings, State, VencordSettings } from "./settings";
@@ -367,7 +367,7 @@ function createMainWindow() {
const { staticTitle, transparencyOption, enableMenu, customTitleBar } = Settings.store;
const { frameless } = VencordSettings.store;
const { frameless, transparent } = VencordSettings.store;
const noFrame = frameless === true || customTitleBar === true;
@@ -383,6 +383,10 @@ function createMainWindow() {
},
icon: ICON_PATH,
frame: !noFrame,
...(transparent && {
transparent: true,
backgroundColor: "#00000000"
}),
...(transparencyOption &&
transparencyOption !== "none" && {
backgroundColor: "#00000000",
@@ -422,7 +426,7 @@ function createMainWindow() {
initSettingsListeners(win);
initSpellCheck(win);
win.webContents.setUserAgent(UserAgent);
win.webContents.setUserAgent(BrowserUserAgent);
const subdomain =
Settings.store.discordBranch === "canary" || Settings.store.discordBranch === "ptb"

View File

@@ -5,41 +5,54 @@
*/
import { createWriteStream } from "fs";
import type { IncomingMessage } from "http";
import { get, RequestOptions } from "https";
import { finished } from "stream/promises";
import { Readable } from "stream";
import { pipeline } from "stream/promises";
import { setTimeout } from "timers/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
})
)
interface FetchieOptions {
retryOnNetworkError?: boolean;
}
export async function downloadFile(url: string, file: string, options: RequestInit = {}, fetchieOpts?: FetchieOptions) {
const res = await fetchie(url, options, fetchieOpts);
await pipeline(
// @ts-expect-error odd type error
Readable.fromWeb(res.body!),
createWriteStream(file, {
autoClose: true
})
);
}
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);
const ONE_MINUTE_MS = 1000 * 60;
resolve(res);
});
});
}
export async function simpleGet(url: string, options: RequestOptions = {}) {
const res = await simpleReq(url, options);
return new Promise<Buffer>((resolve, reject) => {
const chunks = [] as Buffer[];
res.once("error", reject);
res.on("data", chunk => chunks.push(chunk));
res.once("end", () => resolve(Buffer.concat(chunks)));
});
export async function fetchie(url: string, options?: RequestInit, { retryOnNetworkError }: FetchieOptions = {}) {
let res: Response | undefined;
try {
res = await fetch(url, options);
} catch (err) {
if (retryOnNetworkError) {
console.error("Failed to fetch", url + ".", "Gonna retry with backoff.");
for (let tries = 0, delayMs = 500; tries < 20; tries++, delayMs = Math.min(2 * delayMs, ONE_MINUTE_MS)) {
await setTimeout(delayMs);
try {
res = await fetch(url, options);
break;
} catch {}
}
}
if (!res) throw new Error(`Failed to fetch ${url}\n${err}`);
}
if (res.ok) return res;
let msg = `Got non-OK response for ${url}: ${res.status} ${res.statusText}`;
const reason = await res.text().catch(() => "");
if (reason) msg += `\n${reason}`;
throw new Error(msg);
}

View File

@@ -5,6 +5,7 @@
*/
import { BrowserWindow, BrowserWindowConstructorOptions } from "electron";
import { Settings } from "main/settings";
import { handleExternalUrl } from "./makeLinksOpenExternally";
@@ -36,7 +37,7 @@ const DEFAULT_POPOUT_OPTIONS: BrowserWindowConstructorOptions = {
backgroundColor: "#2f3136",
minWidth: MIN_POPOUT_WIDTH,
minHeight: MIN_POPOUT_HEIGHT,
frame: process.platform === "linux",
frame: Settings.store.customTitleBar !== true,
titleBarStyle: process.platform === "darwin" ? "hidden" : undefined,
trafficLightPosition:
process.platform === "darwin"
@@ -48,10 +49,11 @@ const DEFAULT_POPOUT_OPTIONS: BrowserWindowConstructorOptions = {
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
},
autoHideMenuBar: Settings.store.enableMenu
};
const PopoutWindows = new Map<string, BrowserWindow>();
export const PopoutWindows = new Map<string, BrowserWindow>();
function focusWindow(window: BrowserWindow) {
window.setAlwaysOnTop(true);
@@ -97,6 +99,8 @@ export function createOrFocusPopup(key: string, features: string) {
}
export function setupPopout(win: BrowserWindow, key: string) {
win.setMenuBarVisibility(false);
PopoutWindows.set(key, win);
/* win.webContents.on("will-navigate", (evt, url) => {

View File

@@ -5,11 +5,10 @@
*/
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";
import { downloadFile, fetchie } from "./http";
const API_BASE = "https://api.github.com";
@@ -31,27 +30,29 @@ export interface ReleaseData {
}
export async function githubGet(endpoint: string) {
const opts: RequestOptions = {
const opts: RequestInit = {
headers: {
Accept: "application/vnd.github+json",
"User-Agent": USER_AGENT
}
};
if (process.env.GITHUB_TOKEN) opts.headers!.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
if (process.env.GITHUB_TOKEN) (opts.headers! as any).Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
return simpleGet(API_BASE + endpoint, opts);
return fetchie(API_BASE + endpoint, opts, { retryOnNetworkError: true });
}
export async function downloadVencordFiles() {
const release = await githubGet("/repos/Vendicated/Vencord/releases/latest");
const { assets } = JSON.parse(release.toString("utf-8")) as ReleaseData;
const { assets }: ReleaseData = await release.json();
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)))
.map(({ name, browser_download_url }) =>
downloadFile(browser_download_url, join(VENCORD_FILES_DIR, name), {}, { retryOnNetworkError: true })
)
);
}

View File

@@ -4,11 +4,14 @@
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import type { PatchBay } from "@vencord/venmic";
import { app, ipcMain } from "electron";
import { join } from "path";
import { IpcEvents } from "shared/IpcEvents";
import { STATIC_DIR } from "shared/paths";
type LinkData = Parameters<PatchBay["link"]>[0];
let initialized = false;
let patchBay: import("@vencord/venmic").PatchBay | undefined;
let isGlibcxxToOld = false;
@@ -51,17 +54,39 @@ ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => {
: { ok: false, isGlibcxxToOld };
});
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[]) =>
obtainVenmic()?.link({
include: targets.map(target => ({ key: "application.name", value: target })),
exclude: [{ key: "application.process.id", value: getRendererAudioServicePid() }]
})
);
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[], workaround?: boolean) => {
const pid = getRendererAudioServicePid();
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, () =>
obtainVenmic()?.link({
exclude: [{ key: "application.process.id", value: getRendererAudioServicePid() }]
})
);
const data: LinkData = {
include: targets.map(target => ({ key: "application.name", value: target })),
exclude: [{ key: "application.process.id", value: pid }]
};
if (workaround) {
data.workaround = [
{ key: "application.process.id", value: pid },
{ key: "media.name", value: "RecordStream" }
];
}
return obtainVenmic()?.link(data);
});
ipcMain.handle(IpcEvents.VIRT_MIC_START_SYSTEM, (_, workaround?: boolean) => {
const pid = getRendererAudioServicePid();
const data: LinkData = {
exclude: [{ key: "application.process.id", value: pid }]
};
if (workaround) {
data.workaround = [
{ key: "application.process.id", value: pid },
{ key: "media.name", value: "RecordStream" }
];
}
return obtainVenmic()?.link(data);
});
ipcMain.handle(IpcEvents.VIRT_MIC_STOP, () => obtainVenmic()?.unlink());

View File

@@ -52,7 +52,7 @@ export const VesktopNative = {
},
win: {
focus: () => invoke<void>(IpcEvents.FOCUS),
close: () => invoke<void>(IpcEvents.CLOSE),
close: (key?: string) => invoke<void>(IpcEvents.CLOSE, key),
minimize: () => invoke<void>(IpcEvents.MINIMIZE),
maximize: () => invoke<void>(IpcEvents.MAXIMIZE)
},
@@ -63,8 +63,8 @@ export const VesktopNative = {
virtmic: {
list: () =>
invoke<{ ok: false; isGlibcxxToOld: boolean } | { ok: true; targets: string[] }>(IpcEvents.VIRT_MIC_LIST),
start: (targets: string[]) => invoke<void>(IpcEvents.VIRT_MIC_START, targets),
startSystem: () => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM),
start: (targets: string[], workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START, targets, workaround),
startSystem: (workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround),
stop: () => invoke<void>(IpcEvents.VIRT_MIC_STOP)
},
arrpc: {

View File

@@ -6,7 +6,7 @@
import "./screenSharePicker.css";
import { closeModal, Modals, openModal, useAwaiter } from "@vencord/types/utils";
import { closeModal, Margins, Modals, openModal, useAwaiter } from "@vencord/types/utils";
import { findStoreLazy, onceReady } from "@vencord/types/webpack";
import {
Button,
@@ -36,6 +36,7 @@ interface StreamSettings {
fps: StreamFps;
audio: boolean;
audioSource?: string;
workaround?: boolean;
}
export interface StreamPick extends StreamSettings {
@@ -83,11 +84,14 @@ addPatch({
if (isLinux) {
onceReady.then(() => {
FluxDispatcher.subscribe("VOICE_STATE_UPDATES", e => {
for (const state of e.voiceStates) {
if (state.userId === UserStore.getCurrentUser().id && state.oldChannelId && !state.channelId)
VesktopNative.virtmic.stop();
FluxDispatcher.subscribe("STREAM_CLOSE", ({ streamKey }: { streamKey: string }) => {
const owner = streamKey.split(":").at(-1);
if (owner !== UserStore.getCurrentUser().id) {
return;
}
VesktopNative.virtmic.stop();
});
});
}
@@ -104,9 +108,9 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
didSubmit = true;
if (v.audioSource && v.audioSource !== "None") {
if (v.audioSource === "Entire System") {
await VesktopNative.virtmic.startSystem();
await VesktopNative.virtmic.startSystem(v.workaround);
} else {
await VesktopNative.virtmic.start([v.audioSource]);
await VesktopNative.virtmic.start([v.audioSource], v.workaround);
}
}
resolve(v);
@@ -225,7 +229,9 @@ function StreamSettings({
{isLinux && (
<AudioSourcePickerLinux
audioSource={settings.audioSource}
workaround={settings.workaround}
setAudioSource={source => setSettings(s => ({ ...s, audioSource: source }))}
setWorkaround={workaround => setSettings(s => ({ ...s, workaround: workaround }))}
/>
)}
</Card>
@@ -235,10 +241,14 @@ function StreamSettings({
function AudioSourcePickerLinux({
audioSource,
setAudioSource
workaround,
setAudioSource,
setWorkaround
}: {
audioSource?: string;
workaround?: boolean;
setAudioSource(s: string): void;
setWorkaround(b: boolean): void;
}) {
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), {
fallbackValue: { ok: true, targets: [] }
@@ -252,7 +262,7 @@ function AudioSourcePickerLinux({
{!sources.ok &&
(sources.isGlibcxxToOld ? (
<Forms.FormText>
Failed to retrieve Audio Sources because your c++ library is too old to run venmic. If you would
Failed to retrieve Audio Sources because your C++ library is too old to run venmic. If you would
like to stream with Audio, see{" "}
<a href="https://gist.github.com/Vendicated/b655044ffbb16b2716095a448c6d827a" target="_blank">
this guide
@@ -273,6 +283,21 @@ function AudioSourcePickerLinux({
serialize={String}
/>
)}
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
<Switch
onChange={setWorkaround}
value={workaround ?? false}
note={
<>
Work around an issue that causes the microphone to be shared instead of the correct audio. Only
enable if you're experiencing this issue.
</>
}
>
Microphone Workaround
</Switch>
</section>
);
}

View File

@@ -1,201 +0,0 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import "./settings.css";
import { Margins } from "@vencord/types/utils";
import { Button, Forms, Select, Switch, Text, Toasts, useState } from "@vencord/types/webpack/common";
import { setBadge } from "renderer/appBadge";
import { useSettings } from "renderer/settings";
import { isMac } from "renderer/utils";
import { isTruthy } from "shared/utils/guards";
export default function SettingsUi() {
const Settings = useSettings();
const supportsWindowsTransparency = VesktopNative.app.supportsWindowsTransparency();
const { autostart } = VesktopNative;
const [autoStartEnabled, setAutoStartEnabled] = useState(autostart.isEnabled());
const allSwitches: Array<false | [keyof typeof Settings, string, string, boolean?, (() => boolean)?]> = [
[
"customTitleBar",
"Discord Titlebar",
"Use Discord's custom title bar instead of the native system one. Requires a full restart."
],
!isMac && ["tray", "Tray Icon", "Add a tray icon for Vesktop", true],
!isMac && [
"minimizeToTray",
"Minimize to tray",
"Hitting X will make Vesktop minimize to the tray instead of closing",
true,
() => Settings.tray ?? true
],
["arRPC", "Rich Presence", "Enables Rich Presence via arRPC", false],
[
"disableMinSize",
"Disable minimum window size",
"Allows you to make the window as small as your heart desires"
],
["staticTitle", "Static Title", 'Makes the window title "Vesktop" instead of changing to the current page'],
["enableMenu", "Enable Menu Bar", "Enables the application menu bar. Press ALT to toggle visibility."],
["disableSmoothScroll", "Disable smooth scrolling", "Disables smooth scrolling in Vesktop", false],
["hardwareAcceleration", "Hardware Acceleration", "Enable hardware acceleration", true],
["splashTheming", "Splash theming", "Adapt the splash window colors to your custom theme", false],
[
"openLinksWithElectron",
"Open Links in app (experimental)",
"Opens links in a new Vesktop window instead of your web browser"
],
["checkUpdates", "Check for updates", "Automatically check for Vesktop updates", true]
];
const switches = allSwitches.filter(isTruthy);
return (
<Forms.FormSection>
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
Vesktop Settings
</Text>
<Forms.FormTitle className={Margins.top16 + " " + Margins.bottom8}>Discord Branch</Forms.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}
/>
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
<Switch
value={autoStartEnabled}
onChange={async v => {
await autostart[v ? "enable" : "disable"]();
setAutoStartEnabled(v);
}}
note="Automatically start Vesktop on computer start-up"
>
Start With System
</Switch>
<Switch
value={Settings.appBadge ?? true}
onChange={v => {
Settings.appBadge = v;
if (v) setBadge();
else VesktopNative.app.setBadgeCount(0);
}}
note="Show mention badge on the app icon"
>
Notification Badge
</Switch>
{switches.map(([key, text, note, def, predicate]) => (
<Switch
value={(Settings[key as any] ?? def ?? false) && predicate?.() !== false}
disabled={predicate && !predicate()}
onChange={v => (Settings[key as any] = v)}
note={note}
key={key}
>
{text}
</Switch>
))}
{supportsWindowsTransparency && (
<>
<Forms.FormTitle className={Margins.top16 + " " + Margins.bottom8}>
Transparency Options
</Forms.FormTitle>
<Forms.FormText className={Margins.bottom8}>
Requires a full restart. You will need a theme that supports transparency for this to work.
</Forms.FormText>
<Select
placeholder="None"
options={[
{
label: "None",
value: "none",
default: true
},
{
label: "Mica (incorporates system theme + desktop wallpaper to paint the background)",
value: "mica"
},
{ label: "Tabbed (variant of Mica with stronger background tinting)", value: "tabbed" },
{
label: "Acrylic (blurs the window behind Vesktop for a translucent background)",
value: "acrylic"
}
]}
closeOnSelect={true}
select={v => (Settings.transparencyOption = v)}
isSelected={v => v === Settings.transparencyOption}
serialize={s => s}
/>
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
</>
)}
<Forms.FormTitle>Vencord Location</Forms.FormTitle>
<Forms.FormText>
Vencord files are loaded from{" "}
{Settings.vencordDir ? (
<a
href="about:blank"
onClick={e => {
e.preventDefault();
VesktopNative.fileManager.showItemInFolder(Settings.vencordDir!);
}}
>
{Settings.vencordDir}
</a>
) : (
"the default location"
)}
</Forms.FormText>
<div className="vcd-location-btns">
<Button
size={Button.Sizes.SMALL}
onClick={async () => {
const choice = await VesktopNative.fileManager.selectVencordDir();
switch (choice) {
case "cancelled":
return;
case "invalid":
Toasts.show({
message:
"You did not choose a valid Vencord install. Make sure you're selecting the dist dir!",
id: Toasts.genId(),
type: Toasts.Type.FAILURE
});
return;
}
Settings.vencordDir = choice;
}}
>
Change
</Button>
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.RED}
onClick={() => (Settings.vencordDir = void 0)}
>
Reset
</Button>
</div>
</Forms.FormSection>
);
}

View File

@@ -5,4 +5,3 @@
*/
export * as ScreenShare from "./ScreenSharePicker";
export { default as Settings } from "./Settings";

View File

@@ -0,0 +1,26 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { Switch, useState } from "@vencord/types/webpack/common";
import { SettingsComponent } from "./Settings";
export const AutoStartToggle: SettingsComponent = () => {
const [autoStartEnabled, setAutoStartEnabled] = useState(VesktopNative.autostart.isEnabled());
return (
<Switch
value={autoStartEnabled}
onChange={async v => {
await VesktopNative.autostart[v ? "enable" : "disable"]();
setAutoStartEnabled(v);
}}
note="Automatically start Vesktop on computer start-up"
>
Start With System
</Switch>
);
};

View File

@@ -0,0 +1,26 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { Select } from "@vencord/types/webpack/common";
import { SettingsComponent } from "./Settings";
export const DiscordBranchPicker: SettingsComponent = ({ settings }) => {
return (
<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}
/>
);
};

View File

@@ -0,0 +1,26 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { Switch } from "@vencord/types/webpack/common";
import { setBadge } from "renderer/appBadge";
import { SettingsComponent } from "./Settings";
export const NotificationBadgeToggle: SettingsComponent = ({ settings }) => {
return (
<Switch
value={settings.appBadge ?? true}
onChange={v => {
settings.appBadge = v;
if (v) setBadge();
else VesktopNative.app.setBadgeCount(0);
}}
note="Show mention badge on the app icon"
>
Notification Badge
</Switch>
);
};

View File

@@ -0,0 +1,170 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import "./settings.css";
import { Forms, Switch, Text } from "@vencord/types/webpack/common";
import { ComponentType } from "react";
import { Settings, useSettings } from "renderer/settings";
import { isMac, isWindows } from "renderer/utils";
import { AutoStartToggle } from "./AutoStartToggle";
import { DiscordBranchPicker } from "./DiscordBranchPicker";
import { NotificationBadgeToggle } from "./NotificationBadgeToggle";
import { VencordLocationPicker } from "./VencordLocationPicker";
import { WindowsTransparencyControls } from "./WindowsTransparencyControls";
interface BooleanSetting {
key: keyof typeof Settings.store;
title: string;
description: string;
defaultValue: boolean;
disabled?(): boolean;
invisible?(): boolean;
}
export type SettingsComponent = ComponentType<{ settings: typeof Settings.store }>;
const SettingsOptions: Record<string, Array<BooleanSetting | SettingsComponent>> = {
"Discord Branch": [DiscordBranchPicker],
"System Startup & Performance": [
AutoStartToggle,
{
key: "hardwareAcceleration",
title: "Hardware Acceleration",
description: "Enable hardware acceleration",
defaultValue: true
}
],
"User Interface": [
{
key: "customTitleBar",
title: "Discord Titlebar",
description: "Use Discord's custom title bar instead of the native system one. Requires a full restart.",
defaultValue: isWindows
},
{
key: "staticTitle",
title: "Static Title",
description: 'Makes the window title "Vesktop" instead of changing to the current page',
defaultValue: false
},
{
key: "enableMenu",
title: "Enable Menu Bar",
description: "Enables the application menu bar. Press ALT to toggle visibility.",
defaultValue: false,
disabled: () => Settings.store.customTitleBar ?? isWindows
},
{
key: "splashTheming",
title: "Splash theming",
description: "Adapt the splash window colors to your custom theme",
defaultValue: false
},
WindowsTransparencyControls
],
Behaviour: [
{
key: "tray",
title: "Tray Icon",
description: "Add a tray icon for Vesktop",
defaultValue: true,
invisible: () => isMac
},
{
key: "minimizeToTray",
title: "Minimize to tray",
description: "Hitting X will make Vesktop minimize to the tray instead of closing",
defaultValue: true,
invisible: () => isMac,
disabled: () => Settings.store.tray === false
},
{
key: "disableMinSize",
title: "Disable minimum window size",
description: "Allows you to make the window as small as your heart desires",
defaultValue: false
},
{
key: "disableSmoothScroll",
title: "Disable smooth scrolling",
description: "Disables smooth scrolling",
defaultValue: false
}
],
"Notifications & Updates": [
NotificationBadgeToggle,
{
key: "checkUpdates",
title: "Check for updates",
description: "Automatically check for Vesktop updates",
defaultValue: true
}
],
Miscelleanous: [
{
key: "arRPC",
title: "Rich Presence",
description: "Enables Rich Presence via arRPC",
defaultValue: false
},
{
key: "openLinksWithElectron",
title: "Open Links in app (experimental)",
description: "Opens links in a new Vesktop window instead of your web browser",
defaultValue: false
}
],
"Vencord Location": [VencordLocationPicker]
};
function SettingsSections() {
const Settings = useSettings();
const sections = Object.entries(SettingsOptions).map(([title, settings]) => (
<Forms.FormSection
title={title}
key={title}
className="vcd-settings-section"
titleClassName="vcd-settings-title"
>
{settings.map(Setting => {
if (typeof Setting === "function") return <Setting settings={Settings} />;
const { defaultValue, title, description, key, disabled, invisible } = Setting;
if (invisible?.()) return null;
return (
<Switch
value={Settings[key as any] ?? defaultValue}
onChange={v => (Settings[key as any] = v)}
note={description}
disabled={disabled?.()}
key={key}
>
{title}
</Switch>
);
})}
</Forms.FormSection>
));
return <>{sections}</>;
}
export default function SettingsUi() {
return (
<Forms.FormSection>
<Text variant="heading-lg/semibold" style={{ color: "var(--header-primary)" }} tag="h2">
Vesktop Settings
</Text>
<SettingsSections />
</Forms.FormSection>
);
}

View File

@@ -0,0 +1,62 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { Button, Forms, Toasts } from "@vencord/types/webpack/common";
import { SettingsComponent } from "./Settings";
export const VencordLocationPicker: SettingsComponent = ({ settings }) => {
return (
<>
<Forms.FormText>
Vencord files are loaded from{" "}
{settings.vencordDir ? (
<a
href="about:blank"
onClick={e => {
e.preventDefault();
VesktopNative.fileManager.showItemInFolder(settings.vencordDir!);
}}
>
{settings.vencordDir}
</a>
) : (
"the default location"
)}
</Forms.FormText>
<div className="vcd-location-btns">
<Button
size={Button.Sizes.SMALL}
onClick={async () => {
const choice = await VesktopNative.fileManager.selectVencordDir();
switch (choice) {
case "cancelled":
return;
case "invalid":
Toasts.show({
message:
"You did not choose a valid Vencord install. Make sure you're selecting the dist dir!",
id: Toasts.genId(),
type: Toasts.Type.FAILURE
});
return;
}
settings.vencordDir = choice;
}}
>
Change
</Button>
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.RED}
onClick={() => (settings.vencordDir = void 0)}
>
Reset
</Button>
</div>
</>
);
};

View File

@@ -0,0 +1,49 @@
/*
* SPDX-License-Identifier: GPL-3.0
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
* Copyright (c) 2023 Vendicated and Vencord contributors
*/
import { Margins } from "@vencord/types/utils";
import { Forms, Select } from "@vencord/types/webpack/common";
import { SettingsComponent } from "./Settings";
export const WindowsTransparencyControls: SettingsComponent = ({ settings }) => {
if (!VesktopNative.app.supportsWindowsTransparency()) return null;
return (
<>
<Forms.FormTitle className={Margins.top16 + " " + Margins.bottom8}>Transparency Options</Forms.FormTitle>
<Forms.FormText className={Margins.bottom8}>
Requires a full restart. You will need a theme that supports transparency for this to work.
</Forms.FormText>
<Select
placeholder="None"
options={[
{
label: "None",
value: "none",
default: true
},
{
label: "Mica (incorporates system theme + desktop wallpaper to paint the background)",
value: "mica"
},
{ label: "Tabbed (variant of Mica with stronger background tinting)", value: "tabbed" },
{
label: "Acrylic (blurs the window behind Vesktop for a translucent background)",
value: "acrylic"
}
]}
closeOnSelect={true}
select={v => (settings.transparencyOption = v)}
isSelected={v => v === settings.transparencyOption}
serialize={s => s}
/>
<Forms.FormDivider className={Margins.top16 + " " + Margins.bottom16} />
</>
);
};

View File

@@ -4,3 +4,11 @@
gap: 0.5em;
margin-top: 0.5em;
}
.vcd-settings-section {
margin-top: 1.5rem;
}
.vcd-settings-title {
margin-bottom: 0.5rem;
}

View File

@@ -15,7 +15,7 @@ export * as Components from "./components";
import { findByPropsLazy } from "@vencord/types/webpack";
import { FluxDispatcher } from "@vencord/types/webpack/common";
import SettingsUi from "./components/Settings";
import SettingsUi from "./components/settings/Settings";
import { Settings } from "./settings";
export { Settings };

View File

@@ -6,7 +6,7 @@
import { addContextMenuPatch } from "@vencord/types/api/ContextMenu";
import { findStoreLazy } from "@vencord/types/webpack";
import { ContextMenu, FluxDispatcher, Menu } from "@vencord/types/webpack/common";
import { FluxDispatcher, Menu, useStateFromStores } from "@vencord/types/webpack/common";
import { addPatch } from "./shared";
@@ -46,7 +46,8 @@ addPatch({
}
});
addContextMenuPatch("textarea-context", children => () => {
addContextMenuPatch("textarea-context", children => {
const spellCheckEnabled = useStateFromStores([SpellCheckStore], () => SpellCheckStore.isEnabled());
const hasCorrections = Boolean(word && corrections?.length);
children.push(
@@ -71,11 +72,9 @@ addContextMenuPatch("textarea-context", children => () => {
<Menu.MenuCheckboxItem
id="vcd-spellcheck-enabled"
label="Enable Spellcheck"
checked={SpellCheckStore.isEnabled()}
checked={spellCheckEnabled}
action={() => {
FluxDispatcher.dispatch({ type: "SPELLCHECK_TOGGLE" });
// Haven't found a good way to update state, so just close for now 🤷‍♀️
ContextMenu.close();
}}
/>
</Menu.MenuGroup>

View File

@@ -81,7 +81,7 @@ export async function checkUpdates() {
try {
const raw = await githubGet("/repos/Vencord/Vesktop/releases/latest");
const data = JSON.parse(raw.toString("utf-8")) as ReleaseData;
const data: ReleaseData = await raw.json();
const oldVersion = app.getVersion();
const newVersion = data.tag_name.replace(/^v/, "");