diff --git a/browser/VencordNativeStub.ts b/browser/VencordNativeStub.ts
index da5157f8..283150d3 100644
--- a/browser/VencordNativeStub.ts
+++ b/browser/VencordNativeStub.ts
@@ -20,15 +20,12 @@
///
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 { 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>();
@@ -76,6 +73,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) {
@@ -91,7 +96,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 00c0b45b..c95640b2 100644
--- a/scripts/build/build.mjs
+++ b/scripts/build/build.mjs
@@ -34,6 +34,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 dadaf294..3be51d67 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,
@@ -108,6 +109,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/Vencord.ts b/src/Vencord.ts
index b43564a2..5b954a07 100644
--- a/src/Vencord.ts
+++ b/src/Vencord.ts
@@ -153,7 +153,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/api/Notifications/styles.css b/src/api/Notifications/styles.css
index 98dff6df..ba8a246a 100644
--- a/src/api/Notifications/styles.css
+++ b/src/api/Notifications/styles.css
@@ -11,6 +11,10 @@
width: 100%;
}
+.visual-refresh .vc-notification-root {
+ background-color: var(--background-base-low);
+}
+
.vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) {
position: absolute;
z-index: 2147483647;
diff --git a/src/globals.d.ts b/src/globals.d.ts
index c04fe994..aad0d544 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/main/updater/common.ts b/src/main/updater/common.ts
index bd77c417..178266af 100644
--- a/src/main/updater/common.ts
+++ b/src/main/updater/common.ts
@@ -30,7 +30,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 c0affa69..13929daa 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";
@@ -30,8 +30,8 @@ import { ASAR_FILE, serializeErrors } from "./common";
const API_BASE = `https://api.github.com/repos/${gitRemote}`;
let PendingUpdate: string | null = null;
-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.
@@ -45,9 +45,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),
@@ -57,9 +56,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;
@@ -74,7 +72,7 @@ async function fetchUpdates() {
async function applyUpdates() {
if (!PendingUpdate) return true;
- const data = await get(PendingUpdate);
+ const data = await fetchBuffer(PendingUpdate);
originalWriteFileSync(__dirname, data);
PendingUpdate = null;
diff --git a/src/main/utils/extensions.ts b/src/main/utils/extensions.ts
index 4af7129f..b48eb33d 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} ~ Equicord (https://github.com/Equicord/Equicord)`
}
});
- 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)));
- });
- });
-}
diff --git a/src/plugins/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx
index 74985d15..d556480a 100644
--- a/src/plugins/typingTweaks/index.tsx
+++ b/src/plugins/typingTweaks/index.tsx
@@ -111,7 +111,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: ""
},
{
diff --git a/src/utils/quickCss.ts b/src/utils/quickCss.ts
index 13848cec..221bcbf1 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");
@@ -92,6 +92,8 @@ async function initThemes() {
}
document.addEventListener("DOMContentLoaded", () => {
+ if (IS_USERSCRIPT) return;
+
initSystemValues();
initThemes();
@@ -104,9 +106,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/";
+}