vee's tray updates

This commit is contained in:
thororen 2026-10-03 16:35:13 -04:00
parent ebf7242051
commit e8a11aa0a2
No known key found for this signature in database
10 changed files with 115 additions and 148 deletions

View file

@ -73,8 +73,8 @@ window.VencordNative = {
updater: { updater: {
getRepo: async () => ({ ok: true, value: "https://github.com/Equicord/Equicord" }), getRepo: async () => ({ ok: true, value: "https://github.com/Equicord/Equicord" }),
getUpdates: async () => ({ ok: true, value: [] }), getUpdates: async () => ({ ok: true, value: [] }),
update: async () => ({ ok: true, value: false }), fetchUpdate: async () => ({ ok: true, value: false }),
rebuild: async () => ({ ok: true, value: true }), applyUpdate: async () => ({ ok: true, value: true }),
}, },
quickCss: { quickCss: {
@ -136,9 +136,4 @@ window.VencordNative = {
pluginHelpers: {} as any, pluginHelpers: {} as any,
csp: {} as any, csp: {} as any,
tray: {
setUpdateState: NOOP,
onCheckUpdates: NOOP,
onRepair: NOOP,
},
}; };

View file

@ -38,13 +38,13 @@ import { StartAt } from "@utils/types";
import { SettingsRouter } from "@webpack/common"; import { SettingsRouter } from "@webpack/common";
import { get as dsGet } from "./api/DataStore"; import { get as dsGet } from "./api/DataStore";
import { popNotice, showNotice } from "./api/Notices"; import { showNotice } from "./api/Notices";
import { NotificationData, showNotification } from "./api/Notifications"; import { NotificationData, showNotification } from "./api/Notifications";
import { initPluginManager, PMLogger, startAllPlugins } from "./api/PluginManager"; import { initPluginManager, PMLogger, startAllPlugins } from "./api/PluginManager";
import { PlainSettings, Settings, SettingsStore } from "./api/Settings"; import { PlainSettings, Settings, SettingsStore } from "./api/Settings";
import { areLocalSettingsDirty, getCloudSettings, getCloudSyncDirection, markLocalSettingsDirty, putCloudSettings, shouldCloudSync } from "./api/SettingsSync/cloudSync"; import { areLocalSettingsDirty, getCloudSettings, getCloudSyncDirection, markLocalSettingsDirty, putCloudSettings, shouldCloudSync } from "./api/SettingsSync/cloudSync";
import { relaunch } from "./utils/native"; import { relaunch } from "./utils/native";
import { checkForUpdates, isOutdated as getIsOutdated, update, UpdateLogger } from "./utils/updater"; import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
import { onceReady } from "./webpack"; import { onceReady } from "./webpack";
import { patches } from "./webpack/patchWebpack"; import { patches } from "./webpack/patchWebpack";
@ -137,7 +137,6 @@ async function runUpdateCheck() {
try { try {
const isOutdated = await checkForUpdates(); const isOutdated = await checkForUpdates();
if (IS_DISCORD_DESKTOP) VencordNative.tray.setUpdateState(isOutdated);
if (!isOutdated) return; if (!isOutdated) return;
if (Settings.autoUpdate) { if (Settings.autoUpdate) {
@ -168,43 +167,11 @@ async function runUpdateCheck() {
} }
} }
function initTrayIpc() {
if (IS_WEB || IS_UPDATER_DISABLED) return;
VencordNative.tray.onCheckUpdates(async () => {
try {
const isOutdated = await checkForUpdates();
VencordNative.tray.setUpdateState(isOutdated);
if (isOutdated) {
showNotice("An Equicord update is available!", "View Update", () => openSettingsTabModal(UpdaterTab!));
} else {
showNotice("No updates available, you're on the latest version!", "OK", popNotice);
}
} catch (err) {
UpdateLogger.error("Failed to check for updates from tray", err);
showNotice("Failed to check for updates, check the console for more info", "OK", popNotice);
}
});
VencordNative.tray.onRepair(async () => {
try {
await update();
relaunch();
} catch (err) {
UpdateLogger.error("Failed to repair Equicord", err);
}
});
VencordNative.tray.setUpdateState(getIsOutdated);
}
async function init() { async function init() {
await onceReady; await onceReady;
startAllPlugins(StartAt.WebpackReady); startAllPlugins(StartAt.WebpackReady);
syncSettings(); syncSettings();
initTrayIpc();
if (!IS_DEV && !IS_WEB && !IS_UPDATER_DISABLED) { if (!IS_DEV && !IS_WEB && !IS_UPDATER_DISABLED) {
runUpdateCheck(); runUpdateCheck();

View file

@ -8,6 +8,7 @@ import type { Settings } from "@api/Settings";
import type { CspRequestResult } from "@main/csp/manager"; import type { CspRequestResult } from "@main/csp/manager";
import type { PluginIpcMappings } from "@main/ipcPlugins"; import type { PluginIpcMappings } from "@main/ipcPlugins";
import { UserThemeHeader } from "@main/themes"; import { UserThemeHeader } from "@main/themes";
import { UpdateData } from "@main/updater";
import { IpcEvents } from "@shared/IpcEvents"; import { IpcEvents } from "@shared/IpcEvents";
import type { IpcRes } from "@utils/types"; import type { IpcRes } from "@utils/types";
import { ipcRenderer } from "electron/renderer"; import { ipcRenderer } from "electron/renderer";
@ -46,10 +47,10 @@ export default {
}, },
updater: { updater: {
getUpdates: () => invoke<IpcRes<Record<"hash" | "author" | "message", string>[]>>(IpcEvents.GET_UPDATES), getRepo: () => invoke<IpcRes<string>>(IpcEvents.UPDATER_GET_REPO),
update: () => invoke<IpcRes<boolean>>(IpcEvents.UPDATE), getUpdates: () => invoke<IpcRes<UpdateData[]>>(IpcEvents.UPDATER_LIST_UPDATES),
rebuild: () => invoke<IpcRes<boolean>>(IpcEvents.BUILD), fetchUpdate: () => invoke<IpcRes<boolean>>(IpcEvents.UPDATER_FETCH_UPDATE),
getRepo: () => invoke<IpcRes<string>>(IpcEvents.GET_REPO), applyUpdate: () => invoke<IpcRes<boolean>>(IpcEvents.UPDATER_APPLY_UPDATE),
}, },
settings: { settings: {
@ -101,11 +102,5 @@ export default {
invoke<CspRequestResult>(IpcEvents.CSP_REQUEST_ADD_OVERRIDE, url, directives, callerName), invoke<CspRequestResult>(IpcEvents.CSP_REQUEST_ADD_OVERRIDE, url, directives, callerName),
}, },
tray: {
setUpdateState: (available: boolean) => ipcRenderer.send(IpcEvents.SET_TRAY_UPDATE_STATE, available),
onCheckUpdates: (cb: () => void) => { ipcRenderer.on(IpcEvents.TRAY_CHECK_UPDATES, cb); },
onRepair: (cb: () => void) => { ipcRenderer.on(IpcEvents.TRAY_REPAIR, cb); },
},
pluginHelpers: PluginHelpers pluginHelpers: PluginHelpers
}; };

View file

@ -354,8 +354,8 @@ function ChangelogContent() {
if (!logged) { if (!logged) {
setChangelog([]); setChangelog([]);
showToast("Already up to date with repository", "message", { showToast("Already up to date with repository", "message", {
position: ToastPosition.BOTTOM, position: ToastPosition.BOTTOM,
}); });
} }
return; return;
} }
@ -381,16 +381,16 @@ function ChangelogContent() {
setRecentlyChecked(true); setRecentlyChecked(true);
showToast(`Found ${updates.value.length} commit${updates.value.length === 1 ? "" : "s"} from repository`, "success", { showToast(`Found ${updates.value.length} commit${updates.value.length === 1 ? "" : "s"} from repository`, "success", {
position: ToastPosition.BOTTOM, position: ToastPosition.BOTTOM,
}); });
} else { } else {
const logged = await ensureLocalUpdateLogged(); const logged = await ensureLocalUpdateLogged();
setRecentlyChecked(true); setRecentlyChecked(true);
showToast(logged showToast(logged
? "Logged commits from your latest update" ? "Logged commits from your latest update"
: "Repository is up to date with your local copy", logged ? "success" : "message", { : "Repository is up to date with your local copy", logged ? "success" : "message", {
position: ToastPosition.BOTTOM, position: ToastPosition.BOTTOM,
}); });
if (!logged) { if (!logged) {
setChangelog([]); setChangelog([]);
} }
@ -409,8 +409,8 @@ function ChangelogContent() {
// funny little error toast hopefully doesn't happen! // funny little error toast hopefully doesn't happen!
showToast("Could not fetch commits from repository", "failure", { showToast("Could not fetch commits from repository", "failure", {
position: ToastPosition.BOTTOM, position: ToastPosition.BOTTOM,
}); });
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@ -502,8 +502,8 @@ function ChangelogContent() {
await loadChangelogHistory(); await loadChangelogHistory();
setShowHistory(false); setShowHistory(false);
showToast("All logs have been cleared", "success", { showToast("All logs have been cleared", "success", {
position: ToastPosition.BOTTOM, position: ToastPosition.BOTTOM,
}); });
}, },
}); });
}} }}
@ -636,8 +636,8 @@ function ChangelogContent() {
), ),
); );
showToast("Log has been cleared", "success", { showToast("Log has been cleared", "success", {
position: ToastPosition.BOTTOM, position: ToastPosition.BOTTOM,
}); });
}, },
}); });
}} }}

