From 806960f1c640822e190e2c7f355bda38d596601b Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 29 Feb 2024 23:15:19 -0300 Subject: [PATCH 01/30] ClientTheme: do not use lodash on start method --- src/plugins/clientTheme/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 5d8cd4dc0..4e07daf42 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -12,7 +12,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import definePlugin, { OptionType, StartAt } from "@utils/types"; import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; -import { Button, Forms, lodash as _, useStateFromStores } from "@webpack/common"; +import { Button, Forms, useStateFromStores } from "@webpack/common"; const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); @@ -200,8 +200,8 @@ function captureOne(str, regex) { return (result === null) ? null : result[1]; } -function mapReject(arr, mapFunc, rejectFunc = _.isNull) { - return _.reject(arr.map(mapFunc), rejectFunc); +function mapReject(arr, mapFunc) { + return arr.map(mapFunc).filter(Boolean); } function updateColorVars(color: string) { From 553a48b6ce50a107e36e16f9914e723717b82c96 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 1 Mar 2024 01:20:12 -0300 Subject: [PATCH 02/30] FakeNitro: Fix hyperlink text setting for stickers --- src/plugins/fakeNitro/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 1cf1e536f..5d662feed 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -162,7 +162,7 @@ const settings = definePluginSettings({ default: true }, hyperLinkText: { - description: "What text the hyperlink should use. {{NAME}} will be replaced with the emoji name.", + description: "What text the hyperlink should use. {{NAME}} will be replaced with the emoji/sticker name.", type: OptionType.STRING, default: "{{NAME}}" } @@ -864,7 +864,9 @@ export default definePlugin({ const url = new URL(link); url.searchParams.set("name", sticker.name); - messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}${s.useHyperLinks ? `[${sticker.name}](${url})` : url}`; + const linkText = s.hyperLinkText.replaceAll("{{NAME}}", sticker.name); + + messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}${s.useHyperLinks ? `[${linkText}](${url})` : url}`; extra.stickers!.length = 0; } } From 23ff82fa627807fcbcdea03e88b3f21f65d007ca Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 1 Mar 2024 05:33:20 -0300 Subject: [PATCH 03/30] FakeNitro: Remove extra space in modal --- src/plugins/fakeNitro/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 5d662feed..8cf2492a8 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -791,8 +791,8 @@ export default definePlugin({ title: "Hold on!", body:
- You are trying to send/edit a message that contains a FakeNitro emoji or sticker - , however you do not have permissions to embed links in the current channel. + You are trying to send/edit a message that contains a FakeNitro emoji or sticker, + however you do not have permissions to embed links in the current channel. Are you sure you want to send this message? Your FakeNitro items will appear as a link only. From 4f0c0a12dc1832fbff3a71e05b0eb503e6b9c9c2 Mon Sep 17 00:00:00 2001 From: dolfies Date: Tue, 5 Mar 2024 17:38:49 -0500 Subject: [PATCH 04/30] feat(plugin): ResurrectHome (#2232) --- src/plugins/resurrectHome/README.md | 5 ++ src/plugins/resurrectHome/index.tsx | 125 ++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 src/plugins/resurrectHome/README.md create mode 100644 src/plugins/resurrectHome/index.tsx diff --git a/src/plugins/resurrectHome/README.md b/src/plugins/resurrectHome/README.md new file mode 100644 index 000000000..215ef816c --- /dev/null +++ b/src/plugins/resurrectHome/README.md @@ -0,0 +1,5 @@ +# ResurrectHome + +Brings back the phased out [Server Home](https://support.discord.com/hc/en-us/articles/6156116949911-Server-Home-Beta) feature! + +![](https://private-user-images.githubusercontent.com/47677887/309572891-a9ee7354-9e5e-4b81-8faf-304d9c44f512.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk0OTE5MTIsIm5iZiI6MTcwOTQ5MTYxMiwicGF0aCI6Ii80NzY3Nzg4Ny8zMDk1NzI4OTEtYTllZTczNTQtOWU1ZS00YjgxLThmYWYtMzA0ZDljNDRmNTEyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDMlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzAzVDE4NDY1MlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTBhYzUxMWY1MzQxNTA4NDE1MWU0YjAxNzM1NzI1YWJkMTNiZmNkNjRmYTRkZDg1ZDE5NzdkMjM0MGVjMDA0OWQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.TPYWPRWHTJstfviT9HOaBWFkbBhokyxiDC-gOVL2dqs) diff --git a/src/plugins/resurrectHome/index.tsx b/src/plugins/resurrectHome/index.tsx new file mode 100644 index 000000000..91ed87a02 --- /dev/null +++ b/src/plugins/resurrectHome/index.tsx @@ -0,0 +1,125 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 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 { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { ContextMenuApi, Menu } from "@webpack/common"; + +const settings = definePluginSettings({ + forceServerHome: { + type: OptionType.BOOLEAN, + description: "Force the Server Guide to be the Server Home tab when it is enabled.", + default: false + } +}); + +const contextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => { + if (!props?.guild) return; + + const group = findGroupChildrenByChildId("hide-muted-channels", children); + + group?.unshift( + { + settings.store.forceServerHome = !settings.store.forceServerHome; + ContextMenuApi.closeContextMenu(); + }} + /> + + ); +}; + +export default definePlugin({ + name: "ResurrectHome", + description: "Re-enables the Server Home tab when there isn't a Server Guide. Also has an option to force the Server Home over the Server Guide, which is accessible through right-clicking the Server Guide.", + authors: [Devs.Dolfies, Devs.Nuckyz], + settings, + + patches: [ + // Force home deprecation override + { + find: "GuildFeatures.GUILD_HOME_DEPRECATION_OVERRIDE", + all: true, + replacement: [ + { + match: /\i\.hasFeature\(\i\.GuildFeatures\.GUILD_HOME_DEPRECATION_OVERRIDE\)/g, + replace: "true" + } + ], + }, + // Disable feedback prompts + { + find: "GuildHomeFeedbackExperiment.definition.id", + replacement: [ + { + match: /return{showFeedback:\i,setOnDismissedFeedback:(\i)}/, + replace: "return{showFeedback:false,setOnDismissedFeedback:$1}" + } + ] + }, + // This feature was never finished, so the patch is disabled + + // Enable guild feed render mode selector + // { + // find: "2022-01_home_feed_toggle", + // replacement: [ + // { + // match: /showSelector:!1/, + // replace: "showSelector:true" + // } + // ] + // }, + + // Fix focusMessage clearing previously cached messages and causing a loop when fetching messages around home messages + { + find: '"MessageActionCreators"', + replacement: { + match: /(?<=focusMessage\(\i\){.+?)(?=focus:{messageId:(\i)})/, + replace: "before:$1," + } + }, + // Force Server Home instead of Server Guide + { + find: "61eef9_2", + replacement: { + match: /(?<=getMutableGuildChannelsForGuild\(\i\)\);)(?=if\(null==\i\|\|)/, + replace: "if($self.useForceServerHome())return false;" + } + } + ], + + start() { + addContextMenuPatch("guild-context", contextMenuPatch); + }, + + stop() { + removeContextMenuPatch("guild-context", contextMenuPatch); + }, + + useForceServerHome() { + const { forceServerHome } = settings.use(["forceServerHome"]); + + return forceServerHome; + } +}); From 612fdf8952c8b3ad80e16552a146088deadecb53 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 6 Mar 2024 07:11:14 -0300 Subject: [PATCH 05/30] FakeNitro: Fix trimming wrong content --- src/plugins/fakeNitro/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 8cf2492a8..68560817c 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -585,13 +585,15 @@ export default definePlugin({ for (const [index, child] of children.entries()) children[index] = modifyChild(child); children = this.clearEmptyArrayItems(children); - this.trimContent(children); return children; }; try { - return modifyChildren(lodash.cloneDeep(content)); + const newContent = modifyChildren(lodash.cloneDeep(content)); + this.trimContent(newContent); + + return newContent; } catch (err) { new Logger("FakeNitro").error(err); return content; From 42a9fa2d47d2e0d0e4233436086e93f0b3dcb1af Mon Sep 17 00:00:00 2001 From: Kyuuhachi <1547062+Kyuuhachi@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:06:24 +0100 Subject: [PATCH 06/30] Refactor ContextMenuAPI (#2236) --- src/api/ContextMenu.ts | 53 ++++++++++++++++------- src/plugins/_api/contextMenu.ts | 6 +-- src/plugins/_core/settings.tsx | 12 ++--- src/plugins/biggerStreamPreview/index.tsx | 14 +++--- src/plugins/copyUserURLs/index.tsx | 15 +++---- src/plugins/emoteCloner/index.tsx | 18 +++----- src/plugins/imageZoom/index.tsx | 23 +++++----- src/plugins/index.ts | 17 +++++++- src/plugins/messageLogger/index.tsx | 13 +++--- src/plugins/permissionsViewer/index.tsx | 27 ++++-------- src/plugins/pinDms/contextMenus.tsx | 19 +++----- src/plugins/pinDms/index.tsx | 6 +-- src/plugins/resurrectHome/index.tsx | 52 ++++++++++------------ src/plugins/reverseImageSearch/index.tsx | 18 +++----- src/plugins/reviewDB/index.tsx | 13 +++--- src/plugins/searchReply/index.tsx | 19 ++++---- src/plugins/serverProfile/index.tsx | 14 +++--- src/plugins/translate/index.tsx | 9 ++-- src/plugins/unsuppressEmbeds/index.tsx | 15 +++---- src/plugins/vencordToolbox/index.tsx | 9 ++-- src/plugins/viewIcons/index.tsx | 17 +++----- src/plugins/viewRaw/index.tsx | 22 ++++------ src/plugins/voiceMessages/index.tsx | 45 +++++++++---------- src/utils/constants.ts | 4 ++ src/utils/types.ts | 5 +++ 25 files changed, 220 insertions(+), 245 deletions(-) diff --git a/src/api/ContextMenu.ts b/src/api/ContextMenu.ts index d66d98c4f..fdd4facf4 100644 --- a/src/api/ContextMenu.ts +++ b/src/api/ContextMenu.ts @@ -17,22 +17,20 @@ */ import { Logger } from "@utils/Logger"; +import { Menu, React } from "@webpack/common"; import type { ReactElement } from "react"; -type ContextMenuPatchCallbackReturn = (() => void) | void; /** * @param children The rendered context menu elements * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example - * @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates) */ -export type NavContextMenuPatchCallback = (children: Array, ...args: Array) => ContextMenuPatchCallbackReturn; +export type NavContextMenuPatchCallback = (children: Array, ...args: Array) => void; /** * @param navId The navId of the context menu being patched * @param children The rendered context menu elements * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example - * @returns A callback which is only ran once used to modify the context menu elements (Use to avoid duplicates) */ -export type GlobalContextMenuPatchCallback = (navId: string, children: Array, ...args: Array) => ContextMenuPatchCallbackReturn; +export type GlobalContextMenuPatchCallback = (navId: string, children: Array, ...args: Array) => void; const ContextMenuLogger = new Logger("ContextMenu"); @@ -93,14 +91,19 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba * @param id The id of the child. If an array is specified, all ids will be tried * @param children The context menu children */ -export function findGroupChildrenByChildId(id: string | string[], children: Array, _itemsArray?: Array): Array | null { +export function findGroupChildrenByChildId(id: string | string[], children: Array): Array | null { for (const child of children) { if (child == null) continue; + if (Array.isArray(child)) { + const found = findGroupChildrenByChildId(id, child); + if (found !== null) return found; + } + if ( (Array.isArray(id) && id.some(id => child.props?.id === id)) || child.props?.id === id - ) return _itemsArray ?? null; + ) return children; let nextChildren = child.props?.children; if (nextChildren) { @@ -109,7 +112,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra child.props.children = nextChildren; } - const found = findGroupChildrenByChildId(id, nextChildren, nextChildren); + const found = findGroupChildrenByChildId(id, nextChildren); if (found !== null) return found; } } @@ -126,9 +129,12 @@ interface ContextMenuProps { onClose: (callback: (...args: Array) => any) => void; } -const patchedMenus = new WeakSet(); +export function _usePatchContextMenu(props: ContextMenuProps) { + props = { + ...props, + children: cloneMenuChildren(props.children), + }; -export function _patchContextMenu(props: ContextMenuProps) { props.contextMenuApiArguments ??= []; const contextMenuPatches = navPatches.get(props.navId); @@ -137,8 +143,7 @@ export function _patchContextMenu(props: ContextMenuProps) { if (contextMenuPatches) { for (const patch of contextMenuPatches) { try { - const callback = patch(props.children, ...props.contextMenuApiArguments); - if (!patchedMenus.has(props)) callback?.(); + patch(props.children, ...props.contextMenuApiArguments); } catch (err) { ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err); } @@ -147,12 +152,30 @@ export function _patchContextMenu(props: ContextMenuProps) { for (const patch of globalPatches) { try { - const callback = patch(props.navId, props.children, ...props.contextMenuApiArguments); - if (!patchedMenus.has(props)) callback?.(); + patch(props.navId, props.children, ...props.contextMenuApiArguments); } catch (err) { ContextMenuLogger.error("Global patch errored,", err); } } - patchedMenus.add(props); + return props; +} + +function cloneMenuChildren(obj: ReactElement | Array | null) { + if (Array.isArray(obj)) { + return obj.map(cloneMenuChildren); + } + + if (React.isValidElement(obj)) { + obj = React.cloneElement(obj); + + if ( + obj?.props?.children && + (obj.type !== Menu.MenuControlItem || obj.type === Menu.MenuControlItem && obj.props.control != null) + ) { + obj.props.children = cloneMenuChildren(obj.props.children); + } + } + + return obj; } diff --git a/src/plugins/_api/contextMenu.ts b/src/plugins/_api/contextMenu.ts index 55fdf3eae..01619546d 100644 --- a/src/plugins/_api/contextMenu.ts +++ b/src/plugins/_api/contextMenu.ts @@ -22,15 +22,15 @@ import definePlugin from "@utils/types"; export default definePlugin({ name: "ContextMenuAPI", description: "API for adding/removing items to/from context menus.", - authors: [Devs.Nuckyz, Devs.Ven], + authors: [Devs.Nuckyz, Devs.Ven, Devs.Kyuuhachi], required: true, patches: [ { find: "♫ (つ。◕‿‿◕。)つ ♪", replacement: { - match: /let{navId:/, - replace: "Vencord.Api.ContextMenu._patchContextMenu(arguments[0]);$&" + match: /(?=let{navId:)(?<=function \i\((\i)\).+?)/, + replace: "$1=Vencord.Api.ContextMenu._usePatchContextMenu($1);" } }, { diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index 6f43b76a8..01220eb4e 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { addContextMenuPatch } from "@api/ContextMenu"; +import { findGroupChildrenByChildId } from "@api/ContextMenu"; import { Settings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; @@ -30,21 +30,21 @@ export default definePlugin({ authors: [Devs.Ven, Devs.Megu], required: true, - start() { + contextMenus: { // The settings shortcuts in the user settings cog context menu // read the elements from a hardcoded map which for obvious reason // doesn't contain our sections. This patches the actions of our // sections to manually use SettingsRouter (which only works on desktop // but the context menu is usually not available on mobile anyway) - addContextMenuPatch("user-settings-cog", children => () => { - const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any; + "user-settings-cog"(children) { + const section = findGroupChildrenByChildId("VencordSettings", children); section?.forEach(c => { const id = c?.props?.id; if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) { - c.props.action = () => SettingsRouter.open(id); + c!.props.action = () => SettingsRouter.open(id); } }); - }); + } }, patches: [{ diff --git a/src/plugins/biggerStreamPreview/index.tsx b/src/plugins/biggerStreamPreview/index.tsx index 40bbe30a8..8cca912bc 100644 --- a/src/plugins/biggerStreamPreview/index.tsx +++ b/src/plugins/biggerStreamPreview/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { ScreenshareIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import { openImageModal } from "@utils/discord"; @@ -60,7 +60,7 @@ export const handleViewPreview = async ({ guildId, channelId, ownerId }: Applica openImageModal(previewUrl); }; -export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => () => { +export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => { const stream = ApplicationStreamingStore.getAnyStreamForUser(userId); if (!stream) return; @@ -89,12 +89,8 @@ export default definePlugin({ name: "BiggerStreamPreview", description: "This plugin allows you to enlarge stream previews", authors: [Devs.phil], - start: () => { - addContextMenuPatch("user-context", userContextPatch); - addContextMenuPatch("stream-context", streamContextPatch); - }, - stop: () => { - removeContextMenuPatch("user-context", userContextPatch); - removeContextMenuPatch("stream-context", streamContextPatch); + contextMenus: { + "user-context": userContextPatch, + "stream-context": streamContextPatch } }); diff --git a/src/plugins/copyUserURLs/index.tsx b/src/plugins/copyUserURLs/index.tsx index 9f69674cf..7af8502db 100644 --- a/src/plugins/copyUserURLs/index.tsx +++ b/src/plugins/copyUserURLs/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { LinkIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; @@ -29,7 +29,7 @@ interface UserContextProps { user: User; } -const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => () => { +const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: UserContextProps) => { if (!user) return; children.push( @@ -46,12 +46,7 @@ export default definePlugin({ name: "CopyUserURLs", authors: [Devs.castdrian], description: "Adds a 'Copy User URL' option to the user context menu.", - - start() { - addContextMenuPatch("user-context", UserContextMenuPatch); - }, - - stop() { - removeContextMenuPatch("user-context", UserContextMenuPatch); - }, + contextMenus: { + "user-context": UserContextMenuPatch + } }); diff --git a/src/plugins/emoteCloner/index.tsx b/src/plugins/emoteCloner/index.tsx index 219ce435f..b25e1be27 100644 --- a/src/plugins/emoteCloner/index.tsx +++ b/src/plugins/emoteCloner/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { CheckedTextInput } from "@components/CheckedTextInput"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; @@ -312,7 +312,7 @@ function isGifUrl(url: string) { return new URL(url).pathname.endsWith(".gif"); } -const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => { +const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => { const { favoriteableId, itemHref, itemSrc, favoriteableType } = props ?? {}; if (!favoriteableId) return; @@ -341,7 +341,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) = findGroupChildrenByChildId("copy-link", children)?.push(menuItem); }; -const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => () => { +const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => { const { id, name, type } = props?.target?.dataset ?? {}; if (!id) return; @@ -363,14 +363,8 @@ export default definePlugin({ description: "Allows you to clone Emotes & Stickers to your own server (right click them)", tags: ["StickerCloner"], authors: [Devs.Ven, Devs.Nuckyz], - - start() { - addContextMenuPatch("message", messageContextMenuPatch); - addContextMenuPatch("expression-picker", expressionPickerPatch); - }, - - stop() { - removeContextMenuPatch("message", messageContextMenuPatch); - removeContextMenuPatch("expression-picker", expressionPickerPatch); + contextMenus: { + "message": messageContextMenuPatch, + "expression-picker": expressionPickerPatch } }); diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index 8d8b6726d..048c0ed5b 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -16,14 +16,14 @@ * along with this program. If not, see . */ -import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; import { debounce } from "@utils/debounce"; import definePlugin, { OptionType } from "@utils/types"; -import { ContextMenuApi, Menu, React, ReactDOM } from "@webpack/common"; +import { Menu, React, ReactDOM } from "@webpack/common"; import type { Root } from "react-dom/client"; import { Magnifier, MagnifierProps } from "./components/Magnifier"; @@ -80,25 +80,25 @@ export const settings = definePluginSettings({ }); -const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => { +const imageContextMenuPatch: NavContextMenuPatchCallback = children => { + const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]); + children.push( { - settings.store.square = !settings.store.square; - ContextMenuApi.closeContextMenu(); + settings.store.square = !square; }} /> { - settings.store.nearestNeighbour = !settings.store.nearestNeighbour; - ContextMenuApi.closeContextMenu(); + settings.store.nearestNeighbour = !nearestNeighbour; }} /> | null, @@ -245,7 +248,6 @@ export default definePlugin({ start() { enableStyle(styles); - addContextMenuPatch("image-context", imageContextMenuPatch); this.element = document.createElement("div"); this.element.classList.add("MagnifierContainer"); document.body.appendChild(this.element); @@ -256,6 +258,5 @@ export default definePlugin({ // so componenetWillUnMount gets called if Magnifier component is still alive this.root && this.root.unmount(); this.element?.remove(); - removeContextMenuPatch("image-context", imageContextMenuPatch); } }); diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 234838606..7092001ee 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -17,6 +17,7 @@ */ import { registerCommand, unregisterCommand } from "@api/Commands"; +import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu"; import { Settings } from "@api/Settings"; import { Logger } from "@utils/Logger"; import { Patch, Plugin, StartAt } from "@utils/types"; @@ -119,7 +120,7 @@ export function startDependenciesRecursive(p: Plugin) { } export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) { - const { name, commands, flux } = p; + const { name, commands, flux, contextMenus } = p; if (p.start) { logger.info("Starting plugin", name); @@ -154,11 +155,17 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p: } } + if (contextMenus) { + for (const navId in contextMenus) { + addContextMenuPatch(navId, contextMenus[navId]); + } + } + return true; }, p => `startPlugin ${p.name}`); export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) { - const { name, commands, flux } = p; + const { name, commands, flux, contextMenus } = p; if (p.stop) { logger.info("Stopping plugin", name); if (!p.started) { @@ -192,5 +199,11 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu } } + if (contextMenus) { + for (const navId in contextMenus) { + removeContextMenuPatch(navId, contextMenus[navId]); + } + } + return true; }, p => `stopPlugin ${p.name}`); diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index ef986bf87..8bc563b19 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -18,7 +18,7 @@ import "./messageLogger.css"; -import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { Settings } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; @@ -45,7 +45,7 @@ function addDeleteStyle() { const REMOVE_HISTORY_ID = "ml-remove-history"; const TOGGLE_DELETE_STYLE_ID = "ml-toggle-style"; -const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) => () => { +const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) => { const { message } = props; const { deleted, editHistory, id, channel_id } = message; @@ -94,13 +94,12 @@ export default definePlugin({ description: "Temporarily logs deleted and edited messages.", authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN], - start() { - addDeleteStyle(); - addContextMenuPatch("message", patchMessageContextMenu); + contextMenus: { + "message": patchMessageContextMenu }, - stop() { - removeContextMenuPatch("message", patchMessageContextMenu); + start() { + addDeleteStyle(); }, renderEdit(edit: { timestamp: any, content: string; }) { diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx index 9e0131e64..07f073d63 100644 --- a/src/plugins/permissionsViewer/index.tsx +++ b/src/plugins/permissionsViewer/index.tsx @@ -18,7 +18,7 @@ import "./styles.css"; -import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; @@ -125,10 +125,10 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { } function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback { - return (children, props) => () => { + return (children, props) => { if (!props) return; if ((type === MenuItemParentType.User && !props.user) || (type === MenuItemParentType.Guild && !props.guild) || (type === MenuItemParentType.Channel && (!props.channel || !props.guild))) - return children; + return; const group = findGroupChildrenByChildId(childId, children); @@ -173,19 +173,10 @@ export default definePlugin({ UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBoder: boolean) => !!guildMember && , - userContextMenuPatch: makeContextMenuPatch("roles", MenuItemParentType.User), - channelContextMenuPatch: makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel), - guildContextMenuPatch: makeContextMenuPatch("privacy", MenuItemParentType.Guild), - - start() { - addContextMenuPatch("user-context", this.userContextMenuPatch); - addContextMenuPatch("channel-context", this.channelContextMenuPatch); - addContextMenuPatch(["guild-context", "guild-header-popout"], this.guildContextMenuPatch); - }, - - stop() { - removeContextMenuPatch("user-context", this.userContextMenuPatch); - removeContextMenuPatch("channel-context", this.channelContextMenuPatch); - removeContextMenuPatch(["guild-context", "guild-header-popout"], this.guildContextMenuPatch); - }, + contextMenus: { + "user-context": makeContextMenuPatch("roles", MenuItemParentType.User), + "channel-context": makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel), + "guild-context": makeContextMenuPatch("privacy", MenuItemParentType.Guild), + "guild-header-popout": makeContextMenuPatch("privacy", MenuItemParentType.Guild) + } }); diff --git a/src/plugins/pinDms/contextMenus.tsx b/src/plugins/pinDms/contextMenus.tsx index 7d89ec123..1db8b25a9 100644 --- a/src/plugins/pinDms/contextMenus.tsx +++ b/src/plugins/pinDms/contextMenus.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { Menu } from "@webpack/common"; import { isPinned, movePin, PinOrder, settings, snapshotArray, togglePin } from "./settings"; @@ -50,13 +50,13 @@ function PinMenuItem(channelId: string) { ); } -const GroupDMContext: NavContextMenuPatchCallback = (children, props) => () => { +const GroupDMContext: NavContextMenuPatchCallback = (children, props) => { const container = findGroupChildrenByChildId("leave-channel", children); if (container) container.unshift(PinMenuItem(props.channel.id)); }; -const UserContext: NavContextMenuPatchCallback = (children, props) => () => { +const UserContext: NavContextMenuPatchCallback = (children, props) => { const container = findGroupChildrenByChildId("close-dm", children); if (container) { const idx = container.findIndex(c => c?.props?.id === "close-dm"); @@ -64,12 +64,7 @@ const UserContext: NavContextMenuPatchCallback = (children, props) => () => { } }; -export function addContextMenus() { - addContextMenuPatch("gdm-context", GroupDMContext); - addContextMenuPatch("user-context", UserContext); -} - -export function removeContextMenus() { - removeContextMenuPatch("gdm-context", GroupDMContext); - removeContextMenuPatch("user-context", UserContext); -} +export const contextMenus = { + "gdm-context": GroupDMContext, + "user-context": UserContext +}; diff --git a/src/plugins/pinDms/index.tsx b/src/plugins/pinDms/index.tsx index 792bdab6a..943f0f1b1 100644 --- a/src/plugins/pinDms/index.tsx +++ b/src/plugins/pinDms/index.tsx @@ -20,7 +20,7 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { Channel } from "discord-types/general"; -import { addContextMenus, removeContextMenus } from "./contextMenus"; +import { contextMenus } from "./contextMenus"; import { getPinAt, isPinned, settings, snapshotArray, sortedSnapshot, usePinnedDms } from "./settings"; export default definePlugin({ @@ -29,9 +29,7 @@ export default definePlugin({ authors: [Devs.Ven, Devs.Strencher], settings, - - start: addContextMenus, - stop: removeContextMenus, + contextMenus, usePinCount(channelIds: string[]) { const pinnedDms = usePinnedDms(); diff --git a/src/plugins/resurrectHome/index.tsx b/src/plugins/resurrectHome/index.tsx index 91ed87a02..6b0069a7f 100644 --- a/src/plugins/resurrectHome/index.tsx +++ b/src/plugins/resurrectHome/index.tsx @@ -16,11 +16,11 @@ * along with this program. If not, see . */ -import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { findGroupChildrenByChildId } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { ContextMenuApi, Menu } from "@webpack/common"; +import { Menu } from "@webpack/common"; const settings = definePluginSettings({ forceServerHome: { @@ -30,25 +30,11 @@ const settings = definePluginSettings({ } }); -const contextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => { - if (!props?.guild) return; +function useForceServerHome() { + const { forceServerHome } = settings.use(["forceServerHome"]); - const group = findGroupChildrenByChildId("hide-muted-channels", children); - - group?.unshift( - { - settings.store.forceServerHome = !settings.store.forceServerHome; - ContextMenuApi.closeContextMenu(); - }} - /> - - ); -}; + return forceServerHome; +} export default definePlugin({ name: "ResurrectHome", @@ -109,17 +95,25 @@ export default definePlugin({ } ], - start() { - addContextMenuPatch("guild-context", contextMenuPatch); - }, + useForceServerHome, - stop() { - removeContextMenuPatch("guild-context", contextMenuPatch); - }, + contextMenus: { + "guild-context"(children, props) { + const forceServerHome = useForceServerHome(); - useForceServerHome() { - const { forceServerHome } = settings.use(["forceServerHome"]); + if (!props?.guild) return; - return forceServerHome; + const group = findGroupChildrenByChildId("hide-muted-channels", children); + + group?.unshift( + settings.store.forceServerHome = !forceServerHome} + /> + ); + } } }); diff --git a/src/plugins/reverseImageSearch/index.tsx b/src/plugins/reverseImageSearch/index.tsx index 6c5f3e729..415dc13d8 100644 --- a/src/plugins/reverseImageSearch/index.tsx +++ b/src/plugins/reverseImageSearch/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { Flex } from "@components/Flex"; import { OpenExternalIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; @@ -84,7 +84,7 @@ function makeSearchItem(src: string) { ); } -const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => { +const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => { if (props?.reverseImageSearchType !== "img") return; const src = props.itemHref ?? props.itemSrc; @@ -93,7 +93,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) = group?.push(makeSearchItem(src)); }; -const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => () => { +const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => { if (!props?.src) return; const group = findGroupChildrenByChildId("copy-native-link", children) ?? children; @@ -115,14 +115,8 @@ export default definePlugin({ } } ], - - start() { - addContextMenuPatch("message", messageContextMenuPatch); - addContextMenuPatch("image-context", imageContextMenuPatch); - }, - - stop() { - removeContextMenuPatch("message", messageContextMenuPatch); - removeContextMenuPatch("image-context", imageContextMenuPatch); + contextMenus: { + "message": messageContextMenuPatch, + "image-context": imageContextMenuPatch } }); diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index 50bb62184..ad24e9696 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -18,7 +18,7 @@ import "./style.css"; -import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import ErrorBoundary from "@components/ErrorBoundary"; import ExpandableHeader from "@components/ExpandableHeader"; import { OpenExternalIcon } from "@components/Icons"; @@ -36,7 +36,7 @@ import { getCurrentUserInfo, readNotification } from "./reviewDbApi"; import { settings } from "./settings"; import { showToast } from "./utils"; -const guildPopoutPatch: NavContextMenuPatchCallback = (children, props: { guild: Guild, onClose(): void; }) => () => { +const guildPopoutPatch: NavContextMenuPatchCallback = (children, props: { guild: Guild, onClose(): void; }) => { children.push( { const [reviewCount, setReviewCount] = useState(); diff --git a/src/plugins/searchReply/index.tsx b/src/plugins/searchReply/index.tsx index b151712af..35b197874 100644 --- a/src/plugins/searchReply/index.tsx +++ b/src/plugins/searchReply/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { ReplyIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; @@ -27,7 +27,7 @@ import { Message } from "discord-types/general"; const messageUtils = findByPropsLazy("replyToMessage"); -const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => () => { +const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => { // make sure the message is in the selected channel if (SelectedChannelStore.getChannelId() !== message.channel_id) return; const channel = ChannelStore.getChannel(message?.channel_id); @@ -38,7 +38,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { messag const dmGroup = findGroupChildrenByChildId("pin", children); if (dmGroup && !dmGroup.some(child => child?.props?.id === "reply")) { const pinIndex = dmGroup.findIndex(c => c?.props.id === "pin"); - return dmGroup.splice(pinIndex + 1, 0, ( + dmGroup.splice(pinIndex + 1, 0, ( messageUtils.replyToMessage(channel, message, e)} /> )); + return; } // servers const serverGroup = findGroupChildrenByChildId("mark-unread", children); if (serverGroup && !serverGroup.some(child => child?.props?.id === "reply")) { - return serverGroup.unshift(( + serverGroup.unshift(( messageUtils.replyToMessage(channel, message, e)} /> )); + return; } }; @@ -67,12 +69,7 @@ export default definePlugin({ name: "SearchReply", description: "Adds a reply button to search results", authors: [Devs.Aria], - - start() { - addContextMenuPatch("message", messageContextMenuPatch); - }, - - stop() { - removeContextMenuPatch("message", messageContextMenuPatch); + contextMenus: { + "message": messageContextMenuPatch } }); diff --git a/src/plugins/serverProfile/index.tsx b/src/plugins/serverProfile/index.tsx index 68f6193cc..9d495c9d3 100644 --- a/src/plugins/serverProfile/index.tsx +++ b/src/plugins/serverProfile/index.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { Menu } from "@webpack/common"; @@ -12,7 +12,7 @@ import { Guild } from "discord-types/general"; import { openGuildProfileModal } from "./GuildProfileModal"; -const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => () => { +const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => { const group = findGroupChildrenByChildId("privacy", children); group?.push( @@ -29,12 +29,8 @@ export default definePlugin({ description: "Allows you to view info about a server by right clicking it in the server list", authors: [Devs.Ven, Devs.Nuckyz], tags: ["guild", "info"], - - start() { - addContextMenuPatch(["guild-context", "guild-header-popout"], Patch); - }, - - stop() { - removeContextMenuPatch(["guild-context", "guild-header-popout"], Patch); + contextMenus: { + "guild-context": Patch, + "guild-header-popout": Patch } }); diff --git a/src/plugins/translate/index.tsx b/src/plugins/translate/index.tsx index 702e60cf7..f602d1255 100644 --- a/src/plugins/translate/index.tsx +++ b/src/plugins/translate/index.tsx @@ -19,7 +19,7 @@ import "./styles.css"; import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons"; -import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addAccessory, removeAccessory } from "@api/MessageAccessories"; import { addPreSendListener, removePreSendListener } from "@api/MessageEvents"; import { addButton, removeButton } from "@api/MessagePopover"; @@ -32,7 +32,7 @@ import { TranslateChatBarIcon, TranslateIcon } from "./TranslateIcon"; import { handleTranslate, TranslationAccessory } from "./TranslationAccessory"; import { translate } from "./utils"; -const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => () => { +const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => { if (!message.content) return; const group = findGroupChildrenByChildId("copy-text", children); @@ -57,13 +57,15 @@ export default definePlugin({ authors: [Devs.Ven], dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"], settings, + contextMenus: { + "message": messageCtxPatch + }, // not used, just here in case some other plugin wants it or w/e translate, start() { addAccessory("vc-translation", props => ); - addContextMenuPatch("message", messageCtxPatch); addChatBarButton("vc-translate", TranslateChatBarIcon); addButton("vc-translate", message => { @@ -91,7 +93,6 @@ export default definePlugin({ stop() { removePreSendListener(this.preSend); - removeContextMenuPatch("message", messageCtxPatch); removeChatBarButton("vc-translate"); removeButton("vc-translate"); removeAccessory("vc-translation"); diff --git a/src/plugins/unsuppressEmbeds/index.tsx b/src/plugins/unsuppressEmbeds/index.tsx index a21960774..0e87201c6 100644 --- a/src/plugins/unsuppressEmbeds/index.tsx +++ b/src/plugins/unsuppressEmbeds/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { ImageInvisible, ImageVisible } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; @@ -24,7 +24,7 @@ import { Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@web const EMBED_SUPPRESSED = 1 << 2; -const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => () => { +const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => { const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0; if (!isEmbedSuppressed && !embeds.length) return; @@ -56,12 +56,7 @@ export default definePlugin({ name: "UnsuppressEmbeds", authors: [Devs.rad, Devs.HypedDomi], description: "Allows you to unsuppress embeds in messages", - - start() { - addContextMenuPatch("message", messageContextMenuPatch); - }, - - stop() { - removeContextMenuPatch("message", messageContextMenuPatch); - }, + contextMenus: { + "message": messageContextMenuPatch + } }); diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index 0a805a0d2..ba295fa72 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -19,7 +19,7 @@ import "./index.css"; import { openNotificationLogModal } from "@api/Notifications/notificationLog"; -import { Settings } from "@api/Settings"; +import { Settings, useSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; @@ -30,6 +30,8 @@ import type { ReactNode } from "react"; const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider"); function VencordPopout(onClose: () => void) { + const { useQuickCss } = useSettings(); + const pluginEntries = [] as ReactNode[]; for (const plugin of Object.values(Vencord.Plugins.plugins)) { @@ -68,11 +70,10 @@ function VencordPopout(onClose: () => void) { /> { - Settings.useQuickCss = !Settings.useQuickCss; - onClose(); + Settings.useQuickCss = !useQuickCss; }} /> . */ -import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; import { ImageIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; @@ -80,7 +80,7 @@ function openImage(url: string) { }); } -const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => () => { +const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: UserContextProps) => { if (!user) return; const memberAvatar = GuildMemberStore.getMember(guildId!, user.id)?.avatar || null; @@ -109,7 +109,7 @@ const UserContext: NavContextMenuPatchCallback = (children, { user, guildId }: U )); }; -const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildContextProps) => () => { +const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildContextProps) => { if (!guild) return; const { id, icon, banner } = guild; @@ -155,14 +155,9 @@ export default definePlugin({ openImage, - start() { - addContextMenuPatch("user-context", UserContext); - addContextMenuPatch("guild-context", GuildContext); - }, - - stop() { - removeContextMenuPatch("user-context", UserContext); - removeContextMenuPatch("guild-context", GuildContext); + contextMenus: { + "user-context": UserContext, + "guild-context": GuildContext }, patches: [ diff --git a/src/plugins/viewRaw/index.tsx b/src/plugins/viewRaw/index.tsx index 08acdc4c5..68b33eed0 100644 --- a/src/plugins/viewRaw/index.tsx +++ b/src/plugins/viewRaw/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { addButton, removeButton } from "@api/MessagePopover"; import { definePluginSettings } from "@api/Settings"; import { CodeBlock } from "@components/CodeBlock"; @@ -117,8 +117,8 @@ const settings = definePluginSettings({ } }); -function MakeContextCallback(name: "Guild" | "User" | "Channel") { - const callback: NavContextMenuPatchCallback = (children, props) => () => { +function MakeContextCallback(name: "Guild" | "User" | "Channel"): NavContextMenuPatchCallback { + return (children, props) => { const value = props[name.toLowerCase()]; if (!value) return; if (props.label === i18n.Messages.CHANNEL_ACTIONS_MENU_LABEL) return; // random shit like notification settings @@ -141,16 +141,19 @@ function MakeContextCallback(name: "Guild" | "User" | "Channel") { /> ); }; - return callback; } - export default definePlugin({ name: "ViewRaw", description: "Copy and view the raw content/data of any message, channel or guild", authors: [Devs.KingFish, Devs.Ven, Devs.rad, Devs.ImLvna], dependencies: ["MessagePopoverAPI"], settings, + contextMenus: { + "guild-context": MakeContextCallback("Guild"), + "channel-context": MakeContextCallback("Channel"), + "user-context": MakeContextCallback("User") + }, start() { addButton("ViewRaw", msg => { @@ -187,16 +190,9 @@ export default definePlugin({ onContextMenu: handleContextMenu }; }); - - addContextMenuPatch("guild-context", MakeContextCallback("Guild")); - addContextMenuPatch("channel-context", MakeContextCallback("Channel")); - addContextMenuPatch("user-context", MakeContextCallback("User")); }, stop() { - removeButton("CopyRawMessage"); - removeContextMenuPatch("guild-context", MakeContextCallback("Guild")); - removeContextMenuPatch("channel-context", MakeContextCallback("Channel")); - removeContextMenuPatch("user-context", MakeContextCallback("User")); + removeButton("ViewRaw"); } }); diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx index 2393ef2b6..2f232f341 100644 --- a/src/plugins/voiceMessages/index.tsx +++ b/src/plugins/voiceMessages/index.tsx @@ -18,7 +18,7 @@ import "./styles.css"; -import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { Microphone } from "@components/Icons"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; @@ -48,18 +48,30 @@ export type VoiceRecorder = ComponentType<{ const VoiceRecorder = IS_DISCORD_DESKTOP ? VoiceRecorderDesktop : VoiceRecorderWeb; +const ctxMenuPatch: NavContextMenuPatchCallback = (children, props) => { + if (props.channel.guild_id && !(PermissionStore.can(PermissionsBits.SEND_VOICE_MESSAGES, props.channel) && PermissionStore.can(PermissionsBits.SEND_MESSAGES, props.channel))) return; + + children.push( + + +
Send voice message
+
+ } + action={() => openModal(modalProps => )} + /> + ); +}; + export default definePlugin({ name: "VoiceMessages", description: "Allows you to send voice messages like on mobile. To do so, right click the upload button and click Send Voice Message", authors: [Devs.Ven, Devs.Vap, Devs.Nickyux], settings, - - start() { - addContextMenuPatch("channel-attach", ctxMenuPatch); - }, - - stop() { - removeContextMenuPatch("channel-attach", ctxMenuPatch); + contextMenus: { + "channel-attach": ctxMenuPatch } }); @@ -234,20 +246,3 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) { ); } - -const ctxMenuPatch: NavContextMenuPatchCallback = (children, props) => () => { - if (props.channel.guild_id && !(PermissionStore.can(PermissionsBits.SEND_VOICE_MESSAGES, props.channel) && PermissionStore.can(PermissionsBits.SEND_MESSAGES, props.channel))) return; - - children.push( - - -
Send voice message
- - } - action={() => openModal(modalProps => )} - /> - ); -}; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index d66bdc826..d213ce272 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -418,6 +418,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ Av32000: { name: "Av32000", id: 593436735380127770n, + }, + Kyuuhachi: { + name: "Kyuuhachi", + id: 236588665420251137n, } } satisfies Record); diff --git a/src/utils/types.ts b/src/utils/types.ts index 16867a43c..bec7cb0b3 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -17,6 +17,7 @@ */ import { Command } from "@api/Commands"; +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { FluxEvents } from "@webpack/types"; import { Promisable } from "type-fest"; @@ -115,6 +116,10 @@ export interface PluginDef { flux?: { [E in FluxEvents]?: (event: any) => void; }; + /** + * Allows you to manipulate context menus + */ + contextMenus?: Record; /** * Allows you to add custom actions to the Vencord Toolbox. * The key will be used as text for the button From 980206d31568e479f0f0c7a32ab24035374f06d5 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 7 Mar 2024 09:36:59 -0300 Subject: [PATCH 07/30] Fix waitFor initial finds traces getting logged to the console even though they always fail --- src/webpack/webpack.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index a68890a83..0f7d8b73c 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -475,8 +475,10 @@ export function waitFor(filter: string | string[] | FilterFn, callback: Callback else if (typeof filter !== "function") throw new Error("filter must be a string, string[] or function, got " + typeof filter); - const [existing, id] = find(filter!, { isIndirect: true, isWaitFor: true }); - if (existing) return void callback(existing, id); + if (cache != null) { + const [existing, id] = find(filter, { isIndirect: true, isWaitFor: true }); + if (existing) return void callback(existing, id); + } subscriptions.set(filter, callback); } From a59c14f9aa8034fcb3df45af855a7a57c2882b82 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Thu, 7 Mar 2024 19:51:14 +0700 Subject: [PATCH 08/30] CustomRPC: Change timestamp to milisecond (#2231) --- src/plugins/customRPC/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index 3653a0776..e70f8c908 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -175,7 +175,7 @@ const settings = definePluginSettings({ }, startTime: { type: OptionType.NUMBER, - description: "Start timestamp (only for custom timestamp mode)", + description: "Start timestamp in milisecond (only for custom timestamp mode)", onChange: onChange, disabled: isTimestampDisabled, isValid: (value: number) => { @@ -185,7 +185,7 @@ const settings = definePluginSettings({ }, endTime: { type: OptionType.NUMBER, - description: "End timestamp (only for custom timestamp mode)", + description: "End timestamp in milisecond (only for custom timestamp mode)", onChange: onChange, disabled: isTimestampDisabled, isValid: (value: number) => { @@ -313,12 +313,12 @@ async function createActivity(): Promise { switch (settings.store.timestampMode) { case TimestampMode.NOW: activity.timestamps = { - start: Math.floor(Date.now() / 1000) + start: Date.now() }; break; case TimestampMode.TIME: activity.timestamps = { - start: Math.floor(Date.now() / 1000) - (new Date().getHours() * 3600) - (new Date().getMinutes() * 60) - new Date().getSeconds() + start: Date.now() - (new Date().getHours() * 3600 + new Date().getMinutes() * 60 + new Date().getSeconds()) * 1000 }; break; case TimestampMode.CUSTOM: From f70114238c1307e086db537640926a0db78adb10 Mon Sep 17 00:00:00 2001 From: Sam <149597648+cheesesamwich@users.noreply.github.com> Date: Thu, 7 Mar 2024 12:55:51 +0000 Subject: [PATCH 09/30] MemberCount: Add options to choose where the member count will be displayed (#2224) --- src/plugins/memberCount/index.tsx | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx index eb4ce372c..92e9a2057 100644 --- a/src/plugins/memberCount/index.tsx +++ b/src/plugins/memberCount/index.tsx @@ -18,10 +18,11 @@ import "./style.css"; +import { definePluginSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; import { findStoreLazy } from "@webpack"; import { FluxStore } from "@webpack/types"; @@ -32,6 +33,21 @@ export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxSto getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; }; }; +const settings = definePluginSettings({ + toolTip: { + type: OptionType.BOOLEAN, + description: "If the member count should be displayed on the server tooltip", + default: true, + restartNeeded: true + }, + memberList: { + type: OptionType.BOOLEAN, + description: "If the member count should be displayed on the member list", + default: true, + restartNeeded: true + } +}); + const sharedIntlNumberFormat = new Intl.NumberFormat(); export const numberFormat = (value: number) => sharedIntlNumberFormat.format(value); export const cl = classNameFactory("vc-membercount-"); @@ -40,6 +56,7 @@ export default definePlugin({ name: "MemberCount", description: "Shows the amount of online & total members in the server member list and tooltip", authors: [Devs.Ven, Devs.Commandtechno], + settings, patches: [ { @@ -47,17 +64,18 @@ export default definePlugin({ replacement: { match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, replace: ":[$1?.startsWith('members')?$self.render():null,$2" - } + }, + predicate: () => settings.store.memberList }, { find: ".invitesDisabledTooltip", replacement: { match: /(?<=\.VIEW_AS_ROLES_MENTIONS_WARNING.{0,100})]/, replace: ",$self.renderTooltip(arguments[0].guild)]" - } + }, + predicate: () => settings.store.toolTip } ], - render: ErrorBoundary.wrap(MemberCount, { noop: true }), renderTooltip: ErrorBoundary.wrap(guild => , { noop: true }) }); From 19799767adef192a8451adf0f8c1710933b21a41 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 7 Mar 2024 10:09:04 -0300 Subject: [PATCH 10/30] Fix trying to check for updates if origin doesn't have same branch --- src/main/updater/git.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/updater/git.ts b/src/main/updater/git.ts index 2ff3ba512..20a5d7003 100644 --- a/src/main/updater/git.ts +++ b/src/main/updater/git.ts @@ -49,9 +49,12 @@ async function getRepo() { async function calculateGitChanges() { await git("fetch"); - const branch = await git("branch", "--show-current"); + const branch = (await git("branch", "--show-current")).stdout.trim(); - const res = await git("log", `HEAD...origin/${branch.stdout.trim()}`, "--pretty=format:%an/%h/%s"); + const existsOnOrigin = (await git("ls-remote", "origin", branch)).stdout.length > 0; + if (!existsOnOrigin) return []; + + const res = await git("log", `HEAD...origin/${branch}`, "--pretty=format:%an/%h/%s"); const commits = res.stdout.trim(); return commits ? commits.split("\n").map(line => { From 102842d5288fd22821e2a6eb295255fd88603964 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:33:00 -0300 Subject: [PATCH 11/30] Close Ipc FS watchers if window is closed --- src/main/ipcMain.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/ipcMain.ts b/src/main/ipcMain.ts index 47d400eb6..3ac8a14c5 100644 --- a/src/main/ipcMain.ts +++ b/src/main/ipcMain.ts @@ -23,7 +23,7 @@ import { debounce } from "@utils/debounce"; import { IpcEvents } from "@utils/IpcEvents"; import { Queue } from "@utils/Queue"; import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron"; -import { mkdirSync, readFileSync, watch } from "fs"; +import { FSWatcher, mkdirSync, readFileSync, watch } from "fs"; import { open, readdir, readFile, writeFile } from "fs/promises"; import { join, normalize } from "path"; @@ -126,16 +126,23 @@ ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => { export function initIpc(mainWindow: BrowserWindow) { + let quickCssWatcher: FSWatcher | undefined; + open(QUICKCSS_PATH, "a+").then(fd => { fd.close(); - watch(QUICKCSS_PATH, { persistent: false }, debounce(async () => { + quickCssWatcher = watch(QUICKCSS_PATH, { persistent: false }, debounce(async () => { mainWindow.webContents.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss()); }, 50)); - }); + }).catch(() => { }); - watch(THEMES_DIR, { persistent: false }, debounce(() => { + const themesWatcher = watch(THEMES_DIR, { persistent: false }, debounce(() => { mainWindow.webContents.postMessage(IpcEvents.THEME_UPDATE, void 0); })); + + mainWindow.once("closed", () => { + quickCssWatcher?.close(); + themesWatcher.close(); + }); } ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => { From 1c1d82f9a820ce4b7611032488624d98624a7ecb Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:06:08 -0300 Subject: [PATCH 12/30] VencordToolbox: don't subscribe to all settings Also remove one indirection from useSettings --- src/api/Settings.ts | 14 ++++++++------ src/plugins/vencordToolbox/index.tsx | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 004a8988b..c1ff6915b 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -223,13 +223,13 @@ export const Settings = makeProxy(settings); export function useSettings(paths?: UseSettings[]) { const [, forceUpdate] = React.useReducer(() => ({}), {}); - const onUpdate: SubscriptionCallback = paths - ? (value, path) => paths.includes(path as UseSettings) && forceUpdate() - : forceUpdate; + if (paths) { + (forceUpdate as SubscriptionCallback)._paths = paths; + } React.useEffect(() => { - subscriptions.add(onUpdate); - return () => void subscriptions.delete(onUpdate); + subscriptions.add(forceUpdate); + return () => void subscriptions.delete(forceUpdate); }, []); return Settings; @@ -253,8 +253,10 @@ type ResolvePropDeep = P extends "" ? T : export function addSettingsListener(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void): void; export function addSettingsListener(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep, path: Path extends "" ? string : Path) => void): void; export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) { - if (path) + if (path) { ((onUpdate as SubscriptionCallback)._paths ??= []).push(path); + } + subscriptions.add(onUpdate); } diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx index ba295fa72..00805fbd3 100644 --- a/src/plugins/vencordToolbox/index.tsx +++ b/src/plugins/vencordToolbox/index.tsx @@ -30,7 +30,7 @@ import type { ReactNode } from "react"; const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider"); function VencordPopout(onClose: () => void) { - const { useQuickCss } = useSettings(); + const { useQuickCss } = useSettings(["useQuickCss"]); const pluginEntries = [] as ReactNode[]; From 2e90d4c03d98b9e5ea1dd8505cb0740d90b051d0 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 7 Mar 2024 19:53:26 +0100 Subject: [PATCH 13/30] New plugin: BetterRoleContext ~ edit/copy colour shortcuts in profile --- src/plugins/betterRoleContext/README.md | 6 ++ src/plugins/betterRoleContext/index.tsx | 79 ++++++++++++++++++++++ src/webpack/common/settingsStores.ts | 7 +- src/webpack/common/types/index.d.ts | 4 +- src/webpack/common/types/settingsStores.ts | 11 +++ 5 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 src/plugins/betterRoleContext/README.md create mode 100644 src/plugins/betterRoleContext/index.tsx create mode 100644 src/webpack/common/types/settingsStores.ts diff --git a/src/plugins/betterRoleContext/README.md b/src/plugins/betterRoleContext/README.md new file mode 100644 index 000000000..3f3086bdb --- /dev/null +++ b/src/plugins/betterRoleContext/README.md @@ -0,0 +1,6 @@ +# BetterRoleContext + +Adds options to copy role color and edit role when right clicking roles in the user profile + +![](https://github.com/Vendicated/Vencord/assets/45497981/d1765e9e-7db2-4a3c-b110-139c59235326) + diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx new file mode 100644 index 000000000..7a914293a --- /dev/null +++ b/src/plugins/betterRoleContext/index.tsx @@ -0,0 +1,79 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import { getCurrentGuild } from "@utils/discord"; +import definePlugin from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { Clipboard, Menu, PermissionStore, TextAndImagesSettingsStores } from "@webpack/common"; + +const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild"); + +function PencilIcon() { + return ( + + + + ); +} + +function AppearanceIcon() { + return ( + + + + ); +} + +export default definePlugin({ + name: "BetterRoleContext", + description: "Adds options to copy role color / edit role when right clicking roles in the user profile", + authors: [Devs.Ven], + + start() { + // DeveloperMode needs to be enabled for the context menu to be shown + TextAndImagesSettingsStores.DeveloperMode.updateSetting(true); + }, + + contextMenus: { + "dev-context"(children, { id }: { id: string; }) { + const guild = getCurrentGuild(); + const role = guild?.roles[id]; + if (!role) return; + + if (role.colorString) { + children.push( + Clipboard.copy(role.colorString!)} + icon={AppearanceIcon} + /> + ); + } + + if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) { + children.push( + { + await GuildSettingsActions.open(guild.id, "ROLES"); + GuildSettingsActions.selectRole(id); + }} + icon={PencilIcon} + /> + ); + } + } + } +}); diff --git a/src/webpack/common/settingsStores.ts b/src/webpack/common/settingsStores.ts index 6db21949a..4a48efda6 100644 --- a/src/webpack/common/settingsStores.ts +++ b/src/webpack/common/settingsStores.ts @@ -6,7 +6,10 @@ import { findByPropsLazy } from "@webpack"; -export const TextAndImagesSettingsStores = findByPropsLazy("MessageDisplayCompact"); -export const StatusSettingsStores = findByPropsLazy("ShowCurrentGame"); +import * as t from "./types/settingsStores"; + + +export const TextAndImagesSettingsStores = findByPropsLazy("MessageDisplayCompact") as Record; +export const StatusSettingsStores = findByPropsLazy("ShowCurrentGame") as Record; export const UserSettingsActionCreators = findByPropsLazy("PreloadedUserSettingsActionCreators"); diff --git a/src/webpack/common/types/index.d.ts b/src/webpack/common/types/index.d.ts index af4b5e1fb..01c968553 100644 --- a/src/webpack/common/types/index.d.ts +++ b/src/webpack/common/types/index.d.ts @@ -16,9 +16,11 @@ * along with this program. If not, see . */ +export * from "./classes"; export * from "./components"; export * from "./fluxEvents"; +export * from "./i18nMessages"; export * from "./menu"; +export * from "./settingsStores"; export * from "./stores"; export * from "./utils"; - diff --git a/src/webpack/common/types/settingsStores.ts b/src/webpack/common/types/settingsStores.ts new file mode 100644 index 000000000..5453ca352 --- /dev/null +++ b/src/webpack/common/types/settingsStores.ts @@ -0,0 +1,11 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export interface SettingsStore { + getSetting(): T; + updateSetting(value: T): void; + useSetting(): T; +} From 688ff255d27de8b5bd611c7e678fac69c7f47032 Mon Sep 17 00:00:00 2001 From: Amia <9750071+aamiaa@users.noreply.github.com> Date: Fri, 8 Mar 2024 04:18:18 +0100 Subject: [PATCH 14/30] fix(MutualGroupDMs): update regex (#2242) --- src/plugins/mutualGroupDMs/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 40d5201cb..f5e4b6149 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -47,8 +47,8 @@ export default definePlugin({ { find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded replacement: { - match: /(?<=\.MUTUAL_GUILDS\}\),)(?=(\i\.bot).{0,20}(\(0,\i\.jsx\)\(.{0,100}id:))/, - replace: '($1||arguments[0].isCurrentUser)?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),' + match: /(?<=\.tabBarItem.{0,50}MUTUAL_GUILDS.+?}\),)(?=.+?(\(0,\i\.jsxs?\)\(.{0,100}id:))/, + replace: '(arguments[0].user.bot||arguments[0].isCurrentUser)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),' } }, { From 10f33b3dec4f2d197f117f780a6c8283f6f1046d Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 8 Mar 2024 00:23:35 -0300 Subject: [PATCH 15/30] Make more finds use filters.componentByCode --- src/plugins/reviewDB/components/ReviewsView.tsx | 6 +++--- src/webpack/common/components.ts | 2 +- src/webpack/webpack.ts | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx index eea92bb81..46bd7fb84 100644 --- a/src/plugins/reviewDB/components/ReviewsView.tsx +++ b/src/plugins/reviewDB/components/ReviewsView.tsx @@ -16,8 +16,8 @@ * along with this program. If not, see . */ -import { LazyComponent, useAwaiter, useForceUpdater } from "@utils/react"; -import { find, findByPropsLazy } from "@webpack"; +import { useAwaiter, useForceUpdater } from "@utils/react"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common"; import { Auth, authorize } from "../auth"; @@ -31,7 +31,7 @@ import ReviewComponent from "./ReviewComponent"; const { Editor, Transforms } = findByPropsLazy("Editor", "Transforms"); const { ChatInputTypes } = findByPropsLazy("ChatInputTypes"); -const InputComponent = LazyComponent(() => find(m => m.default?.type?.render?.toString().includes("default.CHANNEL_TEXT_AREA")).default); +const InputComponent = findComponentByCodeLazy("default.CHANNEL_TEXT_AREA"); const { createChannelRecordFromServer } = findByPropsLazy("createChannelRecordFromServer"); interface UserProps { diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index d7bb5d759..048e65d68 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -51,7 +51,7 @@ export let Avatar: t.Avatar; /** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */ export let useToken: t.useToken; -export const MaskedLink = waitForComponent("MaskedLink", m => m?.type?.toString().includes("MASKED_LINK)")); +export const MaskedLink = waitForComponent("MaskedLink", filters.componentByCode("MASKED_LINK)")); export const Timestamp = waitForComponent("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format")); export const Flex = waitForComponent("Flex", ["Justify", "Align", "Wrap"]); diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 0f7d8b73c..992bf38f3 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -60,6 +60,7 @@ export const filters = { return m => { if (filter(m)) return true; if (!m.$$typeof) return false; + if (m.type && m.type.render) return filter(m.type.render); // memo + forwardRef if (m.type) return filter(m.type); // memos if (m.render) return filter(m.render); // forwardRefs return false; From cf7830e747a5f2ff0b1bace93a9fab13f6936477 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 8 Mar 2024 00:24:04 -0300 Subject: [PATCH 16/30] ShowHiddenChannels: Fix patches --- src/plugins/showHiddenChannels/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 919f3f3c5..2d091c24a 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -305,27 +305,27 @@ export default definePlugin({ ] }, { - find: ".avatars),children", + find: '+1]})},"overflow"))', replacement: [ { // Create a variable for the channel prop - match: /maxUsers:\i,users:\i.+?=(\i).+?;/, + match: /maxUsers:\i,users:\i.+?}=(\i).*?;/, replace: (m, props) => `${m}let{shcChannel}=${props};` }, { // Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen match: /\i>0(?=&&.{0,60}renderPopout)/, - replace: m => `($self.isHiddenChannel(shcChannel,true)?true:${m})` + replace: m => `($self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)?true:${m})` }, { // Prevent Discord from overwriting the last children with the plus button if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen match: /(?<=\.value\(\),(\i)=.+?length-)1(?=\]=.{0,60}renderPopout)/, - replace: (_, amount) => `($self.isHiddenChannel(shcChannel,true)&&${amount}<=0?0:1)` + replace: (_, amount) => `($self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)&&${amount}<=0?0:1)` }, { // Show only the plus text without overflowed children amount if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen match: /(?<="\+",)(\i)\+1/, - replace: (m, amount) => `$self.isHiddenChannel(shcChannel,true)&&${amount}<=0?"":${m}` + replace: (m, amount) => `$self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)&&${amount}<=0?"":${m}` } ] }, From b0d37c981e6bbb47b4b25f28d3e87488f9038bac Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 8 Mar 2024 00:29:06 -0300 Subject: [PATCH 17/30] Bump to 1.7.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dde55d311..dbf8aaf34 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.7.0", + "version": "1.7.1", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 992533245bc08ccaac258aaca803d362942dde74 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:05:30 -0300 Subject: [PATCH 18/30] RoleColorEverywhere: Wrap roleGroupColor in ErrorBoundary --- src/plugins/roleColorEverywhere/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 968027163..14d38e8ee 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -17,6 +17,7 @@ */ import { definePluginSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { ChannelStore, GuildMemberStore, GuildStore } from "@webpack/common"; @@ -112,7 +113,7 @@ export default definePlugin({ return colorString && parseInt(colorString.slice(1), 16); }, - roleGroupColor({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) { + roleGroupColor: ErrorBoundary.wrap(({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) => { const guild = GuildStore.getGuild(guildId); const role = guild?.roles[id]; @@ -125,7 +126,7 @@ export default definePlugin({ {title ?? label} — {count} ); - }, + }, { noop: true }), getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) { return { From 497f0de9a1dc7f228a67e73b25f12c0d6fa93240 Mon Sep 17 00:00:00 2001 From: Jack <30497388+FieryFlames@users.noreply.github.com> Date: Mon, 11 Mar 2024 10:52:11 -0400 Subject: [PATCH 19/30] chore(Decor): Change URL formula for cost savings (#2247) --- src/plugins/decor/index.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index 8cfd8c036..713b17d76 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -131,9 +131,10 @@ export default definePlugin({ getDecorAvatarDecorationURL({ avatarDecoration, canAnimate }: { avatarDecoration: AvatarDecoration | null; canAnimate?: boolean; }) { // Only Decor avatar decorations have this SKU ID if (avatarDecoration?.skuId === SKU_ID) { - const url = new URL(`${CDN_URL}/${avatarDecoration.asset}.png`); - url.searchParams.set("animate", (!!canAnimate && isAnimatedAvatarDecoration(avatarDecoration.asset)).toString()); - return url.toString(); + const parts = avatarDecoration.asset.split("_"); + // Remove a_ prefix if it's animated and animation is disabled + if (isAnimatedAvatarDecoration(avatarDecoration.asset) && !canAnimate) parts.shift(); + return `${CDN_URL}/${parts.join("_")}.png`; } else if (avatarDecoration?.skuId === RAW_SKU_ID) { return avatarDecoration.asset; } From 34390e03652f523fd92e3974feb93395bb825bec Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 11 Mar 2024 16:13:07 +0100 Subject: [PATCH 20/30] Add workaround for guild role api changes on canary/ptb fixes RoleColorEverywhere, ServerInfo, PermissionViewer, BetterRoleContext --- src/plugins/betterRoleContext/index.tsx | 6 ++++-- .../components/RolesAndUsersPermissions.tsx | 8 +++++--- src/plugins/permissionsViewer/index.tsx | 3 ++- src/plugins/permissionsViewer/utils.ts | 13 ++++++++----- src/plugins/roleColorEverywhere/index.tsx | 6 +++--- src/plugins/serverProfile/GuildProfileModal.tsx | 4 ++-- src/plugins/xsOverlay.desktop/index.ts | 3 ++- src/utils/discord.tsx | 10 +++++++++- 8 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx index 7a914293a..e73779adf 100644 --- a/src/plugins/betterRoleContext/index.tsx +++ b/src/plugins/betterRoleContext/index.tsx @@ -5,7 +5,7 @@ */ import { Devs } from "@utils/constants"; -import { getCurrentGuild } from "@utils/discord"; +import { getCurrentGuild, getGuildRoles } from "@utils/discord"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; import { Clipboard, Menu, PermissionStore, TextAndImagesSettingsStores } from "@webpack/common"; @@ -47,7 +47,9 @@ export default definePlugin({ contextMenus: { "dev-context"(children, { id }: { id: string; }) { const guild = getCurrentGuild(); - const role = guild?.roles[id]; + if (!guild) return; + + const role = getGuildRoles(guild.id)[id]; if (!role) return; if (role.colorString) { diff --git a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx index e0d25c7ab..adadc90b5 100644 --- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx +++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx @@ -19,7 +19,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { InfoIcon, OwnerCrownIcon } from "@components/Icons"; -import { getUniqueUsername } from "@utils/discord"; +import { getGuildRoles, getUniqueUsername } from "@utils/discord"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ContextMenuApi, FluxDispatcher, GuildMemberStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common"; import type { Guild } from "discord-types/general"; @@ -78,6 +78,8 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea const [selectedItemIndex, selectItem] = useState(0); const selectedItem = permissions[selectedItemIndex]; + const roles = getGuildRoles(guild.id); + return ( {permissions.map((permission, index) => { const user = UserStore.getUser(permission.id ?? ""); - const role = guild.roles[permission.id ?? ""]; + const role = roles[permission.id ?? ""]; return (