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/"; +}