From e7076f5aeeae02ae7f1793408a1b2995b199ee2b Mon Sep 17 00:00:00 2001 From: Vending Machine Date: Fri, 6 Jun 2025 18:30:19 +0200 Subject: [PATCH 001/162] Use much stricter, whitelist based CSP (#3162) --- src/components/ErrorCard.css | 4 + src/components/Link.tsx | 3 + src/components/VencordSettings/ThemesTab.tsx | 31 +++++ src/main/csp.ts | 138 +++++++++++++++++++ src/main/index.ts | 68 +-------- src/plugins/_api/badges/index.tsx | 2 +- src/plugins/devCompanion.dev/index.tsx | 2 +- src/plugins/reverseImageSearch/index.tsx | 6 +- src/utils/constants.ts | 6 +- src/utils/cspViolations.ts | 34 +++++ src/utils/index.ts | 1 + 11 files changed, 221 insertions(+), 74 deletions(-) create mode 100644 src/main/csp.ts create mode 100644 src/utils/cspViolations.ts diff --git a/src/components/ErrorCard.css b/src/components/ErrorCard.css index 5146aa03..6401c59c 100644 --- a/src/components/ErrorCard.css +++ b/src/components/ErrorCard.css @@ -4,4 +4,8 @@ border: 1px solid #e78284; border-radius: 5px; color: var(--text-normal, white); + + & a:hover { + text-decoration: underline; + } } diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 0f4eb07d..2eb7ab00 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -28,6 +28,9 @@ export function Link(props: React.PropsWithChildren) { props.style.pointerEvents = "none"; props["aria-disabled"] = true; } + + props.rel ??= "noreferrer"; + return ( {props.children} diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index f718ab11..c507ef88 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -18,13 +18,16 @@ import { Settings, useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; +import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons"; import { Link } from "@components/Link"; import { openPluginModal } from "@components/PluginSettings/PluginModal"; import type { UserThemeHeader } from "@main/themes"; +import { useCspErrors } from "@utils/cspViolations"; import { openInviteModal } from "@utils/discord"; import { Margins } from "@utils/margins"; +import { classes } from "@utils/misc"; import { showItemInFolder } from "@utils/native"; import { useAwaiter } from "@utils/react"; import { findLazy } from "@webpack"; @@ -219,6 +222,12 @@ function ThemesTab() { If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder. + + External Resources + For security reasons, loading resources (styles, fonts, images, ...) from most sites is blocked. + Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts. + + <> @@ -347,10 +356,32 @@ function ThemesTab() { + {currentTab === ThemeTab.LOCAL && renderLocalThemes()} {currentTab === ThemeTab.ONLINE && renderOnlineThemes()} ); } +export function CspErrorCard() { + const errors = useCspErrors(); + + if (!errors.length) return null; + + return ( + + Blocked Resources + Some images, styles, or fonts were blocked because they come from disallowed domains. + Make sure that your themes and custom css only load resources from whitelisted websites, such as GitHub, Imgur and Google Fonts. + + Blocked URLs + + {errors.map(url => ( + {url} + ))} + + + ); +} + export default wrapTab(ThemesTab, "Themes"); diff --git a/src/main/csp.ts b/src/main/csp.ts new file mode 100644 index 00000000..b35a11a8 --- /dev/null +++ b/src/main/csp.ts @@ -0,0 +1,138 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { session } from "electron"; + +type PolicyMap = Record; + +export const ConnectSrc = ["connect-src"]; +export const MediaSrc = [...ConnectSrc, "img-src", "media-src"]; +export const CssSrc = ["style-src", "font-src"]; +export const MediaAndCssSrc = [...MediaSrc, ...CssSrc]; +export const MediaScriptsAndCssSrc = [...MediaAndCssSrc, "script-src", "worker-src"]; + +// Plugins can whitelist their own domains by importing this object in their native.ts +// script and just adding to it. But generally, you should just edit this file instead + +export const CspPolicies: PolicyMap = { + "*.github.io": MediaAndCssSrc, // GitHub pages, used by most themes + "raw.githubusercontent.com": MediaAndCssSrc, // GitHub raw, used by some themes + "*.gitlab.io": MediaAndCssSrc, // GitLab pages, used by some themes + "gitlab.com": MediaAndCssSrc, // GitLab raw, used by some themes + "*.codeberg.page": MediaAndCssSrc, // Codeberg pages, used by some themes + "codeberg.org": MediaAndCssSrc, // Codeberg raw, used by some themes + + "*.githack.com": MediaAndCssSrc, // githack (namely raw.githack.com), used by some themes + "jsdelivr.net": MediaAndCssSrc, // jsDelivr, used by very few themes + + "fonts.googleapis.com": CssSrc, // Google Fonts, used by many themes + + "i.imgur.com": MediaSrc, // Imgur, used by some themes + "i.ibb.co": MediaSrc, // ImgBB, used by some themes + + "cdn.discordapp.com": MediaAndCssSrc, // Discord CDN, used by Vencord and some themes to load media + "media.discordapp.net": MediaSrc, // Discord media CDN, possible alternative to Discord CDN + + // CDNs used for some things by Vencord. + // FIXME: we really should not be using CDNs anymore + "cdnjs.cloudflare.com": MediaScriptsAndCssSrc, + "cdn.jsdelivr.net": MediaScriptsAndCssSrc, + + // Function Specific + "api.github.com": ConnectSrc, // used for updating Vencord itself + "ws.audioscrobbler.com": ConnectSrc, // Last.fm API + "translate-pa.googleapis.com": ConnectSrc, // Google Translate API + "*.vencord.dev": MediaSrc, // VenCloud (api.vencord.dev) and Badges (badges.vencord.dev) + "manti.vendicated.dev": MediaSrc, // ReviewDB API + "decor.fieryflames.dev": ConnectSrc, // Decor API + "ugc.decor.fieryflames.dev": MediaSrc, // Decor CDN + "sponsor.ajay.app": ConnectSrc, // Dearrow API + "dearrow-thumb.ajay.app": MediaSrc, // Dearrow Thumbnail CDN + "usrbg.is-hardly.online": MediaSrc, // USRBG API + "icons.duckduckgo.com": MediaSrc, // DuckDuckGo Favicon API (Reverse Image Search) +}; + +const findHeader = (headers: PolicyMap, headerName: Lowercase) => { + return Object.keys(headers).find(h => h.toLowerCase() === headerName); +}; + +const parsePolicy = (policy: string): PolicyMap => { + const result: PolicyMap = {}; + policy.split(";").forEach(directive => { + const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g); + if (directiveKey && !Object.prototype.hasOwnProperty.call(result, directiveKey)) { + result[directiveKey] = directiveValue; + } + }); + + return result; +}; + +const stringifyPolicy = (policy: PolicyMap): string => + Object.entries(policy) + .filter(([, values]) => values?.length) + .map(directive => directive.flat().join(" ")) + .join("; "); + + +const patchCsp = (headers: PolicyMap) => { + const reportOnlyHeader = findHeader(headers, "content-security-policy-report-only"); + if (reportOnlyHeader) + delete headers[reportOnlyHeader]; + + const header = findHeader(headers, "content-security-policy"); + + if (header) { + const csp = parsePolicy(headers[header][0]); + + const pushDirective = (directive: string, ...values: string[]) => { + csp[directive] ??= [...(csp["default-src"] ?? [])]; + csp[directive].push(...values); + }; + + pushDirective("style-src", "'unsafe-inline'"); + // we could make unsafe-inline safe by using strict-dynamic with a random nonce on our Vencord loader script https://content-security-policy.com/strict-dynamic/ + // HOWEVER, at the time of writing (24 Jan 2025), Discord is INSANE and also uses unsafe-inline + // Once they stop using it, we also should + pushDirective("script-src", "'unsafe-inline'", "'unsafe-eval'"); + + for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) { + pushDirective(directive, "blob:", "data:", "vencord:"); + } + + for (const [host, directives] of Object.entries(CspPolicies)) { + for (const directive of directives) { + pushDirective(directive, host); + } + } + + headers[header] = [stringifyPolicy(csp)]; + } +}; + +export function initCsp() { + session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => { + if (responseHeaders) { + if (resourceType === "mainFrame") + patchCsp(responseHeaders); + + // Fix hosts that don't properly set the css content type, such as + // raw.githubusercontent.com + if (resourceType === "stylesheet") { + const header = findHeader(responseHeaders, "content-type"); + if (header) + responseHeaders[header] = ["text/css"]; + } + } + + cb({ cancel: false, responseHeaders }); + }); + + // assign a noop to onHeadersReceived to prevent other mods from adding their own incompatible ones. + // For instance, OpenAsar adds their own that doesn't fix content-type for stylesheets which makes it + // impossible to load css from github raw despite our fix above + session.defaultSession.webRequest.onHeadersReceived = () => { }; +} diff --git a/src/main/index.ts b/src/main/index.ts index 4cc2e0db..a001a490 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -16,9 +16,10 @@ * along with this program. If not, see . */ -import { app, protocol, session } from "electron"; +import { app, protocol } from "electron"; import { join } from "path"; +import { initCsp } from "./csp"; import { ensureSafePath } from "./ipcMain"; import { RendererSettings } from "./settings"; import { IS_VANILLA, THEMES_DIR } from "./utils/constants"; @@ -63,70 +64,7 @@ if (IS_VESKTOP || !IS_VANILLA) { } catch { } - const findHeader = (headers: Record, headerName: Lowercase) => { - return Object.keys(headers).find(h => h.toLowerCase() === headerName); - }; - - // Remove CSP - type PolicyResult = Record; - - const parsePolicy = (policy: string): PolicyResult => { - const result: PolicyResult = {}; - policy.split(";").forEach(directive => { - const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g); - if (directiveKey && !Object.prototype.hasOwnProperty.call(result, directiveKey)) { - result[directiveKey] = directiveValue; - } - }); - - return result; - }; - const stringifyPolicy = (policy: PolicyResult): string => - Object.entries(policy) - .filter(([, values]) => values?.length) - .map(directive => directive.flat().join(" ")) - .join("; "); - - const patchCsp = (headers: Record) => { - const header = findHeader(headers, "content-security-policy"); - - if (header) { - const csp = parsePolicy(headers[header][0]); - - for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) { - csp[directive] ??= []; - csp[directive].push("*", "blob:", "data:", "vencord:", "'unsafe-inline'"); - } - - // TODO: Restrict this to only imported packages with fixed version. - // Perhaps auto generate with esbuild - csp["script-src"] ??= []; - csp["script-src"].push("'unsafe-eval'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com"); - headers[header] = [stringifyPolicy(csp)]; - } - }; - - session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => { - if (responseHeaders) { - if (resourceType === "mainFrame") - patchCsp(responseHeaders); - - // Fix hosts that don't properly set the css content type, such as - // raw.githubusercontent.com - if (resourceType === "stylesheet") { - const header = findHeader(responseHeaders, "content-type"); - if (header) - responseHeaders[header] = ["text/css"]; - } - } - - cb({ cancel: false, responseHeaders }); - }); - - // assign a noop to onHeadersReceived to prevent other mods from adding their own incompatible ones. - // For instance, OpenAsar adds their own that doesn't fix content-type for stylesheets which makes it - // impossible to load css from github raw despite our fix above - session.defaultSession.webRequest.onHeadersReceived = () => { }; + initCsp(); }); } diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 8745584e..b00df6b0 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -33,7 +33,7 @@ import definePlugin from "@utils/types"; import { Forms, Toasts, UserStore } from "@webpack/common"; import { User } from "discord-types/general"; -const CONTRIBUTOR_BADGE = "https://vencord.dev/assets/favicon.png"; +const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64"; const ContributorBadge: ProfileBadge = { description: "Vencord Contributor", diff --git a/src/plugins/devCompanion.dev/index.tsx b/src/plugins/devCompanion.dev/index.tsx index 4ac2e993..7a9a1930 100644 --- a/src/plugins/devCompanion.dev/index.tsx +++ b/src/plugins/devCompanion.dev/index.tsx @@ -91,7 +91,7 @@ function parseNode(node: Node) { function initWs(isManual = false) { let wasConnected = isManual; let hasErrored = false; - const ws = socket = new WebSocket(`ws://localhost:${PORT}`); + const ws = socket = new WebSocket(`ws://127.0.0.1:${PORT}`); ws.addEventListener("open", () => { wasConnected = true; diff --git a/src/plugins/reverseImageSearch/index.tsx b/src/plugins/reverseImageSearch/index.tsx index 17fdb180..a2e7b7e6 100644 --- a/src/plugins/reverseImageSearch/index.tsx +++ b/src/plugins/reverseImageSearch/index.tsx @@ -53,14 +53,12 @@ function makeSearchItem(src: string) { = 3 // Do not round Google, Yandex & SauceNAO - ? "50%" - : void 0 + borderRadius: "50%", }} aria-hidden="true" height={16} width={16} - src={new URL("/favicon.ico", Engines[engine]).toString().replace("lens.", "")} + src={`https://icons.duckduckgo.com/ip3/${new URL(Engines[engine]).host}.ico`} /> {engine} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 7bcadb42..afe1651f 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -40,7 +40,7 @@ export interface Dev { */ export const Devs = /* #__PURE__*/ Object.freeze({ Ven: { - name: "Vee", + name: "V", id: 343383572805058560n }, Arjix: { @@ -194,7 +194,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({ }, axyie: { name: "'ax", - id: 273562710745284628n, + id: 929877747151548487n, }, pointy: { name: "pointy", @@ -587,7 +587,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({ }, samsam: { name: "samsam", - id: 836452332387565589n, + id: 400482410279469056n, }, Cootshk: { name: "Cootshk", diff --git a/src/utils/cspViolations.ts b/src/utils/cspViolations.ts new file mode 100644 index 00000000..9477bcaa --- /dev/null +++ b/src/utils/cspViolations.ts @@ -0,0 +1,34 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { useLayoutEffect } from "@webpack/common"; + +import { useForceUpdater } from "./react"; + +const cssRelevantDirectives = ["style-src", "img-src", "font-src"] as const; + +export const CspBlockedUrls = new Set(); +const CspErrorListeners = new Set<() => void>(); + +document.addEventListener("securitypolicyviolation", ({ effectiveDirective, blockedURI }) => { + if (!blockedURI || !cssRelevantDirectives.includes(effectiveDirective as any)) return; + + CspBlockedUrls.add(blockedURI); + + CspErrorListeners.forEach(listener => listener()); +}); + +export function useCspErrors() { + const forceUpdate = useForceUpdater(); + + useLayoutEffect(() => { + CspErrorListeners.add(forceUpdate); + + return () => void CspErrorListeners.delete(forceUpdate); + }, [forceUpdate]); + + return [...CspBlockedUrls] as const; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 70ac9d42..3e7aa0e1 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -21,6 +21,7 @@ export * from "../shared/onceDefined"; export * from "./ChangeList"; export * from "./clipboard"; export * from "./constants"; +export * from "./cspViolations"; export * from "./discord"; export * from "./guards"; export * from "./intlHash"; From fae15dbdfe6e1044b740c82c38e0f971022537f3 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 6 Jun 2025 18:48:56 +0200 Subject: [PATCH 002/162] avoid showing ugly red error cards to users --- src/api/MessageAccessories.tsx | 2 +- src/components/ErrorBoundary.tsx | 7 ++++++- src/components/PluginSettings/PluginModal.tsx | 2 +- src/plugins/decor/index.tsx | 2 +- src/plugins/favGifSearch/index.tsx | 2 +- src/plugins/mentionAvatars/index.tsx | 2 +- src/plugins/messageLinkEmbeds/index.tsx | 13 +++++-------- src/plugins/mutualGroupDMs/index.tsx | 2 +- src/plugins/pauseInvitesForever/index.tsx | 2 +- src/plugins/sortFriendRequests/index.tsx | 2 +- src/plugins/vencordToolbox/index.tsx | 2 +- 11 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/api/MessageAccessories.tsx b/src/api/MessageAccessories.tsx index 71664e93..d2bc081e 100644 --- a/src/api/MessageAccessories.tsx +++ b/src/api/MessageAccessories.tsx @@ -48,7 +48,7 @@ export function _modifyAccessories( ) { for (const [key, accessory] of accessories.entries()) { const res = ( - + ); diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 0ca20440..7058b5fd 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -75,10 +75,15 @@ const ErrorBoundary = LazyComponent(() => { logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack); } + get isNoop() { + if (IS_DEV) return false; + return this.props.noop; + } + render() { if (this.state.error === NO_ERROR) return this.props.children; - if (this.props.noop) return null; + if (this.isNoop) return null; if (this.props.fallback) return ( diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 7baeba08..17ab2662 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -270,7 +270,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti {!!plugin.settingsAboutComponent && (
- + diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index 63963d09..0a6dd85d 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -150,5 +150,5 @@ export default definePlugin({ } }, - DecorSection: ErrorBoundary.wrap(DecorSection) + DecorSection: ErrorBoundary.wrap(DecorSection, { noop: true }) }); diff --git a/src/plugins/favGifSearch/index.tsx b/src/plugins/favGifSearch/index.tsx index d71f5679..5e302912 100644 --- a/src/plugins/favGifSearch/index.tsx +++ b/src/plugins/favGifSearch/index.tsx @@ -118,7 +118,7 @@ export default definePlugin({ renderSearchBar(instance: Instance, SearchBarComponent: TSearchBarComponent) { this.instance = instance; return ( - + ); diff --git a/src/plugins/mentionAvatars/index.tsx b/src/plugins/mentionAvatars/index.tsx index c4a3adce..5466a9e2 100644 --- a/src/plugins/mentionAvatars/index.tsx +++ b/src/plugins/mentionAvatars/index.tsx @@ -99,7 +99,7 @@ export default definePlugin({ src={`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${roleId}/${role.icon}.webp?size=24&quality=lossless`} /> ); - }), + }, { noop: true }), }); function getUsernameString(username: string) { diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index c248167f..69727862 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -20,7 +20,6 @@ import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccesso import { updateMessage } from "@api/MessageUpdater"; import { definePluginSettings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants.js"; import { classes } from "@utils/misc"; import { Queue } from "@utils/Queue"; @@ -373,7 +372,7 @@ export default definePlugin({ settings, start() { - addMessageAccessory("messageLinkEmbed", props => { + addMessageAccessory("MessageLinkEmbeds", props => { if (!messageLinkRegex.test(props.message.content)) return null; @@ -381,16 +380,14 @@ export default definePlugin({ messageLinkRegex.lastIndex = 0; return ( - - - + ); }, 4 /* just above rich embeds */); }, stop() { - removeMessageAccessory("messageLinkEmbed"); + removeMessageAccessory("MessageLinkEmbeds"); } }); diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 1058410f..796a91db 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -204,5 +204,5 @@ export default definePlugin({ /> ); - }) + }, { noop: true }) }); diff --git a/src/plugins/pauseInvitesForever/index.tsx b/src/plugins/pauseInvitesForever/index.tsx index b648f92e..432d1c1c 100644 --- a/src/plugins/pauseInvitesForever/index.tsx +++ b/src/plugins/pauseInvitesForever/index.tsx @@ -75,5 +75,5 @@ export default definePlugin({ }}> Pause Indefinitely.}
); - }) + }, { noop: true }) }); diff --git a/src/plugins/sortFriendRequests/index.tsx b/src/plugins/sortFriendRequests/index.tsx index 5f45902e..d8b64cf9 100644 --- a/src/plugins/sortFriendRequests/index.tsx +++ b/src/plugins/sortFriendRequests/index.tsx @@ -86,5 +86,5 @@ export default definePlugin({ )} ; - }) + }, { noop: true }) }); diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index 754af009..2f671a23 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -125,7 +125,7 @@ function VencordPopoutButton() { function ToolboxFragmentWrapper({ children }: { children: ReactNode[]; }) { children.splice( children.length - 1, 0, - + ); From c19827a0e53f886798f1209e184da125e82d9e01 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 7 Jun 2025 00:06:01 +0200 Subject: [PATCH 003/162] 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 004/162] 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 005/162] 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 006/162] 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) { From 5f21eaabf8ecd7a7e428c77394decc4e43d30af9 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 7 Jun 2025 01:02:52 +0200 Subject: [PATCH 007/162] bump to v1.12.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c6d5023..f45d9f2f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.2", + "version": "1.12.3", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 3a2a16a09c68b116e66e4af3e8b8de232c9658af Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 7 Jun 2025 01:18:14 +0200 Subject: [PATCH 008/162] whitelist tenor and pinterest for use in themes --- src/main/csp.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/csp.ts b/src/main/csp.ts index b35a11a8..3e592ccb 100644 --- a/src/main/csp.ts +++ b/src/main/csp.ts @@ -19,6 +19,7 @@ export const MediaScriptsAndCssSrc = [...MediaAndCssSrc, "script-src", "worker-s export const CspPolicies: PolicyMap = { "*.github.io": MediaAndCssSrc, // GitHub pages, used by most themes + "github.com": MediaAndCssSrc, // GitHub content (stuff uploaded to markdown forms), used by most themes "raw.githubusercontent.com": MediaAndCssSrc, // GitHub raw, used by some themes "*.gitlab.io": MediaAndCssSrc, // GitLab pages, used by some themes "gitlab.com": MediaAndCssSrc, // GitLab raw, used by some themes @@ -32,6 +33,8 @@ export const CspPolicies: PolicyMap = { "i.imgur.com": MediaSrc, // Imgur, used by some themes "i.ibb.co": MediaSrc, // ImgBB, used by some themes + "i.pinimg.com": MediaSrc, // Pinterest, used by some themes + "*.tenor.com": MediaSrc, // Tenor, used by some themes "cdn.discordapp.com": MediaAndCssSrc, // Discord CDN, used by Vencord and some themes to load media "media.discordapp.net": MediaSrc, // Discord media CDN, possible alternative to Discord CDN From bb106b7c49a6f1019194738a66ee75c8584680a8 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 7 Jun 2025 01:31:04 +0200 Subject: [PATCH 009/162] whitelist catbox for use in themes --- src/main/csp.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/csp.ts b/src/main/csp.ts index 3e592ccb..2faee606 100644 --- a/src/main/csp.ts +++ b/src/main/csp.ts @@ -35,6 +35,7 @@ export const CspPolicies: PolicyMap = { "i.ibb.co": MediaSrc, // ImgBB, used by some themes "i.pinimg.com": MediaSrc, // Pinterest, used by some themes "*.tenor.com": MediaSrc, // Tenor, used by some themes + "files.catbox.moe": MediaSrc, // Catbox, used by some themes "cdn.discordapp.com": MediaAndCssSrc, // Discord CDN, used by Vencord and some themes to load media "media.discordapp.net": MediaSrc, // Discord media CDN, possible alternative to Discord CDN From bf68a8a3e859f60d4c6dc47fd38c6ba4922f0661 Mon Sep 17 00:00:00 2001 From: Mia <62818119+xNasuni@users.noreply.github.com> Date: Sun, 8 Jun 2025 10:55:12 -0400 Subject: [PATCH 010/162] MessageClickActions: make delete key detection consistent on lost focus (#3470) Co-authored-by: Vending Machine --- src/plugins/messageClickActions/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/messageClickActions/index.ts b/src/plugins/messageClickActions/index.ts index 19ccaa95..a07c924a 100644 --- a/src/plugins/messageClickActions/index.ts +++ b/src/plugins/messageClickActions/index.ts @@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { FluxDispatcher, PermissionsBits, PermissionStore, UserStore } from "@webpack/common"; +import { FluxDispatcher, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common"; const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage"); const EditStore = findByPropsLazy("isEditing", "isEditingAny"); @@ -28,6 +28,7 @@ const EditStore = findByPropsLazy("isEditing", "isEditingAny"); let isDeletePressed = false; const keydown = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = true); const keyup = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = false); +const focusChanged = () => !WindowStore.isFocused() && (isDeletePressed = false); const settings = definePluginSettings({ enableDeleteOnClick: { @@ -62,11 +63,13 @@ export default definePlugin({ start() { document.addEventListener("keydown", keydown); document.addEventListener("keyup", keyup); + WindowStore.addChangeListener(focusChanged); }, stop() { document.removeEventListener("keydown", keydown); document.removeEventListener("keyup", keyup); + WindowStore.removeChangeListener(focusChanged); }, onMessageClick(msg: any, channel, event) { From a386736dccfda73bf0ed2964108961a8b8286206 Mon Sep 17 00:00:00 2001 From: Cookie <52550063+Covkie@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:47:59 -0400 Subject: [PATCH 011/162] WebScreenShareFixes: only apply stereo parameter to video audio (#3474) --- src/plugins/webScreenShareFixes.web/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/webScreenShareFixes.web/index.ts b/src/plugins/webScreenShareFixes.web/index.ts index 1d2be2c0..2616dd0c 100644 --- a/src/plugins/webScreenShareFixes.web/index.ts +++ b/src/plugins/webScreenShareFixes.web/index.ts @@ -25,8 +25,8 @@ export default definePlugin({ replace: ";b=AS:800000;level-asymmetry-allowed=1" }, { - match: "useinbandfec=1", - replace: "useinbandfec=1;stereo=1;sprop-stereo=1" + match: /;usedtx=".concat\((\i)\?"0":"1"\)/, + replace: '$&.concat($1?";stereo=1;sprop-stereo=1":"")' } ] } From 6d47a340b1c7106ccb7b5117abd9457ad7c4bdcb Mon Sep 17 00:00:00 2001 From: Vending Machine Date: Sun, 8 Jun 2025 19:45:52 +0200 Subject: [PATCH 012/162] QuickReply: correctly handle new & deleted messages (#3473) --- src/plugins/messageClickActions/index.ts | 6 +- src/plugins/quickReply/index.ts | 114 ++++++++++++----------- 2 files changed, 63 insertions(+), 57 deletions(-) diff --git a/src/plugins/messageClickActions/index.ts b/src/plugins/messageClickActions/index.ts index a07c924a..723ece12 100644 --- a/src/plugins/messageClickActions/index.ts +++ b/src/plugins/messageClickActions/index.ts @@ -21,6 +21,7 @@ import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; import { FluxDispatcher, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common"; +import NoReplyMentionPlugin from "plugins/noReplyMention"; const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage"); const EditStore = findByPropsLazy("isEditing", "isEditingAny"); @@ -92,9 +93,8 @@ export default definePlugin({ if (msg.hasFlag(EPHEMERAL)) return; const isShiftPress = event.shiftKey && !settings.store.requireModifier; - const NoReplyMention = Vencord.Plugins.plugins.NoReplyMention as any as typeof import("../noReplyMention").default; - const shouldMention = Vencord.Plugins.isPluginEnabled("NoReplyMention") - ? NoReplyMention.shouldMention(msg, isShiftPress) + const shouldMention = Vencord.Plugins.isPluginEnabled(NoReplyMentionPlugin.name) + ? NoReplyMentionPlugin.shouldMention(msg, isShiftPress) : !isShiftPress; FluxDispatcher.dispatch({ diff --git a/src/plugins/quickReply/index.ts b/src/plugins/quickReply/index.ts index f6ca5b45..5a6b45f9 100644 --- a/src/plugins/quickReply/index.ts +++ b/src/plugins/quickReply/index.ts @@ -16,19 +16,20 @@ * along with this program. If not, see . */ -import { definePluginSettings, Settings } from "@api/Settings"; +import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; +import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageActions, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; import { Message } from "discord-types/general"; +import NoBlockedMessagesPlugin from "plugins/noBlockedMessages"; +import NoReplyMentionPlugin from "plugins/noReplyMention"; -const Kangaroo = findByPropsLazy("jumpToMessage"); const RelationshipStore = findByPropsLazy("getRelationships", "isBlocked"); const isMac = navigator.platform.includes("Mac"); // bruh -let replyIdx = -1; -let editIdx = -1; +let currentlyReplyingId: string | null = null; +let currentlyEditingId: string | null = null; const enum MentionOptions { @@ -69,36 +70,29 @@ export default definePlugin({ flux: { DELETE_PENDING_REPLY() { - replyIdx = -1; + currentlyReplyingId = null; }, MESSAGE_END_EDIT() { - editIdx = -1; + currentlyEditingId = null; + }, + CHANNEL_SELECT() { + currentlyReplyingId = null; + currentlyEditingId = null; }, MESSAGE_START_EDIT: onStartEdit, CREATE_PENDING_REPLY: onCreatePendingReply } }); -function calculateIdx(messages: Message[], id: string) { - const idx = messages.findIndex(m => m.id === id); - return idx === -1 - ? idx - : messages.length - idx - 1; -} - -function onStartEdit({ channelId, messageId, _isQuickEdit }: any) { +function onStartEdit({ messageId, _isQuickEdit }: any) { if (_isQuickEdit) return; - - const meId = UserStore.getCurrentUser().id; - - const messages = MessageStore.getMessages(channelId)._array.filter(m => m.author.id === meId); - editIdx = calculateIdx(messages, messageId); + currentlyEditingId = messageId; } function onCreatePendingReply({ message, _isQuickReply }: { message: Message; _isQuickReply: boolean; }) { if (_isQuickReply) return; - replyIdx = calculateIdx(MessageStore.getMessages(message.channel_id)._array, message.id); + currentlyReplyingId = message.id; } const isCtrl = (e: KeyboardEvent) => isMac ? e.metaKey : e.ctrlKey; @@ -123,10 +117,10 @@ function jumpIfOffScreen(channelId: string, messageId: string) { const vh = Math.max(document.documentElement.clientHeight, window.innerHeight); const rect = element.getBoundingClientRect(); - const isOffscreen = rect.bottom < 200 || rect.top - vh >= -200; + const isOffscreen = rect.bottom < 150 || rect.top - vh >= -150; if (isOffscreen) { - Kangaroo.jumpToMessage({ + MessageActions.jumpToMessage({ channelId, messageId, flash: false, @@ -137,43 +131,48 @@ function jumpIfOffScreen(channelId: string, messageId: string) { function getNextMessage(isUp: boolean, isReply: boolean) { let messages: Array = MessageStore.getMessages(SelectedChannelStore.getChannelId())._array; - if (!isReply) { // we are editing so only include own - const meId = UserStore.getCurrentUser().id; - messages = messages.filter(m => m.author.id === meId); - } - if (Vencord.Plugins.isPluginEnabled("NoBlockedMessages")) { - messages = messages.filter(m => !RelationshipStore.isBlocked(m.author.id)); - } + const meId = UserStore.getCurrentUser().id; + const hasNoBlockedMessages = Vencord.Plugins.isPluginEnabled(NoBlockedMessagesPlugin.name); - const mutate = (i: number) => isUp - ? Math.min(messages.length - 1, i + 1) - : Math.max(-1, i - 1); + messages = messages.filter(m => { + if (m.deleted) return false; + if (!isReply && m.author.id !== meId) return false; // editing only own messages + if (hasNoBlockedMessages && NoBlockedMessagesPlugin.shouldIgnoreMessage(m)) return false; - const findNextNonDeleted = (i: number) => { - do { - i = mutate(i); - } while (i !== -1 && messages[messages.length - i - 1]?.deleted === true); - return i; + return true; + }); + + const findNextNonDeleted = (id: string | null) => { + if (id === null) return messages[messages.length - 1]; + + const idx = messages.findIndex(m => m.id === id); + if (idx === -1) return messages[messages.length - 1]; + + const i = isUp ? idx - 1 : idx + 1; + return messages[i] ?? null; }; - let i: number; - if (isReply) - replyIdx = i = findNextNonDeleted(replyIdx); - else - editIdx = i = findNextNonDeleted(editIdx); - - return i === - 1 ? undefined : messages[messages.length - i - 1]; + if (isReply) { + const msg = findNextNonDeleted(currentlyReplyingId); + currentlyReplyingId = msg?.id ?? null; + return msg; + } else { + const msg = findNextNonDeleted(currentlyEditingId); + currentlyEditingId = msg?.id ?? null; + return msg; + } } -function shouldMention(message) { - const { enabled, userList, shouldPingListed } = Settings.plugins.NoReplyMention; - const shouldPing = !enabled || (shouldPingListed === userList.includes(message.author.id)); - +function shouldMention(message: Message) { switch (settings.store.shouldMention) { - case MentionOptions.NO_REPLY_MENTION_PLUGIN: return shouldPing; - case MentionOptions.DISABLED: return false; - default: return true; + case MentionOptions.NO_REPLY_MENTION_PLUGIN: + if (!Vencord.Plugins.isPluginEnabled(NoReplyMentionPlugin.name)) return true; + return NoReplyMentionPlugin.shouldMention(message, false); + case MentionOptions.DISABLED: + return false; + default: + return true; } } @@ -181,13 +180,16 @@ function shouldMention(message) { function nextReply(isUp: boolean) { const currChannel = ChannelStore.getChannel(SelectedChannelStore.getChannelId()); if (currChannel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, currChannel)) return; + const message = getNextMessage(isUp, true); - if (!message) + if (!message) { return void Dispatcher.dispatch({ type: "DELETE_PENDING_REPLY", channelId: SelectedChannelStore.getChannelId(), }); + } + const channel = ChannelStore.getChannel(message.channel_id); const meId = UserStore.getCurrentUser().id; @@ -199,6 +201,7 @@ function nextReply(isUp: boolean) { showMentionToggle: !channel.isPrivate() && message.author.id !== meId, _isQuickReply: true }); + ComponentDispatch.dispatchToLastSubscribed("TEXTAREA_FOCUS"); jumpIfOffScreen(channel.id, message.id); } @@ -209,11 +212,13 @@ function nextEdit(isUp: boolean) { if (currChannel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, currChannel)) return; const message = getNextMessage(isUp, false); - if (!message) + if (!message) { return Dispatcher.dispatch({ type: "MESSAGE_END_EDIT", channelId: SelectedChannelStore.getChannelId() }); + } + Dispatcher.dispatch({ type: "MESSAGE_START_EDIT", channelId: message.channel_id, @@ -221,5 +226,6 @@ function nextEdit(isUp: boolean) { content: message.content, _isQuickEdit: true }); + jumpIfOffScreen(message.channel_id, message.id); } From b19bb2b7afef5ed621e9a14a110d256ecaba9aa6 Mon Sep 17 00:00:00 2001 From: Vending Machine Date: Sun, 8 Jun 2025 20:10:30 +0200 Subject: [PATCH 013/162] Update README (#3472) --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 61575d4b..52d3c1b6 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,21 @@ # Vencord +![](https://img.shields.io/github/package-json/v/Vendicated/Vencord?style=for-the-badge&logo=github&logoColor=d3869b&label=&color=1d2021&labelColor=282828) [![Codeberg Mirror](https://img.shields.io/static/v1?style=for-the-badge&label=Codeberg%20Mirror&message=codeberg.org/Vee/cord&color=2185D0&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAKbUlEQVR4nNVae3AV5RX/nW/3Pva+b24e5HHzIICQKGoiYiW8NFBFgohaa6ctglpbFSujSGurzUinohWsOij/gGX6R2fqOK0d1FYTEZXaTrWCBbEikJCEyCvkeXNvkrunf+zdkJDkPnex/c3cmd29+53v/M6e73znnF2Cydj4Tntldzi6qrN/qKqzf2jy6b7BnL4B1dI7oMp9AyoRAIdVsNMqhlxWMZjtspzyK/Jhr036OMsm//bh2vzPzNSPzBD6xFutd7R0Dq758ky4orkjYuc05RCAkixbeEq2/UCJ1/LczxcX/c5IPfU5DMHmxpbCpu7o1k/b+xc1n43YjJI7EqV+W2RmvuPt0oDjB2vn5bQbITNjAzzdeKK8qTO0bU9T77zucNQUjzofHrvENWWu3aUBZfW6+ZOOZiIrbYXrmUXo9daX3v6i667O/iGRiRLpwqtIvKDc+0efJ3hb/UIaSkdGWgZ4sqGt9r2m3lc/P9HvSWe80ZiRp3TPL/UsX1+bvyvVsSkb4NE3WjbuPNj5SM8Fcvdk4bAKrqvwv7DxhuCPUxmXNIn6XSy3nWr6R8OhrqrU1btwqJ3m/bgwu/SqZJdEUgbYsuuka09b9/4Pm3tLMlPvwuAbpe6m+RcplfdcURBKdG9CA2zZddLV2Nx1+JO2vlxj1LswqCpynlxc6SxLZIS40bueWfy9vXvv/xt5APhXa1/u7v+EPqvfxXK8++IaoO2Vpn9+cLS33FjVLhw+bOotOX7q6N/i3TOhAX7y+rHN/+sBLxm8fah71k93tjw/0f/jGuDJxtZrdh7setA8tS4sdn7eef+v3mmfP95/Ywxw6x9Yev9I35/6Iubv83WVfl5a6Uu3VkoavZEo7TnS/Vo98xi+Yy6UKC3bDp7sd5ut1OWFDjyzNMib6oq5Oug0ezp8dqLfG3r92Nbzr48ywNONJ8obDnV/z2xlAk4ZW1aUqhaJIAvCb5YVqwFn3GBtCBoO9dz5TOPxUbnMKAM0dYa2d5lc2AgCNi8r5klui3aBgWynjE11QZbI3FV3NjQkjnYNbB+lj36wubGlcE9T71xTNQDw0Px8nlvmHl73GmfCrKCL19Tkmh4P9jT1LHz2vVP5+vmwAZq71a1m1/PXTPXwD68eS5KIEVUZd1yZwwumeEw1Qld/lJrPhF7Sz4cNsO+rUK2ZExd6rfj10iCPZ2GJCCoAZuCJxQUc9FvNVAX72kPX6ccC0Hp4zR0Ru1kT2mTCSzeXqn5l/EAniMAqoDLDYZWwqa5EVSzmhaKmsxHbLxvbbgdiBmjpHFxj2mwANlxXxBdPUib8nwgQgqAyEFUZxT4L1i/MN3UpHDsTWQvEDHDoTLjCrIluuyzAt8zMSkhGFhp5hrYUFk3z8IqZftOMcKRj4GIAEM80tFccM8n9Z+Qq+MXigqRIWCQCMzQvYIbKwH1X53FFnjkr88iZsLKpoXWa6BiIrjbDzF67hK23lKp2Obm1LAstPEZVjTwDkAio/2ZQ9dolw/VjAB0DfKfoCg9WGy2cADy1NMhBX2rR3CIRGICq8rAhAg4Jj9UWsDBhg+4MR6vF2VC0zGjB99fk8eJp3pQdyyrRMHF9KURVxswCB6+alWO4o3b2RyeLU32D2UYKnVPm5gfm5qWlrF0Wo4hzbCmoDNw0089XlboNNcLpvsFc0RtRDXuNle+x4Lkbi9PO6WWJIBFGEY+qjGjswtq5eVzosRilLnoiUavoH1INiTCyIDy/vETNcmRW1dl0L4gRVxmx3YFhlwnrry1QrZIxASE0yJIIDaiGSHt8UQFXF2Ve1zusYgzxkXGhyGvFvePUE+mgfyAqhGqAqKWVPv5udbYhSjmtkpYWq6OJqzFjqCpjTpmbl1Rk3klSGRBWmTISNC3Hjo1LgoYFJ0GA1aIVR+cTVxlQoS2Pb18a4PLszMKXzSJYuCySmq4Al03CiytKVYfBhYvLKk1IXE+XLRLhwZp81WlNf26HTFHhd0jhdAYTgKduCPLkgPHfQjitYkLiAIEZBDBlu2R6aF7euCV2Mgg45bDw2qWOdAavnp3D109PPdlJBvpTnYg4kVY3MDMuylVw62WJi63x4LHLZ0TAIR9OdWBVodPUclUQwWmT4hLXfgCIUDfDi6oiR8rzBJzyl8LnkD9KZVCOU8aLN5eoshnJ+Qh4bFJC4gztmEjgrtk5anaKnWWfXfpIuBTLjmSpSILw/E0laq7LuGxsIngVCYmIa96hLRG3TaZ1C/KTfjAEQLFIO8TPFk7aH/RZI8kMWrdgEs8udqXLKSUoMkEW4ETEQTRsoHyPlVZfmVw+Uuy3hR9bVHBQAMD0XPu/Ew24dqqH777K/La1DiKCxyYlRRzQymgG4+oyDxZOTdxZnp5r3wvEWmJ5btuL8W4uzbJh87LitLebdOFVpKSJx4IlwIzbL81CcYLO8iSX/IImGQCYae6Wg/2tXQNjNnW7LPDKyilqZd7ETU2zEBlifNTSS4i9PNFIx44x4jh2nZlBsUr0dN8QP/6XVhEaHJvnlfhtkXd/NF0BUextKRFXFznfGk+JDdcX8tdBHtDa6YpFsB4I9ac88omf8wbEgqa2XAIOme6bM35foqrQ+QZIKwGG80ifVbrXZZNGDfhOVYBvviS9JMMoaP3AEcQpPnHdOxiMGXkKbrx4dGfZY5c4T8H9+vmwAeqXFLXOKXW9r59fWuDA44sKv1byAOBzyCkTH+kdS2f4MLPgXJI0p9T17vrFxcf181GVxEUB+0qfIqt+RcKWFSWGNR4ygd4RTpW4HiCJgFWzstmnSPA7ZLU827pypPwxDB/687GXl1X6Vs6bbGz/LRN80hZCT+yLFZ0cgHED4egACeiXm89GsP9EePuzy4rvGil7jAGYmQDsBjDHUBYZ4GhHBMfORigd4rpnyIS9u6d4rqgnGrUtjCmmSYuOqwB0GcwjbWh9xviurpNnxnDA1IspMPe6bOL755MHJvhKjIgOA7jbJD4pw22Thj+kSIW47h2KRaydVezeP57sCdspRPQqgGeNJJIuBAE+ReJUiOv32mXaXjPZs21C2QnmXgdghyEsMoRfkVMiDgCywF/by9z3xJMb1wCxeHAPgDczZpAh/Iq+HSYmDjCsstgThmf5t4ii8eQm7CgS0SCA5QBezoRApnBaBSyCEhIHCLJEb4ZUd+2SqZSwzE+qpUpEQ9CC4qb01M8cRIQsh8zxiKsMtsn08nvlnrpkyAPj5AGJwMw3AtgGwJ/q2ExxvHsQB74KxfKBMblAyGmTHq4pc4/5GjQeUm6qE9FrAK4E8H6ie41GlkN/jTk6F5Ak2ueUpNmpkgfSMAAAENERAAsB3AHgZDoy0oFdFnBYpXPEBfU4beLRD6Z4qmumug+kIzPjaoeZfQDWAHgAQFam8hLh4MkwWjsHemyS2OF08IYrCjynzZ4zKTCzi5nXMvOnzBw16bevIxR95JOj7DNKb1PqXWa+HMDtAGoBXII0lxq0N2OfAmgA8Hsi2muMhudgesHPzNkA5gKoADADwFRoS8UHQO+x9wLoBNAB4AsAnwM4AOADIjLVxf8L9kdXUOE0IskAAAAASUVORK5CYII=)](https://codeberg.org/Vee/cord) The cutest Discord client mod -| ![image](https://github.com/Vendicated/Vencord/assets/45497981/706722b1-32de-4d99-bee9-93993b504334) | -| :--------------------------------------------------------------------------------------------------: | -| A screenshot of vencord showcasing the [vencord-theme](https://github.com/synqat/vencord-theme) | +![](https://github.com/user-attachments/assets/3fac98c0-c411-4d2a-97a3-13b7da8687a2) ## Features -- Super easy to install (Download Installer, open, click install button, done) -- 100+ plugins built in: [See a list](https://vencord.dev/plugins) - - Some highlights: SpotifyControls, MessageLogger, Experiments, GameActivityToggle, Translate, NoTrack, QuickReply, Free Emotes/Stickers, PermissionsViewer, CustomCommands, ShowHiddenChannels, PronounDB +- Easy to install +- [100+ built in plugins](https://vencord.dev/plugins) - Fairly lightweight despite the many inbuilt plugins - Excellent Browser Support: Run Vencord in your Browser via extension or UserScript -- Works on any Discord branch: Stable, Canary or PTB all work (though for the best experience I recommend stable!) +- Works on any Discord branch: Stable, Canary or PTB all work - Custom CSS and Themes: Inbuilt css editor with support to import any css files (including BetterDiscord themes) -- Privacy friendly, blocks Discord analytics & crash reporting out of the box and has no telemetry +- Privacy friendly: blocks Discord analytics & crash reporting out of the box and has no telemetry - Maintained very actively, broken plugins are usually fixed within 12 hours - Settings sync: Keep your plugins and their settings synchronised between devices / apps (optional) From 18f2b49b6744792c52eb6ffb6f14bde52bbb1622 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 9 Jun 2025 01:56:04 +0200 Subject: [PATCH 014/162] fix loading themes with spaces in their name --- src/main/index.ts | 32 ++++++++++++++++++++------------ src/main/ipcMain.ts | 2 +- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index a001a490..95301ff7 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -16,8 +16,9 @@ * along with this program. If not, see . */ -import { app, protocol } from "electron"; +import { app, net, protocol } from "electron"; import { join } from "path"; +import { pathToFileURL } from "url"; import { initCsp } from "./csp"; import { ensureSafePath } from "./ipcMain"; @@ -27,21 +28,27 @@ import { installExt } from "./utils/extensions"; if (IS_VESKTOP || !IS_VANILLA) { app.whenReady().then(() => { - // Source Maps! Maybe there's a better way but since the renderer is executed - // from a string I don't think any other form of sourcemaps would work - protocol.registerFileProtocol("vencord", ({ url: unsafeUrl }, cb) => { - let url = unsafeUrl.slice("vencord://".length); + protocol.handle("vencord", ({ url: unsafeUrl }) => { + let url = decodeURI(unsafeUrl).slice("vencord://".length).replace(/\?v=\d+$/, ""); + if (url.endsWith("/")) url = url.slice(0, -1); + if (url.startsWith("/themes/")) { const theme = url.slice("/themes/".length); + const safeUrl = ensureSafePath(THEMES_DIR, theme); if (!safeUrl) { - cb({ statusCode: 403 }); - return; + return new Response(null, { + status: 404 + }); } - cb(safeUrl.replace(/\?v=\d+$/, "")); - return; + + return net.fetch(pathToFileURL(safeUrl).toString()); } + + // Source Maps! Maybe there's a better way but since the renderer is executed + // from a string I don't think any other form of sourcemaps would work + switch (url) { case "renderer.js.map": case "vencordDesktopRenderer.js.map": @@ -49,10 +56,11 @@ if (IS_VESKTOP || !IS_VANILLA) { case "vencordDesktopPreload.js.map": case "patcher.js.map": case "vencordDesktopMain.js.map": - cb(join(__dirname, url)); - break; + return net.fetch(pathToFileURL(join(__dirname, url)).toString()); default: - cb({ statusCode: 403 }); + return new Response(null, { + status: 404 + }); } }); diff --git a/src/main/ipcMain.ts b/src/main/ipcMain.ts index 62785867..3979a1bc 100644 --- a/src/main/ipcMain.ts +++ b/src/main/ipcMain.ts @@ -35,7 +35,7 @@ import { makeLinksOpenExternally } from "./utils/externalLinks"; mkdirSync(THEMES_DIR, { recursive: true }); export function ensureSafePath(basePath: string, path: string) { - const normalizedBasePath = normalize(basePath); + const normalizedBasePath = normalize(basePath + "/"); const newPath = join(basePath, path); const normalizedPath = normalize(newPath); return normalizedPath.startsWith(normalizedBasePath) ? normalizedPath : null; From 7112caaedd80cd9bbb1134f5979bb755d0d92d43 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 11 Jun 2025 19:07:45 -0300 Subject: [PATCH 015/162] Experiments: Fix toolbar help menu dev icon patch --- src/plugins/experiments/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index 2482d051..967197ad 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -81,7 +81,7 @@ export default definePlugin({ }, // Change top right chat toolbar button from the help one to the dev one { - find: ".CONTEXTLESS,isActivityPanelMode:", + find: '"M9 3v18"', replacement: { match: /hasBugReporterAccess:(\i)/, replace: "_hasBugReporterAccess:$1=true" From 7f2c4a35660002cbf31df72ad43bff67b7026fc0 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 11 Jun 2025 19:10:14 -0300 Subject: [PATCH 016/162] Delete Banger plugin ~ Ban modal no longer contains a gif Discord removed the gifs displayed in the ban modal, so this plugin serves no purpose anymore. --- src/plugins/banger/index.ts | 49 ------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 src/plugins/banger/index.ts diff --git a/src/plugins/banger/index.ts b/src/plugins/banger/index.ts deleted file mode 100644 index eed0e1b4..00000000 --- a/src/plugins/banger/index.ts +++ /dev/null @@ -1,49 +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 { definePluginSettings } from "@api/Settings"; -import { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; - -const settings = definePluginSettings({ - source: { - description: "Source to replace ban GIF with (Video or Gif)", - type: OptionType.STRING, - default: "https://i.imgur.com/wp5q52C.mp4", - restartNeeded: true, - } -}); - -export default definePlugin({ - name: "BANger", - description: "Replaces the GIF in the ban dialogue with a custom one.", - authors: [Devs.Xinto, Devs.Glitch], - settings, - patches: [ - { - find: "#{intl::jeKpoq::raw}", // BAN_CONFIRM_TITLE - replacement: { - match: /src:\i\("?\d+"?\)/g, - replace: "src:$self.source" - } - } - ], - get source() { - return settings.store.source; - } -}); From ed5ed4b80a7b3a50858eefef083b385449147486 Mon Sep 17 00:00:00 2001 From: Vending Machine Date: Thu, 12 Jun 2025 02:19:45 +0200 Subject: [PATCH 017/162] Allow users to manually whitelist Domains for use in themes (#3476) --- browser/VencordNativeStub.ts | 1 + src/VencordNative.ts | 13 ++ src/components/VencordSettings/CloudTab.tsx | 4 +- src/components/VencordSettings/ThemesTab.tsx | 69 ++++++++-- .../VencordSettings/themesStyles.css | 23 ++++ src/main/{csp.ts => csp/index.ts} | 64 +++++---- src/main/csp/manager.ts | 125 ++++++++++++++++++ src/main/ipcMain.ts | 3 + src/main/settings.ts | 6 +- src/shared/IpcEvents.ts | 4 + src/utils/cloud.tsx | 39 +++++- src/utils/settingsSync.ts | 8 +- 12 files changed, 313 insertions(+), 46 deletions(-) rename src/main/{csp.ts => csp/index.ts} (69%) create mode 100644 src/main/csp/manager.ts diff --git a/browser/VencordNativeStub.ts b/browser/VencordNativeStub.ts index 9080f644..8cb3ff29 100644 --- a/browser/VencordNativeStub.ts +++ b/browser/VencordNativeStub.ts @@ -115,4 +115,5 @@ window.VencordNative = { }, pluginHelpers: {} as any, + csp: {} as any, }; diff --git a/src/VencordNative.ts b/src/VencordNative.ts index 3bed5a59..faf9b052 100644 --- a/src/VencordNative.ts +++ b/src/VencordNative.ts @@ -5,6 +5,7 @@ */ import type { Settings } from "@api/Settings"; +import { CspRequestResult } from "@main/csp/manager"; import { PluginIpcMappings } from "@main/ipcPlugins"; import type { UserThemeHeader } from "@main/themes"; import { IpcEvents } from "@shared/IpcEvents"; @@ -73,5 +74,17 @@ export default { openExternal: (url: string) => invoke(IpcEvents.OPEN_EXTERNAL, url) }, + csp: { + /** + * Note: Only supports full explicit matches, not wildcards. + * + * If `*.example.com` is allowed, `isDomainAllowed("https://sub.example.com")` will return false. + */ + isDomainAllowed: (url: string, directives: string[]) => invoke(IpcEvents.CSP_IS_DOMAIN_ALLOWED, url, directives), + removeOverride: (url: string) => invoke(IpcEvents.CSP_REMOVE_OVERRIDE, url), + requestAddOverride: (url: string, directives: string[], callerName: string) => + invoke(IpcEvents.CSP_REQUEST_ADD_OVERRIDE, url, directives, callerName), + }, + pluginHelpers: PluginHelpers }; diff --git a/src/components/VencordSettings/CloudTab.tsx b/src/components/VencordSettings/CloudTab.tsx index a13c3f6c..809d4062 100644 --- a/src/components/VencordSettings/CloudTab.tsx +++ b/src/components/VencordSettings/CloudTab.tsx @@ -21,7 +21,7 @@ import { Settings, useSettings } from "@api/Settings"; import { CheckedTextInput } from "@components/CheckedTextInput"; import { Grid } from "@components/Grid"; import { Link } from "@components/Link"; -import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud"; +import { authorizeCloud, checkCloudUrlCsp, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud"; import { Margins } from "@utils/margins"; import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync"; import { Alerts, Button, Forms, Switch, Tooltip } from "@webpack/common"; @@ -38,6 +38,8 @@ function validateUrl(url: string) { } async function eraseAllData() { + if (!await checkCloudUrlCsp()) return; + const res = await fetch(new URL("/v1/", getCloudUrl()), { method: "DELETE", headers: { Authorization: await getCloudAuth() } diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index 0c66ec5d..ceb59898 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -24,15 +24,15 @@ import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIc import { Link } from "@components/Link"; import { openPluginModal } from "@components/PluginSettings/PluginModal"; import type { UserThemeHeader } from "@main/themes"; -import { useCspErrors } from "@utils/cspViolations"; +import { CspBlockedUrls, useCspErrors } from "@utils/cspViolations"; import { openInviteModal } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; -import { showItemInFolder } from "@utils/native"; -import { useAwaiter } from "@utils/react"; +import { relaunch, showItemInFolder } from "@utils/native"; +import { useAwaiter, useForceUpdater } 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 { Alerts, Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common"; import type { ComponentType, Ref, SyntheticEvent } from "react"; import Plugins from "~plugins"; @@ -365,22 +365,73 @@ function ThemesTab() { } export function CspErrorCard() { + if (IS_WEB) return null; + const errors = useCspErrors(); + const forceUpdate = useForceUpdater(); if (!errors.length) return null; + const isImgurHtmlDomain = (url: string) => url.startsWith("https://imgur.com/"); + + const allowUrl = async (url: string) => { + const { origin: baseUrl, hostname } = new URL(url); + + const result = await VencordNative.csp.requestAddOverride(baseUrl, ["connect-src", "img-src", "style-src", "font-src"], "Vencord Themes"); + if (result !== "ok") return; + + CspBlockedUrls.forEach(url => { + if (new URL(url).hostname === hostname) { + CspBlockedUrls.delete(url); + } + }); + + forceUpdate(); + + Alerts.show({ + title: "Restart Required", + body: "A restart is required to apply this change", + confirmText: "Restart now", + cancelText: "Later!", + onConfirm: relaunch + }); + }; + + const hasImgurHtmlDomain = errors.some(isImgurHtmlDomain); + return ( Blocked Resources Some images, styles, or fonts were blocked because they come from disallowed domains. - Make sure that your themes and custom css only load resources from whitelisted websites, such as GitHub, Imgur and Google Fonts. + It is highly recommended to move them to GitHub or Imgur. But you may also allow domains if you fully trust them. + + After allowing a domain, you have to fully close (from tray / task manager) and restart {IS_DISCORD_DESKTOP ? "Discord" : "Vesktop"} to apply the change. + Blocked URLs - - {errors.map(url => ( - {url} +
+ {errors.map((url, i) => ( +
+ {i !== 0 && } +
+ {url} + +
+
))} - +
+ + {hasImgurHtmlDomain && ( + <> + + + Imgur links should be direct links in the form of https://i.imgur.com/... + + To obtain a direct link, right-click the image and select "Copy image address". + + )}
); } diff --git a/src/components/VencordSettings/themesStyles.css b/src/components/VencordSettings/themesStyles.css index 6038274f..b10daff3 100644 --- a/src/components/VencordSettings/themesStyles.css +++ b/src/components/VencordSettings/themesStyles.css @@ -27,3 +27,26 @@ .vc-settings-theme-author::before { content: "by "; } + +.vc-settings-csp-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.vc-settings-csp-row { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + + + & a { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + line-height: 1.2em; + } + + --custom-button-button-md-height: 26px; +} diff --git a/src/main/csp.ts b/src/main/csp/index.ts similarity index 69% rename from src/main/csp.ts rename to src/main/csp/index.ts index 2faee606..fefbc774 100644 --- a/src/main/csp.ts +++ b/src/main/csp/index.ts @@ -4,59 +4,63 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { NativeSettings } from "@main/settings"; import { session } from "electron"; type PolicyMap = Record; export const ConnectSrc = ["connect-src"]; -export const MediaSrc = [...ConnectSrc, "img-src", "media-src"]; +export const ImageSrc = [...ConnectSrc, "img-src"]; export const CssSrc = ["style-src", "font-src"]; -export const MediaAndCssSrc = [...MediaSrc, ...CssSrc]; -export const MediaScriptsAndCssSrc = [...MediaAndCssSrc, "script-src", "worker-src"]; +export const ImageAndCssSrc = [...ImageSrc, ...CssSrc]; +export const ImageScriptsAndCssSrc = [...ImageAndCssSrc, "script-src", "worker-src"]; // Plugins can whitelist their own domains by importing this object in their native.ts // script and just adding to it. But generally, you should just edit this file instead export const CspPolicies: PolicyMap = { - "*.github.io": MediaAndCssSrc, // GitHub pages, used by most themes - "github.com": MediaAndCssSrc, // GitHub content (stuff uploaded to markdown forms), used by most themes - "raw.githubusercontent.com": MediaAndCssSrc, // GitHub raw, used by some themes - "*.gitlab.io": MediaAndCssSrc, // GitLab pages, used by some themes - "gitlab.com": MediaAndCssSrc, // GitLab raw, used by some themes - "*.codeberg.page": MediaAndCssSrc, // Codeberg pages, used by some themes - "codeberg.org": MediaAndCssSrc, // Codeberg raw, used by some themes + "localhost": ImageAndCssSrc, + "127.0.0.1": ImageAndCssSrc, - "*.githack.com": MediaAndCssSrc, // githack (namely raw.githack.com), used by some themes - "jsdelivr.net": MediaAndCssSrc, // jsDelivr, used by very few themes + "*.github.io": ImageAndCssSrc, // GitHub pages, used by most themes + "github.com": ImageAndCssSrc, // GitHub content (stuff uploaded to markdown forms), used by most themes + "raw.githubusercontent.com": ImageAndCssSrc, // GitHub raw, used by some themes + "*.gitlab.io": ImageAndCssSrc, // GitLab pages, used by some themes + "gitlab.com": ImageAndCssSrc, // GitLab raw, used by some themes + "*.codeberg.page": ImageAndCssSrc, // Codeberg pages, used by some themes + "codeberg.org": ImageAndCssSrc, // Codeberg raw, used by some themes + + "*.githack.com": ImageAndCssSrc, // githack (namely raw.githack.com), used by some themes + "jsdelivr.net": ImageAndCssSrc, // jsDelivr, used by very few themes "fonts.googleapis.com": CssSrc, // Google Fonts, used by many themes - "i.imgur.com": MediaSrc, // Imgur, used by some themes - "i.ibb.co": MediaSrc, // ImgBB, used by some themes - "i.pinimg.com": MediaSrc, // Pinterest, used by some themes - "*.tenor.com": MediaSrc, // Tenor, used by some themes - "files.catbox.moe": MediaSrc, // Catbox, used by some themes + "i.imgur.com": ImageSrc, // Imgur, used by some themes + "i.ibb.co": ImageSrc, // ImgBB, used by some themes + "i.pinimg.com": ImageSrc, // Pinterest, used by some themes + "*.tenor.com": ImageSrc, // Tenor, used by some themes + "files.catbox.moe": ImageAndCssSrc, // Catbox, used by some themes - "cdn.discordapp.com": MediaAndCssSrc, // Discord CDN, used by Vencord and some themes to load media - "media.discordapp.net": MediaSrc, // Discord media CDN, possible alternative to Discord CDN + "cdn.discordapp.com": ImageAndCssSrc, // Discord CDN, used by Vencord and some themes to load media + "media.discordapp.net": ImageSrc, // Discord media CDN, possible alternative to Discord CDN // CDNs used for some things by Vencord. // FIXME: we really should not be using CDNs anymore - "cdnjs.cloudflare.com": MediaScriptsAndCssSrc, - "cdn.jsdelivr.net": MediaScriptsAndCssSrc, + "cdnjs.cloudflare.com": ImageScriptsAndCssSrc, + "cdn.jsdelivr.net": ImageScriptsAndCssSrc, // Function Specific "api.github.com": ConnectSrc, // used for updating Vencord itself "ws.audioscrobbler.com": ConnectSrc, // Last.fm API "translate-pa.googleapis.com": ConnectSrc, // Google Translate API - "*.vencord.dev": MediaSrc, // VenCloud (api.vencord.dev) and Badges (badges.vencord.dev) - "manti.vendicated.dev": MediaSrc, // ReviewDB API + "*.vencord.dev": ImageSrc, // VenCloud (api.vencord.dev) and Badges (badges.vencord.dev) + "manti.vendicated.dev": ImageSrc, // ReviewDB API "decor.fieryflames.dev": ConnectSrc, // Decor API - "ugc.decor.fieryflames.dev": MediaSrc, // Decor CDN + "ugc.decor.fieryflames.dev": ImageSrc, // Decor CDN "sponsor.ajay.app": ConnectSrc, // Dearrow API - "dearrow-thumb.ajay.app": MediaSrc, // Dearrow Thumbnail CDN - "usrbg.is-hardly.online": MediaSrc, // USRBG API - "icons.duckduckgo.com": MediaSrc, // DuckDuckGo Favicon API (Reverse Image Search) + "dearrow-thumb.ajay.app": ImageSrc, // Dearrow Thumbnail CDN + "usrbg.is-hardly.online": ImageSrc, // USRBG API + "icons.duckduckgo.com": ImageSrc, // DuckDuckGo Favicon API (Reverse Image Search) }; const findHeader = (headers: PolicyMap, headerName: Lowercase) => { @@ -107,6 +111,12 @@ const patchCsp = (headers: PolicyMap) => { pushDirective(directive, "blob:", "data:", "vencord:"); } + for (const [host, directives] of Object.entries(NativeSettings.store.customCspRules)) { + for (const directive of directives) { + pushDirective(directive, host); + } + } + for (const [host, directives] of Object.entries(CspPolicies)) { for (const directive of directives) { pushDirective(directive, host); diff --git a/src/main/csp/manager.ts b/src/main/csp/manager.ts new file mode 100644 index 00000000..b8fbbea3 --- /dev/null +++ b/src/main/csp/manager.ts @@ -0,0 +1,125 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { NativeSettings } from "@main/settings"; +import { IpcEvents } from "@shared/IpcEvents"; +import { dialog, ipcMain, IpcMainInvokeEvent } from "electron"; + +import { CspPolicies, ImageAndCssSrc } from "."; + +export type CspRequestResult = "invalid" | "cancelled" | "unchecked" | "ok" | "conflict"; + +export function registerCspIpcHandlers() { + ipcMain.handle(IpcEvents.CSP_REMOVE_OVERRIDE, removeCspRule); + ipcMain.handle(IpcEvents.CSP_REQUEST_ADD_OVERRIDE, addCspRule); + ipcMain.handle(IpcEvents.CSP_IS_DOMAIN_ALLOWED, isDomainAllowed); +} + +function validate(url: string, directives: string[]) { + try { + const { hostname } = new URL(url); + + if (/[;'"\\]/.test(hostname)) return false; + } catch { + return false; + } + + if (directives.length === 0) return false; + if (directives.some(d => !ImageAndCssSrc.includes(d))) return false; + + return true; +} + +function getMessage(url: string, directives: string[], callerName: string) { + const domain = new URL(url).hostname; + + const message = `${callerName} wants to allow connections to ${domain}`; + + let detail = + `Unless you recognise and fully trust ${domain}, you should cancel this request!\n\n` + + `You will have to fully close and restart ${IS_DISCORD_DESKTOP ? "Discord" : "Vesktop"} for the changes to take effect.`; + + if (directives.length === 1 && directives[0] === "connect-src") { + return { message, detail }; + } + + const contentTypes = directives + .filter(type => type !== "connect-src") + .map(type => { + switch (type) { + case "img-src": + return "Images"; + case "style-src": + return "CSS & Themes"; + case "font-src": + return "Fonts"; + default: + throw new Error(`Illegal CSP directive: ${type}`); + } + }) + .sort() + .join(", "); + + detail = `The following types of content will be allowed to load from ${domain}:\n${contentTypes}\n\n${detail}`; + + return { message, detail }; +} + +async function addCspRule(_: IpcMainInvokeEvent, url: string, directives: string[], callerName: string): Promise { + if (!validate(url, directives)) { + return "invalid"; + } + + const domain = new URL(url).hostname; + + if (domain in NativeSettings.store.customCspRules) { + return "conflict"; + } + + const { checkboxChecked, response } = await dialog.showMessageBox({ + ...getMessage(url, directives, callerName), + type: callerName ? "info" : "warning", + title: "Vencord Host Permissions", + buttons: ["Cancel", "Allow"], + defaultId: 0, + cancelId: 0, + checkboxLabel: `I fully trust ${domain} and understand the risks of allowing connections to it.`, + checkboxChecked: false, + }); + + if (response !== 1) { + return "cancelled"; + } + + if (!checkboxChecked) { + return "unchecked"; + } + + NativeSettings.store.customCspRules[domain] = directives; + return "ok"; +} + +function removeCspRule(_: IpcMainInvokeEvent, domain: string) { + if (domain in NativeSettings.store.customCspRules) { + delete NativeSettings.store.customCspRules[domain]; + return true; + } + + return false; +} + +function isDomainAllowed(_: IpcMainInvokeEvent, url: string, directives: string[]) { + try { + const domain = new URL(url).hostname; + + const ruleForDomain = CspPolicies[domain] ?? NativeSettings.store.customCspRules[domain]; + if (!ruleForDomain) return false; + + return directives.every(d => ruleForDomain.includes(d)); + } catch (e) { + return false; + } +} diff --git a/src/main/ipcMain.ts b/src/main/ipcMain.ts index 3979a1bc..b9e4099b 100644 --- a/src/main/ipcMain.ts +++ b/src/main/ipcMain.ts @@ -28,12 +28,15 @@ import { FSWatcher, mkdirSync, watch, writeFileSync } from "fs"; import { open, readdir, readFile } from "fs/promises"; import { join, normalize } from "path"; +import { registerCspIpcHandlers } from "./csp/manager"; import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes"; import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, THEMES_DIR } from "./utils/constants"; import { makeLinksOpenExternally } from "./utils/externalLinks"; mkdirSync(THEMES_DIR, { recursive: true }); +registerCspIpcHandlers(); + export function ensureSafePath(basePath: string, path: string) { const normalizedBasePath = normalize(basePath + "/"); const newPath = join(basePath, path); diff --git a/src/main/settings.ts b/src/main/settings.ts index 3d367a94..ed2f4850 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -49,16 +49,18 @@ export interface NativeSettings { [setting: string]: any; }; }; + customCspRules: Record; } const DefaultNativeSettings: NativeSettings = { - plugins: {} + plugins: {}, + customCspRules: {} }; const nativeSettings = readSettings("native", NATIVE_SETTINGS_FILE); mergeDefaults(nativeSettings, DefaultNativeSettings); -export const NativeSettings = new SettingsStore(nativeSettings); +export const NativeSettings = new SettingsStore(nativeSettings as NativeSettings); NativeSettings.addGlobalChangeListener(() => { try { diff --git a/src/shared/IpcEvents.ts b/src/shared/IpcEvents.ts index 2027df9c..914a0a9f 100644 --- a/src/shared/IpcEvents.ts +++ b/src/shared/IpcEvents.ts @@ -42,4 +42,8 @@ export const enum IpcEvents { OPEN_IN_APP__RESOLVE_REDIRECT = "VencordOIAResolveRedirect", VOICE_MESSAGES_READ_RECORDING = "VencordVMReadRecording", + + CSP_IS_DOMAIN_ALLOWED = "VencordCspIsDomainAllowed", + CSP_REMOVE_OVERRIDE = "VencordCspRemoveOverride", + CSP_REQUEST_ADD_OVERRIDE = "VencordCspRequestAddOverride", } diff --git a/src/utils/cloud.tsx b/src/utils/cloud.tsx index 508b1c7e..3b482d4d 100644 --- a/src/utils/cloud.tsx +++ b/src/utils/cloud.tsx @@ -19,15 +19,40 @@ import * as DataStore from "@api/DataStore"; import { showNotification } from "@api/Notifications"; import { Settings } from "@api/Settings"; -import { OAuth2AuthorizeModal, UserStore } from "@webpack/common"; +import { Alerts, OAuth2AuthorizeModal, UserStore } from "@webpack/common"; import { Logger } from "./Logger"; import { openModal } from "./modal"; +import { relaunch } from "./native"; export const cloudLogger = new Logger("Cloud", "#39b7e0"); -export const getCloudUrl = () => new URL(Settings.cloud.url); -const cloudUrlOrigin = () => getCloudUrl().origin; +export const getCloudUrl = () => new URL(Settings.cloud.url); +const getCloudUrlOrigin = () => getCloudUrl().origin; + +export async function checkCloudUrlCsp() { + if (IS_WEB) return true; + + const { host } = getCloudUrl(); + if (host === "api.vencord.dev") return true; + + if (await VencordNative.csp.isDomainAllowed(Settings.cloud.url, ["connect-src"])) { + return true; + } + + const res = await VencordNative.csp.requestAddOverride(Settings.cloud.url, ["connect-src"], "Cloud Sync"); + if (res === "ok") { + Alerts.show({ + title: "Cloud Integration enabled", + body: `${host} has been added to the whitelist. Please restart the app for the changes to take effect.`, + confirmText: "Restart now", + cancelText: "Later!", + onConfirm: relaunch + }); + } + return false; +} + const getUserId = () => { const id = UserStore.getCurrentUser()?.id; if (!id) throw new Error("User not yet logged in"); @@ -37,7 +62,7 @@ const getUserId = () => { export async function getAuthorization() { const secrets = await DataStore.get>("Vencord_cloudSecret") ?? {}; - const origin = cloudUrlOrigin(); + const origin = getCloudUrlOrigin(); // we need to migrate from the old format here if (secrets[origin]) { @@ -59,7 +84,7 @@ export async function getAuthorization() { async function setAuthorization(secret: string) { await DataStore.update>("Vencord_cloudSecret", secrets => { secrets ??= {}; - secrets[`${cloudUrlOrigin()}:${getUserId()}`] = secret; + secrets[`${getCloudUrlOrigin()}:${getUserId()}`] = secret; return secrets; }); } @@ -67,7 +92,7 @@ async function setAuthorization(secret: string) { export async function deauthorizeCloud() { await DataStore.update>("Vencord_cloudSecret", secrets => { secrets ??= {}; - delete secrets[`${cloudUrlOrigin()}:${getUserId()}`]; + delete secrets[`${getCloudUrlOrigin()}:${getUserId()}`]; return secrets; }); } @@ -78,6 +103,8 @@ export async function authorizeCloud() { return; } + if (!await checkCloudUrlCsp()) return; + try { const oauthConfiguration = await fetch(new URL("/v1/oauth/settings", getCloudUrl())); var { clientId, redirectUri } = await oauthConfiguration.json(); diff --git a/src/utils/settingsSync.ts b/src/utils/settingsSync.ts index 6ec3e527..c711aacd 100644 --- a/src/utils/settingsSync.ts +++ b/src/utils/settingsSync.ts @@ -21,7 +21,7 @@ import { PlainSettings, Settings } from "@api/Settings"; import { moment, Toasts } from "@webpack/common"; import { deflateSync, inflateSync } from "fflate"; -import { getCloudAuth, getCloudUrl } from "./cloud"; +import { checkCloudUrlCsp, getCloudAuth, getCloudUrl } from "./cloud"; import { Logger } from "./Logger"; import { relaunch } from "./native"; import { chooseFile, saveFile } from "./web"; @@ -115,6 +115,8 @@ const cloudSettingsLogger = new Logger("Cloud:Settings", "#39b7e0"); export async function putCloudSettings(manual?: boolean) { const settings = await exportSettings({ minify: true }); + if (!await checkCloudUrlCsp()) return; + try { const res = await fetch(new URL("/v1/settings", getCloudUrl()), { method: "PUT", @@ -159,6 +161,8 @@ export async function putCloudSettings(manual?: boolean) { } export async function getCloudSettings(shouldNotify = true, force = false) { + if (!await checkCloudUrlCsp()) return; + try { const res = await fetch(new URL("/v1/settings", getCloudUrl()), { method: "GET", @@ -248,6 +252,8 @@ export async function getCloudSettings(shouldNotify = true, force = false) { } export async function deleteCloudSettings() { + if (!await checkCloudUrlCsp()) return; + try { const res = await fetch(new URL("/v1/settings", getCloudUrl()), { method: "DELETE", From a366693e96bb67ef76d705f737458b3e58d624cd Mon Sep 17 00:00:00 2001 From: Randomuser8219 <168323856+Randomuser8219@users.noreply.github.com> Date: Wed, 11 Jun 2025 17:24:24 -0700 Subject: [PATCH 018/162] ServerInfo: rename "Nitro Boosts" -> "Server Boosts" (#3364) --- src/plugins/serverInfo/GuildInfoModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index 0f08af59..60401b71 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -198,7 +198,7 @@ function ServerInfoTab({ guild }: GuildProps) { "Vanity Link": guild.vanityURLCode ? ({`discord.gg/${guild.vanityURLCode}`}) : "-", // Making the anchor href valid would cause Discord to reload "Preferred Locale": guild.preferredLocale || "-", "Verification Level": ["None", "Low", "Medium", "High", "Highest"][guild.verificationLevel] || "?", - "Nitro Boosts": `${guild.premiumSubscriberCount ?? 0} (Level ${guild.premiumTier ?? 0})`, + "Server Boosts": `${guild.premiumSubscriberCount ?? 0} (Level ${guild.premiumTier ?? 0})`, "Channels": GuildChannelStore.getChannels(guild.id)?.count - 1 || "?", // - null category "Roles": Object.keys(GuildStore.getRoles(guild.id)).length - 1, // - @everyone }; From b35b72c066710943bc4569d4e5791fb758559b11 Mon Sep 17 00:00:00 2001 From: T1ckbase <146760065+T1ckbase@users.noreply.github.com> Date: Thu, 12 Jun 2025 08:29:50 +0800 Subject: [PATCH 019/162] Translate: Make translation more readable (#3252) --- src/plugins/translate/TranslationAccessory.tsx | 2 +- src/plugins/translate/styles.css | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/translate/TranslationAccessory.tsx b/src/plugins/translate/TranslationAccessory.tsx index 9b6393cc..db9461d8 100644 --- a/src/plugins/translate/TranslationAccessory.tsx +++ b/src/plugins/translate/TranslationAccessory.tsx @@ -57,7 +57,7 @@ export function TranslationAccessory({ message }: { message: Message; }) { {Parser.parse(translation.text)} - {" "} +
(translated from {translation.sourceLanguage} - setTranslation(undefined)} />)
); diff --git a/src/plugins/translate/styles.css b/src/plugins/translate/styles.css index c07c9e36..33a1fc85 100644 --- a/src/plugins/translate/styles.css +++ b/src/plugins/translate/styles.css @@ -15,6 +15,8 @@ margin-top: 0.5em; font-style: italic; font-weight: 400; + line-height: 1.2rem; + white-space: break-spaces; } .vc-trans-accessory-icon { From 2a398985cf2e704f086193e868ca355da6c96685 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 14 Jun 2025 00:55:14 +0200 Subject: [PATCH 020/162] fix: correctly allow resources from localhost --- src/components/VencordSettings/ThemesTab.tsx | 4 ++-- src/main/csp/index.ts | 6 ++++-- src/main/csp/manager.ts | 10 +++++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index ceb59898..e3fbe53a 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -375,13 +375,13 @@ export function CspErrorCard() { const isImgurHtmlDomain = (url: string) => url.startsWith("https://imgur.com/"); const allowUrl = async (url: string) => { - const { origin: baseUrl, hostname } = new URL(url); + const { origin: baseUrl, host } = new URL(url); const result = await VencordNative.csp.requestAddOverride(baseUrl, ["connect-src", "img-src", "style-src", "font-src"], "Vencord Themes"); if (result !== "ok") return; CspBlockedUrls.forEach(url => { - if (new URL(url).hostname === hostname) { + if (new URL(url).host === host) { CspBlockedUrls.delete(url); } }); diff --git a/src/main/csp/index.ts b/src/main/csp/index.ts index fefbc774..c4192f4b 100644 --- a/src/main/csp/index.ts +++ b/src/main/csp/index.ts @@ -19,8 +19,10 @@ export const ImageScriptsAndCssSrc = [...ImageAndCssSrc, "script-src", "worker-s // script and just adding to it. But generally, you should just edit this file instead export const CspPolicies: PolicyMap = { - "localhost": ImageAndCssSrc, - "127.0.0.1": ImageAndCssSrc, + "http://localhost:*": ImageAndCssSrc, + "http://127.0.0.1:*": ImageAndCssSrc, + "localhost:*": ImageAndCssSrc, + "127.0.0.1:*": ImageAndCssSrc, "*.github.io": ImageAndCssSrc, // GitHub pages, used by most themes "github.com": ImageAndCssSrc, // GitHub content (stuff uploaded to markdown forms), used by most themes diff --git a/src/main/csp/manager.ts b/src/main/csp/manager.ts index b8fbbea3..e6b1a0e3 100644 --- a/src/main/csp/manager.ts +++ b/src/main/csp/manager.ts @@ -20,9 +20,9 @@ export function registerCspIpcHandlers() { function validate(url: string, directives: string[]) { try { - const { hostname } = new URL(url); + const { host } = new URL(url); - if (/[;'"\\]/.test(hostname)) return false; + if (/[;'"\\]/.test(host)) return false; } catch { return false; } @@ -34,7 +34,7 @@ function validate(url: string, directives: string[]) { } function getMessage(url: string, directives: string[], callerName: string) { - const domain = new URL(url).hostname; + const domain = new URL(url).host; const message = `${callerName} wants to allow connections to ${domain}`; @@ -73,7 +73,7 @@ async function addCspRule(_: IpcMainInvokeEvent, url: string, directives: string return "invalid"; } - const domain = new URL(url).hostname; + const domain = new URL(url).host; if (domain in NativeSettings.store.customCspRules) { return "conflict"; @@ -113,7 +113,7 @@ function removeCspRule(_: IpcMainInvokeEvent, domain: string) { function isDomainAllowed(_: IpcMainInvokeEvent, url: string, directives: string[]) { try { - const domain = new URL(url).hostname; + const domain = new URL(url).host; const ruleForDomain = CspPolicies[domain] ?? NativeSettings.store.customCspRules[domain]; if (!ruleForDomain) return false; From 78d3330ccf509b03cb3de2e790092b9d0707984e Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 14 Jun 2025 18:51:40 +0200 Subject: [PATCH 021/162] make Open Themes/Settings folder properly open the folder --- browser/VencordNativeStub.ts | 5 ++++- src/VencordNative.ts | 4 ++++ src/components/VencordSettings/ThemesTab.tsx | 4 ++-- src/components/VencordSettings/VencordTab.tsx | 8 ++------ src/main/ipcMain.ts | 4 +++- src/shared/IpcEvents.ts | 3 +++ 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/browser/VencordNativeStub.ts b/browser/VencordNativeStub.ts index 8cb3ff29..ce14d28d 100644 --- a/browser/VencordNativeStub.ts +++ b/browser/VencordNativeStub.ts @@ -48,6 +48,8 @@ window.VencordNative = { ), getThemeData: (fileName: string) => DataStore.get(fileName, themeStore), getSystemValues: async () => ({}), + + openFolder: async () => Promise.reject("themes:openFolder is not supported on web"), }, native: { @@ -111,7 +113,8 @@ window.VencordNative = { } }, set: async (s: Settings) => localStorage.setItem("VencordSettings", JSON.stringify(s)), - getSettingsDir: async () => "LocalStorage" + getSettingsDir: async () => "LocalStorage", + openFolder: async () => Promise.reject("settings:openFolder is not supported on web"), }, pluginHelpers: {} as any, diff --git a/src/VencordNative.ts b/src/VencordNative.ts index faf9b052..5c581ccf 100644 --- a/src/VencordNative.ts +++ b/src/VencordNative.ts @@ -38,6 +38,8 @@ export default { getThemesList: () => invoke>(IpcEvents.GET_THEMES_LIST), getThemeData: (fileName: string) => invoke(IpcEvents.GET_THEME_DATA, fileName), getSystemValues: () => invoke>(IpcEvents.GET_THEME_SYSTEM_VALUES), + + openFolder: () => invoke(IpcEvents.OPEN_THEMES_FOLDER), }, updater: { @@ -51,6 +53,8 @@ export default { get: () => sendSync(IpcEvents.GET_SETTINGS), set: (settings: Settings, pathToNotify?: string) => invoke(IpcEvents.SET_SETTINGS, settings, pathToNotify), getSettingsDir: () => invoke(IpcEvents.GET_SETTINGS_DIR), + + openFolder: () => invoke(IpcEvents.OPEN_SETTINGS_FOLDER), }, quickCss: { diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index e3fbe53a..9682d51a 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -28,7 +28,7 @@ import { CspBlockedUrls, useCspErrors } from "@utils/cspViolations"; import { openInviteModal } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; -import { relaunch, showItemInFolder } from "@utils/native"; +import { relaunch } from "@utils/native"; import { useAwaiter, useForceUpdater } from "@utils/react"; import { getStylusWebStoreUrl } from "@utils/web"; import { findLazy } from "@webpack"; @@ -251,7 +251,7 @@ function ThemesTab() { ) : ( showItemInFolder(themeDir!)} + action={() => VencordNative.themes.openFolder()} disabled={themeDirPending} Icon={FolderIcon} /> diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index 54da5a3f..047ba17d 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -26,8 +26,7 @@ import { gitRemote } from "@shared/vencordUserAgent"; import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants"; import { Margins } from "@utils/margins"; import { identity, isPluginDev } from "@utils/misc"; -import { relaunch, showItemInFolder } from "@utils/native"; -import { useAwaiter } from "@utils/react"; +import { relaunch } from "@utils/native"; import { Button, Forms, GuildMemberStore, React, Select, Switch, UserStore } from "@webpack/common"; import BadgeAPI from "../../plugins/_api/badges"; @@ -53,9 +52,6 @@ type KeysOfType = { }[keyof Object]; function VencordSettings() { - const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, { - fallbackValue: "Loading..." - }); const settings = useSettings(); const donateImage = React.useMemo(() => Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE, []); @@ -171,7 +167,7 @@ function VencordSettings() { showItemInFolder(settingsDir)} + action={() => VencordNative.settings.openFolder()} /> )} ({ "os-accent-color": `#${systemPreferences.getAccentColor?.() || ""}` })); +ipcMain.handle(IpcEvents.OPEN_THEMES_FOLDER, () => shell.openPath(THEMES_DIR)); +ipcMain.handle(IpcEvents.OPEN_SETTINGS_FOLDER, () => shell.openPath(SETTINGS_DIR)); export function initIpc(mainWindow: BrowserWindow) { let quickCssWatcher: FSWatcher | undefined; diff --git a/src/shared/IpcEvents.ts b/src/shared/IpcEvents.ts index 914a0a9f..0354e697 100644 --- a/src/shared/IpcEvents.ts +++ b/src/shared/IpcEvents.ts @@ -46,4 +46,7 @@ export const enum IpcEvents { CSP_IS_DOMAIN_ALLOWED = "VencordCspIsDomainAllowed", CSP_REMOVE_OVERRIDE = "VencordCspRemoveOverride", CSP_REQUEST_ADD_OVERRIDE = "VencordCspRequestAddOverride", + + OPEN_THEMES_FOLDER = "VencordOpenThemesFolder", + OPEN_SETTINGS_FOLDER = "VencordOpenSettingsFolder", } From 3a1e17e04d70cec42967942e8da758c22693c09a Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 14 Jun 2025 18:55:12 +0200 Subject: [PATCH 022/162] remove redundant methods --- browser/VencordNativeStub.ts | 2 -- src/VencordNative.ts | 2 -- src/components/VencordSettings/ThemesTab.tsx | 2 -- src/main/ipcMain.ts | 1 - src/main/settings.ts | 1 - src/shared/IpcEvents.ts | 26 +++++++++++--------- 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/browser/VencordNativeStub.ts b/browser/VencordNativeStub.ts index ce14d28d..c99c176c 100644 --- a/browser/VencordNativeStub.ts +++ b/browser/VencordNativeStub.ts @@ -42,7 +42,6 @@ window.VencordNative = { themes: { uploadTheme: (fileName: string, fileData: string) => DataStore.set(fileName, fileData, themeStore), deleteTheme: (fileName: string) => DataStore.del(fileName, themeStore), - getThemesDir: async () => "", getThemesList: () => DataStore.entries(themeStore).then(entries => entries.map(([name, css]) => getThemeInfo(css, name.toString())) ), @@ -113,7 +112,6 @@ window.VencordNative = { } }, set: async (s: Settings) => localStorage.setItem("VencordSettings", JSON.stringify(s)), - getSettingsDir: async () => "LocalStorage", openFolder: async () => Promise.reject("settings:openFolder is not supported on web"), }, diff --git a/src/VencordNative.ts b/src/VencordNative.ts index 5c581ccf..048b30c7 100644 --- a/src/VencordNative.ts +++ b/src/VencordNative.ts @@ -34,7 +34,6 @@ export default { themes: { uploadTheme: (fileName: string, fileData: string) => invoke(IpcEvents.UPLOAD_THEME, fileName, fileData), deleteTheme: (fileName: string) => invoke(IpcEvents.DELETE_THEME, fileName), - getThemesDir: () => invoke(IpcEvents.GET_THEMES_DIR), getThemesList: () => invoke>(IpcEvents.GET_THEMES_LIST), getThemeData: (fileName: string) => invoke(IpcEvents.GET_THEME_DATA, fileName), getSystemValues: () => invoke>(IpcEvents.GET_THEME_SYSTEM_VALUES), @@ -52,7 +51,6 @@ export default { settings: { get: () => sendSync(IpcEvents.GET_SETTINGS), set: (settings: Settings, pathToNotify?: string) => invoke(IpcEvents.SET_SETTINGS, settings, pathToNotify), - getSettingsDir: () => invoke(IpcEvents.GET_SETTINGS_DIR), openFolder: () => invoke(IpcEvents.OPEN_SETTINGS_FOLDER), }, diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index 9682d51a..128b6817 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -163,7 +163,6 @@ function ThemesTab() { const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL); const [themeText, setThemeText] = useState(settings.themeLinks.join("\n")); const [userThemes, setUserThemes] = useState(null); - const [themeDir, , themeDirPending] = useAwaiter(VencordNative.themes.getThemesDir); useEffect(() => { refreshLocalThemes(); @@ -252,7 +251,6 @@ function ThemesTab() { VencordNative.themes.openFolder()} - disabled={themeDirPending} Icon={FolderIcon} /> )} diff --git a/src/main/ipcMain.ts b/src/main/ipcMain.ts index 324a20e1..6990cea9 100644 --- a/src/main/ipcMain.ts +++ b/src/main/ipcMain.ts @@ -92,7 +92,6 @@ ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) => writeFileSync(QUICKCSS_PATH, css) ); -ipcMain.handle(IpcEvents.GET_THEMES_DIR, () => THEMES_DIR); ipcMain.handle(IpcEvents.GET_THEMES_LIST, () => listThemes()); ipcMain.handle(IpcEvents.GET_THEME_DATA, (_, fileName) => getThemeData(fileName)); ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => ({ diff --git a/src/main/settings.ts b/src/main/settings.ts index ed2f4850..962bbaa7 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -36,7 +36,6 @@ RendererSettings.addGlobalChangeListener(() => { } }); -ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR); ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = RendererSettings.plain); ipcMain.handle(IpcEvents.SET_SETTINGS, (_, data: Settings, pathToNotify?: string) => { diff --git a/src/shared/IpcEvents.ts b/src/shared/IpcEvents.ts index 0354e697..e7eb7594 100644 --- a/src/shared/IpcEvents.ts +++ b/src/shared/IpcEvents.ts @@ -17,25 +17,30 @@ */ export const enum IpcEvents { - QUICK_CSS_UPDATE = "VencordQuickCssUpdate", - THEME_UPDATE = "VencordThemeUpdate", + OPEN_QUICKCSS = "VencordOpenQuickCss", GET_QUICK_CSS = "VencordGetQuickCss", SET_QUICK_CSS = "VencordSetQuickCss", - UPLOAD_THEME = "VencordUploadTheme", - DELETE_THEME = "VencordDeleteTheme", - GET_THEMES_DIR = "VencordGetThemesDir", + QUICK_CSS_UPDATE = "VencordQuickCssUpdate", + + GET_SETTINGS = "VencordGetSettings", + SET_SETTINGS = "VencordSetSettings", + GET_THEMES_LIST = "VencordGetThemesList", GET_THEME_DATA = "VencordGetThemeData", GET_THEME_SYSTEM_VALUES = "VencordGetThemeSystemValues", - GET_SETTINGS_DIR = "VencordGetSettingsDir", - GET_SETTINGS = "VencordGetSettings", - SET_SETTINGS = "VencordSetSettings", + UPLOAD_THEME = "VencordUploadTheme", + DELETE_THEME = "VencordDeleteTheme", + THEME_UPDATE = "VencordThemeUpdate", + OPEN_EXTERNAL = "VencordOpenExternal", - OPEN_QUICKCSS = "VencordOpenQuickCss", + OPEN_THEMES_FOLDER = "VencordOpenThemesFolder", + OPEN_SETTINGS_FOLDER = "VencordOpenSettingsFolder", + GET_UPDATES = "VencordGetUpdates", GET_REPO = "VencordGetRepo", UPDATE = "VencordUpdate", BUILD = "VencordBuild", + OPEN_MONACO_EDITOR = "VencordOpenMonacoEditor", GET_PLUGIN_IPC_METHOD_MAP = "VencordGetPluginIpcMethodMap", @@ -46,7 +51,4 @@ export const enum IpcEvents { CSP_IS_DOMAIN_ALLOWED = "VencordCspIsDomainAllowed", CSP_REMOVE_OVERRIDE = "VencordCspRemoveOverride", CSP_REQUEST_ADD_OVERRIDE = "VencordCspRequestAddOverride", - - OPEN_THEMES_FOLDER = "VencordOpenThemesFolder", - OPEN_SETTINGS_FOLDER = "VencordOpenSettingsFolder", } From 8d97863db6d1c6e628a057e3cb66ea1634d0a3f9 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 17 Jun 2025 21:58:12 +0200 Subject: [PATCH 023/162] Fix ImplicitRelationships, RelationshipNotifier & ServerInfo --- src/plugins/implicitRelationships/index.ts | 18 ++++++++---------- src/plugins/noBlockedMessages/index.ts | 5 +---- src/plugins/quickReply/index.ts | 4 ---- src/plugins/relationshipNotifier/utils.ts | 2 +- src/plugins/serverInfo/GuildInfoModal.tsx | 4 ++-- src/webpack/common/stores.ts | 6 +----- src/webpack/common/types/stores.d.ts | 17 +++++++++++++++++ 7 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/plugins/implicitRelationships/index.ts b/src/plugins/implicitRelationships/index.ts index 13c12ed9..edaf4be9 100644 --- a/src/plugins/implicitRelationships/index.ts +++ b/src/plugins/implicitRelationships/index.ts @@ -23,7 +23,7 @@ import { findStoreLazy } from "@webpack"; import { Constants, FluxDispatcher, GuildStore, RelationshipStore, RestAPI, SnowflakeUtils, UserStore } from "@webpack/common"; import { Settings } from "Vencord"; -const UserAffinitiesStore = findStoreLazy("UserAffinitiesStore"); +const UserAffinitiesStore = findStoreLazy("UserAffinitiesV2Store"); export default definePlugin({ name: "ImplicitRelationships", @@ -117,7 +117,7 @@ export default definePlugin({ wrapSort(comparator: Function, row: any) { return row.type === 5 - ? -(UserAffinitiesStore.getUserAffinity(row.user.id)?.affinity ?? 0) + ? (UserAffinitiesStore.getUserAffinity(row.user.id)?.communicationRank ?? 0) : comparator(row); }, @@ -139,17 +139,15 @@ export default definePlugin({ // 1. Have an affinity for // 2. Do not have a relationship with await this.refreshUserAffinities(); - const userAffinities: Set = UserAffinitiesStore.getUserAffinitiesUserIds(); - const relationships = RelationshipStore.getRelationships(); - const nonFriendAffinities = Array.from(userAffinities).filter( - id => !RelationshipStore.getRelationshipType(id) - ); - nonFriendAffinities.forEach(id => { - relationships[id] = 5; + const userAffinities: Record[] = UserAffinitiesStore.getUserAffinities(); + const relationships = RelationshipStore.getMutableRelationships(); + const nonFriendAffinities = userAffinities.filter(a => !RelationshipStore.getRelationshipType(a.otherUserId)); + nonFriendAffinities.forEach(a => { + relationships[a.otherUserId] = 5; }); RelationshipStore.emitChange(); - const toRequest = nonFriendAffinities.filter(id => !UserStore.getUser(id)); + const toRequest = nonFriendAffinities.filter(a => !UserStore.getUser(a.otherUserId)); const allGuildIds = Object.keys(GuildStore.getGuilds()); const sentNonce = SnowflakeUtils.fromTimestamp(Date.now()); let count = allGuildIds.length * Math.ceil(toRequest.length / 100); diff --git a/src/plugins/noBlockedMessages/index.ts b/src/plugins/noBlockedMessages/index.ts index efa4ed08..973e3796 100644 --- a/src/plugins/noBlockedMessages/index.ts +++ b/src/plugins/noBlockedMessages/index.ts @@ -21,12 +21,9 @@ import { Devs } from "@utils/constants"; import { runtimeHashMessageKey } from "@utils/intlHash"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; -import { i18n } from "@webpack/common"; +import { i18n, RelationshipStore } from "@webpack/common"; import { Message } from "discord-types/general"; -const RelationshipStore = findByPropsLazy("getRelationships", "isBlocked"); - interface MessageDeleteProps { // Internal intl message for BLOCKED_MESSAGE_COUNT collapsedReason: () => any; diff --git a/src/plugins/quickReply/index.ts b/src/plugins/quickReply/index.ts index 5a6b45f9..dcd2038c 100644 --- a/src/plugins/quickReply/index.ts +++ b/src/plugins/quickReply/index.ts @@ -19,19 +19,15 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageActions, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; import { Message } from "discord-types/general"; import NoBlockedMessagesPlugin from "plugins/noBlockedMessages"; import NoReplyMentionPlugin from "plugins/noReplyMention"; -const RelationshipStore = findByPropsLazy("getRelationships", "isBlocked"); - const isMac = navigator.platform.includes("Mac"); // bruh let currentlyReplyingId: string | null = null; let currentlyEditingId: string | null = null; - const enum MentionOptions { DISABLED, ENABLED, diff --git a/src/plugins/relationshipNotifier/utils.ts b/src/plugins/relationshipNotifier/utils.ts index 84e812a7..75cf2d3a 100644 --- a/src/plugins/relationshipNotifier/utils.ts +++ b/src/plugins/relationshipNotifier/utils.ts @@ -172,7 +172,7 @@ export async function syncFriends() { friends.friends = []; friends.requests = []; - const relationShips = RelationshipStore.getRelationships(); + const relationShips = RelationshipStore.getMutableRelationships(); for (const id in relationShips) { switch (relationShips[id]) { case RelationshipType.FRIEND: diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index 60401b71..8300380d 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -220,12 +220,12 @@ function FriendsTab({ guild, setCount }: RelationshipProps) { } function BlockedUsersTab({ guild, setCount }: RelationshipProps) { - const blockedIds = Object.keys(RelationshipStore.getRelationships()).filter(id => RelationshipStore.isBlocked(id)); + const blockedIds = Object.keys(RelationshipStore.getMutableRelationships()).filter(id => RelationshipStore.isBlocked(id)); return UserList("blocked", guild, blockedIds, setCount); } function IgnoredUserTab({ guild, setCount }: RelationshipProps) { - const ignoredIds = Object.keys(RelationshipStore.getRelationships()).filter(id => RelationshipStore.isIgnored(id)); + const ignoredIds = Object.keys(RelationshipStore.getMutableRelationships()).filter(id => RelationshipStore.isIgnored(id)); return UserList("ignored", guild, ignoredIds, setCount); } diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 1dcda187..e0c00f6d 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -46,11 +46,7 @@ export let SelectedChannelStore: Stores.SelectedChannelStore & t.FluxStore; export let SelectedGuildStore: t.FluxStore & Record; export let ChannelStore: Stores.ChannelStore & t.FluxStore; export let GuildMemberStore: Stores.GuildMemberStore & t.FluxStore; -export let RelationshipStore: Stores.RelationshipStore & t.FluxStore & { - /** Get the date (as a string) that the relationship was created */ - getSince(userId: string): string; - isIgnored(userId: string): boolean; -}; +export let RelationshipStore: t.RelationshipStore; export let EmojiStore: t.EmojiStore; export let ThemeStore: t.ThemeStore; diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts index 67148303..497de739 100644 --- a/src/webpack/common/types/stores.d.ts +++ b/src/webpack/common/types/stores.d.ts @@ -233,3 +233,20 @@ export type useStateFromStores = ( dependencies?: any, isEqual?: (old: T, newer: T) => boolean ) => T; + +export class RelationshipStore extends FluxStore { + getFriendIDs(): string[]; + /** Related to friend nicknames experiment. */ + getNickname(userId: string): string; + getPendingCount(): number; + getRelationshipCount(): number; + /** @returns Enum value from constants.RelationshipTypes */ + getRelationshipType(userId: string): number; + /** @returns Format: [userId: Enum value from constants.RelationshipTypes] */ + getMutableRelationships(): Record; + isBlocked(userId: string): boolean; + isFriend(userId: string): boolean; + + getSince(userId: string): string; + isIgnored(userId: string): boolean; +} From a6c1f97d12311a7379ffa39fb07db030a20a226d Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 17 Jun 2025 22:03:13 +0200 Subject: [PATCH 024/162] Fix AnonymiseFileNames --- src/plugins/anonymiseFileNames/index.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx index d24de222..8237be8b 100644 --- a/src/plugins/anonymiseFileNames/index.tsx +++ b/src/plugins/anonymiseFileNames/index.tsx @@ -75,11 +75,7 @@ export default definePlugin({ find: "async uploadFiles(", replacement: [ { - match: /async uploadFiles\((\i),\i\){/, - replace: "$&$1.forEach($self.anonymise);" - }, - { - match: /async uploadFilesSimple\((\i)\){/, + match: /async uploadFiles\((\i)\){/, replace: "$&$1.forEach($self.anonymise);" } ], From 0444831073d9a6d9b165dbdfb9e220c79642ee9b Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 17 Jun 2025 22:15:11 +0200 Subject: [PATCH 025/162] RoleColorEverywhere: fix chat mentions --- src/plugins/roleColorEverywhere/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index dc60bb64..61ea7868 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -84,8 +84,8 @@ export default definePlugin({ find: ".USER_MENTION)", replacement: [ { - match: /(?<=onContextMenu:\i,color:)\i(?<=\.getNickname\((\i),\i,(\i).+?)/, - replace: "$self.getColorInt($2?.id,$1)", + match: /(?<=user:(\i),guildId:([^,]+?),.{0,100}?children:\i=>\i)\((\i)\)/, + replace: "({...$3,color:$self.getColorInt($1?.id,$2)})", } ], predicate: () => settings.store.chatMentions From 7779e5a1ecf24b74eb2171e2483f2d3826c18ea6 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 17 Jun 2025 22:37:56 +0200 Subject: [PATCH 026/162] fix IrcColors --- src/plugins/ircColors/index.ts | 50 +++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/plugins/ircColors/index.ts b/src/plugins/ircColors/index.ts index d543c995..d0d020af 100644 --- a/src/plugins/ircColors/index.ts +++ b/src/plugins/ircColors/index.ts @@ -67,9 +67,8 @@ export default definePlugin({ find: '="SYSTEM_TAG"', replacement: { // Override colorString with our custom color and disable gradients if applying the custom color. - match: /&&null!=\i\.secondaryColor,(?<=colorString:(\i).+?(\i)=.+?)/, - replace: (m, colorString, hasGradientColors) => `${m}` + - `vcIrcColorsDummy=[${colorString},${hasGradientColors}]=$self.getMessageColorsVariables(arguments[0],${hasGradientColors}),` + match: /(?<=colorString:\i,colorStrings:\i,colorRoleName:\i}=)(\i),/, + replace: "$self.wrapMessageColorProps($1, arguments[0])," } }, { @@ -82,11 +81,26 @@ export default definePlugin({ } ], - getMessageColorsVariables(context: any, hasGradientColors: boolean) { - const colorString = this.calculateNameColorForMessageContext(context); - const originalColorString = context?.author?.colorString; + wrapMessageColorProps(colorProps: { colorString: string, colorStrings?: Record<"primaryColor" | "secondaryColor" | "tertiaryColor", string>; }, context: any) { + try { + const colorString = this.calculateNameColorForMessageContext(context); + if (colorString === colorProps.colorString) { + return colorProps; + } - return [colorString, hasGradientColors && colorString === originalColorString]; + return { + ...colorProps, + colorString, + colorStrings: colorProps.colorStrings && { + primaryColor: colorString, + secondaryColor: undefined, + tertiaryColor: undefined + } + }; + } catch (e) { + console.error("Failed to calculate message color strings:", e); + return colorProps; + } }, calculateNameColorForMessageContext(context: any) { @@ -108,16 +122,20 @@ export default definePlugin({ }, calculateNameColorForListContext(context: any) { - const id = context?.user?.id; - const colorString = context?.colorString; - const color = calculateNameColorForUser(id); + try { + const id = context?.user?.id; + const colorString = context?.colorString; + const color = calculateNameColorForUser(id); - if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) { - return colorString; + if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) { + return colorString; + } + + return (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString) + ? color + : colorString; + } catch (e) { + console.error("Failed to calculate name color for list context:", e); } - - return (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString) - ? color - : colorString; } }); From e4b1a196ae685a490baf561224107952c610c1f5 Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Tue, 17 Jun 2025 18:55:10 -0700 Subject: [PATCH 027/162] fix Settings::onChange being fired twice (#3496) --- src/shared/SettingsStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/SettingsStore.ts b/src/shared/SettingsStore.ts index 0b6aa25b..1d7dc7f6 100644 --- a/src/shared/SettingsStore.ts +++ b/src/shared/SettingsStore.ts @@ -160,7 +160,7 @@ export class SettingsStore { // So, we need to extract the top-level setting path (plugins.pluginName.settingName), // to be able to notify globalListeners and top-level setting name listeners (let { settingName } = settings.use(["settingName"]), // with the new value - if (paths.length > 2 && paths[0] === "plugins") { + if (paths.length > 3 && paths[0] === "plugins") { const settingPath = paths.slice(0, 3); const settingPathStr = settingPath.join("."); const settingValue = settingPath.reduce((acc, curr) => acc[curr], root); From 96516f113ae4c8699cdd2a362d23135968f95ede Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 18 Jun 2025 15:47:55 +0200 Subject: [PATCH 028/162] ReplaceGoogleSearch: fix broken icons --- src/plugins/implicitRelationships/index.ts | 15 +-------------- src/plugins/replaceGoogleSearch/index.tsx | 2 +- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/plugins/implicitRelationships/index.ts b/src/plugins/implicitRelationships/index.ts index edaf4be9..4a8cf5fb 100644 --- a/src/plugins/implicitRelationships/index.ts +++ b/src/plugins/implicitRelationships/index.ts @@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findStoreLazy } from "@webpack"; -import { Constants, FluxDispatcher, GuildStore, RelationshipStore, RestAPI, SnowflakeUtils, UserStore } from "@webpack/common"; +import { Constants, FluxDispatcher, GuildStore, RelationshipStore, SnowflakeUtils, UserStore } from "@webpack/common"; import { Settings } from "Vencord"; const UserAffinitiesStore = findStoreLazy("UserAffinitiesV2Store"); @@ -121,19 +121,6 @@ export default definePlugin({ : comparator(row); }, - async refreshUserAffinities() { - try { - await RestAPI.get({ url: "/users/@me/affinities/users", retries: 3 }).then(({ body }) => { - FluxDispatcher.dispatch({ - type: "LOAD_USER_AFFINITIES_SUCCESS", - affinities: body, - }); - }); - } catch (e) { - // Not a critical error if this fails for some reason - } - }, - async fetchImplicitRelationships() { // Implicit relationships are defined as users that you: // 1. Have an affinity for diff --git a/src/plugins/replaceGoogleSearch/index.tsx b/src/plugins/replaceGoogleSearch/index.tsx index 593c9863..47952610 100644 --- a/src/plugins/replaceGoogleSearch/index.tsx +++ b/src/plugins/replaceGoogleSearch/index.tsx @@ -69,7 +69,7 @@ function makeSearchItem(src: string) { aria-hidden="true" height={16} width={16} - src={`https://www.google.com/s2/favicons?domain=${Engines[engine]}&sz=64`} + src={`https://icons.duckduckgo.com/ip3/${new URL(Engines[engine]).hostname}.ico`} /> {engine} From ba76c43a265957a70171a3ddf98beb0aafc0954e Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 19 Jun 2025 22:50:53 +0200 Subject: [PATCH 029/162] ServerInfo: fix Blocked & Ignored tabs --- src/plugins/serverInfo/GuildInfoModal.tsx | 4 ++-- src/webpack/common/types/stores.d.ts | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index 8300380d..44f10afa 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -220,12 +220,12 @@ function FriendsTab({ guild, setCount }: RelationshipProps) { } function BlockedUsersTab({ guild, setCount }: RelationshipProps) { - const blockedIds = Object.keys(RelationshipStore.getMutableRelationships()).filter(id => RelationshipStore.isBlocked(id)); + const blockedIds = RelationshipStore.getBlockedIDs(); return UserList("blocked", guild, blockedIds, setCount); } function IgnoredUserTab({ guild, setCount }: RelationshipProps) { - const ignoredIds = Object.keys(RelationshipStore.getMutableRelationships()).filter(id => RelationshipStore.isIgnored(id)); + const ignoredIds = RelationshipStore.getIgnoredIDs(); return UserList("ignored", guild, ignoredIds, setCount); } diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts index 497de739..cb06ca4b 100644 --- a/src/webpack/common/types/stores.d.ts +++ b/src/webpack/common/types/stores.d.ts @@ -236,17 +236,21 @@ export type useStateFromStores = ( export class RelationshipStore extends FluxStore { getFriendIDs(): string[]; - /** Related to friend nicknames experiment. */ - getNickname(userId: string): string; + getIgnoredIDs(): string[]; + getBlockedIDs(): string[]; + getPendingCount(): number; getRelationshipCount(): number; + + /** Related to friend nicknames. */ + getNickname(userId: string): string; /** @returns Enum value from constants.RelationshipTypes */ getRelationshipType(userId: string): number; + isFriend(userId: string): boolean; + isBlocked(userId: string): boolean; + isIgnored(userId: string): boolean; + getSince(userId: string): string; + /** @returns Format: [userId: Enum value from constants.RelationshipTypes] */ getMutableRelationships(): Record; - isBlocked(userId: string): boolean; - isFriend(userId: string): boolean; - - getSince(userId: string): string; - isIgnored(userId: string): boolean; } From f6bfd18816de45c6fd9a903b7595cb19d59266c4 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 19 Jun 2025 23:03:15 +0200 Subject: [PATCH 030/162] ViewIcons: fix viewing animated icons/banners --- src/plugins/viewIcons/index.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index 07630a00..2afdb2d5 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -71,9 +71,14 @@ const openAvatar = (url: string) => openImage(url, 512, 512); const openBanner = (url: string) => openImage(url, 1024); function openImage(url: string, width: number, height?: number) { - const format = url.startsWith("/") ? "png" : settings.store.format; - const u = new URL(url, window.location.href); + + const format = url.startsWith("/") + ? "png" + : u.searchParams.get("animated") === "true" + ? "gif" + : settings.store.format; + u.searchParams.set("size", settings.store.imgSize); u.pathname = u.pathname.replace(/\.(png|jpe?g|webp)$/, `.${format}`); url = u.toString(); From 658a62860e40f9411c26f30b8327f61431d3a92e Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Fri, 20 Jun 2025 12:45:52 -0400 Subject: [PATCH 031/162] BetterFolders: Fix sidebar filter patch (#3498) --- src/plugins/betterFolders/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 3dc812a4..62567f51 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -179,7 +179,7 @@ export default definePlugin({ }, // If we are rendering the Better Folders sidebar, we filter out everything but the Guild List from the Sidebar children { - match: /unreadMentionsFixedFooter\].+?\]/, + match: /unreadMentionsFixedFooter\].+?\}\)\]/, replace: "$&.filter($self.makeGuildsBarSidebarFilter(!!arguments[0]?.isBetterFolders))" } ] From 9b24535d44f314b052227f015b1a55942c604df6 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:44:11 -0400 Subject: [PATCH 032/162] FakeNitro: fix crash when embed.url is undefined (#3510) --- src/plugins/fakeNitro/index.tsx | 48 ++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 4bdf194c..a4da103e 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -671,32 +671,38 @@ export default definePlugin({ }, shouldIgnoreEmbed(embed: Message["embeds"][number], message: Message) { - const contentItems = message.content.split(/\s/); - if (contentItems.length > 1 && !settings.store.transformCompoundSentence) return false; + try { + const contentItems = message.content.split(/\s/); + if (contentItems.length > 1 && !settings.store.transformCompoundSentence) return false; - switch (embed.type) { - case "image": { - if ( - !settings.store.transformCompoundSentence - && !contentItems.some(item => item === embed.url! || item.match(hyperLinkRegex)?.[1] === embed.url!) - ) return false; + switch (embed.type) { + case "image": { + const url = embed.url ?? embed.image?.url; + if (!url) return false; + if ( + !settings.store.transformCompoundSentence + && !contentItems.some(item => item === url || item.match(hyperLinkRegex)?.[1] === url) + ) return false; - if (settings.store.transformEmojis) { - if (fakeNitroEmojiRegex.test(embed.url!)) return true; - } - - if (settings.store.transformStickers) { - if (fakeNitroStickerRegex.test(embed.url!)) return true; - - const gifMatch = embed.url!.match(fakeNitroGifStickerRegex); - if (gifMatch) { - // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickerStore contains the id of the fake sticker - if (StickerStore.getStickerById(gifMatch[1])) return true; + if (settings.store.transformEmojis) { + if (fakeNitroEmojiRegex.test(url)) return true; } - } - break; + if (settings.store.transformStickers) { + if (fakeNitroStickerRegex.test(url)) return true; + + const gifMatch = url.match(fakeNitroGifStickerRegex); + if (gifMatch) { + // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickerStore contains the id of the fake sticker + if (StickerStore.getStickerById(gifMatch[1])) return true; + } + } + + break; + } } + } catch (e) { + new Logger("FakeNitro").error("Error in shouldIgnoreEmbed:", e); } return false; From b6ffb33adca2e26e728ee89becae3202d4caf65f Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Tue, 24 Jun 2025 21:25:36 -0400 Subject: [PATCH 033/162] Fix plugins for latest discord update (#3509) Co-authored-by: Etorix <92535668+EtorixDev@users.noreply.github.com> Co-authored-by: V --- src/plugins/callTimer/index.tsx | 3 +- .../clientTheme/components/Settings.tsx | 5 +- src/plugins/fakeNitro/index.tsx | 2 +- src/plugins/fakeProfileThemes/index.tsx | 95 +++++++++---------- .../pinDms/components/CreateCategoryModal.tsx | 10 +- src/plugins/typingIndicator/index.tsx | 2 +- src/webpack/common/components.ts | 3 + src/webpack/common/types/components.d.ts | 7 ++ 8 files changed, 61 insertions(+), 66 deletions(-) diff --git a/src/plugins/callTimer/index.tsx b/src/plugins/callTimer/index.tsx index bdcca777..dcab0551 100644 --- a/src/plugins/callTimer/index.tsx +++ b/src/plugins/callTimer/index.tsx @@ -75,7 +75,8 @@ export default definePlugin({ patches: [{ find: "renderConnectionStatus(){", replacement: { - match: /(renderConnectionStatus\(\){.+\.channel,children:)(.+?}\):\i)(?=}\))/, + // in renderConnectionStatus() + match: /(lineClamp:1,children:)(\i)(?=,|}\))/, replace: "$1[$2,$self.renderTimer(this.props.channel.id)]" } }], diff --git a/src/plugins/clientTheme/components/Settings.tsx b/src/plugins/clientTheme/components/Settings.tsx index f38380fa..9e7f867e 100644 --- a/src/plugins/clientTheme/components/Settings.tsx +++ b/src/plugins/clientTheme/components/Settings.tsx @@ -7,14 +7,13 @@ import { classNameFactory } from "@api/Styles"; import { ErrorCard } from "@components/ErrorCard"; import { Margins } from "@utils/margins"; -import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; -import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common"; +import { findByCodeLazy, findStoreLazy } from "@webpack"; +import { Button, ColorPicker, Forms, ThemeStore, useStateFromStores } from "@webpack/common"; import { settings } from ".."; import { relativeLuminance } from "../utils/colorUtils"; import { createOrUpdateThemeColorVars } from "../utils/styleUtils"; -const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)"); const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE', '"system"==='); const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore"); diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index a4da103e..f362a355 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -394,7 +394,7 @@ export default definePlugin({ }, // Separate patch for allowing using custom app icons { - find: "?24:30,", + find: "getCurrentDesktopIcon(),", replacement: { match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/, replace: "true" diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index 4c789b76..61ab1731 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -24,10 +24,9 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { Margins } from "@utils/margins"; import { classes, copyWithToast } from "@utils/misc"; -import { useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; -import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack"; -import { Button, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common"; +import { findComponentByCodeLazy } from "@webpack"; +import { Button, ColorPicker, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common"; import { User } from "discord-types/general"; import { ReactElement } from "react"; import virtualMerge from "virtual-merge"; @@ -109,10 +108,8 @@ interface ProfileModalProps { isTryItOutFlow: boolean; } -const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)"); const ProfileModal = findComponentByCodeLazy("isTryItOutFlow:", "pendingThemeColors:", "pendingAvatarDecoration:", "EDIT_PROFILE_BANNER"); -const requireColorPicker = extractAndLoadChunksLazy(["#{intl::USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON}"], /createPromise:\(\)=>\i\.\i(\("?.+?"?\)).then\(\i\.bind\(\i,"?(.+?)"?\)\)/); export default definePlugin({ name: "FakeProfileThemes", @@ -141,8 +138,6 @@ export default definePlugin({ const [color1, setColor1] = useState(existingColors[0]); const [color2, setColor2] = useState(existingColors[1]); - const [, , loadingColorPickerChunk] = useAwaiter(requireColorPicker); - return ( Usage @@ -162,51 +157,49 @@ export default definePlugin({ className={classes(Margins.top8, Margins.bottom8)} /> Color pickers - {!loadingColorPickerChunk && ( - + + Primary + + } + onChange={(color: number) => { + setColor1(color); + }} + /> + + Accent + + } + onChange={(color: number) => { + setColor2(color); + }} + /> + - - )} + Copy 3y3 + + diff --git a/src/plugins/pinDms/components/CreateCategoryModal.tsx b/src/plugins/pinDms/components/CreateCategoryModal.tsx index 8c0fc659..e769f4b6 100644 --- a/src/plugins/pinDms/components/CreateCategoryModal.tsx +++ b/src/plugins/pinDms/components/CreateCategoryModal.tsx @@ -7,18 +7,11 @@ import { classNameFactory } from "@api/Styles"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal"; import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack"; -import { Button, Forms, Text, TextInput, Toasts, useMemo, useState } from "@webpack/common"; +import { Button, ColorPicker, Forms, Text, TextInput, Toasts, useMemo, useState } from "@webpack/common"; import { DEFAULT_COLOR, SWATCHES } from "../constants"; import { categoryLen, createCategory, getCategory } from "../data"; -interface ColorPickerProps { - color: number | null; - showEyeDropper?: boolean; - suggestedColors?: string[]; - onChange(value: number | null): void; -} - interface ColorPickerWithSwatchesProps { defaultColor: number; colors: number[]; @@ -29,7 +22,6 @@ interface ColorPickerWithSwatchesProps { renderCustomButton?: () => React.ReactNode; } -const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)"); const ColorPickerWithSwatches = findComponentByCodeLazy('id:"color-picker"'); export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/); diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx index e6903bcd..a5c9f49c 100644 --- a/src/plugins/typingIndicator/index.tsx +++ b/src/plugins/typingIndicator/index.tsx @@ -178,7 +178,7 @@ export default definePlugin({ // Theads { // This is the thread "spine" that shows in the left - find: "M11 9H4C2.89543 9 2 8.10457 2 7V1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1V7C0 9.20914 1.79086 11 4 11H11C11.5523 11 12 10.5523 12 10C12 9.44771 11.5523 9 11 9Z", + find: "M0 15H2c0 1.6569", replacement: { match: /mentionsCount:\i.+?null(?<=channel:(\i).+?)/, replace: "$&,$self.TypingIndicator($1.id,$1.getGuildId())" diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index a772c98d..bcce3cbb 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -65,6 +65,9 @@ export const Paginator = waitForComponent("Paginator", filters.comp export const Clickable = waitForComponent("Clickable", filters.componentByCode("this.context?this.renderNonInteractive():")); export const Avatar = waitForComponent("Avatar", filters.componentByCode(".size-1.375*")); +export const ColorPicker = waitForComponent("ColorPicker", filters.componentByCode("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", "showEyeDropper")); + + export let createScroller: (scrollbarClassName: string, fadeClassName: string, customThemeClassName: string) => t.ScrollerThin; export let scrollerClasses: Record; waitFor(filters.byCode('="ltr",orientation:', "customTheme:", "forwardRef"), m => createScroller = m); diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index 7a9e848b..783663cb 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -536,3 +536,10 @@ export type Icon = ComponentType>; +export type ColorPicker = ComponentType<{ + color: number | null; + showEyeDropper?: boolean; + suggestedColors?: string[]; + label?: ReactNode; + onChange(value: number | null): void; +}>; From decb49fc0ab99f0cbf7f229738ddfcacbcf384ff Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 25 Jun 2025 03:27:34 +0200 Subject: [PATCH 034/162] v1.12.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f45d9f2f..87c484a7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.12.3", + "version": "1.12.4", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 864ee7c7ad833508c18eb888f730fc341df5f722 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Fri, 27 Jun 2025 12:45:22 -0400 Subject: [PATCH 035/162] Fix BlurNSFW and ShowMeYourName for latest version (#3517) --- src/plugins/blurNsfw/index.ts | 2 +- src/plugins/showMeYourName/index.tsx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/plugins/blurNsfw/index.ts b/src/plugins/blurNsfw/index.ts index c4023f09..c1670fe8 100644 --- a/src/plugins/blurNsfw/index.ts +++ b/src/plugins/blurNsfw/index.ts @@ -43,7 +43,7 @@ export default definePlugin({ patches: [ { - find: ".embedWrapper,embed", + find: "}renderEmbeds(", replacement: [{ match: /\.container/, replace: "$&+(this.props.channel.nsfw? ' vc-nsfw-img': '')" diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index ac727f69..ba92e82a 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -48,10 +48,9 @@ export default definePlugin({ authors: [Devs.Rini, Devs.TheKodeToad], patches: [ { - find: '"BaseUsername"', + find: '="SYSTEM_TAG"', replacement: { - /* TODO: remove \i+\i once change makes it to stable */ - match: /(?<=onContextMenu:\i,children:)(?:\i\+\i|\i)/, + match: /(?<=onContextMenu:\i,children:)\i/, replace: "$self.renderUsername(arguments[0])" } }, From 65f41cb7bd8335059126783f27f6d7cb4c1e8a3f Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 27 Jun 2025 21:36:31 +0200 Subject: [PATCH 036/162] Update GuildStore -> GuildRoleStore --- src/plugins/betterRoleContext/index.tsx | 4 ++-- src/plugins/mentionAvatars/index.tsx | 4 ++-- .../components/RolesAndUsersPermissions.tsx | 6 +++--- src/plugins/permissionsViewer/index.tsx | 4 ++-- src/plugins/permissionsViewer/utils.ts | 6 +++--- src/plugins/roleColorEverywhere/index.tsx | 4 ++-- src/plugins/serverInfo/GuildInfoModal.tsx | 4 ++-- src/plugins/viewRaw/index.tsx | 4 ++-- src/plugins/xsOverlay/index.tsx | 4 ++-- src/webpack/common/stores.ts | 9 +++++++-- src/webpack/common/types/stores.d.ts | 3 +++ 11 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx index a9bab1a7..f9eae586 100644 --- a/src/plugins/betterRoleContext/index.tsx +++ b/src/plugins/betterRoleContext/index.tsx @@ -12,7 +12,7 @@ import { Devs } from "@utils/constants"; import { getCurrentGuild, openImageModal } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { GuildStore, Menu, PermissionStore } from "@webpack/common"; +import { GuildRoleStore, Menu, PermissionStore } from "@webpack/common"; const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild"); @@ -80,7 +80,7 @@ export default definePlugin({ const guild = getCurrentGuild(); if (!guild) return; - const role = GuildStore.getRole(guild.id, id); + const role = GuildRoleStore.getRole(guild.id, id); if (!role) return; if (role.colorString) { diff --git a/src/plugins/mentionAvatars/index.tsx b/src/plugins/mentionAvatars/index.tsx index 5466a9e2..8f18567e 100644 --- a/src/plugins/mentionAvatars/index.tsx +++ b/src/plugins/mentionAvatars/index.tsx @@ -10,7 +10,7 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { GuildStore, SelectedGuildStore, useState } from "@webpack/common"; +import { GuildRoleStore, SelectedGuildStore, useState } from "@webpack/common"; import { User } from "discord-types/general"; const settings = definePluginSettings({ @@ -89,7 +89,7 @@ export default definePlugin({ // Discord uses Role Mentions for uncached users because .... idk if (!roleId) return null; - const role = GuildStore.getRole(guildId, roleId); + const role = GuildRoleStore.getRole(guildId, roleId); if (!role?.icon) return ; diff --git a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx index ed620d7f..46ddb146 100644 --- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx +++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx @@ -23,7 +23,7 @@ import { copyToClipboard } from "@utils/clipboard"; import { getIntlMessage, getUniqueUsername } from "@utils/discord"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { findByCodeLazy } from "@webpack"; -import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, i18n, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, useMemo, UserStore, useState, useStateFromStores } from "@webpack/common"; +import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildRoleStore, i18n, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, useMemo, UserStore, useState, useStateFromStores } from "@webpack/common"; import { UnicodeEmoji } from "@webpack/types"; import type { Guild, Role, User } from "discord-types/general"; @@ -85,7 +85,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea const [selectedItemIndex, selectItem] = useState(0); const selectedItem = permissions[selectedItemIndex]; - const roles = GuildStore.getRoles(guild.id); + const roles = GuildRoleStore.getRoles(guild.id); return ( { - const role = GuildStore.getRole(guild.id, roleId); + const role = GuildRoleStore.getRole(guild.id, roleId); if (!role) return; onClose(); diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx index 3fd4428c..cc942f09 100644 --- a/src/plugins/permissionsViewer/index.tsx +++ b/src/plugins/permissionsViewer/index.tsx @@ -26,7 +26,7 @@ import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, match, Menu, PermissionsBits, Popout, TooltipContainer, useRef, UserStore } from "@webpack/common"; +import { Button, ChannelStore, Dialog, GuildMemberStore, GuildRoleStore, GuildStore, match, Menu, PermissionsBits, Popout, TooltipContainer, useRef, UserStore } from "@webpack/common"; import type { Guild, GuildMember } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions"; @@ -107,7 +107,7 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { }; }) .otherwise(() => { - const permissions = Object.values(GuildStore.getRoles(guild.id)).map(role => ({ + const permissions = Object.values(GuildRoleStore.getRoles(guild.id)).map(role => ({ type: PermissionType.Role, ...role })); diff --git a/src/plugins/permissionsViewer/utils.ts b/src/plugins/permissionsViewer/utils.ts index dfa81e73..409c0896 100644 --- a/src/plugins/permissionsViewer/utils.ts +++ b/src/plugins/permissionsViewer/utils.ts @@ -18,7 +18,7 @@ import { classNameFactory } from "@api/Styles"; import { findByPropsLazy } from "@webpack"; -import { GuildStore } from "@webpack/common"; +import { GuildRoleStore } from "@webpack/common"; import { Guild, GuildMember, Role } from "discord-types/general"; import { PermissionsSortOrder, settings } from "."; @@ -29,7 +29,7 @@ export const { getGuildPermissionSpecMap } = findByPropsLazy("getGuildPermission export const cl = classNameFactory("vc-permviewer-"); export function getSortedRoles({ id }: Guild, member: GuildMember) { - const roles = GuildStore.getRoles(id); + const roles = GuildRoleStore.getRoles(id); return [...member.roles, id] .map(id => roles[id]) @@ -48,7 +48,7 @@ export function sortUserRoles(roles: Role[]) { } export function sortPermissionOverwrites(overwrites: T[], guildId: string) { - const roles = GuildStore.getRoles(guildId); + const roles = GuildRoleStore.getRoles(guildId); return overwrites.sort((a, b) => { if (a.type !== PermissionType.Role || b.type !== PermissionType.Role) return 0; diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 61ea7868..65da676c 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -23,7 +23,7 @@ import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy } from "@webpack"; -import { ChannelStore, GuildMemberStore, GuildStore } from "@webpack/common"; +import { ChannelStore, GuildMemberStore, GuildRoleStore, GuildStore } from "@webpack/common"; const useMessageAuthor = findByCodeLazy('"Result cannot be null because the message is not null"'); @@ -210,7 +210,7 @@ export default definePlugin({ }, RoleGroupColor: ErrorBoundary.wrap(({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) => { - const role = GuildStore.getRole(guildId, id); + const role = GuildRoleStore.getRole(guildId, id); return ( m.ANNOUNCEMENT_THREAD === 10); @@ -267,7 +267,7 @@ export default definePlugin({ // color role mentions (unity styling btw lol) if (message.mention_roles.length > 0) { for (const roleId of message.mention_roles) { - const role = GuildStore.getRole(channel.guild_id, roleId); + const role = GuildRoleStore.getRole(channel.guild_id, roleId); if (!role) continue; const roleColor = role.colorString ?? `#${pingColor}`; finalMsg = finalMsg.replace(`<@&${roleId}>`, `@${role.name}`); diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index e0c00f6d..712f5bdc 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { findByCodeLazy, findByPropsLazy } from "@webpack"; +import { findByCodeLazy, findByPropsLazy, waitFor } from "@webpack"; import type * as Stores from "discord-types/stores"; import { waitForStore } from "./internal"; @@ -40,12 +40,13 @@ export let ReadStateStore: GenericStore; export let PresenceStore: GenericStore; export let GuildStore: t.GuildStore; +export let GuildRoleStore: t.GuildRoleStore; +export let GuildMemberStore: Stores.GuildMemberStore & t.FluxStore; export let UserStore: Stores.UserStore & t.FluxStore; export let UserProfileStore: GenericStore; export let SelectedChannelStore: Stores.SelectedChannelStore & t.FluxStore; export let SelectedGuildStore: t.FluxStore & Record; export let ChannelStore: Stores.ChannelStore & t.FluxStore; -export let GuildMemberStore: Stores.GuildMemberStore & t.FluxStore; export let RelationshipStore: t.RelationshipStore; export let EmojiStore: t.EmojiStore; @@ -86,3 +87,7 @@ waitForStore("ThemeStore", m => { // Importing this directly can easily cause circular imports. For this reason, use a non import access here. Vencord.QuickCss.initQuickCssThemeStore(); }); + +// GuildRoleStore is new, this code is for stable + canary compatibility +// TODO: Change to waitForStore once GuildRoleStore is on stable +waitFor(["getRole", "getRoles"], m => GuildRoleStore = m); diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts index cb06ca4b..9a2dd132 100644 --- a/src/webpack/common/types/stores.d.ts +++ b/src/webpack/common/types/stores.d.ts @@ -214,6 +214,9 @@ export class GuildStore extends FluxStore { getGuildCount(): number; getGuilds(): Record; getGuildIds(): string[]; +} + +export class GuildRoleStore extends FluxStore { getRole(guildId: string, roleId: string): Role; getRoles(guildId: string): Record; getAllGuildRoles(): Record>; From a25d26e9211ad9a1c920faee1564614e810bd768 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 28 Jun 2025 03:42:31 +0200 Subject: [PATCH 037/162] Update to new Discord css variable names Co-Authored-By: sadan <117494111+sadan4@users.noreply.github.com> Co-Authored-By: Nuckyz <61953774+Nuckyz@users.noreply.github.com> --- src/api/Notifications/styles.css | 4 ++-- src/components/ErrorCard.css | 2 +- src/components/VencordSettings/UpdaterTab.tsx | 2 +- src/components/VencordSettings/addonCard.css | 2 +- src/components/VencordSettings/quickActions.css | 6 +++--- .../VencordSettings/settingsStyles.css | 2 +- src/components/VencordSettings/themesStyles.css | 2 +- src/plugins/betterSessions/index.tsx | 2 +- src/plugins/customRPC/index.tsx | 2 +- src/plugins/expressionCloner/index.tsx | 2 +- src/plugins/messageLinkEmbeds/index.tsx | 2 +- src/plugins/messageLogger/deleteStyleText.css | 2 +- .../components/UserPermissions.tsx | 4 ++-- .../permissionsViewer/components/icons.tsx | 2 +- src/plugins/permissionsViewer/styles.css | 10 ++++------ src/plugins/reviewDB/style.css | 2 +- src/plugins/roleColorEverywhere/index.tsx | 2 +- src/plugins/sendTimestamps/styles.css | 2 +- src/plugins/serverInfo/styles.css | 2 +- .../components/Highlighter.tsx | 4 ++-- src/plugins/shikiCodeblocks.desktop/shiki.css | 2 +- src/plugins/showHiddenChannels/style.css | 16 ++++++++-------- src/plugins/spotifyControls/spotifyStyles.css | 8 ++++---- .../visualRefreshSpotifyStyles.css | 4 ++-- src/plugins/voiceMessages/styles.css | 4 ++-- 25 files changed, 45 insertions(+), 47 deletions(-) diff --git a/src/api/Notifications/styles.css b/src/api/Notifications/styles.css index ba8a246a..10d3c0cf 100644 --- a/src/api/Notifications/styles.css +++ b/src/api/Notifications/styles.css @@ -3,8 +3,8 @@ all: unset; display: flex; flex-direction: column; - color: var(--text-normal); - background-color: var(--background-secondary-alt); + color: var(--text-default); + background-color: var(--background-base-lower-alt); border-radius: 6px; overflow: hidden; cursor: pointer; diff --git a/src/components/ErrorCard.css b/src/components/ErrorCard.css index 6401c59c..2eea2f06 100644 --- a/src/components/ErrorCard.css +++ b/src/components/ErrorCard.css @@ -3,7 +3,7 @@ background-color: #e7828430; border: 1px solid #e78284; border-radius: 5px; - color: var(--text-normal, white); + color: var(--text-default, white); & a:hover { text-decoration: underline; diff --git a/src/components/VencordSettings/UpdaterTab.tsx b/src/components/VencordSettings/UpdaterTab.tsx index e29d7dfd..9871bfcc 100644 --- a/src/components/VencordSettings/UpdaterTab.tsx +++ b/src/components/VencordSettings/UpdaterTab.tsx @@ -94,7 +94,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof {message} - {author} ))} diff --git a/src/components/VencordSettings/addonCard.css b/src/components/VencordSettings/addonCard.css index 67bdece5..c8a2a81e 100644 --- a/src/components/VencordSettings/addonCard.css +++ b/src/components/VencordSettings/addonCard.css @@ -1,5 +1,5 @@ .vc-addon-card { - background-color: var(--background-secondary-alt); + background-color: var(--background-base-lower-alt); color: var(--interactive-active); border-radius: 8px; display: block; diff --git a/src/components/VencordSettings/quickActions.css b/src/components/VencordSettings/quickActions.css index 39137818..f65fba21 100644 --- a/src/components/VencordSettings/quickActions.css +++ b/src/components/VencordSettings/quickActions.css @@ -14,7 +14,7 @@ .vc-settings-quickActions-pill { all: unset; - background: var(--background-secondary); + background: var(--background-base-lower); color: var(--header-secondary); display: flex; align-items: center; @@ -26,7 +26,7 @@ } .vc-settings-quickActions-pill:hover { - background: var(--background-secondary-alt); + background: var(--background-base-lower-alt); transform: translateY(-1px); box-shadow: var(--elevation-high); } @@ -47,4 +47,4 @@ .vc-settings-quickActions-img { width: 24px; height: 24px; -} \ No newline at end of file +} diff --git a/src/components/VencordSettings/settingsStyles.css b/src/components/VencordSettings/settingsStyles.css index 5349993d..ccf01cfd 100644 --- a/src/components/VencordSettings/settingsStyles.css +++ b/src/components/VencordSettings/settingsStyles.css @@ -29,7 +29,7 @@ .vc-settings-theme-links { /* Needed to fix bad themes that hide certain textarea elements for whatever eldritch reason */ display: inline-block !important; - color: var(--text-normal) !important; + color: var(--text-default) !important; padding: 0.5em; border: 1px solid var(--background-modifier-accent); max-height: unset; diff --git a/src/components/VencordSettings/themesStyles.css b/src/components/VencordSettings/themesStyles.css index b10daff3..b4717406 100644 --- a/src/components/VencordSettings/themesStyles.css +++ b/src/components/VencordSettings/themesStyles.css @@ -7,7 +7,7 @@ .vc-settings-theme-card { display: flex; flex-direction: column; - background-color: var(--background-secondary-alt); + background-color: var(--background-base-lower-alt); color: var(--interactive-active); border-radius: 8px; padding: 1em; diff --git a/src/plugins/betterSessions/index.tsx b/src/plugins/betterSessions/index.tsx index 8f8ef141..b40c3c1e 100644 --- a/src/plugins/betterSessions/index.tsx +++ b/src/plugins/betterSessions/index.tsx @@ -138,7 +138,7 @@ export default definePlugin({ borderRadius: "50%", backgroundColor: "var(--interactive-normal)", - color: "var(--background-secondary)", + color: "var(--background-base-lower)", }} > diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index 6f771933..d35dad13 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -456,7 +456,7 @@ export default definePlugin({ -
+
{activity[0] && {channelLabel} - diff --git a/src/plugins/messageLogger/deleteStyleText.css b/src/plugins/messageLogger/deleteStyleText.css index a114b7de..655294c1 100644 --- a/src/plugins/messageLogger/deleteStyleText.css +++ b/src/plugins/messageLogger/deleteStyleText.css @@ -1,5 +1,5 @@ .messagelogger-deleted { - --text-normal: var(--status-danger, #f04747); + --text-default: var(--status-danger, #f04747); --interactive-normal: var(--status-danger, #f04747); --text-muted: var(--status-danger, #f04747); --embed-title: var(--red-460, #be3535); diff --git a/src/plugins/permissionsViewer/components/UserPermissions.tsx b/src/plugins/permissionsViewer/components/UserPermissions.tsx index d0c0ed50..85b1d418 100644 --- a/src/plugins/permissionsViewer/components/UserPermissions.tsx +++ b/src/plugins/permissionsViewer/components/UserPermissions.tsx @@ -159,7 +159,7 @@ function UserPermissionsComponent({ guild, guildMember, closePopout }: { guild: viewBox="0 96 960 960" transform={permissionsSortOrder === PermissionsSortOrder.HighestRole ? "scale(1 1)" : "scale(1 -1)"} > - +
)} @@ -181,7 +181,7 @@ function UserPermissionsComponent({ guild, guildMember, closePopout }: { guild: height="24" viewBox="0 0 24 24" > - +
)} diff --git a/src/plugins/permissionsViewer/components/icons.tsx b/src/plugins/permissionsViewer/components/icons.tsx index 8b58a440..fb5b0341 100644 --- a/src/plugins/permissionsViewer/components/icons.tsx +++ b/src/plugins/permissionsViewer/components/icons.tsx @@ -51,7 +51,7 @@ export function PermissionDefaultIcon() { > Not overwritten - + ); diff --git a/src/plugins/permissionsViewer/styles.css b/src/plugins/permissionsViewer/styles.css index 2ca61025..c149933f 100644 --- a/src/plugins/permissionsViewer/styles.css +++ b/src/plugins/permissionsViewer/styles.css @@ -140,10 +140,9 @@ /* copy pasted from discord cause impossible to webpack find */ .vc-permviewer-role-button { - border-radius: var(--radius-xs); - background: var(--bg-mod-faint); + border-radius: var(--radius-sm); color: var(--interactive-normal); - border: 1px solid var(--border-faint); + border: 1px solid var(--user-profile-border); /* stylelint-disable-next-line value-no-vendor-prefix */ width: -moz-fit-content; width: fit-content; @@ -151,9 +150,8 @@ padding: 4px } -.custom-profile-theme .vc-permviewer-role-button { - background: rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-6)); - border-color: var(--profile-body-border-color) +.vc-permviewer-role-button:hover { + background-color: var(--user-profile-background-hover); } .vc-permviewer-granted-by-container { diff --git a/src/plugins/reviewDB/style.css b/src/plugins/reviewDB/style.css index c62c300e..50499b29 100644 --- a/src/plugins/reviewDB/style.css +++ b/src/plugins/reviewDB/style.css @@ -72,7 +72,7 @@ overflow-y: hidden; margin-top: 1px; margin-bottom: 8px; - color: var(--text-normal); + color: var(--text-default); font-size: 15px; } diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 65da676c..84b58199 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -197,7 +197,7 @@ export default definePlugin({ const value = `color-mix(in oklab, ${author.colorString} ${messageSaturation}%, var({DEFAULT}))`; return { - color: value.replace("{DEFAULT}", "--text-normal"), + color: value.replace("{DEFAULT}", "--text-default"), "--header-primary": value.replace("{DEFAULT}", "--header-primary"), "--text-muted": value.replace("{DEFAULT}", "--text-muted") }; diff --git a/src/plugins/sendTimestamps/styles.css b/src/plugins/sendTimestamps/styles.css index e7efbe59..d96e886a 100644 --- a/src/plugins/sendTimestamps/styles.css +++ b/src/plugins/sendTimestamps/styles.css @@ -1,6 +1,6 @@ .vc-st-date-picker { background-color: var(--input-background); - color: var(--text-normal); + color: var(--text-default); width: 95%; padding: 8px 8px 8px 12px; margin: 1em 0; diff --git a/src/plugins/serverInfo/styles.css b/src/plugins/serverInfo/styles.css index 42a7899c..0fc44e02 100644 --- a/src/plugins/serverInfo/styles.css +++ b/src/plugins/serverInfo/styles.css @@ -68,7 +68,7 @@ } .vc-gp-server-info-pair { - color: var(--text-normal); + color: var(--text-default); } .vc-gp-server-info-pair [class^="timestamp"] { diff --git a/src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx b/src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx index 2d62af6e..926a6cae 100644 --- a/src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx +++ b/src/plugins/shikiCodeblocks.desktop/components/Highlighter.tsx @@ -78,12 +78,12 @@ export const Highlighter = ({ }); const themeBase: ThemeBase = { - plainColor: currentTheme?.fg || "var(--text-normal)", + plainColor: currentTheme?.fg || "var(--text-default)", accentBgColor: currentTheme?.colors?.["statusBar.background"] || (useHljs ? "#7289da" : "#007BC8"), accentFgColor: currentTheme?.colors?.["statusBar.foreground"] || "#FFF", backgroundColor: - currentTheme?.colors?.["editor.background"] || "var(--background-secondary)", + currentTheme?.colors?.["editor.background"] || "var(--background-base-lower)", }; let langName; diff --git a/src/plugins/shikiCodeblocks.desktop/shiki.css b/src/plugins/shikiCodeblocks.desktop/shiki.css index 9d3a52c6..9ca34484 100644 --- a/src/plugins/shikiCodeblocks.desktop/shiki.css +++ b/src/plugins/shikiCodeblocks.desktop/shiki.css @@ -1,6 +1,6 @@ .vc-shiki-container { border: 4px; - background-color: var(--background-secondary); + background-color: var(--background-base-lower); } .vc-shiki-root { diff --git a/src/plugins/showHiddenChannels/style.css b/src/plugins/showHiddenChannels/style.css index 56e50a2f..7104f047 100644 --- a/src/plugins/showHiddenChannels/style.css +++ b/src/plugins/showHiddenChannels/style.css @@ -22,12 +22,12 @@ } .vc-shc-heading-nsfw-icon { - color: var(--text-normal); + color: var(--text-default); } .vc-shc-topic-container { - color: var(--text-normal); - background: var(--bg-overlay-3, var(--background-secondary)); + color: var(--text-default); + background: var(--bg-overlay-5, var(--background-base-lower)); border-radius: 5px; padding: 10px; max-width: 70vw; @@ -37,7 +37,7 @@ display: flex; flex-direction: row; align-items: center; - background: var(--bg-overlay-3, var(--background-secondary)); + background: var(--bg-overlay-5, var(--background-base-lower)); border-radius: 8px; padding: 0.75em; margin-left: 0.75em; @@ -46,7 +46,7 @@ .vc-shc-tags-container { display: flex; flex-direction: column; - background: var(--bg-overlay-3, var(--background-secondary)); + background: var(--bg-overlay-5, var(--background-base-lower)); border-radius: 5px; padding: 0.75em; gap: 0.75em; @@ -65,7 +65,7 @@ display: flex; flex-direction: column; align-items: center; - background: var(--bg-overlay-3, var(--background-secondary)); + background: var(--bg-overlay-5, var(--background-base-lower)); border-radius: 5px; padding: 0.75em; max-width: 70vw; @@ -83,7 +83,7 @@ cursor: pointer; display: flex; align-items: center; - color: var(--text-normal); + color: var(--text-default); } .vc-shc-allowed-users-and-roles-container-permdetails-btn { @@ -91,7 +91,7 @@ cursor: pointer; display: flex; align-items: center; - color: var(--text-normal); + color: var(--text-default); } .vc-shc-allowed-users-and-roles-container > [class^="members"] { diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/plugins/spotifyControls/spotifyStyles.css index 0b760377..58d74504 100644 --- a/src/plugins/spotifyControls/spotifyStyles.css +++ b/src/plugins/spotifyControls/spotifyStyles.css @@ -8,11 +8,11 @@ } .theme-light #vc-spotify-player { - background: var(--bg-overlay-3, var(--background-secondary-alt)); + background: var(--bg-overlay-3, var(--background-base-lower-alt)); } .theme-dark #vc-spotify-player { - background: var(--bg-overlay-1, var(--background-secondary-alt)); + background: var(--bg-overlay-1, var(--background-base-lower-alt)); } .vc-spotify-button { @@ -143,7 +143,7 @@ #vc-spotify-progress-bar { position: relative; - color: var(--text-normal); + color: var(--text-default); width: 100%; margin: 0.5em 0; margin-bottom: 5px; @@ -195,5 +195,5 @@ .vc-spotify-fallback { padding: 0.5em; - color: var(--text-normal); + color: var(--text-default); } diff --git a/src/plugins/spotifyControls/visualRefreshSpotifyStyles.css b/src/plugins/spotifyControls/visualRefreshSpotifyStyles.css index 3a140a17..0d6d30ba 100644 --- a/src/plugins/spotifyControls/visualRefreshSpotifyStyles.css +++ b/src/plugins/spotifyControls/visualRefreshSpotifyStyles.css @@ -2,7 +2,7 @@ .visual-refresh { #vc-spotify-player { padding: 12px; - background: var(--bg-overlay-floating, var(--background-base-low, var(--background-secondary-alt))); + background: var(--bg-overlay-floating, var(--background-base-low, var(--background-base-lower-alt))); margin: 0; border-top-left-radius: 10px; border-top-right-radius: 10px; @@ -22,7 +22,7 @@ #vc-spotify-progress-bar { position: relative; - color: var(--text-normal); + color: var(--text-default); width: 100%; } diff --git a/src/plugins/voiceMessages/styles.css b/src/plugins/voiceMessages/styles.css index 4f2e1d57..4f1f2c62 100644 --- a/src/plugins/voiceMessages/styles.css +++ b/src/plugins/voiceMessages/styles.css @@ -10,9 +10,9 @@ } .vc-vmsg-preview { - color: var(--text-normal); + color: var(--text-default); border-radius: 24px; - background-color: var(--background-secondary); + background-color: var(--background-base-lower); position: relative; display: flex; align-items: center; From f6d92e5024d2b1aaa9731406bf0405420a446436 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 28 Jun 2025 03:56:49 +0200 Subject: [PATCH 038/162] Update more Discord css variables --- src/components/PluginSettings/styles.css | 2 +- src/components/VencordSettings/PatchHelperTab.tsx | 2 +- src/components/VencordSettings/ThemesTab.tsx | 2 +- src/components/VencordSettings/settingsStyles.css | 2 +- src/components/iconStyles.css | 2 +- src/plugins/permissionsViewer/components/icons.tsx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/PluginSettings/styles.css b/src/components/PluginSettings/styles.css index a4f9aeee..ed5e9aa1 100644 --- a/src/components/PluginSettings/styles.css +++ b/src/components/PluginSettings/styles.css @@ -70,7 +70,7 @@ padding: 1em; background: var(--info-warning-background); border: 1px solid var(--info-warning-foreground); - color: var(--info-warning-text); + color: var(--info-warning-foreground); } .vc-plugins-restart-button { diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx index 55822069..accccd49 100644 --- a/src/components/VencordSettings/PatchHelperTab.tsx +++ b/src/components/VencordSettings/PatchHelperTab.tsx @@ -148,7 +148,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError )} {compileResult && - + {compileResult[1]} } diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index 128b6817..692e20bf 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -69,7 +69,7 @@ function Validator({ link }: { link: string; }) { : "Valid!"; return {text}; } diff --git a/src/components/VencordSettings/settingsStyles.css b/src/components/VencordSettings/settingsStyles.css index ccf01cfd..35c12197 100644 --- a/src/components/VencordSettings/settingsStyles.css +++ b/src/components/VencordSettings/settingsStyles.css @@ -23,7 +23,7 @@ .vc-backup-restore-card { background-color: var(--info-warning-background); border-color: var(--info-warning-foreground); - color: var(--info-warning-text); + color: var(--info-warning-foreground); } .vc-settings-theme-links { diff --git a/src/components/iconStyles.css b/src/components/iconStyles.css index e6d49a26..9b139b32 100644 --- a/src/components/iconStyles.css +++ b/src/components/iconStyles.css @@ -3,7 +3,7 @@ } .vc-owner-crown-icon { - color: var(--text-warning); + color: var(--status-warning); } .vc-heart-icon { diff --git a/src/plugins/permissionsViewer/components/icons.tsx b/src/plugins/permissionsViewer/components/icons.tsx index fb5b0341..18b4ad0b 100644 --- a/src/plugins/permissionsViewer/components/icons.tsx +++ b/src/plugins/permissionsViewer/components/icons.tsx @@ -37,7 +37,7 @@ export function PermissionAllowedIcon() { viewBox="0 0 24 24" > Allowed - + ); } From 468b290d28771da571b63d4b3ebd20afb266e3a8 Mon Sep 17 00:00:00 2001 From: Damian <43420467+DAMcraft@users.noreply.github.com> Date: Sun, 29 Jun 2025 20:56:20 +0200 Subject: [PATCH 039/162] FakeNitro: add 96 to available emoji sizes (#3526) --- src/plugins/fakeNitro/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index f362a355..b3909595 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -127,7 +127,7 @@ const settings = definePluginSettings({ description: "Size of the emojis when sending", type: OptionType.SLIDER, default: 48, - markers: [32, 48, 64, 128, 160, 256, 512] + markers: [32, 48, 64, 96, 128, 160, 256, 512] }, transformEmojis: { description: "Whether to transform fake emojis into real ones", From 18274e4f0e262b3371a8bc62fc0b0f291bf58cdb Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 1 Jul 2025 22:36:25 +0200 Subject: [PATCH 040/162] Fix TypingTweaks --- src/plugins/typingTweaks/index.tsx | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/plugins/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx index 0aa8dba7..59b967c4 100644 --- a/src/plugins/typingTweaks/index.tsx +++ b/src/plugins/typingTweaks/index.tsx @@ -45,14 +45,16 @@ const settings = definePluginSettings({ } }); -export function buildSeveralUsers({ a, b, count }: { a: string, b: string, count: number; }) { - return [ - {a}, - ", ", - {b}, - `, and ${count} others are typing...` - ]; -} +export const buildSeveralUsers = ErrorBoundary.wrap(({ a, b, count }: { a: string, b: string, count: number; }) => { + return ( + <> + {a}, + ", ", + {b}, + `, and ${count} others are typing...` + + ); +}, { noop: true }); interface Props { user: User; @@ -96,11 +98,12 @@ export default definePlugin({ patches: [ { find: "#{intl::THREE_USERS_TYPING}", + group: true, replacement: [ { // Style the indicator and add function call to modify the children before rendering - match: /(?<=children:\[(\i)\.length>0.{0,200}?"aria-atomic":!0,children:)\i(?<=guildId:(\i).+?)/, - replace: "$self.renderTypingUsers({ users: $1, guildId: $2, children: $& })" + match: /(?<=children:\[(\i)\.length>0.{0,300}?"aria-atomic":!0,children:)\i/, + replace: "$self.renderTypingUsers({ users: $1, guildId: arguments[0]?.channel?.guild_id, children: $& })" }, { // Changes the indicator to keep the user object when creating the list of typing users From 1b2bc07592da7fe9831075f24f7cb18bd3b79470 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 1 Jul 2025 22:57:22 +0200 Subject: [PATCH 041/162] TypingTweaks: fix several users are typing display --- src/plugins/typingTweaks/index.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/plugins/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx index 59b967c4..b20840f2 100644 --- a/src/plugins/typingTweaks/index.tsx +++ b/src/plugins/typingTweaks/index.tsx @@ -22,7 +22,7 @@ import { Devs } from "@utils/constants"; import { openUserProfile } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common"; -import { User } from "discord-types/general"; +import { Channel, User } from "discord-types/general"; import { PropsWithChildren } from "react"; import managedStyle from "./style.css?managed"; @@ -45,13 +45,14 @@ const settings = definePluginSettings({ } }); -export const buildSeveralUsers = ErrorBoundary.wrap(({ a, b, count }: { a: string, b: string, count: number; }) => { +export const buildSeveralUsers = ErrorBoundary.wrap(({ a, b, count, channel }: { a: User, b: User, count: number; channel: Channel; }) => { return ( <> - {a}, - ", ", - {b}, - `, and ${count} others are typing...` + + {", "} + + {", "} + and {count} others are typing... ); }, { noop: true }); @@ -113,7 +114,8 @@ export default definePlugin({ { // Adds the alternative formatting for several users typing match: /(,{a:(\i),b:(\i),c:\i}\):\i\.length>3&&\(\i=)\i\.\i\.string\(\i\.\i#{intl::SEVERAL_USERS_TYPING}\)(?<=(\i)\.length.+?)/, - replace: (_, rest, a, b, users) => `${rest}$self.buildSeveralUsers({ a: ${a}, b: ${b}, count: ${users}.length - 2 })`, + replace: (_, rest, a, b, users) => + `${rest}$self.buildSeveralUsers({ a: ${a}, b: ${b}, count: ${users}.length - 2, channel: arguments[0]?.channel })`, predicate: () => settings.store.alternativeFormatting } ] From 9e22ab305ce66a700270828515cdeab61a75f00b Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 1 Jul 2025 23:06:06 +0200 Subject: [PATCH 042/162] Fix plugins using Message Popover buttons --- src/plugins/_api/messagePopover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/_api/messagePopover.ts b/src/plugins/_api/messagePopover.ts index 3d55f62e..a297fc87 100644 --- a/src/plugins/_api/messagePopover.ts +++ b/src/plugins/_api/messagePopover.ts @@ -27,7 +27,7 @@ export default definePlugin({ { find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}", replacement: { - match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, + match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?children:\[(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" + `]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},` } From 23eb85e8989a9678803f7d55927b04361d7649bd Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 1 Jul 2025 23:13:38 +0200 Subject: [PATCH 043/162] AppleMusicRichPresence: fix broken album covers --- src/plugins/appleMusic.desktop/native.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/appleMusic.desktop/native.ts b/src/plugins/appleMusic.desktop/native.ts index 5a547997..b73d0733 100644 --- a/src/plugins/appleMusic.desktop/native.ts +++ b/src/plugins/appleMusic.desktop/native.ts @@ -27,7 +27,7 @@ interface RemoteData { let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null; const APPLE_MUSIC_BUNDLE_REGEX = /