From c19827a0e53f886798f1209e184da125e82d9e01 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 7 Jun 2025 00:06:01 +0200 Subject: [PATCH 1/4] UserScript: disable theme ui, instead recommend Stylus --- browser/VencordNativeStub.ts | 17 +++++++++++------ scripts/build/build.mjs | 1 + scripts/build/buildWeb.mjs | 2 ++ src/components/VencordSettings/ThemesTab.tsx | 19 ++++++++++++++++++- src/globals.d.ts | 3 ++- src/utils/quickCss.ts | 8 ++++++-- src/utils/web.ts | 8 ++++++++ 7 files changed, 48 insertions(+), 10 deletions(-) diff --git a/browser/VencordNativeStub.ts b/browser/VencordNativeStub.ts index 79f0f2cd..9080f644 100644 --- a/browser/VencordNativeStub.ts +++ b/browser/VencordNativeStub.ts @@ -20,16 +20,13 @@ /// import monacoHtmlLocal from "file://monacoWin.html?minify"; -import monacoHtmlCdn from "file://../src/main/monacoWin.html?minify"; import * as DataStore from "../src/api/DataStore"; -import { debounce } from "../src/utils"; +import { debounce, localStorage } from "../src/utils"; import { EXTENSION_BASE_URL } from "../src/utils/web-metadata"; import { getTheme, Theme } from "../src/utils/discord"; import { getThemeInfo } from "../src/main/themes"; import { Settings } from "../src/Vencord"; - -// Discord deletes this so need to store in variable -const { localStorage } = window; +import { getStylusWebStoreUrl } from "@utils/web"; // listeners for ipc.on const cssListeners = new Set<(css: string) => void>(); @@ -77,6 +74,14 @@ window.VencordNative = { addThemeChangeListener: NOOP, openFile: NOOP_ASYNC, async openEditor() { + if (IS_USERSCRIPT) { + const shouldOpenWebStore = confirm("QuickCSS is not supported on the Userscript. You can instead use the Stylus extension.\n\nDo you want to open the Stylus web store page?"); + if (shouldOpenWebStore) { + window.open(getStylusWebStoreUrl(), "_blank"); + } + return; + } + const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`; const win = open("about:blank", "VencordQuickCss", features); if (!win) { @@ -92,7 +97,7 @@ window.VencordNative = { ? "vs-light" : "vs-dark"; - win.document.write(IS_EXTENSION ? monacoHtmlLocal : monacoHtmlCdn); + win.document.write(monacoHtmlLocal); }, }, diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 7d21cd24..0ee24a31 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -31,6 +31,7 @@ const defines = stringifyValues({ IS_UPDATER_DISABLED, IS_WEB: false, IS_EXTENSION: false, + IS_USERSCRIPT: false, VERSION, BUILD_TIMESTAMP }); diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index 824194cf..b22df8ab 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -43,6 +43,7 @@ const commonOptions = { define: stringifyValues({ IS_WEB: true, IS_EXTENSION: false, + IS_USERSCRIPT: false, IS_STANDALONE: true, IS_DEV, IS_REPORTER, @@ -98,6 +99,7 @@ const buildConfigs = [ inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])], define: { ...commonOptions.define, + IS_USERSCRIPT: "true", window: "unsafeWindow", }, outfile: "dist/Vencord.user.js", diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index c507ef88..0c66ec5d 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -30,6 +30,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { showItemInFolder } from "@utils/native"; import { useAwaiter } from "@utils/react"; +import { getStylusWebStoreUrl } from "@utils/web"; import { findLazy } from "@webpack"; import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common"; import type { ComponentType, Ref, SyntheticEvent } from "react"; @@ -384,4 +385,20 @@ export function CspErrorCard() { ); } -export default wrapTab(ThemesTab, "Themes"); +function UserscriptThemesTab() { + return ( + + + Themes are not supported on the Userscript! + + + You can instead install themes with the Stylus extension! + + + + ); +} + +export default IS_USERSCRIPT + ? wrapTab(UserscriptThemesTab, "Themes") + : wrapTab(ThemesTab, "Themes"); diff --git a/src/globals.d.ts b/src/globals.d.ts index 4456564c..8dd2bd8d 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -29,11 +29,12 @@ declare global { * replace: "IS_WEB?foo:bar" * // GOOD * replace: IS_WEB ? "foo" : "bar" - * // also good + * // also okay * replace: `${IS_WEB}?foo:bar` */ export var IS_WEB: boolean; export var IS_EXTENSION: boolean; + export var IS_USERSCRIPT: boolean; export var IS_STANDALONE: boolean; export var IS_UPDATER_DISABLED: boolean; export var IS_DEV: boolean; diff --git a/src/utils/quickCss.ts b/src/utils/quickCss.ts index f32ae7e6..b2da24de 100644 --- a/src/utils/quickCss.ts +++ b/src/utils/quickCss.ts @@ -39,7 +39,7 @@ async function initSystemValues() { createStyle("vencord-os-theme-values").textContent = `:root{${variables}}`; } -export async function toggle(isEnabled: boolean) { +async function toggle(isEnabled: boolean) { if (!style) { if (isEnabled) { style = createStyle("vencord-custom-css"); @@ -91,6 +91,8 @@ async function initThemes() { } document.addEventListener("DOMContentLoaded", () => { + if (IS_USERSCRIPT) return; + initSystemValues(); initThemes(); @@ -103,9 +105,11 @@ document.addEventListener("DOMContentLoaded", () => { if (!IS_WEB) { VencordNative.quickCss.addThemeChangeListener(initThemes); } -}); +}, { once: true }); export function initQuickCssThemeStore() { + if (IS_USERSCRIPT) return; + initThemes(); let currentTheme = ThemeStore.theme; diff --git a/src/utils/web.ts b/src/utils/web.ts index 5c46aec0..c65005a4 100644 --- a/src/utils/web.ts +++ b/src/utils/web.ts @@ -53,3 +53,11 @@ export function chooseFile(mimeTypes: string) { setImmediate(() => document.body.removeChild(input)); }); } + +export function getStylusWebStoreUrl() { + const isChromium = (navigator as any).userAgentData?.brands?.some(b => b.brand === "Chromium"); + + return isChromium + ? "https://chromewebstore.google.com/detail/stylus/clngdbkpkpeebahjckkjfobafhncgmne" + : "https://addons.mozilla.org/firefox/addon/styl-us/"; +} From 9430803f36d19fdbb58090c4638554d77c8563d9 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 6 Jun 2025 19:11:06 -0300 Subject: [PATCH 2/4] TypingTweaks: Fix typing avatars and names disappearing --- src/plugins/typingTweaks/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx index 22013992..0aa8dba7 100644 --- a/src/plugins/typingTweaks/index.tsx +++ b/src/plugins/typingTweaks/index.tsx @@ -104,7 +104,7 @@ export default definePlugin({ }, { // Changes the indicator to keep the user object when creating the list of typing users - match: /\.map\((\i)=>\i\.\i\.getName\(\i,\i\.id,\1\)\)/, + match: /\.map\((\i)=>\i\.\i\.getName\(\i(?:\.guild_id)?,\i\.id,\1\)\)/, replace: "" }, { From 47856a26f1d2b4b0f50a1851c5f4690747629c56 Mon Sep 17 00:00:00 2001 From: Vending Machine Date: Sat, 7 Jun 2025 00:46:49 +0200 Subject: [PATCH 3/4] Updater: fix network errors triggering popups (#3436) --- src/Vencord.ts | 6 +++- src/main/updater/common.ts | 5 ++- src/main/updater/http.ts | 28 ++++++++------- src/main/utils/extensions.ts | 7 ++-- src/main/utils/http.ts | 70 ++++++++++++++++++++++++++++++++++++ src/main/utils/simpleGet.ts | 37 ------------------- 6 files changed, 98 insertions(+), 55 deletions(-) create mode 100644 src/main/utils/http.ts delete mode 100644 src/main/utils/simpleGet.ts diff --git a/src/Vencord.ts b/src/Vencord.ts index 48ecce97..f18c7347 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -134,7 +134,11 @@ async function init() { if (!IS_WEB && !IS_UPDATER_DISABLED) { runUpdateCheck(); - setInterval(runUpdateCheck, 1000 * 60 * 30); // 30 minutes + + // this tends to get really annoying, so only do this if the user has auto-update without notification enabled + if (Settings.autoUpdate && !Settings.autoUpdateNotification) { + setInterval(runUpdateCheck, 1000 * 60 * 30); // 30 minutes + } } if (IS_DEV) { diff --git a/src/main/updater/common.ts b/src/main/updater/common.ts index 41b9837c..1a0b8da9 100644 --- a/src/main/updater/common.ts +++ b/src/main/updater/common.ts @@ -35,7 +35,10 @@ export function serializeErrors(func: (...args: any[]) => any) { ok: false, error: e instanceof Error ? { // prototypes get lost, so turn error into plain object - ...e + ...e, + message: e.message, + name: e.name, + stack: e.stack } : e }; } diff --git a/src/main/updater/http.ts b/src/main/updater/http.ts index 9d42b5c6..a112dde3 100644 --- a/src/main/updater/http.ts +++ b/src/main/updater/http.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { get } from "@main/utils/simpleGet"; +import { fetchBuffer, fetchJson } from "@main/utils/http"; import { IpcEvents } from "@shared/IpcEvents"; import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent"; import { ipcMain } from "electron"; @@ -31,8 +31,8 @@ import { serializeErrors, VENCORD_FILES } from "./common"; const API_BASE = `https://api.github.com/repos/${gitRemote}`; let PendingUpdates = [] as [string, string][]; -async function githubGet(endpoint: string) { - return get(API_BASE + endpoint, { +async function githubGet(endpoint: string) { + return fetchJson(API_BASE + endpoint, { headers: { Accept: "application/vnd.github+json", // "All API requests MUST include a valid User-Agent header. @@ -46,9 +46,8 @@ async function calculateGitChanges() { const isOutdated = await fetchUpdates(); if (!isOutdated) return []; - const res = await githubGet(`/compare/${gitHash}...HEAD`); + const data = await githubGet(`/compare/${gitHash}...HEAD`); - const data = JSON.parse(res.toString("utf-8")); return data.commits.map((c: any) => ({ // github api only sends the long sha hash: c.sha.slice(0, 7), @@ -58,9 +57,8 @@ async function calculateGitChanges() { } async function fetchUpdates() { - const release = await githubGet("/releases/latest"); + const data = await githubGet("/releases/latest"); - const data = JSON.parse(release.toString()); const hash = data.name.slice(data.name.lastIndexOf(" ") + 1); if (hash === gitHash) return false; @@ -70,16 +68,20 @@ async function fetchUpdates() { PendingUpdates.push([name, browser_download_url]); } }); + return true; } async function applyUpdates() { - await Promise.all(PendingUpdates.map( - async ([name, data]) => writeFile( - join(__dirname, name), - await get(data) - ) - )); + const fileContents = await Promise.all(PendingUpdates.map(async ([name, url]) => { + const contents = await fetchBuffer(url); + return [join(__dirname, name), contents] as const; + })); + + await Promise.all(fileContents.map(async ([filename, contents]) => + writeFile(filename, contents)) + ); + PendingUpdates = []; return true; } diff --git a/src/main/utils/extensions.ts b/src/main/utils/extensions.ts index 1323bd37..1d7559fb 100644 --- a/src/main/utils/extensions.ts +++ b/src/main/utils/extensions.ts @@ -24,7 +24,7 @@ import { join } from "path"; import { DATA_DIR } from "./constants"; import { crxToZip } from "./crxToZip"; -import { get } from "./simpleGet"; +import { fetchBuffer } from "./http"; const extensionCacheDir = join(DATA_DIR, "ExtensionCache"); @@ -69,13 +69,14 @@ export async function installExt(id: string) { } catch (err) { const url = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=${process.versions.chrome}`; - const buf = await get(url, { + const buf = await fetchBuffer(url, { headers: { "User-Agent": `Electron ${process.versions.electron} ~ Vencord (https://github.com/Vendicated/Vencord)` } }); - await extract(crxToZip(buf), extDir).catch(console.error); + await extract(crxToZip(buf), extDir) + .catch(err => console.error(`Failed to extract extension ${id}`, err)); } session.defaultSession.loadExtension(extDir); diff --git a/src/main/utils/http.ts b/src/main/utils/http.ts new file mode 100644 index 00000000..05dbca40 --- /dev/null +++ b/src/main/utils/http.ts @@ -0,0 +1,70 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * 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 . +*/ + +import { createWriteStream } from "original-fs"; +import { Readable } from "stream"; +import { finished } from "stream/promises"; + +type Url = string | URL; + +export async function checkedFetch(url: Url, options?: RequestInit) { + try { + var res = await fetch(url, options); + } catch (err) { + if (err instanceof Error && err.cause) { + err = err.cause; + } + + throw new Error(`${options?.method ?? "GET"} ${url} failed: ${err}`); + } + + if (res.ok) { + return res; + } + + let message = `${options?.method ?? "GET"} ${url}: ${res.status} ${res.statusText}`; + try { + const reason = await res.text(); + message += `\n${reason}`; + } catch { } + + throw new Error(message); +} + +export async function fetchJson(url: Url, options?: RequestInit) { + const res = await checkedFetch(url, options); + return res.json() as Promise; +} + +export async function fetchBuffer(url: Url, options?: RequestInit) { + const res = await checkedFetch(url, options); + const buf = await res.arrayBuffer(); + + return Buffer.from(buf); +} + +export async function downloadToFile(url: Url, path: string, options?: RequestInit) { + const res = await checkedFetch(url, options); + if (!res.body) { + throw new Error(`Download ${url}: response body is empty`); + } + + // @ts-expect-error weird type conflict + const body = Readable.fromWeb(res.body); + await finished(body.pipe(createWriteStream(path))); +} diff --git a/src/main/utils/simpleGet.ts b/src/main/utils/simpleGet.ts deleted file mode 100644 index 1a8302c0..00000000 --- a/src/main/utils/simpleGet.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * 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 . -*/ - -import https from "https"; - -export function get(url: string, options: https.RequestOptions = {}) { - return new Promise((resolve, reject) => { - https.get(url, options, res => { - const { statusCode, statusMessage, headers } = res; - if (statusCode! >= 400) - return void reject(`${statusCode}: ${statusMessage} - ${url}`); - if (statusCode! >= 300) - return void resolve(get(headers.location!, options)); - - const chunks = [] as Buffer[]; - res.on("error", reject); - - res.on("data", chunk => chunks.push(chunk)); - res.once("end", () => resolve(Buffer.concat(chunks))); - }); - }); -} From 4436e6d81d1791c978906251fe382987c3feb040 Mon Sep 17 00:00:00 2001 From: Vanilla Date: Fri, 6 Jun 2025 18:53:58 -0400 Subject: [PATCH 4/4] Fix missing background on notifications (#3386) Co-authored-by: Vending Machine --- src/api/Notifications/styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/Notifications/styles.css b/src/api/Notifications/styles.css index ad5c9cbc..ba8a246a 100644 --- a/src/api/Notifications/styles.css +++ b/src/api/Notifications/styles.css @@ -12,7 +12,7 @@ } .visual-refresh .vc-notification-root { - background-color: var(--bg-overlay-floating, var(--background-base-low)); + background-color: var(--background-base-low); } .vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) {