From 3109ff102ee8deb9f796caadacf5ebf25f195e0d Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 8 May 2026 16:00:46 +0200 Subject: [PATCH] Fix Vencord context menu options missing Also merges MenuItemDemanglerAPI into ContextMenuAPI because these patch the same module anyway and depend on each other --- src/plugins/_api/contextMenu.ts | 75 +++++++++++++++++++++++---- src/plugins/_api/menuItemDemangler.ts | 71 ------------------------- 2 files changed, 66 insertions(+), 80 deletions(-) delete mode 100644 src/plugins/_api/menuItemDemangler.ts diff --git a/src/plugins/_api/contextMenu.ts b/src/plugins/_api/contextMenu.ts index debc55273..8fe49d650 100644 --- a/src/plugins/_api/contextMenu.ts +++ b/src/plugins/_api/contextMenu.ts @@ -17,7 +17,23 @@ */ 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 = { + radio: "MenuRadioItem", + separator: "MenuSeparator", + checkbox: "MenuCheckboxItem", + groupstart: "MenuGroup", + + control: "MenuControlItem", + compositecontrol: "MenuControlItem", + + item: "MenuItem", + customitem: "MenuItem", +}; export default definePlugin({ name: "ContextMenuAPI", @@ -26,13 +42,6 @@ export default definePlugin({ required: true, patches: [ - { - find: "♫ (つ。◕‿‿◕。)つ ♪", - replacement: { - match: /(?=let{navId:)(?<=function \i\((\i)\).+?)/, - replace: "$1=Vencord.Api.ContextMenu._usePatchContextMenu($1);" - } - }, { find: "navId:", all: true, @@ -50,6 +59,54 @@ export default definePlugin({ } } ] - } - ] + }, + + { + find: "Menu API only allows Items", + replacement: [ + // Patch the central context menu handler + { + match: /(?=let{navId:)(?<=function \i\((\i)\).+?)/, + replace: "$1=Vencord.Api.ContextMenu._usePatchContextMenu($1);" + }, + + // Demangle Discord's Menu Item module + { + match: /(?<=(\(\i\.type===(\i\.\i)\).{0,50}?navigable:.+Menu API).+?)}$/s, + replace: (_, m) => { + const registerCalls = [] as string[]; + + 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; + while ((typeMatch = typeCheckRe.exec(m)) !== null) { + 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]; + registerCalls.push(`$self.registerMenuItem("${name}",${component})`); + } + } + + if (registerCalls.length < 6) { + console.warn("[MenuItemDemanglerAPI] Expected to remap 6 items, only remapped", registerCalls.length); + } + + return `${registerCalls.join(";")};}`; + }, + } + ], + }, + ], + + registerMenuItem(name: string, component: any) { + Object.defineProperty(component, "name", { value: name }); + Menu[name] = component; + } }); diff --git a/src/plugins/_api/menuItemDemangler.ts b/src/plugins/_api/menuItemDemangler.ts deleted file mode 100644 index 72b8fee6d..000000000 --- a/src/plugins/_api/menuItemDemangler.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2025 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -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 = { - radio: "MenuRadioItem", - separator: "MenuSeparator", - checkbox: "MenuCheckboxItem", - groupstart: "MenuGroup", - - control: "MenuControlItem", - compositecontrol: "MenuControlItem", - - item: "MenuItem", - customitem: "MenuItem", -}; - -export default definePlugin({ - name: "MenuItemDemanglerAPI", - description: "Demangles Discord's Menu Item module", - authors: [Devs.Ven], - required: true, - patches: [ - { - find: "Menu API only allows Items", - replacement: { - match: /(?<=(\(\i\.type===(\i\.\i)\).{0,50}?navigable:.+Menu API).+?)}$/s, - replace: (_, m) => { - const registerCalls = [] as string[]; - - 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; - while ((typeMatch = typeCheckRe.exec(m)) !== null) { - 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]; - registerCalls.push(`$self.registerMenuItem("${name}",${component})`); - } - } - - if (registerCalls.length < 6) { - console.warn("[MenuItemDemanglerAPI] Expected to remap 6 items, only remapped", registerCalls.length); - } - - return `${registerCalls.join(";")};}`; - }, - }, - }, - ], - - registerMenuItem(name: string, component: any) { - Object.defineProperty(component, "name", { value: name }); - Menu[name] = component; - } -});