From 5c88284ed3d089feeceb495607f5b948d51007b6 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Fri, 26 Jul 2024 11:55:32 -0400 Subject: [PATCH 1/7] feat(showHiddenChannels): Fix Broken Patch (#2726) --- src/plugins/showHiddenChannels/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 2d8b0c190..68778915b 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -307,7 +307,7 @@ export default definePlugin({ ] }, { - find: '+1]})},"overflow"))', + find: '})},"overflow"))', replacement: [ { // Create a variable for the channel prop From 0f5cf37ef9e5661fea4c0aeb03af3cd749ac6267 Mon Sep 17 00:00:00 2001 From: Sqaaakoi Date: Wed, 31 Jul 2024 09:18:42 +1200 Subject: [PATCH 2/7] fix(ShowHiddenThings): always render highest role in ModView (#2709) --- src/plugins/showHiddenThings/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/plugins/showHiddenThings/index.ts b/src/plugins/showHiddenThings/index.ts index 90bb345ef..b6c8e4104 100644 --- a/src/plugins/showHiddenThings/index.ts +++ b/src/plugins/showHiddenThings/index.ts @@ -66,6 +66,15 @@ export default definePlugin({ replace: "return true", } }, + // fixes a bug where Members page must be loaded to see highest role, why is Discord depending on MemberSafetyStore.getEnhancedMember for something that can be obtained here? + { + find: "Messages.GUILD_MEMBER_MOD_VIEW_PERMISSION_GRANTED_BY_ARIA_LABEL,tooltipContentClassName", + predicate: () => settings.store.showModView, + replacement: { + match: /(role:)\i(?=,guildId.{0,100}role:(\i\[))/, + replace: "$1$2arguments[0].member.highestRoleId]", + } + }, { find: "prod_discoverable_guilds", predicate: () => settings.store.disableDiscoveryFilters, From 51ae019cd5c59a321caee2c3ab84a8dc88f0fbd6 Mon Sep 17 00:00:00 2001 From: Surge <112782958+surgedevs@users.noreply.github.com> Date: Tue, 30 Jul 2024 23:42:57 +0200 Subject: [PATCH 3/7] feat(plugins/openInApp) Refactor code and add Apple Music support (#2744) Co-authored-by: v Co-authored-by: Shiggy <136832773+shiggybot@users.noreply.github.com> --- src/main/utils/constants.ts | 3 +- src/plugins/openInApp/README.md | 11 +++ src/plugins/openInApp/index.ts | 148 +++++++++++++++----------------- src/utils/constants.ts | 4 + 4 files changed, 84 insertions(+), 82 deletions(-) create mode 100644 src/plugins/openInApp/README.md diff --git a/src/main/utils/constants.ts b/src/main/utils/constants.ts index 9513da51c..428752021 100644 --- a/src/main/utils/constants.ts +++ b/src/main/utils/constants.ts @@ -35,7 +35,8 @@ export const ALLOWED_PROTOCOLS = [ "steam:", "spotify:", "com.epicgames.launcher:", - "tidal:" + "tidal:", + "itunes:", ]; export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla"); diff --git a/src/plugins/openInApp/README.md b/src/plugins/openInApp/README.md new file mode 100644 index 000000000..2b1385c62 --- /dev/null +++ b/src/plugins/openInApp/README.md @@ -0,0 +1,11 @@ +# OpenInApp + +Open links in their respective apps instead of your browser + +## Currently supports: + +- Spotify +- Steam +- EpicGames +- Tidal +- Apple Music (iTunes) diff --git a/src/plugins/openInApp/index.ts b/src/plugins/openInApp/index.ts index cb05324a0..1a68e8f5d 100644 --- a/src/plugins/openInApp/index.ts +++ b/src/plugins/openInApp/index.ts @@ -18,46 +18,70 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; -import definePlugin, { OptionType, PluginNative } from "@utils/types"; +import definePlugin, { OptionType, PluginNative, SettingsDefinition } from "@utils/types"; import { showToast, Toasts } from "@webpack/common"; import type { MouseEvent } from "react"; -const ShortUrlMatcher = /^https:\/\/(spotify\.link|s\.team)\/.+$/; -const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/; -const SteamMatcher = /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/; -const EpicMatcher = /^https:\/\/store\.epicgames\.com\/(.+)$/; -const TidalMatcher = /^https:\/\/tidal\.com\/browse\/(track|album|artist|playlist|user|video|mix)\/(.+)(?:\?.+?)?$/; +interface URLReplacementRule { + match: RegExp; + replace: (...matches: string[]) => string; + description: string; + shortlinkMatch?: RegExp; + accountViewReplace?: (userId: string) => string; +} -const settings = definePluginSettings({ +// Do not forget to add protocols to the ALLOWED_PROTOCOLS constant +const UrlReplacementRules: Record = { spotify: { - type: OptionType.BOOLEAN, + match: /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/, + replace: (_, type, id) => `spotify://${type}/${id}`, description: "Open Spotify links in the Spotify app", - default: true, + shortlinkMatch: /^https:\/\/spotify\.link\/.+$/, + accountViewReplace: userId => `spotify:user:${userId}`, }, steam: { - type: OptionType.BOOLEAN, + match: /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/, + replace: match => `steam://openurl/${match}`, description: "Open Steam links in the Steam app", - default: true, + shortlinkMatch: /^https:\/\/s.team\/.+$/, + accountViewReplace: userId => `steam://openurl/https://steamcommunity.com/profiles/${userId}`, }, epic: { - type: OptionType.BOOLEAN, + match: /^https:\/\/store\.epicgames\.com\/(.+)$/, + replace: (_, id) => `com.epicgames.launcher://store/${id}`, description: "Open Epic Games links in the Epic Games Launcher", - default: true, }, tidal: { - type: OptionType.BOOLEAN, + match: /^https:\/\/tidal\.com\/browse\/(track|album|artist|playlist|user|video|mix)\/(.+)(?:\?.+?)?$/, + replace: (_, type, id) => `tidal://${type}/${id}`, description: "Open Tidal links in the Tidal app", - default: true, - } -}); + }, + itunes: { + match: /^https:\/\/music\.apple\.com\/([a-z]{2}\/)?(album|artist|playlist|song|curator)\/([^/?#]+)\/?([^/?#]+)?(?:\?.*)?(?:#.*)?$/, + replace: (_, lang, type, name, id) => id ? `itunes://music.apple.com/us/${type}/${name}/${id}` : `itunes://music.apple.com/us/${type}/${name}`, + description: "Open Apple Music links in the iTunes app" + }, +}; + +const pluginSettings = definePluginSettings( + Object.entries(UrlReplacementRules).reduce((acc, [key, rule]) => { + acc[key] = { + type: OptionType.BOOLEAN, + description: rule.description, + default: true, + }; + return acc; + }, {} as SettingsDefinition) +); + const Native = VencordNative.pluginHelpers.OpenInApp as PluginNative; export default definePlugin({ name: "OpenInApp", - description: "Open Spotify, Tidal, Steam and Epic Games URLs in their respective apps instead of your browser", - authors: [Devs.Ven], - settings, + description: "Open links in their respective apps instead of your browser", + authors: [Devs.Ven, Devs.surgedevs], + settings: pluginSettings, patches: [ { @@ -70,7 +94,7 @@ export default definePlugin({ // Make Spotify profile activity links open in app on web { find: "WEB_OPEN(", - predicate: () => !IS_DISCORD_DESKTOP && settings.store.spotify, + predicate: () => !IS_DISCORD_DESKTOP && pluginSettings.store.spotify, replacement: { match: /\i\.\i\.isProtocolRegistered\(\)(.{0,100})window.open/g, replace: "true$1VencordNative.native.openExternal" @@ -79,8 +103,8 @@ export default definePlugin({ { find: ".CONNECTED_ACCOUNT_VIEWED,", replacement: { - match: /(?<=href:\i,onClick:\i=>\{)(?=.{0,10}\i=(\i)\.type,.{0,100}CONNECTED_ACCOUNT_VIEWED)/, - replace: "$self.handleAccountView(arguments[0],$1.type,$1.id);" + match: /(?<=href:\i,onClick:(\i)=>\{)(?=.{0,10}\i=(\i)\.type,.{0,100}CONNECTED_ACCOUNT_VIEWED)/, + replace: "if($self.handleAccountView($1,$2.type,$2.id)) return;" } } ], @@ -89,61 +113,25 @@ export default definePlugin({ if (!data) return false; let url = data.href; - if (!IS_WEB && ShortUrlMatcher.test(url)) { - event?.preventDefault(); - // CORS jumpscare - url = await Native.resolveRedirect(url); - } + if (!url) return false; - spotify: { - if (!settings.store.spotify) break spotify; + for (const [key, rule] of Object.entries(UrlReplacementRules)) { + if (!pluginSettings.store[key]) continue; - const match = SpotifyMatcher.exec(url); - if (!match) break spotify; + if (rule.shortlinkMatch?.test(url)) { + event?.preventDefault(); + url = await Native.resolveRedirect(url); + } - const [, type, id] = match; - VencordNative.native.openExternal(`spotify:${type}:${id}`); + if (rule.match.test(url)) { + showToast("Opened link in native app", Toasts.Type.SUCCESS); - event?.preventDefault(); - return true; - } + const newUrl = url.replace(rule.match, rule.replace); + VencordNative.native.openExternal(newUrl); - steam: { - if (!settings.store.steam) break steam; - - if (!SteamMatcher.test(url)) break steam; - - VencordNative.native.openExternal(`steam://openurl/${url}`); - event?.preventDefault(); - - // Steam does not focus itself so show a toast so it's slightly less confusing - showToast("Opened link in Steam", Toasts.Type.SUCCESS); - return true; - } - - epic: { - if (!settings.store.epic) break epic; - - const match = EpicMatcher.exec(url); - if (!match) break epic; - - VencordNative.native.openExternal(`com.epicgames.launcher://store/${match[1]}`); - event?.preventDefault(); - - return true; - } - - tidal: { - if (!settings.store.tidal) break tidal; - - const match = TidalMatcher.exec(url); - if (!match) break tidal; - - const [, type, id] = match; - VencordNative.native.openExternal(`tidal://${type}/${id}`); - - event?.preventDefault(); - return true; + event?.preventDefault(); + return true; + } } // in case short url didn't end up being something we can handle @@ -155,14 +143,12 @@ export default definePlugin({ return false; }, - handleAccountView(event: { preventDefault(): void; }, platformType: string, userId: string) { - if (platformType === "spotify" && settings.store.spotify) { - VencordNative.native.openExternal(`spotify:user:${userId}`); - event.preventDefault(); - } else if (platformType === "steam" && settings.store.steam) { - VencordNative.native.openExternal(`steam://openurl/https://steamcommunity.com/profiles/${userId}`); - showToast("Opened link in Steam", Toasts.Type.SUCCESS); - event.preventDefault(); + handleAccountView(e: MouseEvent, platformType: string, userId: string) { + const rule = UrlReplacementRules[platformType]; + if (rule?.accountViewReplace && pluginSettings.store[platformType]) { + VencordNative.native.openExternal(rule.accountViewReplace(userId)); + e.preventDefault(); + return true; } } }); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 4de8e243a..f46618e21 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -538,6 +538,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Joona", id: 297410829589020673n }, + surgedevs: { + name: "Chloe", + id: 1084592643784331324n + } } satisfies Record); // iife so #__PURE__ works correctly From 902a86c3b24ee1b64af5a51b404ae4fb7c0ad95c Mon Sep 17 00:00:00 2001 From: Nyako <24845294+nyakowint@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:48:11 -0400 Subject: [PATCH 4/7] XSOverlay: Update to new API (#2736) Co-authored-by: v --- src/plugins/xsOverlay.desktop/README.md | 15 --- src/plugins/xsOverlay.desktop/native.ts | 16 ---- src/plugins/xsOverlay/README.md | 14 +++ .../index.ts => xsOverlay/index.tsx} | 95 ++++++++++++++++--- 4 files changed, 94 insertions(+), 46 deletions(-) delete mode 100644 src/plugins/xsOverlay.desktop/README.md delete mode 100644 src/plugins/xsOverlay.desktop/native.ts create mode 100644 src/plugins/xsOverlay/README.md rename src/plugins/{xsOverlay.desktop/index.ts => xsOverlay/index.tsx} (83%) diff --git a/src/plugins/xsOverlay.desktop/README.md b/src/plugins/xsOverlay.desktop/README.md deleted file mode 100644 index 477e30bf3..000000000 --- a/src/plugins/xsOverlay.desktop/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# XSOverlay Notifier - -Sends Discord messages to [XSOverlay](https://store.steampowered.com/app/1173510/XSOverlay/) for easier viewing while using VR. - -## Preview - -![](https://github.com/Vendicated/Vencord/assets/24845294/205d2055-bb4a-44e4-b7e3-265391bccd40) - -![](https://github.com/Vendicated/Vencord/assets/24845294/f15eff61-2d52-4620-bcab-808ecb1606d2) - -## Usage -- Enable this plugin -- Set plugin settings as desired -- Open XSOverlay -- get ping spammed diff --git a/src/plugins/xsOverlay.desktop/native.ts b/src/plugins/xsOverlay.desktop/native.ts deleted file mode 100644 index 82809383a..000000000 --- a/src/plugins/xsOverlay.desktop/native.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2023 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { createSocket, Socket } from "dgram"; - -let xsoSocket: Socket; - -export function sendToOverlay(_, data: any) { - data.icon = Buffer.from(data.icon).toString("base64"); - const json = JSON.stringify(data); - xsoSocket ??= createSocket("udp4"); - xsoSocket.send(json, 42069, "127.0.0.1"); -} diff --git a/src/plugins/xsOverlay/README.md b/src/plugins/xsOverlay/README.md new file mode 100644 index 000000000..7f517c173 --- /dev/null +++ b/src/plugins/xsOverlay/README.md @@ -0,0 +1,14 @@ +# XSOverlay Notifier + +Sends Discord messages to [XSOverlay](https://store.steampowered.com/app/1173510/XSOverlay/) for easier viewing while using VR. + +## Preview + +![Resulting notification inside XSOverlay](https://github.com/Vendicated/Vencord/assets/24845294/205d2055-bb4a-44e4-b7e3-265391bccd40) + +![Test notification inside XSOverlay](https://github.com/user-attachments/assets/d3b0c387-1d67-4697-a470-d4a927e228f4) + +## Usage +- Enable this plugin +- Set port and plugin settings as desired (defaults should work fine) +- Open SteamVR and XSOverlay diff --git a/src/plugins/xsOverlay.desktop/index.ts b/src/plugins/xsOverlay/index.tsx similarity index 83% rename from src/plugins/xsOverlay.desktop/index.ts rename to src/plugins/xsOverlay/index.tsx index 8b06475c0..ab76a0e79 100644 --- a/src/plugins/xsOverlay.desktop/index.ts +++ b/src/plugins/xsOverlay/index.tsx @@ -8,9 +8,9 @@ import { definePluginSettings } from "@api/Settings"; import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; -import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; +import definePlugin, { OptionType, ReporterTestable } from "@utils/types"; import { findByCodeLazy, findLazy } from "@webpack"; -import { ChannelStore, GuildStore, UserStore } from "@webpack/common"; +import { Button, ChannelStore, GuildStore, UserStore } from "@webpack/common"; import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general"; const ChannelTypes = findLazy(m => m.ANNOUNCEMENT_THREAD === 10); @@ -68,10 +68,40 @@ interface Call { ringing: string[]; } +interface ApiObject { + sender: string, + target: string, + command: string, + jsonData: string, + rawData: string | null, +} + +interface NotificationObject { + type: number; + timeout: number; + height: number; + opacity: number; + volume: number; + audioPath: string; + title: string; + content: string; + useBase64Icon: boolean; + icon: ArrayBuffer | string; + sourceApp: string; +} + const notificationsShouldNotify = findByCodeLazy(".SUPPRESS_NOTIFICATIONS))return!1"); -const XSLog = new Logger("XSOverlay"); +const logger = new Logger("XSOverlay"); const settings = definePluginSettings({ + webSocketPort: { + type: OptionType.NUMBER, + description: "Websocket port", + default: 42070, + async onChange() { + await start(); + } + }, botNotifications: { type: OptionType.BOOLEAN, description: "Allow bot notifications", @@ -136,7 +166,17 @@ const settings = definePluginSettings({ }, }); -const Native = VencordNative.pluginHelpers.XSOverlay as PluginNative; +let socket: WebSocket; + +async function start() { + if (socket) socket.close(); + socket = new WebSocket(`ws://127.0.0.1:${settings.store.webSocketPort ?? 42070}/?client=Vencord`); + return new Promise((resolve, reject) => { + socket.onopen = resolve; + socket.onerror = reject; + setTimeout(reject, 3000); + }); +} export default definePlugin({ name: "XSOverlay", @@ -248,7 +288,21 @@ export default definePlugin({ if (shouldIgnoreForChannelType(channel)) return; sendMsgNotif(titleString, finalMsg, message); } - } + }, + + start, + + stop() { + socket.close(); + }, + + settingsAboutComponent: () => ( + <> + + + ) }); function shouldIgnoreForChannelType(channel: Channel) { @@ -259,9 +313,8 @@ function shouldIgnoreForChannelType(channel: Channel) { function sendMsgNotif(titleString: string, content: string, message: Message) { fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => { - const msgData = { - messageType: 1, - index: 0, + const msgData: NotificationObject = { + type: 1, timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout, height: calculateHeight(content), opacity: settings.store.opacity, @@ -270,17 +323,17 @@ function sendMsgNotif(titleString: string, content: string, message: Message) { title: titleString, content: content, useBase64Icon: true, - icon: result, + icon: new TextDecoder().decode(result), sourceApp: "Vencord" }; - Native.sendToOverlay(msgData); + + sendToOverlay(msgData); }); } function sendOtherNotif(content: string, titleString: string) { - const msgData = { - messageType: 1, - index: 0, + const msgData: NotificationObject = { + type: 1, timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout, height: calculateHeight(content), opacity: settings.store.opacity, @@ -289,10 +342,22 @@ function sendOtherNotif(content: string, titleString: string) { title: titleString, content: content, useBase64Icon: false, - icon: null, + icon: "default", sourceApp: "Vencord" }; - Native.sendToOverlay(msgData); + sendToOverlay(msgData); +} + +async function sendToOverlay(notif: NotificationObject) { + const apiObject: ApiObject = { + sender: "Vencord", + target: "xsoverlay", + command: "SendNotification", + jsonData: JSON.stringify(notif), + rawData: null + }; + if (socket.readyState !== socket.OPEN) await start(); + socket.send(JSON.stringify(apiObject)); } function shouldNotify(message: Message, channel: string) { From e460b5efb675e507225b9fd9d9a8b8ae02befde5 Mon Sep 17 00:00:00 2001 From: fres621 <126067139+fres621@users.noreply.github.com> Date: Wed, 31 Jul 2024 00:26:58 +0100 Subject: [PATCH 5/7] Fix MessagePopoverAPI patch (#2746) Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> --- src/plugins/_api/messagePopover.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/plugins/_api/messagePopover.ts b/src/plugins/_api/messagePopover.ts index be4b6393a..42a1bb765 100644 --- a/src/plugins/_api/messagePopover.ts +++ b/src/plugins/_api/messagePopover.ts @@ -27,12 +27,8 @@ export default definePlugin({ find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL", replacement: { // foo && !bar ? createElement(reactionStuffs)... createElement(blah,...makeElement(reply-other)) - match: /\i&&!\i\?\(0,\i\.jsxs?\)\(.{0,200}renderEmojiPicker:.{0,500}\?(\i)\(\{key:"reply-other"/, - replace: (m, makeElement) => { - const msg = m.match(/message:(.{1,3}),/)?.[1]; - if (!msg) throw new Error("Could not find message variable"); - return `...Vencord.Api.MessagePopover._buildPopoverElements(${msg},${makeElement}),${m}`; - } + match: /\i&&!\i\?\(0,\i\.jsxs?\)\(.{0,200}renderEmojiPicker:.{0,500}\?(\i)\(\{key:"reply-other"(?<=message:(\i).+?)/, + replace: (m, makeElement, msg) => `...Vencord.Api.MessagePopover._buildPopoverElements(${msg},${makeElement}),${m}` } }], }); From 3013c669c035a9e6447aaa87d16c0f6a985c8f5f Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 31 Jul 2024 03:02:10 +0200 Subject: [PATCH 6/7] Fix ShowConnections Co-Authored-By: Masterjoona <69722179+Masterjoona@users.noreply.github.com> --- src/plugins/showConnections/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index d6909dc91..f9f3d9eb7 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -211,9 +211,9 @@ export default definePlugin({ } }, { - find: /\.BITE_SIZE,onOpenProfile:\i,usernameIcon:/, + find: '"BiteSizeProfileBody"', replacement: { - match: /currentUser:\i,guild:\i,onOpenProfile:.+?}\)(?=])(?<=user:(\i),bio:null==(\i)\?.+?)/, + match: /currentUser:\i,guild:\i}\)(?<=user:(\i),bio:null==(\i)\?.+?)/, replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2, simplified: true })" } } From 1bfdcf2697f2fc09d4ec5b1caf4d2be95d02534f Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 31 Jul 2024 03:08:57 +0200 Subject: [PATCH 7/7] fix BetterUploadButton on canary --- src/plugins/betterUploadButton/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/plugins/betterUploadButton/index.ts b/src/plugins/betterUploadButton/index.ts index 511d12a4a..2406b71e5 100644 --- a/src/plugins/betterUploadButton/index.ts +++ b/src/plugins/betterUploadButton/index.ts @@ -25,11 +25,9 @@ export default definePlugin({ description: "Upload with a single click, open menu with right click", patches: [ { - find: "Messages.CHAT_ATTACH_UPLOAD_OR_INVITE", + find: '"ChannelAttachButton"', replacement: { - // Discord merges multiple props here with Object.assign() - // This patch passes a third object to it with which we override onClick and onContextMenu - match: /CHAT_ATTACH_UPLOAD_OR_INVITE,onDoubleClick:(.+?:void 0),\.\.\.(\i),/, + match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),\.\.\.(\i),/, replace: "$&onClick:$1,onContextMenu:$2.onClick,", }, },