From 6ee6033beb45b1f493789e74b7d930cdf7fa905c Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 21 Apr 2026 21:25:38 +0200 Subject: [PATCH 1/3] 1.14.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aef46773b..225743a35 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.14.7", + "version": "1.14.8", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 66562976d191205752f8c3bdc7c4deac2f40a4cc Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 23 Apr 2026 04:27:58 +0200 Subject: [PATCH 2/3] fix context menu crashes & some plugins (#4139) - sadan4 Co-Authored-By: sadan4 <117494111+sadan4@users.noreply.github.com> --- src/api/ContextMenu.ts | 2 ++ src/plugins/_api/menuItemDemangler.ts | 33 ++++++++++-------- src/plugins/_core/noTrack.ts | 14 ++++---- src/plugins/_core/settings.tsx | 2 +- src/plugins/ctrlEnterSend/index.ts | 2 +- src/plugins/fakeNitro/index.tsx | 40 ++++++++++++++++++++-- src/plugins/messageLogger/index.tsx | 49 +++++++++++++++++++++++++-- src/plugins/spotifyCrack/index.ts | 2 +- src/webpack/common/menu.ts | 21 +----------- 9 files changed, 114 insertions(+), 51 deletions(-) diff --git a/src/api/ContextMenu.ts b/src/api/ContextMenu.ts index a632aea6f..cee43ae60 100644 --- a/src/api/ContextMenu.ts +++ b/src/api/ContextMenu.ts @@ -131,6 +131,8 @@ interface ContextMenuProps { } export function _usePatchContextMenu(props: ContextMenuProps) { + if (!Menu.MenuItem) return props; // Prevent crashes in case we fail to acquire menu items for some reason + props = { ...props, children: cloneMenuChildren(props.children), diff --git a/src/plugins/_api/menuItemDemangler.ts b/src/plugins/_api/menuItemDemangler.ts index f71e8499b..72b8fee6d 100644 --- a/src/plugins/_api/menuItemDemangler.ts +++ b/src/plugins/_api/menuItemDemangler.ts @@ -7,6 +7,7 @@ import { Devs } from "@utils/constants"; import { canonicalizeMatch } from "@utils/patches"; import definePlugin from "@utils/types"; +import { Menu } from "@webpack/common"; // duplicate values have multiple branches with different types. Just include all to be safe const nameMap = { @@ -31,38 +32,40 @@ export default definePlugin({ { find: "Menu API only allows Items", replacement: { - match: /function.{0,80}type===(\i\.\i)\).{0,50}navigable:.+?Menu API/s, - replace: (m, mod) => { - const nameAssignments = [] as string[]; + match: /(?<=(\(\i\.type===(\i\.\i)\).{0,50}?navigable:.+Menu API).+?)}$/s, + replace: (_, m) => { + const registerCalls = [] as string[]; - // if (t.type === m.MenuItem) - const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g); - // push({type:"item"}) - const pushTypeRe = /type:"(\w+)"/g; + const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g); // if (t.type === m.MenuItem) + const pushTypeRe = /type:"(\w+)"/g; // push({type:"item"}) let typeMatch: RegExpExecArray | null; - // for each if (t.type === ...) while ((typeMatch = typeCheckRe.exec(m)) !== null) { - // extract the current menu item - const item = typeMatch[1]; + const component = typeMatch[1]; // Set the starting index of the second regex to that of the first to start // matching from after the if pushTypeRe.lastIndex = typeCheckRe.lastIndex; + // extract the first type: "..." const type = pushTypeRe.exec(m)?.[1]; if (type && type in nameMap) { const name = nameMap[type]; - nameAssignments.push(`Object.defineProperty(${item},"name",{value:"${name}"})`); + registerCalls.push(`$self.registerMenuItem("${name}",${component})`); } } - if (nameAssignments.length < 6) { - console.warn("[MenuItemDemanglerAPI] Expected to at least remap 6 items, only remapped", nameAssignments.length); + + if (registerCalls.length < 6) { + console.warn("[MenuItemDemanglerAPI] Expected to remap 6 items, only remapped", registerCalls.length); } - // Merge all our redefines with the actual module - return `${nameAssignments.join(";")};${m}`; + return `${registerCalls.join(";")};}`; }, }, }, ], + + registerMenuItem(name: string, component: any) { + Object.defineProperty(component, "name", { value: name }); + Menu[name] = component; + } }); diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts index cd6234dc4..ea7c9f09f 100644 --- a/src/plugins/_core/noTrack.ts +++ b/src/plugins/_core/noTrack.ts @@ -86,14 +86,14 @@ export default definePlugin({ // It has everything it needs preloaded, so, it doesn't include any chunk loading functionality. // Because of that, its WebpackInstance doesnt export wreq.m or wreq.c - // To circuvent this and disable Sentry we are gonna hook when wreq.g of its WebpackInstance is set. + // To circuvent this and disable Sentry we are gonna hook when wreq.d of its WebpackInstance is set. // When that happens we are gonna forcefully throw an error and abort everything. - Object.defineProperty(Function.prototype, "g", { + Object.defineProperty(Function.prototype, "d", { configurable: true, - set(this: WebpackRequire, globalObj: WebpackRequire["g"]) { - Object.defineProperty(this, "g", { - value: globalObj, + set(this: WebpackRequire, esmDeclareFunc: WebpackRequire["d"]) { + Object.defineProperty(this, "d", { + value: esmDeclareFunc, configurable: true, enumerable: true, writable: true @@ -123,7 +123,7 @@ export default definePlugin({ new Logger("NoTrack", "#8caaee").info("Disabling Sentry by erroring its WebpackInstance"); - Reflect.deleteProperty(Function.prototype, "g"); + Reflect.deleteProperty(Function.prototype, "d"); Reflect.deleteProperty(window, "DiscordSentry"); throw new Error("Sentry successfully disabled"); @@ -136,7 +136,7 @@ export default definePlugin({ set() { new Logger("NoTrack", "#8caaee").error("Failed to disable Sentry. Falling back to deleting window.DiscordSentry"); - Reflect.deleteProperty(Function.prototype, "g"); + Reflect.deleteProperty(Function.prototype, "d"); Reflect.deleteProperty(window, "DiscordSentry"); } }); diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index 369094373..b2f362613 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -105,7 +105,7 @@ export default definePlugin({ find: "#{intl::COPY_VERSION}", replacement: [ { - match: /"text-xxs\/normal".{0,300}?(?=null!=(\i)&&(.{0,20}\i\.Text.{0,200}?,children:).{0,15}?("span"),({className:\i\.\i,children:\["Build Override: ",\1\.id\]\})\)\}\))/, + match: /"text-xxs\/normal".{0,300}?(?=null!=(\i)&&(.{0,20}\i\.\i.{0,200}?,children:).{0,15}?("span"),({className:\i\.\i,children:\["Build Override: ",\1\.id\]\})\)\}\))/, replace: (m, _buildOverride, makeRow, component, props) => { props = props.replace(/children:\[.+\]/, ""); return `${m},$self.makeInfoElements(${component},${props}).map(e=>${makeRow}e})),`; diff --git a/src/plugins/ctrlEnterSend/index.ts b/src/plugins/ctrlEnterSend/index.ts index ba6eb3df6..bd52f5ad6 100644 --- a/src/plugins/ctrlEnterSend/index.ts +++ b/src/plugins/ctrlEnterSend/index.ts @@ -45,7 +45,7 @@ export default definePlugin({ { find: ".selectPreviousCommandOption(", replacement: { - match: /(?<=(\i)\.which!==\i\.\i.ENTER\|\|).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=\|\|\(\i\.preventDefault)/, + match: /(?<=(\i)\.key!==\i\.\i.ENTER\|\|).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=\|\|\(\i\.preventDefault)/, replace: "!$self.shouldSubmit($1,$2)" } }, diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 1e6b9ef9f..07fb6b74a 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -165,6 +165,7 @@ function makeBypassPatches(): Omit { replacement: mapping.map(({ func, predicate }) => ({ match: new RegExp(String.raw`(?<=${func}:)\i`), replace: "() => true", + noWarn: true, predicate })) }; @@ -180,8 +181,36 @@ export default definePlugin({ settings, patches: [ - // General bypass patches + // TODO: remove makeBypassPatches(), + { + find: "canUseCustomStickersEverywhere:", + replacement: [ + { + match: /(?<=canUseCustomStickersEverywhere:function\(\i\)\{)/, + replace: "return true;", + predicate: () => settings.store.enableStickerBypass + }, + { + match: /(?<=canUseHighVideoUploadQuality:function\(\i\)\{)/, + replace: "return true;", + predicate: () => settings.store.enableStreamQualityBypass + }, + { + match: /(?<=canStreamQuality:function\(\i,\i\)\{)/, + replace: "return true;", + predicate: () => settings.store.enableStreamQualityBypass + }, + { + match: /(?<=canUseClientThemes:function\(\i\)\{)/, + replace: "return true;" + }, + { + match: /(?<=canUsePremiumAppIcons:function\(\i\)\{)/, + replace: "return true;" + } + ], + }, // Patch the emoji picker in voice calls to not be bypassed by fake nitro { find: '.getByName("fork_and_knife")', @@ -254,8 +283,13 @@ export default definePlugin({ replacement: [ { // Overwrite incoming connection settings proto with our local settings + match: /(?<=CONNECTION_OPEN:function\((\i)\){)/, + replace: (_, props) => `$self.handleProtoChange(${props}.userSettingsProto,${props}.user);` + }, + { match: /function (\i)\((\i)\){(?=.*CONNECTION_OPEN:\1)/, - replace: (m, funcName, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);` + replace: (m, funcName, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);`, + noWarn: true }, { // Overwrite non local proto changes with our local settings @@ -352,7 +386,7 @@ export default definePlugin({ predicate: () => settings.store.transformEmojis, replacement: { // Add the fake nitro emoji notice - match: /(?<=emojiDescription:)(\i)(?<=\1=\i\((\i)\).+?)/, + match: /(?<=emojiDescription:)(\i)(?<=\1=\(\i=>\{.+?\}\)\((\i)\)[,;].+?)/, replace: (_, reactNode, props) => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!${props}?.fakeNitroNode?.fake)` } }, diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 0d364132e..ac3436f3e 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -350,11 +350,20 @@ export default definePlugin({ patches: [ { - // MessageStore find: '"MessageStore"', replacement: [ { // Add deleted=true to all target messages in the MESSAGE_DELETE event + match: /(?<=MESSAGE_DELETE:function\((\i)\)\{)(?=let.{0,100}(\i\.\i)\.getOrCreate)/, + replace: ` + let cache = $2.getOrCreate($1.channelId); + cache = $self.handleDelete(cache, $1, false); + $2.commit(cache); + return; + ` + }, + { + noWarn: true, match: /function (?=.+?MESSAGE_DELETE:(\i))\1\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?}(?=function)/, replace: "function $1($2){" + @@ -365,7 +374,17 @@ export default definePlugin({ }, { // Add deleted=true to all target messages in the MESSAGE_DELETE_BULK event + match: /(?<=MESSAGE_DELETE_BULK:function\((\i)\){)(?=let.{0,100}(\i\.\i)\.getOrCreate)/, + replace: ` + let cache = $2.getOrCreate($1.channelId); + cache = $self.handleDelete(cache, $1, true); + $2.commit(cache); + return; + ` + }, + { match: /function (?=.+?MESSAGE_DELETE_BULK:(\i))\1\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?}(?=function)/, + noWarn: true, replace: "function $1($2){" + " var cache = $3getOrCreate($2.channelId);" + @@ -375,6 +394,20 @@ export default definePlugin({ }, { // Add current cached content + new edit time to cached message's editHistory + match: /(MESSAGE_UPDATE:function\((\i)\).+?)\.update\((\i)/, + replace: ` + $1 + .update($3, m => + (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message, true)) ? m : + $2.message.edited_timestamp && $2.message.content !== m.content ? + m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) : + m + ) + .update($3 + ` + }, + { + noWarn: true, match: /(function (\i)\((\i)\).+?)\.update\((\i)(?=.*MESSAGE_UPDATE:\2)/, replace: "$1" + ".update($4,m =>" + @@ -493,13 +526,23 @@ export default definePlugin({ // ReferencedMessageStore find: '"ReferencedMessageStore"', replacement: [ + { + match: /(?<=MESSAGE_DELETE:function\(\i\)\{)/, + replace: "return;" + }, + { + match: /(?<=MESSAGE_DELETE_BULK:function\(\i\)\{)/, + replace: "return;" + }, { match: /MESSAGE_DELETE:\i,/, - replace: "MESSAGE_DELETE:()=>{}," + replace: "MESSAGE_DELETE:()=>{},", + noWarn: true }, { match: /MESSAGE_DELETE_BULK:\i,/, - replace: "MESSAGE_DELETE_BULK:()=>{}," + replace: "MESSAGE_DELETE_BULK:()=>{},", + noWarn: true } ] }, diff --git a/src/plugins/spotifyCrack/index.ts b/src/plugins/spotifyCrack/index.ts index d15d1fd86..07c659057 100644 --- a/src/plugins/spotifyCrack/index.ts +++ b/src/plugins/spotifyCrack/index.ts @@ -38,7 +38,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "SpotifyCrack", description: "Free listen along, no auto-pausing in voice chat, and allows activity to continue playing when idling", - tags: ["Media", "Utility"], + tags: ["Media", "Utility", "Activity"], authors: [Devs.Cyn, Devs.Nuckyz], settings, diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts index 4029877f2..d0a7dbf34 100644 --- a/src/webpack/common/menu.ts +++ b/src/webpack/common/menu.ts @@ -17,29 +17,10 @@ */ import type * as t from "@vencord/discord-types"; -import { filters, mapMangledModuleLazy, waitFor, wreq } from "@webpack"; +import { filters, mapMangledModuleLazy, waitFor } from "@webpack"; export const Menu = {} as t.Menu; -// Relies on .name properties added by the MenuItemDemanglerAPI -waitFor(m => m.name === "MenuCheckboxItem", (_, id) => { - // We have to do this manual require by ID because m in this case is the MenuCheckBoxItem instead of the entire module - const exports = wreq(id); - - for (const exportKey in exports) { - // Some exports might have not been initialized yet due to circular imports, so try catch it. - try { - var exportValue = exports[exportKey]; - } catch { - continue; - } - - if (typeof exportValue === "function" && exportValue.name.startsWith("Menu")) { - Menu[exportValue.name] = exportValue; - } - } -}); - waitFor(filters.componentByCode('path:["empty"]'), m => Menu.Menu = m); waitFor(filters.componentByCode("SLIDER)", "handleSize:16"), m => Menu.MenuSliderControl = m); waitFor(filters.componentByCode(".SEARCH)", ".focus()", "query:"), m => Menu.MenuSearchControl = m); From 392d76fe6fb49f95edf5493cbcb687693338c541 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 23 Apr 2026 04:51:03 +0200 Subject: [PATCH 3/3] bump to v1.14.9 --- package.json | 2 +- src/plugins/whoReacted/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 225743a35..c80c5a648 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.14.8", + "version": "1.14.9", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index 0eb0a7d0f..9daade638 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -27,7 +27,7 @@ import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, useEffect, use let Scroll: any = null; const queue = new Queue(); -let reactions: Record; +let reactions: Record = {}; function fetchReactions(msg: Message, emoji: ReactionEmoji, type: number) { const key = emoji.name + (emoji.id ? `:${emoji.id}` : "");