View file

@ -4,27 +4,13 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { IpcEvents } from "@shared/IpcEvents"; import { gitHash } from "@shared/vencordUserAgent";
import { gitHashShort } from "@shared/vencordUserAgent"; import { app, BaseWindow, BrowserWindow, dialog, Menu, MenuItemConstructorOptions, shell } from "electron";
import { BrowserWindow, ipcMain, Menu, MenuItemConstructorOptions, shell } from "electron";
import aboutHtml from "file://about.html?minify"; import aboutHtml from "file://about.html?minify";
import updater from "./updater";
import { SETTINGS_DIR, THEMES_DIR } from "./utils/constants"; import { SETTINGS_DIR, THEMES_DIR } from "./utils/constants";
let cachedUpdateAvailable = false;
ipcMain.on(IpcEvents.SET_TRAY_UPDATE_STATE, (_, available: boolean) => {
cachedUpdateAvailable = available;
});
function getMainWindow(): BrowserWindow | undefined {
return BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0];
}
function sendToRenderer(event: IpcEvents): void {
getMainWindow()?.webContents.send(event);
}
function findInsertIndex(template: MenuItemConstructorOptions[]): number { function findInsertIndex(template: MenuItemConstructorOptions[]): number {
const openIndex = template.findIndex(item => { const openIndex = template.findIndex(item => {
const label = item.label?.toLowerCase() ?? ""; const label = item.label?.toLowerCase() ?? "";
@ -61,14 +47,11 @@ function openAboutWindow() {
return; return;
} }
const height = 750;
const width = height * (4 / 3);
aboutWindow = new BrowserWindow({ aboutWindow = new BrowserWindow({
center: true, center: true,
autoHideMenuBar: true, autoHideMenuBar: true,
height, height: 525,
width width: 900
}); });
aboutWindow.webContents.setWindowOpenHandler(({ url }) => { aboutWindow.webContents.setWindowOpenHandler(({ url }) => {
@ -83,7 +66,7 @@ function openAboutWindow() {
const aboutParams = aboutHtml const aboutParams = aboutHtml
.replaceAll("{{VERSION}}", VERSION) .replaceAll("{{VERSION}}", VERSION)
.replaceAll("{{GIT_HASH}}", gitHashShort); .replaceAll("{{GIT_HASH}}", gitHash); // change to gitHashShort if/when its added
const base64Html = Buffer.from(aboutParams).toString("base64"); const base64Html = Buffer.from(aboutParams).toString("base64");
aboutWindow.loadURL(`data:text/html;base64,${base64Html}`); aboutWindow.loadURL(`data:text/html;base64,${base64Html}`);
aboutWindow.on("closed", () => { aboutWindow.on("closed", () => {
@ -91,7 +74,9 @@ function openAboutWindow() {
}); });
} }
function createEquicordMenuItems(): MenuItemConstructorOptions[] { const notify = (window: BaseWindow, title: string, message: string) => dialog.showMessageBox(window, { title, message });
function createVencordMenuItems(): MenuItemConstructorOptions[] {
return [ return [
{ {
label: "Equicord", label: "Equicord",
@ -101,12 +86,28 @@ function createEquicordMenuItems(): MenuItemConstructorOptions[] {
click: () => openAboutWindow() click: () => openAboutWindow()
}, },
{ {
label: cachedUpdateAvailable ? "Update Equicord" : "Check for Updates", label: "Update Equicord",
click: () => sendToRenderer(IpcEvents.TRAY_CHECK_UPDATES) async click(_item, window) {
}, if (!window) return;
{
label: "Repair Equicord", try {
click: () => sendToRenderer(IpcEvents.TRAY_REPAIR) const updateAvailable = await updater.fetchUpdate();
if (!updateAvailable) {
return notify(window, "No Update Available", "You are already using the latest version of Equicord.");
}
const result = await updater.applyUpdate();
if (!result) {
return notify(window, "Update Failed", "Failed to apply the update for Equicord.");
}
await notify(window, "Update Successful", "Now relaunching Equicord to apply the update.");
app.relaunch();
app.exit();
} catch (e) {
notify(window, "Update Error", `An error occurred while updating Equicord.\n\n${String(e)}`);
}
}
}, },
{ type: "separator" }, { type: "separator" },
{ {
@ -130,8 +131,8 @@ export function patchTrayMenu(): void {
const alreadyPatched = template.some(item => item.label === "Equicord"); const alreadyPatched = template.some(item => item.label === "Equicord");
if (isTrayMenu(template) && !alreadyPatched) { if (isTrayMenu(template) && !alreadyPatched) {
const insertIndex = findInsertIndex(template); const insertIndex = findInsertIndex(template);
const equicordItems = createEquicordMenuItems(); const vencordItems = createVencordMenuItems();
template.splice(insertIndex, 0, ...equicordItems); template.splice(insertIndex, 0, ...vencordItems);
} }
return originalBuildFromTemplate.call(this, template); return originalBuildFromTemplate.call(this, template);

View file

@ -16,13 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { IpcEvents } from "@shared/IpcEvents";
import { execFile as cpExecFile } from "child_process"; import { execFile as cpExecFile } from "child_process";
import { ipcMain } from "electron";
import { join } from "path"; import { join } from "path";
import { promisify } from "util"; import { promisify } from "util";
import { serializeErrors } from "./common"; import { Updater } from ".";
const VENCORD_SRC_DIR = join(__dirname, ".."); const VENCORD_SRC_DIR = join(__dirname, "..");
const EQUICORD_DIR = join(__dirname, "../../"); const EQUICORD_DIR = join(__dirname, "../../");
@ -85,7 +83,11 @@ async function build() {
return !res.stderr.includes("Build failed"); return !res.stderr.includes("Build failed");
} }
ipcMain.handle(IpcEvents.GET_REPO, serializeErrors(getRepo)); const GitUpdater: Updater = {
ipcMain.handle(IpcEvents.GET_UPDATES, serializeErrors(calculateGitChanges)); getRepo,
ipcMain.handle(IpcEvents.UPDATE, serializeErrors(pull)); listUpdates: calculateGitChanges,
ipcMain.handle(IpcEvents.BUILD, serializeErrors(build)); fetchUpdate: pull,
applyUpdate: build
};
export default GitUpdater;

View file

@ -17,15 +17,14 @@
*/ */
import { fetchBuffer, fetchJson } from "@main/utils/http"; import { fetchBuffer, fetchJson } from "@main/utils/http";
import { IpcEvents } from "@shared/IpcEvents";
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent"; import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
import { ipcMain } from "electron";
import { writeFileSync } from "original-fs"; import { writeFileSync } from "original-fs";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
import gitRemote from "~git-remote"; import gitRemote from "~git-remote";
import { ASAR_FILE, serializeErrors } from "./common"; import { Updater } from ".";
import { ASAR_FILE } from "./common";
const API_BASE = `https://api.github.com/repos/${gitRemote}`; const API_BASE = `https://api.github.com/repos/${gitRemote}`;
let PendingUpdate: string | null = null; let PendingUpdate: string | null = null;
@ -41,8 +40,8 @@ async function githubGet<T = any>(endpoint: string) {
}); });
} }
async function calculateGitChanges() { async function listUpdates() {
const isOutdated = await fetchUpdates(); const isOutdated = await fetchUpdate();
if (!isOutdated) return []; if (!isOutdated) return [];
const data = await githubGet(`/compare/${gitHash}...HEAD`); const data = await githubGet(`/compare/${gitHash}...HEAD`);
@ -54,7 +53,7 @@ async function calculateGitChanges() {
})); }));
} }
async function fetchUpdates() { async function fetchUpdate() {
const data = await githubGet("/releases/latest"); const data = await githubGet("/releases/latest");
const hash = data.name.slice(data.name.lastIndexOf(" ") + 1); const hash = data.name.slice(data.name.lastIndexOf(" ") + 1);
@ -67,7 +66,7 @@ async function fetchUpdates() {
return true; return true;
} }
async function applyUpdates() { async function applyUpdate() {
if (!PendingUpdate) return true; if (!PendingUpdate) return true;
const data = await fetchBuffer(PendingUpdate); const data = await fetchBuffer(PendingUpdate);
@ -78,7 +77,11 @@ async function applyUpdates() {
return true; return true;
} }
ipcMain.handle(IpcEvents.GET_REPO, serializeErrors(() => `https://github.com/${gitRemote}`)); const HttpUpdater: Updater = {
ipcMain.handle(IpcEvents.GET_UPDATES, serializeErrors(calculateGitChanges)); getRepo: async () => `https://github.com/${gitRemote}`,
ipcMain.handle(IpcEvents.UPDATE, serializeErrors(fetchUpdates)); listUpdates,
ipcMain.handle(IpcEvents.BUILD, serializeErrors(applyUpdates)); fetchUpdate,
applyUpdate
};
export default HttpUpdater;

View file

@ -1,20 +1,8 @@
/* /*
* Vencord, a modification for Discord's desktop app * Vencord, a Discord client mod
* Copyright (c) 2022 Vendicated and contributors * Copyright (c) 2026 Vendicated and contributors
* * SPDX-License-Identifier: GPL-3.0-or-later
* This program is free software: you can redistribute it and/or modify */
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { IpcEvents } from "@shared/IpcEvents"; import { IpcEvents } from "@shared/IpcEvents";
import { ipcMain } from "electron"; import { ipcMain } from "electron";
@ -23,9 +11,27 @@ import gitRemote from "~git-remote";
import { serializeErrors } from "./common"; import { serializeErrors } from "./common";
if (!IS_UPDATER_DISABLED) { export type UpdateData = Record<"hash" | "message" | "author", string>;
require(IS_STANDALONE ? "./http" : "./git");
} else { export interface Updater {
ipcMain.handle(IpcEvents.GET_REPO, serializeErrors(() => `https://github.com/${gitRemote}`)); getRepo(): Promise<string>;
ipcMain.handle(IpcEvents.GET_UPDATES, serializeErrors(() => [])); listUpdates(): Promise<UpdateData[]>;
fetchUpdate(): Promise<boolean>;
applyUpdate(): Promise<boolean>;
} }
const updater: Updater = IS_UPDATER_DISABLED
? {
getRepo: async () => `https://github.com/${gitRemote}`,
listUpdates: async () => [],
fetchUpdate: async () => false,
applyUpdate: async () => false,
}
: require(IS_STANDALONE ? "./http" : "./git").default;
ipcMain.handle(IpcEvents.UPDATER_GET_REPO, serializeErrors(updater.getRepo));
ipcMain.handle(IpcEvents.UPDATER_LIST_UPDATES, serializeErrors(updater.listUpdates));
ipcMain.handle(IpcEvents.UPDATER_FETCH_UPDATE, serializeErrors(updater.fetchUpdate));
ipcMain.handle(IpcEvents.UPDATER_APPLY_UPDATE, serializeErrors(updater.applyUpdate));
export default updater;

View file

@ -32,10 +32,12 @@ export const enum IpcEvents {
SET_SETTINGS = "VencordSetSettings", SET_SETTINGS = "VencordSetSettings",
THEME_UPDATE = "VencordThemeUpdate", THEME_UPDATE = "VencordThemeUpdate",
OPEN_EXTERNAL = "VencordOpenExternal", OPEN_EXTERNAL = "VencordOpenExternal",
GET_UPDATES = "VencordGetUpdates",
GET_REPO = "VencordGetRepo", UPDATER_LIST_UPDATES = "VencordListUpdates",
UPDATE = "VencordUpdate", UPDATER_GET_REPO = "VencordGetRepo",
BUILD = "VencordBuild", UPDATER_FETCH_UPDATE = "VencordFetchUpdate",
UPDATER_APPLY_UPDATE = "VencordApplyUpdate",
OPEN_MONACO_EDITOR = "VencordOpenMonacoEditor", OPEN_MONACO_EDITOR = "VencordOpenMonacoEditor",
GET_MONACO_THEME = "VencordGetMonacoTheme", GET_MONACO_THEME = "VencordGetMonacoTheme",
@ -51,9 +53,5 @@ export const enum IpcEvents {
RENDERER_CSS_UPDATE = "VencordRendererCssUpdate", RENDERER_CSS_UPDATE = "VencordRendererCssUpdate",
PRELOAD_GET_RENDERER_JS = "VencordPreloadGetRendererJs", PRELOAD_GET_RENDERER_JS = "VencordPreloadGetRendererJs",
SET_TRAY_UPDATE_STATE = "VencordSetTrayUpdateState",
TRAY_REPAIR = "VencordTrayRepair",
TRAY_CHECK_UPDATES = "VencordTrayCheckUpdates",
TRAY_ABOUT = "VencordTrayAbout",
SUPPORTS_WINDOWS_MATERIAL = "VencordSupportsWindowsMaterial", SUPPORTS_WINDOWS_MATERIAL = "VencordSupportsWindowsMaterial",
} }

View file

@ -54,12 +54,12 @@ export async function checkForUpdates() {
export async function update() { export async function update() {
if (!isOutdated) return true; if (!isOutdated) return true;
const res = await Unwrap(VencordNative.updater.update()); const res = await Unwrap(VencordNative.updater.fetchUpdate());
if (res) { if (res) {
isOutdated = false; isOutdated = false;
if (!await Unwrap(VencordNative.updater.rebuild())) if (!await Unwrap(VencordNative.updater.applyUpdate()))
throw new Error("The Build failed. Please try manually building the new update"); throw new Error("Failed to apply the update.");
} }
return res; return res;