From cc885b5bb367add1c4e831ab1a4ab6145f6b0f5c Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 3 Feb 2024 02:11:59 +0100 Subject: [PATCH 01/70] remove lumap --- src/plugins/pictureInPicture/index.tsx | 2 +- src/utils/constants.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/pictureInPicture/index.tsx b/src/plugins/pictureInPicture/index.tsx index ba4aa8387..ca766affc 100644 --- a/src/plugins/pictureInPicture/index.tsx +++ b/src/plugins/pictureInPicture/index.tsx @@ -24,7 +24,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "PictureInPicture", description: "Adds picture in picture to videos (next to the Download button)", - authors: [Devs.Lumap], + authors: [Devs.Nobody], settings, patches: [ { diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 899936128..55af93605 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -42,6 +42,10 @@ export interface Dev { * If you are fine with attribution but don't want the badge, add badge: false */ export const Devs = /* #__PURE__*/ Object.freeze({ + Nobody: { + name: "Nobody", + id: 0n, + }, Ven: { name: "Vendicated", id: 343383572805058560n @@ -359,10 +363,6 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "bb010g", id: 72791153467990016n, }, - Lumap: { - name: "lumap", - id: 635383782576357407n - }, Dolfies: { name: "Dolfies", id: 852892297661906993n, From 8938f4a3cf9a36aa490703651934acccbd07bdf9 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Sun, 4 Feb 2024 01:50:51 +0100 Subject: [PATCH 02/70] fix moreUserTags (#2146) --- src/plugins/moreUserTags/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/moreUserTags/index.tsx b/src/plugins/moreUserTags/index.tsx index 9921bca8e..d1ad941b0 100644 --- a/src/plugins/moreUserTags/index.tsx +++ b/src/plugins/moreUserTags/index.tsx @@ -198,7 +198,7 @@ export default definePlugin({ replacement: [ // make the tag show the right text { - match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(\i\.\i\.Messages)\.BOT_TAG_BOT/, + match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=.{0,40}(\i\.\i\.Messages)\.BOT_TAG_BOT/, replace: (_, origSwitch, variant, tags, displayedText, strings) => `${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}` }, From bf977e0047141dd479b26f66cc848923310bcb6b Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 6 Feb 2024 16:29:47 +0100 Subject: [PATCH 03/70] Add chat bar button api ~ fixes buttons for russian users --- src/api/ChatButtons.tsx | 123 ++++++++++++++++++++ src/api/index.ts | 6 + src/plugins/_api/chatButtons.ts | 22 ++++ src/plugins/invisibleChat.desktop/index.tsx | 88 +++++--------- src/plugins/previewMessage/index.tsx | 74 +++++------- src/plugins/sendTimestamps/index.tsx | 101 +++++++--------- src/plugins/silentMessageToggle/index.tsx | 78 +++++-------- src/plugins/silentTyping/index.tsx | 66 ++++------- src/plugins/translate/TranslateIcon.tsx | 52 ++++----- src/plugins/translate/index.tsx | 22 +--- 10 files changed, 325 insertions(+), 307 deletions(-) create mode 100644 src/api/ChatButtons.tsx create mode 100644 src/plugins/_api/chatButtons.ts diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx new file mode 100644 index 000000000..0350965af --- /dev/null +++ b/src/api/ChatButtons.tsx @@ -0,0 +1,123 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import ErrorBoundary from "@components/ErrorBoundary"; +import { Logger } from "@utils/Logger"; +import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common"; +import { Channel } from "discord-types/general"; +import { HTMLProps, MouseEventHandler, ReactNode } from "react"; + +export interface ChatBarProps { + channel: Channel; + disabled: boolean; + isEmpty: boolean; + type: { + analyticsName: string; + attachments: boolean; + autocomplete: { + addReactionShortcut: boolean, + forceChatLayer: boolean, + reactions: boolean; + }, + commands: { + enabled: boolean; + }, + drafts: { + type: number, + commandType: number, + autoSave: boolean; + }, + emojis: { + button: boolean; + }, + gifs: { + button: boolean, + allowSending: boolean; + }, + gifts: { + button: boolean; + }, + permissions: { + requireSendMessages: boolean; + }, + showThreadPromptOnReply: boolean, + stickers: { + button: boolean, + allowSending: boolean, + autoSuggest: boolean; + }, + users: { + allowMentioning: boolean; + }, + submit: { + button: boolean, + ignorePreference: boolean, + disableEnterToSubmit: boolean, + clearOnSubmit: boolean, + useDisabledStylesOnSubmit: boolean; + }, + uploadLongMessages: boolean, + upsellLongMessages: { + iconOnly: boolean; + }, + showCharacterCount: boolean, + sedReplace: boolean; + }; +} + +export type ChatBarButton = (props: ChatBarProps, isMainChat: boolean) => ReactNode; + +const buttonFactories = new Map(); +const logger = new Logger("ChatButtons"); + +export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) { + if (props.type.analyticsName !== "normal") return; + + for (const [key, makeButton] of buttonFactories) { + try { + const res = makeButton(props, props.type.analyticsName === "normal"); + if (res) buttons.push(res); + } catch (e) { + logger.error(`Failed to render button ${key}`, e); + } + } +} + +export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button); +export const removeChatBarButton = (id: string) => buttonFactories.delete(id); + +export interface ChatBarButtonProps { + children: ReactNode; + tooltip: string; + onClick: MouseEventHandler; + onContextMenu?: MouseEventHandler; + buttonProps?: Omit, "size" | "onClick" | "onContextMenu">; +} +export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => { + return ( + + {({ onMouseEnter, onMouseLeave }) => ( +
+ +
+ )} +
+ ); +}, { noop: true }); diff --git a/src/api/index.ts b/src/api/index.ts index 08f238104..5dca63105 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -17,6 +17,7 @@ */ import * as $Badges from "./Badges"; +import * as $ChatButtons from "./ChatButtons"; import * as $Commands from "./Commands"; import * as $ContextMenu from "./ContextMenu"; import * as $DataStore from "./DataStore"; @@ -104,3 +105,8 @@ export const Notifications = $Notifications; * An api allowing you to patch and add/remove items to/from context menus */ export const ContextMenu = $ContextMenu; + +/** + * An API allowing you to add buttons to the chat input + */ +export const ChatButtons = $ChatButtons; diff --git a/src/plugins/_api/chatButtons.ts b/src/plugins/_api/chatButtons.ts new file mode 100644 index 000000000..ca85964c0 --- /dev/null +++ b/src/plugins/_api/chatButtons.ts @@ -0,0 +1,22 @@ +/* + * 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 definePlugin from "@utils/types"; + +export default definePlugin({ + name: "ChatInputButtonAPI", + description: "API to add buttons to the chat input", + authors: [Devs.Ven], + + patches: [{ + find: 'location:"ChannelTextAreaButtons"', + replacement: { + match: /if\(!\i\.isMobile\)\{(?=.+?&&(\i)\.push\(.{0,50}"gift")/, + replace: "$&Vencord.Api.ChatButtons._injectButtons($1,arguments[0]);" + } + }] +}); diff --git a/src/plugins/invisibleChat.desktop/index.tsx b/src/plugins/invisibleChat.desktop/index.tsx index c80c4ce54..3184e025d 100644 --- a/src/plugins/invisibleChat.desktop/index.tsx +++ b/src/plugins/invisibleChat.desktop/index.tsx @@ -16,13 +16,14 @@ * along with this program. If not, see . */ +import { addChatBarButton, ChatBarButton } from "@api/ChatButtons"; import { addButton, removeButton } from "@api/MessagePopover"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getStegCloak } from "@utils/dependencies"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, ButtonLooks, ButtonWrapperClasses, ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common"; +import { ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common"; import { Message } from "discord-types/general"; import { buildDecModal } from "./components/DecryptionModal"; @@ -64,54 +65,32 @@ function Indicator() { } -function ChatBarIcon(chatBoxProps: { - type: { - analyticsName: string; - }; -}) { - if (chatBoxProps.type.analyticsName !== "normal") return null; +const ChatBarIcon: ChatBarButton = (_, isMainChat) => { + if (!isMainChat) return null; return ( - - {({ onMouseEnter, onMouseLeave }) => ( - // size="" = Button.Sizes.NONE - /* - many themes set "> button" to display: none, as the gift button is - the only directly descending button (all the other elements are divs.) - Thus, wrap in a div here to avoid getting hidden by that. - flex is for some reason necessary as otherwise the button goes flying off - */ -
- -
- ) - } -
+ buildEncModal()} + + buttonProps={{ + "aria-haspopup": "dialog", + style: { padding: "0 2px", scale: "0.9" } + }} + > + + + + ); -} +}; const settings = definePluginSettings({ savedPasswords: { @@ -125,7 +104,7 @@ export default definePlugin({ name: "InvisibleChat", description: "Encrypt your Messages in a non-suspicious way!", authors: [Devs.SammCheese], - dependencies: ["MessagePopoverAPI"], + dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI"], patches: [ { // Indicator @@ -135,13 +114,6 @@ export default definePlugin({ replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&" } }, - { - find: "ChannelTextAreaButtons", - replacement: { - match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()", - } - }, ], EMBED_API_URL: "https://embed.sammcheese.net", @@ -154,7 +126,7 @@ export default definePlugin({ const { default: StegCloak } = await getStegCloak(); steggo = new StegCloak(true, false); - addButton("invDecrypt", message => { + addButton("InvisibleChat", message => { return this.INV_REGEX.test(message?.content) ? { label: "Decrypt Message", @@ -170,10 +142,13 @@ export default definePlugin({ } : null; }); + + addChatBarButton("InvisibleChat", ChatBarIcon); }, stop() { - removeButton("invDecrypt"); + removeButton("InvisibleChat"); + removeButton("InvisibleChat"); }, // Gets the Embed of a Link @@ -216,7 +191,6 @@ export default definePlugin({ }); }, - chatBarIcon: ErrorBoundary.wrap(ChatBarIcon, { noop: true }), popOverIcon: () => , indicator: ErrorBoundary.wrap(Indicator, { noop: true }) }); diff --git a/src/plugins/previewMessage/index.tsx b/src/plugins/previewMessage/index.tsx index f2634ae6b..1d8b769d5 100644 --- a/src/plugins/previewMessage/index.tsx +++ b/src/plugins/previewMessage/index.tsx @@ -16,22 +16,14 @@ * along with this program. If not, see . */ +import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { generateId, sendBotMessage } from "@api/Commands"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; +import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common"; import { MessageAttachment } from "discord-types/general"; -interface Props { - type: { - analyticsName: string; - isEmpty: boolean; - attachments: boolean; - }; -} - const UploadStore = findByPropsLazy("getUploads"); const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage); @@ -81,13 +73,13 @@ const getAttachments = async (channelId: string) => ); -export function PreviewButton(chatBoxProps: Props) { - const { isEmpty, attachments } = chatBoxProps.type; +const PreviewButton: ChatBarButton = (props, isMainChat) => { + const { isEmpty, type: { attachments } } = props; const channelId = SelectedChannelStore.getChannelId(); const draft = useStateFromStores([DraftStore], () => getDraft(channelId)); - if (chatBoxProps.type.analyticsName !== "normal") return null; + if (!isMainChat) return null; const hasAttachments = attachments && UploadStore.getUploads(channelId, DraftType.ChannelMessage).length > 0; const hasContent = !isEmpty && draft?.length > 0; @@ -95,47 +87,33 @@ export function PreviewButton(chatBoxProps: Props) { if (!hasContent && !hasAttachments) return null; return ( - - {tooltipProps => ( - - )} - + + sendBotMessage( + channelId, + { + content: getDraft(channelId), + author: UserStore.getCurrentUser(), + attachments: hasAttachments ? await getAttachments(channelId) : undefined, + } + )} + buttonProps={{ + style: { padding: "0 2px", height: "100%" } + }} + > + + ); -} +}; export default definePlugin({ name: "PreviewMessage", description: "Lets you preview your message before sending it.", authors: [Devs.Aria], - patches: [ - { - find: "ChannelTextAreaButtons", - replacement: { - match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()", - } - }, - ], + dependencies: ["ChatInputButtonAPI"], - chatBarIcon: ErrorBoundary.wrap(PreviewButton, { noop: true }), + start: () => addChatBarButton("previewMessage", PreviewButton), + stop: () => removeChatBarButton("previewMessage"), }); diff --git a/src/plugins/sendTimestamps/index.tsx b/src/plugins/sendTimestamps/index.tsx index 6d488add6..bd888a82b 100644 --- a/src/plugins/sendTimestamps/index.tsx +++ b/src/plugins/sendTimestamps/index.tsx @@ -18,6 +18,7 @@ import "./styles.css"; +import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { addPreSendListener, removePreSendListener } from "@api/MessageEvents"; import { definePluginSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; @@ -26,7 +27,7 @@ import { getTheme, insertTextIntoChatInputBox, Theme } from "@utils/discord"; import { Margins } from "@utils/margins"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, ButtonLooks, ButtonWrapperClasses, Forms, Parser, Select, Tooltip, useMemo, useState } from "@webpack/common"; +import { Button, Forms, Parser, Select, useMemo, useState } from "@webpack/common"; const settings = definePluginSettings({ replaceMessageContents: { @@ -122,25 +123,51 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi ); } +const ChatBarIcon: ChatBarButton = (_, isMainChat) => { + if (!isMainChat) return null; + + return ( + { + const key = openModal(props => ( + closeModal(key)} + /> + )); + }} + buttonProps={{ + "aria-haspopup": "dialog", + className: cl("button") + }} + > + + + ); +}; + export default definePlugin({ name: "SendTimestamps", description: "Send timestamps easily via chat box button & text shortcuts. Read the extended description!", authors: [Devs.Ven, Devs.Tyler, Devs.Grzesiek11], - dependencies: ["MessageEventsAPI"], + dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"], - settings: settings, - - patches: [ - { - find: "ChannelTextAreaButtons", - replacement: { - match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()", - } - }, - ], + settings, start() { + addChatBarButton("SendTimestamps", ChatBarIcon); this.listener = addPreSendListener((_, msg) => { if (settings.store.replaceMessageContents) { msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime); @@ -149,56 +176,10 @@ export default definePlugin({ }, stop() { + removeChatBarButton("SendTimestamps"); removePreSendListener(this.listener); }, - chatBarIcon(chatBoxProps: { type: { analyticsName: string; }; }) { - if (chatBoxProps.type.analyticsName !== "normal") return null; - - return ( - - {({ onMouseEnter, onMouseLeave }) => ( -
- -
- ) - } -
- ); - }, - settingsAboutComponent() { const samples = [ "12:00", diff --git a/src/plugins/silentMessageToggle/index.tsx b/src/plugins/silentMessageToggle/index.tsx index b7b33826d..6c7d179be 100644 --- a/src/plugins/silentMessageToggle/index.tsx +++ b/src/plugins/silentMessageToggle/index.tsx @@ -16,12 +16,12 @@ * along with this program. If not, see . */ +import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents"; import { definePluginSettings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, ButtonLooks, ButtonWrapperClasses, React, Tooltip } from "@webpack/common"; +import { React, useEffect, useState } from "@webpack/common"; let lastState = false; @@ -41,19 +41,15 @@ const settings = definePluginSettings({ } }); -function SilentMessageToggle(chatBoxProps: { - type: { - analyticsName: string; - }; -}) { - const [enabled, setEnabled] = React.useState(lastState); +const SilentMessageToggle: ChatBarButton = (_, isMainChat) => { + const [enabled, setEnabled] = useState(lastState); function setEnabledValue(value: boolean) { if (settings.store.persistState) lastState = value; setEnabled(value); } - React.useEffect(() => { + useEffect(() => { const listener: SendListener = (_, message) => { if (enabled) { if (settings.store.autoDisable) setEnabledValue(false); @@ -65,55 +61,37 @@ function SilentMessageToggle(chatBoxProps: { return () => void removePreSendListener(listener); }, [enabled]); - if (chatBoxProps.type.analyticsName !== "normal") return null; + if (!isMainChat) return null; return ( - - {tooltipProps => ( -
- -
- )} -
+ setEnabledValue(!enabled)} + buttonProps={{ + style: { padding: "0 6px" } + }} + > + + + {!enabled && <> + + + + + + } + + ); -} +}; export default definePlugin({ name: "SilentMessageToggle", authors: [Devs.Nuckyz, Devs.CatNoir], description: "Adds a button to the chat bar to toggle sending a silent message.", - dependencies: ["MessageEventsAPI"], - + dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"], settings, - patches: [ - { - find: "ChannelTextAreaButtons", - replacement: { - match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()", - } - }, - ], - chatBarIcon: ErrorBoundary.wrap(SilentMessageToggle, { noop: true }), + start: () => addChatBarButton("SilentMessageToggle", SilentMessageToggle), + stop: () => removeChatBarButton("SilentMessageToggle") }); diff --git a/src/plugins/silentTyping/index.tsx b/src/plugins/silentTyping/index.tsx index dae7ad4c9..1d336b477 100644 --- a/src/plugins/silentTyping/index.tsx +++ b/src/plugins/silentTyping/index.tsx @@ -16,12 +16,12 @@ * along with this program. If not, see . */ +import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands"; import { definePluginSettings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, ButtonLooks, ButtonWrapperClasses, FluxDispatcher, React, Tooltip } from "@webpack/common"; +import { FluxDispatcher, React } from "@webpack/common"; const settings = definePluginSettings({ showIcon: { @@ -37,45 +37,35 @@ const settings = definePluginSettings({ } }); -function SilentTypingToggle(chatBoxProps: { - type: { - analyticsName: string; - }; -}) { - const { isEnabled } = settings.use(["isEnabled"]); +const SilentTypingToggle: ChatBarButton = (_, isMainChat) => { + const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]); const toggle = () => settings.store.isEnabled = !settings.store.isEnabled; - if (chatBoxProps.type.analyticsName !== "normal") return null; + if (!isMainChat || !showIcon) return null; return ( - - {(tooltipProps: any) => ( -
- -
- )} -
+ + + + {isEnabled && } + + ); -} +}; export default definePlugin({ name: "SilentTyping", authors: [Devs.Ven, Devs.Rini], description: "Hide that you are typing", + dependencies: ["CommandsAPI", "ChatInputButtonAPI"], + settings, + patches: [ { find: '.dispatch({type:"TYPING_START_LOCAL"', @@ -84,17 +74,8 @@ export default definePlugin({ replace: "startTyping:$self.startTyping,stop" } }, - { - find: "ChannelTextAreaButtons", - predicate: () => settings.store.showIcon, - replacement: { - match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()", - } - }, ], - dependencies: ["CommandsAPI"], - settings, + commands: [{ name: "silenttype", description: "Toggle whether you're hiding that you're typing or not.", @@ -120,5 +101,6 @@ export default definePlugin({ FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId }); }, - chatBarIcon: ErrorBoundary.wrap(SilentTypingToggle, { noop: true }), + start: () => addChatBarButton("SilentTyping", SilentTypingToggle), + stop: () => removeChatBarButton("SilentTyping"), }); diff --git a/src/plugins/translate/TranslateIcon.tsx b/src/plugins/translate/TranslateIcon.tsx index 649589435..a7d789279 100644 --- a/src/plugins/translate/TranslateIcon.tsx +++ b/src/plugins/translate/TranslateIcon.tsx @@ -16,9 +16,9 @@ * along with this program. If not, see . */ +import { ChatBarButton } from "@api/ChatButtons"; import { classes } from "@utils/misc"; import { openModal } from "@utils/modal"; -import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common"; import { settings } from "./settings"; import { TranslateModal } from "./TranslateModal"; @@ -37,42 +37,30 @@ export function TranslateIcon({ height = 24, width = 24, className }: { height?: ); } -export function TranslateChatBarIcon({ slateProps }: { slateProps: { type: { analyticsName: string; }; }; }) { +export const TranslateChatBarIcon: ChatBarButton = (props, isMainChat) => { const { autoTranslate } = settings.use(["autoTranslate"]); - if (slateProps.type.analyticsName !== "normal") - return null; + if (!isMainChat) return null; const toggle = () => settings.store.autoTranslate = !autoTranslate; return ( - - {({ onMouseEnter, onMouseLeave }) => ( -
- -
- )} -
+ openModal(props => ( + + )); + }} + onContextMenu={() => toggle()} + buttonProps={{ + "aria-haspopup": "dialog", + style: { padding: "0 4px" } + }} + > + + ); -} +}; diff --git a/src/plugins/translate/index.tsx b/src/plugins/translate/index.tsx index 3b067c634..702e60cf7 100644 --- a/src/plugins/translate/index.tsx +++ b/src/plugins/translate/index.tsx @@ -18,11 +18,11 @@ import "./styles.css"; +import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; import { addAccessory, removeAccessory } from "@api/MessageAccessories"; import { addPreSendListener, removePreSendListener } from "@api/MessageEvents"; import { addButton, removeButton } from "@api/MessagePopover"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { ChannelStore, Menu } from "@webpack/common"; @@ -55,25 +55,16 @@ export default definePlugin({ name: "Translate", description: "Translate messages with Google Translate", authors: [Devs.Ven], - dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI"], + dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"], settings, // not used, just here in case some other plugin wants it or w/e translate, - patches: [ - { - find: "ChannelTextAreaButtons", - replacement: { - match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()", - } - }, - ], - start() { addAccessory("vc-translation", props => ); addContextMenuPatch("message", messageCtxPatch); + addChatBarButton("vc-translate", TranslateChatBarIcon); addButton("vc-translate", message => { if (!message.content) return null; @@ -101,13 +92,8 @@ export default definePlugin({ stop() { removePreSendListener(this.preSend); removeContextMenuPatch("message", messageCtxPatch); + removeChatBarButton("vc-translate"); removeButton("vc-translate"); removeAccessory("vc-translation"); }, - - chatBarIcon: (slateProps: any) => ( - - - - ) }); From 2c198e547ce2424ada0c8e2c067b2d2c7b435c7a Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 6 Feb 2024 16:50:21 +0100 Subject: [PATCH 04/70] Fix PreviewMessage icon being offcentre --- src/api/ChatButton.css | 4 ++++ src/api/ChatButtons.tsx | 8 +++++++- src/plugins/invisibleChat.desktop/index.tsx | 6 +++--- src/plugins/previewMessage/index.tsx | 10 ++++++++-- 4 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 src/api/ChatButton.css diff --git a/src/api/ChatButton.css b/src/api/ChatButton.css new file mode 100644 index 000000000..30869a846 --- /dev/null +++ b/src/api/ChatButton.css @@ -0,0 +1,4 @@ +.vc-chatbar-button { + display: flex; + align-items: center; +} diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx index 0350965af..c995033b1 100644 --- a/src/api/ChatButtons.tsx +++ b/src/api/ChatButtons.tsx @@ -4,12 +4,18 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import "./ChatButton.css"; + import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; +import { waitFor } from "@webpack"; import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common"; import { Channel } from "discord-types/general"; import { HTMLProps, MouseEventHandler, ReactNode } from "react"; +let CssClasses: { buttonContainer: string; }; +waitFor(["buttonContainer", "channelTextArea"], m => CssClasses = m); + export interface ChatBarProps { channel: Channel; disabled: boolean; @@ -100,7 +106,7 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => { return ( {({ onMouseEnter, onMouseLeave }) => ( -
+
- {viewAllowedUsersAndRoles && } + {defaultAllowedUsersAndRolesDropdownState && }
From d3bbd2c02ebcab88f0c6a7b19e7687eca6068e1c Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 9 Feb 2024 21:00:15 -0300 Subject: [PATCH 21/70] FakeNitro: option to use hyperlinks or not --- src/plugins/fakeNitro/index.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/plugins/fakeNitro/index.ts b/src/plugins/fakeNitro/index.ts index 2caecc45b..ed3ec59dd 100644 --- a/src/plugins/fakeNitro/index.ts +++ b/src/plugins/fakeNitro/index.ts @@ -157,6 +157,11 @@ const settings = definePluginSettings({ type: OptionType.BOOLEAN, default: true, restartNeeded: true + }, + useHyperLinks: { + description: "Whether to use hyperlinks when sending fake emojis and stickers", + type: OptionType.BOOLEAN, + default: true } }); @@ -708,7 +713,7 @@ export default definePlugin({ }, getStickerLink(stickerId: string) { - return `https://media.discordapp.net/stickers/${stickerId}.png?size=${Settings.plugins.FakeNitro.stickerSize}`; + return `https://media.discordapp.net/stickers/${stickerId}.png?size=${settings.store.stickerSize}`; }, async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) { @@ -813,7 +818,7 @@ export default definePlugin({ const url = new URL(link); url.searchParams.set("name", sticker.name); - messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}[${sticker.name}](${url})`; + messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}${s.useHyperLinks ? `[${sticker.name}](${url})` : url}`; extra.stickers!.length = 0; } } @@ -829,11 +834,11 @@ export default definePlugin({ const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`; const url = new URL(emoji.url); - url.searchParams.set("size", settings.store.emojiSize.toString()); + url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("name", emoji.name); messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => { - return `${getWordBoundary(origStr, offset - 1)}[:${emoji.name}:](${url})${getWordBoundary(origStr, offset + match.length)}`; + return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[:${emoji.name}:](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`; }); } } @@ -856,10 +861,10 @@ export default definePlugin({ if (emoji.guildId === guildId && !emoji.animated) return emojiStr; const url = new URL(emoji.url); - url.searchParams.set("size", settings.store.emojiSize.toString()); + url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("name", emoji.name); - return `${getWordBoundary(origStr, offset - 1)}[:${emoji.name}:](${url})${getWordBoundary(origStr, offset + emojiStr.length)}`; + return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[:${emoji.name}:](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`; }); }); }, From cc0d9a90bc503f6c2055e543eb1414b92127e8b3 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 9 Feb 2024 21:27:34 -0300 Subject: [PATCH 22/70] Fix UserVoiceShow patch --- src/plugins/userVoiceShow/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/userVoiceShow/index.tsx b/src/plugins/userVoiceShow/index.tsx index c95307cc4..935ff1c5d 100644 --- a/src/plugins/userVoiceShow/index.tsx +++ b/src/plugins/userVoiceShow/index.tsx @@ -96,7 +96,7 @@ export default definePlugin({ patches: [ // above message box { - find: ".lastEditedByContainer", + find: ".popularApplicationCommandIds,", replacement: { match: /\(0,\i\.jsx\)\(\i\.\i,{user:\i,setNote/, replace: "$self.patchPopout(arguments[0]),$&", From 38beb93e5f2ea7f7ea0c6021ddaf0ecb8f600406 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 10 Feb 2024 13:42:31 -0300 Subject: [PATCH 23/70] Fix CrashHandler failing to recover and showing gray screen instead --- src/plugins/crashHandler/index.ts | 96 ++++++++++++++----------------- 1 file changed, 44 insertions(+), 52 deletions(-) diff --git a/src/plugins/crashHandler/index.ts b/src/plugins/crashHandler/index.ts index 9d38b7d17..b73641f57 100644 --- a/src/plugins/crashHandler/index.ts +++ b/src/plugins/crashHandler/index.ts @@ -25,7 +25,6 @@ import definePlugin, { OptionType } from "@utils/types"; import { maybePromptToUpdate } from "@utils/updater"; import { filters, findBulk, proxyLazyWebpack } from "@webpack"; import { FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common"; -import type { ReactElement } from "react"; const CrashHandlerLogger = new Logger("CrashHandler"); const { ModalStack, DraftManager, DraftType, closeExpressionPicker } = proxyLazyWebpack(() => { @@ -57,13 +56,12 @@ const settings = definePluginSettings({ } }); -let crashCount: number = 0; -let lastCrashTimestamp: number = 0; -let shouldAttemptNextHandle = false; +let hasCrashedOnce = false; +let shouldAttemptRecover = true; export default definePlugin({ name: "CrashHandler", - description: "Utility plugin for handling and possibly recovering from Crashes without a restart", + description: "Utility plugin for handling and possibly recovering from crashes without a restart", authors: [Devs.Nuckyz], enabledByDefault: true, @@ -74,60 +72,55 @@ export default definePlugin({ find: ".Messages.ERRORS_UNEXPECTED_CRASH", replacement: { match: /(?=this\.setState\()/, - replace: "$self.handleCrash(this)||" + replace: "$self.handleCrash(this);" } } ], - handleCrash(_this: ReactElement & { forceUpdate: () => void; }) { - if (Date.now() - lastCrashTimestamp <= 1_000 && !shouldAttemptNextHandle) return true; + handleCrash(_this: any) { + // 1 ms timeout to avoid react breaking when re-rendering + setTimeout(() => { + if (!shouldAttemptRecover) { + try { + showNotification({ + color: "#eed202", + title: "Discord has crashed!", + body: "Awn :( Discord has crashed two times rapidly, not attempting to recover.", + noPersist: true, + }); + } catch { } - shouldAttemptNextHandle = false; - - if (++crashCount > 5) { - try { - showNotification({ - color: "#eed202", - title: "Discord has crashed!", - body: "Awn :( Discord has crashed more than five times, not attempting to recover.", - noPersist: true, - }); - } catch { } - - lastCrashTimestamp = Date.now(); - return false; - } - - setTimeout(() => crashCount--, 60_000); - - try { - if (crashCount === 1) maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true); - - if (settings.store.attemptToPreventCrashes) { - this.handlePreventCrash(_this); - return true; + return; } - return false; - } catch (err) { - CrashHandlerLogger.error("Failed to handle crash", err); - return false; - } finally { - lastCrashTimestamp = Date.now(); - } + shouldAttemptRecover = false; + // This is enough to avoid a crash loop + setTimeout(() => shouldAttemptRecover = true, 500); + + try { + if (!hasCrashedOnce) { + hasCrashedOnce = true; + maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true); + } + + if (settings.store.attemptToPreventCrashes) { + this.handlePreventCrash(_this); + } + } catch (err) { + CrashHandlerLogger.error("Failed to handle crash", err); + } + }, 1); }, - handlePreventCrash(_this: ReactElement & { forceUpdate: () => void; }) { - if (Date.now() - lastCrashTimestamp >= 1_000) { - try { - showNotification({ - color: "#eed202", - title: "Discord has crashed!", - body: "Attempting to recover...", - noPersist: true, - }); - } catch { } - } + handlePreventCrash(_this: any) { + try { + showNotification({ + color: "#eed202", + title: "Discord has crashed!", + body: "Attempting to recover...", + noPersist: true, + }); + } catch { } try { const channelId = SelectedChannelStore.getChannelId(); @@ -177,8 +170,7 @@ export default definePlugin({ } try { - shouldAttemptNextHandle = true; - _this.forceUpdate(); + _this.setState({ error: null, info: null }); } catch (err) { CrashHandlerLogger.debug("Failed to update crash handler component.", err); } From 8b6a40311bab1c89a11d2384508b997e99c7b59b Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 13 Feb 2024 09:04:28 +0100 Subject: [PATCH 24/70] Bump to v1.6.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 076b2999d..08078d3d5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.6.7", + "version": "1.6.8", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 0c9d2a6a21a42111465c9cecde729c878ffd1c08 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 13 Feb 2024 09:03:25 +0100 Subject: [PATCH 25/70] Fix plugins using the Timestamp component --- src/api/Notifications/notificationLog.tsx | 4 ++-- src/plugins/reviewDB/components/ReviewComponent.tsx | 4 ++-- src/plugins/serverProfile/GuildProfileModal.tsx | 4 ++-- .../components/HiddenChannelLockScreen.tsx | 6 +++--- src/webpack/common/types/components.d.ts | 3 +-- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/api/Notifications/notificationLog.tsx b/src/api/Notifications/notificationLog.tsx index 9535fb62c..6f79ef70a 100644 --- a/src/api/Notifications/notificationLog.tsx +++ b/src/api/Notifications/notificationLog.tsx @@ -21,7 +21,7 @@ import { Settings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; -import { Alerts, Button, Forms, moment, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; +import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; import { nanoid } from "nanoid"; import type { DispatchWithoutAction } from "react"; @@ -129,7 +129,7 @@ function NotificationEntry({ data }: { data: PersistentNotificationData; }) { richBody={
{data.body} - +
} /> diff --git a/src/plugins/reviewDB/components/ReviewComponent.tsx b/src/plugins/reviewDB/components/ReviewComponent.tsx index 977745a25..20b298ccb 100644 --- a/src/plugins/reviewDB/components/ReviewComponent.tsx +++ b/src/plugins/reviewDB/components/ReviewComponent.tsx @@ -20,7 +20,7 @@ import { openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { LazyComponent } from "@utils/react"; import { filters, findBulk } from "@webpack"; -import { Alerts, moment, Parser, Timestamp, useState } from "@webpack/common"; +import { Alerts, Parser, Timestamp, useState } from "@webpack/common"; import { Auth, getToken } from "../auth"; import { Review, ReviewType } from "../entities"; @@ -163,7 +163,7 @@ export default LazyComponent(() => { { !settings.store.hideTimestamps && review.type !== ReviewType.System && ( - + {dateFormat.format(review.timestamp * 1000)} ) } diff --git a/src/plugins/serverProfile/GuildProfileModal.tsx b/src/plugins/serverProfile/GuildProfileModal.tsx index 97b40b764..be1f2cd54 100644 --- a/src/plugins/serverProfile/GuildProfileModal.tsx +++ b/src/plugins/serverProfile/GuildProfileModal.tsx @@ -12,7 +12,7 @@ import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; -import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; +import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; import { Guild, User } from "discord-types/general"; const IconUtils = findByPropsLazy("getGuildBannerURL"); @@ -50,7 +50,7 @@ const fetched = { function renderTimestamp(timestamp: number) { return ( - + ); } diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index 7904cd10b..a8f5735e7 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -20,7 +20,7 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { formatDuration } from "@utils/text"; import { findByPropsLazy, findComponentByCodeLazy, findComponentLazy } from "@webpack"; -import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; +import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; import type { Channel } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions"; @@ -216,12 +216,12 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { {lastMessageId && Last {channel.isForumChannel() ? "post" : "message"} created: - + } {lastPinTimestamp && - Last message pin: + Last message pin: } {(rateLimitPerUser ?? 0) > 0 && Slowmode: {formatDuration(rateLimitPerUser!, "seconds")} diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index b9bc434c6..72a8a69b4 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import type { Moment } from "moment"; import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react"; export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; @@ -154,7 +153,7 @@ export type Switch = ComponentType>; export type Timestamp = ComponentType Date: Wed, 14 Feb 2024 15:00:29 -0300 Subject: [PATCH 26/70] Attempt to fix CrashHandler odd issues --- src/plugins/crashHandler/index.ts | 52 ++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/plugins/crashHandler/index.ts b/src/plugins/crashHandler/index.ts index b73641f57..f8c76d7f7 100644 --- a/src/plugins/crashHandler/index.ts +++ b/src/plugins/crashHandler/index.ts @@ -57,6 +57,7 @@ const settings = definePluginSettings({ }); let hasCrashedOnce = false; +let isRecovering = false; let shouldAttemptRecover = true; export default definePlugin({ @@ -71,38 +72,49 @@ export default definePlugin({ { find: ".Messages.ERRORS_UNEXPECTED_CRASH", replacement: { - match: /(?=this\.setState\()/, - replace: "$self.handleCrash(this);" + match: /this\.setState\((.+?)\)/, + replace: "$self.handleCrash(this,$1);" } } ], - handleCrash(_this: any) { + handleCrash(_this: any, errorState: any) { + _this.setState(errorState); + + // Already recovering, prevent error which happens more than once too fast to trigger another recover + if (isRecovering) return; + isRecovering = true; + // 1 ms timeout to avoid react breaking when re-rendering setTimeout(() => { - if (!shouldAttemptRecover) { - try { - showNotification({ - color: "#eed202", - title: "Discord has crashed!", - body: "Awn :( Discord has crashed two times rapidly, not attempting to recover.", - noPersist: true, - }); - } catch { } + try { + // Prevent a crash loop with an error that could not be handled + if (!shouldAttemptRecover) { + try { + showNotification({ + color: "#eed202", + title: "Discord has crashed!", + body: "Awn :( Discord has crashed two times rapidly, not attempting to recover.", + noPersist: true + }); + } catch { } - return; - } + return; + } - shouldAttemptRecover = false; - // This is enough to avoid a crash loop - setTimeout(() => shouldAttemptRecover = true, 500); + shouldAttemptRecover = false; + // This is enough to avoid a crash loop + setTimeout(() => shouldAttemptRecover = true, 500); + } catch { } try { if (!hasCrashedOnce) { hasCrashedOnce = true; maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true); } + } catch { } + try { if (settings.store.attemptToPreventCrashes) { this.handlePreventCrash(_this); } @@ -118,7 +130,7 @@ export default definePlugin({ color: "#eed202", title: "Discord has crashed!", body: "Attempting to recover...", - noPersist: true, + noPersist: true }); } catch { } @@ -169,6 +181,10 @@ export default definePlugin({ } } + + // Set isRecovering to false before setting the state to allow us to handle the next crash error correcty, in case it happens + setImmediate(() => isRecovering = false); + try { _this.setState({ error: null, info: null }); } catch (err) { From 46ee193cd0d27d6b9f861858621487ec9c78b397 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:07:51 -0300 Subject: [PATCH 27/70] Fix MessageLogger edit Timestamp component --- src/plugins/messageLogger/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index ef0aa03bb..ef986bf87 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -26,7 +26,7 @@ import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { ChannelStore, FluxDispatcher, i18n, Menu, moment, Parser, Timestamp, UserStore } from "@webpack/common"; +import { ChannelStore, FluxDispatcher, i18n, Menu, Parser, Timestamp, UserStore } from "@webpack/common"; import overlayStyle from "./deleteStyleOverlay.css?managed"; import textStyle from "./deleteStyleText.css?managed"; @@ -122,7 +122,7 @@ export default definePlugin({ makeEdit(newMessage: any, oldMessage: any): any { return { - timestamp: moment?.call(newMessage.edited_timestamp), + timestamp: new Date(newMessage.edited_timestamp), content: oldMessage.content }; }, From 93b2095d71a0ac9d68d9f9859dd8b59687885be0 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:16:31 -0300 Subject: [PATCH 28/70] Fix FakeNitro hyperlinks not working sometimes It is caused by emoji names with conflict with default emojis, but have a letter capitalized --- src/plugins/fakeNitro/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/fakeNitro/index.ts b/src/plugins/fakeNitro/index.ts index ed3ec59dd..560cae381 100644 --- a/src/plugins/fakeNitro/index.ts +++ b/src/plugins/fakeNitro/index.ts @@ -838,7 +838,7 @@ export default definePlugin({ url.searchParams.set("name", emoji.name); messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => { - return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[:${emoji.name}:](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`; + return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`; }); } } @@ -864,7 +864,7 @@ export default definePlugin({ url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("name", emoji.name); - return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[:${emoji.name}:](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`; + return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`; }); }); }, From f1bdf385ebbd5418cf6798e599e2c1d4240ecadb Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:25:10 -0300 Subject: [PATCH 29/70] Bump to 1.6.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 08078d3d5..5ffac7ff5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.6.8", + "version": "1.6.9", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 89367e3b2a9e02767f8aca56e3ac7c34ac0d285e Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 15 Feb 2024 09:20:27 +0100 Subject: [PATCH 30/70] WebContextMenus: fix copying images it broke because of the new url expiry parameters --- src/plugins/webContextMenus.web/index.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index 5f6beca2c..faa240783 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -47,18 +47,23 @@ const settings = definePluginSettings({ }); const MEDIA_PROXY_URL = "https://media.discordapp.net"; -const CDN_URL = "https://cdn.discordapp.com"; +const CDN_URL = "cdn.discordapp.com"; -function fixImageUrl(urlString: string, explodeWebp: boolean) { +function fixImageUrl(urlString: string) { const url = new URL(urlString); - if (url.origin === CDN_URL) return urlString; - if (url.origin === MEDIA_PROXY_URL) return CDN_URL + url.pathname; + if (url.host === CDN_URL) return urlString; url.searchParams.delete("width"); url.searchParams.delete("height"); - url.searchParams.set("quality", "lossless"); - if (explodeWebp && url.searchParams.get("format") === "webp") - url.searchParams.set("format", "png"); + + if (url.origin === MEDIA_PROXY_URL) { + url.host = CDN_URL; + url.searchParams.delete("size"); + url.searchParams.delete("quality"); + url.searchParams.delete("format"); + } else { + url.searchParams.set("quality", "lossless"); + } return url.toString(); } @@ -199,7 +204,7 @@ export default definePlugin({ ], async copyImage(url: string) { - url = fixImageUrl(url, true); + url = fixImageUrl(url); let imageData = await fetch(url).then(r => r.blob()); if (imageData.type !== "image/png") { @@ -231,7 +236,7 @@ export default definePlugin({ }, async saveImage(url: string) { - url = fixImageUrl(url, false); + url = fixImageUrl(url); const data = await fetchImage(url); if (!data) return; From bc0a55053d709533143f66a2bc4b6914326e907d Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 15 Feb 2024 10:12:33 +0100 Subject: [PATCH 31/70] MessageLinkEmbeds: fix erroring on some invalid message links --- src/plugins/messageLinkEmbeds/index.tsx | 35 ++++++++++--------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index 762829992..56facf2e7 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -31,13 +31,14 @@ import { GuildStore, MessageStore, Parser, + PermissionsBits, PermissionStore, RestAPI, Text, TextAndImagesSettingsStores, UserStore } from "@webpack/common"; -import { Channel, Guild, Message } from "discord-types/general"; +import { Channel, Message } from "discord-types/general"; const messageCache = new Map id && idList.includes(id)); + const isListed = [linkedChannel.guild_id, channelID, message.author.id].some(id => id && idList.includes(id)); if (listMode === "blacklist" && isListed) continue; if (listMode === "whitelist" && !isListed) continue; @@ -265,8 +265,7 @@ function MessageEmbedAccessory({ message }: { message: Message; }) { const messageProps: MessageEmbedProps = { message: withEmbeddedBy(linkedMessage, [...embeddedBy, message.id]), - channel: linkedChannel, - guildID + channel: linkedChannel }; const type = settings.store.automodEmbeds; @@ -280,10 +279,8 @@ function MessageEmbedAccessory({ message }: { message: Message; }) { return accessories.length ? <>{accessories} : null; } -function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbedProps): JSX.Element | null { - const isDM = guildID === "@me"; - - const guild = !isDM && GuildStore.getGuild(channel.guild_id); +function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null { + const guild = !channel.isDM() && GuildStore.getGuild(channel.guild_id); const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]); @@ -293,11 +290,8 @@ function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbe color: "var(--background-secondary)", author: { name: - {isDM ? "Direct Message - " : (guild as Guild).name + " - "} - {isDM - ? Parser.parse(`<@${dmReceiver.id}>`) - : Parser.parse(`<#${channel.id}>`) - } + {channel.isDM() && Direct Message - } + {Parser.parse(channel.isDM() ? `<@${dmReceiver.id}>` : `<#${channel.id}>`)} , iconProxyURL: guild ? `https://${window.GLOBAL_ENV.CDN_HOST}/icons/${guild.id}/${guild.icon}.png` @@ -318,9 +312,8 @@ function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbe } function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null { - const { message, channel, guildID } = props; + const { message, channel } = props; const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting(); - const isDM = guildID === "@me"; const images = getImages(message); const { parse } = Parser; @@ -328,11 +321,11 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null { channel={channel} childrenAccessories={ - {isDM + {channel.isDM() ? parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`) : parse(`<#${channel.id}>`) } - {isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name} + {channel.isDM() && - Direct Message} } compact={compact} From a501da692f5c6584ae202fade81794e9fee00ef4 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 15 Feb 2024 10:36:01 +0100 Subject: [PATCH 32/70] MessageLinkEmbeds: fix group dm support, improve ui --- src/plugins/messageLinkEmbeds/index.tsx | 79 +++++++++++-------- src/plugins/mutualGroupDMs/index.tsx | 5 +- .../serverProfile/GuildProfileModal.tsx | 12 +-- src/plugins/viewIcons/index.tsx | 21 ++--- src/webpack/common/types/utils.d.ts | 45 +++++++++++ src/webpack/common/utils.ts | 2 + 6 files changed, 107 insertions(+), 57 deletions(-) diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index 56facf2e7..50c09ec9f 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -29,6 +29,7 @@ import { ChannelStore, FluxDispatcher, GuildStore, + IconUtils, MessageStore, Parser, PermissionsBits, @@ -50,6 +51,7 @@ const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageCo const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)"); const SearchResultClasses = findByPropsLazy("message", "searchResult"); +const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor"); const messageLinkRegex = /(?{accessories} : null; } +function getChannelLabelAndIconUrl(channel: Channel) { + if (channel.isDM()) return ["Direct Message", IconUtils.getUserAvatarURL(UserStore.getUser(channel.recipients[0]))]; + if (channel.isGroupDM()) return ["Group DM", IconUtils.getChannelIconURL(channel)]; + return ["Server", IconUtils.getGuildIconURL(GuildStore.getGuild(channel.guild_id))]; +} + function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null { - const guild = !channel.isDM() && GuildStore.getGuild(channel.guild_id); const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]); + const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel); - return - {channel.isDM() && Direct Message - } - {Parser.parse(channel.isDM() ? `<@${dmReceiver.id}>` : `<#${channel.id}>`)} - , - iconProxyURL: guild - ? `https://${window.GLOBAL_ENV.CDN_HOST}/icons/${guild.id}/${guild.icon}.png` - : `https://${window.GLOBAL_ENV.CDN_HOST}/avatars/${dmReceiver.id}/${dmReceiver.avatar}` - } - }} - renderDescription={() => ( -
- -
- )} - />; + return ( + + {channelLabel} - + {Parser.parse(channel.isDM() ? `<@${dmReceiver.id}>` : `<#${channel.id}>`)} + , + iconProxyURL: iconUrl + } + }} + renderDescription={() => ( +
+ +
+ )} + /> + ); } function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null { @@ -317,15 +325,20 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null { const images = getImages(message); const { parse } = Parser; + const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel); + return - {channel.isDM() - ? parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`) - : parse(`<#${channel.id}>`) - } - {channel.isDM() && - Direct Message} + + {iconUrl && } + + {channelLabel} - + {channel.isDM() + ? Parser.parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`) + : Parser.parse(`<#${channel.id}>`) + } + } compact={compact} diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 226d000f1..40d5201cb 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -20,11 +20,10 @@ import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { Avatar, ChannelStore, Clickable, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common"; +import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common"; import { Channel, User } from "discord-types/general"; const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); -const AvatarUtils = findByPropsLazy("getChannelIconURL"); const UserUtils = findByPropsLazy("getGlobalName"); const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds"); @@ -71,7 +70,7 @@ export default definePlugin({ }} > diff --git a/src/plugins/serverProfile/GuildProfileModal.tsx b/src/plugins/serverProfile/GuildProfileModal.tsx index be1f2cd54..834367e0a 100644 --- a/src/plugins/serverProfile/GuildProfileModal.tsx +++ b/src/plugins/serverProfile/GuildProfileModal.tsx @@ -12,10 +12,9 @@ import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; -import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; +import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; import { Guild, User } from "discord-types/general"; -const IconUtils = findByPropsLazy("getGuildBannerURL"); const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper"); const FriendRow = findExportedComponentLazy("FriendRow"); @@ -65,10 +64,7 @@ function GuildProfileModal({ guild }: GuildProps) { const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo); - const bannerUrl = guild.banner && IconUtils.getGuildBannerURL({ - id: guild.id, - banner: guild.banner - }, true).replace(/\?size=\d+$/, "?size=1024"); + const bannerUrl = guild.banner && IconUtils.getGuildBannerURL(guild, true)!.replace(/\?size=\d+$/, "?size=1024"); const iconUrl = guild.icon && IconUtils.getGuildIconURL({ id: guild.id, @@ -89,7 +85,7 @@ function GuildProfileModal({ guild }: GuildProps) { )}
- {guild.icon + {iconUrl ? openImage(BannerStore.getUserAvatarURL(user, true))} + action={() => openImage(IconUtils.getUserAvatarURL(user, true))} icon={ImageIcon} /> {memberAvatar && ( openImage(BannerStore.getGuildMemberAvatarURLSimple({ + action={() => openImage(IconUtils.getGuildMemberAvatarURLSimple({ userId: user.id, avatar: memberAvatar, - guildId, + guildId: guildId!, canAnimate: true - }, true))} + }))} icon={ImageIcon} /> )} @@ -124,11 +122,11 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon id="view-icon" label="View Icon" action={() => - openImage(BannerStore.getGuildIconURL({ + openImage(IconUtils.getGuildIconURL({ id, icon, canAnimate: true - })) + })!) } icon={ImageIcon} /> @@ -138,10 +136,7 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon id="view-banner" label="View Banner" action={() => - openImage(BannerStore.getGuildBannerURL({ - id, - banner, - }, true)) + openImage(IconUtils.getGuildBannerURL(guild, true)!) } icon={ImageIcon} /> diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts index 246659146..2005581a1 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/src/webpack/common/types/utils.d.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { Guild, GuildMember } from "discord-types/general"; import type { ReactNode } from "react"; import type { FluxEvents } from "./fluxEvents"; @@ -182,3 +183,47 @@ export interface NavigationRouter { getLastRouteChangeSource(): any; getLastRouteChangeSourceLocationStack(): any; } + +export interface IconUtils { + getUserAvatarURL(user: User, canAnimate?: boolean, size?: number, format?: string): string; + getDefaultAvatarURL(id: string, discriminator?: string): string; + getUserBannerURL(data: { id: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined; + getAvatarDecorationURL(dara: { avatarDecoration: string, size: number; canCanimate?: boolean; }): string | undefined; + + getGuildMemberAvatarURL(member: GuildMember, canAnimate?: string): string | null; + getGuildMemberAvatarURLSimple(data: { guildId: string, userId: string, avatar: string, canAnimate?: boolean; size?: number; }): string; + getGuildMemberBannerURL(data: { id: string, guildId: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined; + + getGuildIconURL(data: { id: string, icon?: string, size?: number, canAnimate?: boolean; }): string | undefined; + getGuildBannerURL(guild: Guild, canAnimate?: boolean): string | null; + + getChannelIconURL(data: { id: string; icon?: string; applicationId?: string; size?: number; }): string | undefined; + getEmojiURL(data: { id: string, animated: boolean, size: number, forcePNG?: boolean; }): string; + + hasAnimatedGuildIcon(guild: Guild): boolean; + isAnimatedIconHash(hash: string): boolean; + + getGuildSplashURL: any; + getGuildDiscoverySplashURL: any; + getGuildHomeHeaderURL: any; + getResourceChannelIconURL: any; + getNewMemberActionIconURL: any; + getGuildTemplateIconURL: any; + getApplicationIconURL: any; + getGameAssetURL: any; + getVideoFilterAssetURL: any; + + getGuildMemberAvatarSource: any; + getUserAvatarSource: any; + getGuildSplashSource: any; + getGuildDiscoverySplashSource: any; + makeSource: any; + getGameAssetSource: any; + getGuildIconSource: any; + getGuildTemplateIconSource: any; + getGuildBannerSource: any; + getGuildHomeHeaderSource: any; + getChannelIconSource: any; + getApplicationIconSource: any; + getAnimatableSourceWithFallback: any; +} diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index c62f745a9..7060573d5 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -137,3 +137,5 @@ export const { persist: zustandPersist }: typeof import("zustand/middleware") = export const MessageActions = findByPropsLazy("editMessage", "sendMessage"); export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal"); export const InviteActions = findByPropsLazy("resolveInvite"); + +export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL"); From 7b960716439cba093202f9ba37dd6b258be39017 Mon Sep 17 00:00:00 2001 From: Syncx <47534062+Syncxv@users.noreply.github.com> Date: Fri, 16 Feb 2024 17:52:09 +1100 Subject: [PATCH 33/70] fix ImageZoom patch (#2181) --- src/plugins/imageZoom/components/Magnifier.tsx | 11 ++++++----- src/plugins/imageZoom/index.tsx | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/plugins/imageZoom/components/Magnifier.tsx b/src/plugins/imageZoom/components/Magnifier.tsx index 6a3fc05a0..816717350 100644 --- a/src/plugins/imageZoom/components/Magnifier.tsx +++ b/src/plugins/imageZoom/components/Magnifier.tsx @@ -123,14 +123,13 @@ export const Magnifier: React.FC = ({ instance, size: initialSiz waitFor(() => instance.state.readyState === "READY", () => { const elem = document.getElementById(ELEMENT_ID) as HTMLDivElement; element.current = elem; - elem.firstElementChild!.setAttribute("draggable", "false"); + elem.querySelector("img,video")?.setAttribute("draggable", "false"); if (instance.props.animated) { originalVideoElementRef.current = elem!.querySelector("video")!; originalVideoElementRef.current.addEventListener("timeupdate", syncVideos); - setReady(true); - } else { - setReady(true); } + + setReady(true); }); document.addEventListener("keydown", onKeyDown); document.addEventListener("keyup", onKeyUp); @@ -155,7 +154,9 @@ export const Magnifier: React.FC = ({ instance, size: initialSiz if (!ready) return null; - const box = element.current!.getBoundingClientRect(); + const box = element.current?.getBoundingClientRect(); + + if (!box) return null; return (
Date: Sun, 18 Feb 2024 17:42:26 +0100 Subject: [PATCH 34/70] MuteNewGuild -> NewGuildSettings; add 'show all channels' option (#2065) Co-authored-by: MopigamesYT Co-authored-by: V --- .../index.tsx | 25 +++++++++++++------ src/utils/constants.ts | 4 +++ 2 files changed, 22 insertions(+), 7 deletions(-) rename src/plugins/{muteNewGuild => newGuildSettings}/index.tsx (72%) diff --git a/src/plugins/muteNewGuild/index.tsx b/src/plugins/newGuildSettings/index.tsx similarity index 72% rename from src/plugins/muteNewGuild/index.tsx rename to src/plugins/newGuildSettings/index.tsx index 08c558a95..ff6f1c261 100644 --- a/src/plugins/muteNewGuild/index.tsx +++ b/src/plugins/newGuildSettings/index.tsx @@ -16,16 +16,18 @@ * along with this program. If not, see . */ -import { definePluginSettings } from "@api/Settings"; +import { definePluginSettings,migratePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings"); +const { toggleShowAllChannels } = findByPropsLazy("toggleShowAllChannels"); +const { isOptInEnabledForGuild } = findByPropsLazy("isOptInEnabledForGuild"); const settings = definePluginSettings({ guild: { - description: "Mute Guild", + description: "Mute Guild automatically", type: OptionType.BOOLEAN, default: true }, @@ -38,13 +40,20 @@ const settings = definePluginSettings({ description: "Suppress All Role @mentions", type: OptionType.BOOLEAN, default: true + }, + showAllChannels: { + description: "Show all channels automatically", + type: OptionType.BOOLEAN, + default: true } }); +migratePluginSettings("NewGuildSettings", "MuteNewGuild"); export default definePlugin({ - name: "MuteNewGuild", - description: "Mutes newly joined guilds", - authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince], + name: "NewGuildSettings", + description: "Automatically mute new servers and change various other settings upon joining", + tags: ["MuteNewGuild", "mute", "server"], + authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince, Devs.Mopi], patches: [ { find: ",acceptInvite(", @@ -70,7 +79,9 @@ export default definePlugin({ muted: settings.store.guild, suppress_everyone: settings.store.everyone, suppress_roles: settings.store.role - } - ); + }); + if (settings.store.showAllChannels && isOptInEnabledForGuild(guildId)) { + toggleShowAllChannels(guildId); + } } }); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 55af93605..4b8caf825 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -399,6 +399,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "maisy", id: 257109471589957632n, }, + Mopi: { + name: "Mopi", + id: 1022189106614243350n + }, Grzesiek11: { name: "Grzesiek11", id: 368475654662127616n, From 604f4c49aff587a6f0d5038c50123c040d0c2527 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sun, 18 Feb 2024 18:22:33 +0100 Subject: [PATCH 35/70] VoiceMessages: Add warning if audio file is not OggOpus --- src/plugins/voiceMessages/index.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx index f4898de68..2393ef2b6 100644 --- a/src/plugins/voiceMessages/index.tsx +++ b/src/plugins/voiceMessages/index.tsx @@ -20,13 +20,15 @@ import "./styles.css"; import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; import { Microphone } from "@components/Icons"; +import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; +import { Margins } from "@utils/margins"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; import definePlugin from "@utils/types"; import { chooseFile } from "@utils/web"; import { findByPropsLazy, findStoreLazy } from "@webpack"; -import { Button, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common"; +import { Button, Card, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common"; import { ComponentType } from "react"; import { VoiceRecorderDesktop } from "./DesktopRecorder"; @@ -164,6 +166,11 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) { fallbackValue: EMPTY_META, }); + const isUnsupportedFormat = blob && ( + !blob.type.startsWith("audio/ogg") + || blob.type.includes("codecs") && !blob.type.includes("opus") + ); + return ( @@ -200,6 +207,16 @@ function Modal({ modalProps }: { modalProps: ModalProps; }) { recording={isRecording} /> + {isUnsupportedFormat && ( + + Voice Messages have to be OggOpus to be playable on iOS. This file is {blob.type} so it will not be playable on iOS. + + + To fix it, first convert it to OggOpus, for example using the convertio web converter + + + )} + From f922f0bc0d4a4da12663b7ef86bf16cd17c59ba9 Mon Sep 17 00:00:00 2001 From: Manti <67705577+mantikafasi@users.noreply.github.com> Date: Tue, 20 Feb 2024 21:13:25 +0300 Subject: [PATCH 36/70] fix reviewdb auth not working on userscript (#2194) --- src/plugins/reviewDB/auth.tsx | 2 +- src/plugins/reviewDB/reviewDbApi.ts | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/plugins/reviewDB/auth.tsx b/src/plugins/reviewDB/auth.tsx index 136001b2d..4cd81f2ea 100644 --- a/src/plugins/reviewDB/auth.tsx +++ b/src/plugins/reviewDB/auth.tsx @@ -59,7 +59,7 @@ export function authorize(callback?: any) { const url = new URL(response.location); url.searchParams.append("clientMod", "vencord"); const res = await fetch(url, { - headers: new Headers({ Accept: "application/json" }) + headers: { Accept: "application/json" } }); if (!res.ok) { diff --git a/src/plugins/reviewDB/reviewDbApi.ts b/src/plugins/reviewDB/reviewDbApi.ts index ec6d9ff3b..3fe6dd061 100644 --- a/src/plugins/reviewDB/reviewDbApi.ts +++ b/src/plugins/reviewDB/reviewDbApi.ts @@ -118,10 +118,10 @@ export async function addReview(review: any): Promise { export async function deleteReview(id: number): Promise { return await rdbRequest(`/users/${id}/reviews`, { method: "DELETE", - headers: new Headers({ + headers: { "Content-Type": "application/json", Accept: "application/json", - }), + }, body: JSON.stringify({ reviewid: id }) @@ -135,10 +135,10 @@ export async function deleteReview(id: number): Promise { export async function reportReview(id: number) { const res = await rdbRequest("/reports", { method: "PUT", - headers: new Headers({ + headers: { "Content-Type": "application/json", Accept: "application/json", - }), + }, body: JSON.stringify({ reviewid: id, }) @@ -150,10 +150,10 @@ export async function reportReview(id: number) { async function patchBlock(action: "block" | "unblock", userId: string) { const res = await rdbRequest("/blocks", { method: "PATCH", - headers: new Headers({ + headers: { "Content-Type": "application/json", Accept: "application/json", - }), + }, body: JSON.stringify({ action: action, discordId: userId @@ -180,9 +180,9 @@ export const unblockUser = (userId: string) => patchBlock("unblock", userId); export async function fetchBlocks(): Promise { const res = await rdbRequest("/blocks", { method: "GET", - headers: new Headers({ + headers: { Accept: "application/json", - }) + } }); if (!res.ok) throw new Error(`${res.status}: ${res.statusText}`); From e3fd954512cb56cf05141f44465429530403a223 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 22 Feb 2024 21:31:15 -0300 Subject: [PATCH 37/70] Fix Decor patch --- src/plugins/decor/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx index ce546d309..8cfd8c036 100644 --- a/src/plugins/decor/index.tsx +++ b/src/plugins/decor/index.tsx @@ -72,7 +72,7 @@ export default definePlugin({ replacement: [ // Add Decor avatar decoration hook to avatar decoration hook { - match: /(?<=TryItOut:\i}\),)(?<=user:(\i).+?)/, + match: /(?<=TryItOut:\i,guildId:\i}\),)(?<=user:(\i).+?)/, replace: "vcDecorAvatarDecoration=$self.useUserDecorAvatarDecoration($1)," }, // Use added hook From 2d8715adf01890d422f3c1471ce0f34c56e5adeb Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 23 Feb 2024 13:06:03 +0100 Subject: [PATCH 38/70] SuperReactionTweaks: only super react by default if user has nitro --- src/plugins/superReactionTweaks/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts index 0e58eb0a8..89197b4c3 100644 --- a/src/plugins/superReactionTweaks/index.ts +++ b/src/plugins/superReactionTweaks/index.ts @@ -7,6 +7,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; +import { UserStore } from "@webpack/common"; export const settings = definePluginSettings({ superReactByDefault: { @@ -49,7 +50,7 @@ export default definePlugin({ find: ".trackEmojiSearchEmpty,200", replacement: { match: /(\.trackEmojiSearchEmpty,200(?=.+?isBurstReaction:(\i).+?(\i===\i\.EmojiIntention.REACTION)).+?\[\2,\i\]=\i\.useState\().+?\)/, - replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.settings.store.superReactByDefault&&${isReactionIntention})` + replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.shouldSuperReactByDefault&&${isReactionIntention})` } } ], @@ -59,5 +60,9 @@ export default definePlugin({ if (settings.store.unlimitedSuperReactionPlaying) return true; if (playingCount <= settings.store.superReactionPlayingLimit) return true; return false; + }, + + get shouldSuperReactByDefault() { + return settings.store.superReactByDefault && UserStore.getCurrentUser().premiumType != null; } }); From 414184ef25387248e9eb810ea725dfab9d3ef82a Mon Sep 17 00:00:00 2001 From: Sqaaakoi Date: Mon, 26 Feb 2024 12:51:09 +1300 Subject: [PATCH 39/70] ImageZoom: negate the border offsetting the lens (#2117) Co-authored-by: Lewis Crichton --- src/plugins/imageZoom/styles.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/imageZoom/styles.css b/src/plugins/imageZoom/styles.css index 51e225c05..c3776d90e 100644 --- a/src/plugins/imageZoom/styles.css +++ b/src/plugins/imageZoom/styles.css @@ -9,6 +9,9 @@ box-shadow: inset 0 0 10px 2px grey; filter: drop-shadow(0 0 2px grey); pointer-events: none; + + /* negate the border offsetting the lens */ + margin: -2px; } .vc-imgzoom-square { From 9958f5a2ea651a7890a613a9a6661654548a0a8a Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 27 Feb 2024 05:04:54 -0300 Subject: [PATCH 40/70] Fix ReviewDB --- src/plugins/reviewDB/components/ReviewsView.tsx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx index 64cea1815..eea92bb81 100644 --- a/src/plugins/reviewDB/components/ReviewsView.tsx +++ b/src/plugins/reviewDB/components/ReviewsView.tsx @@ -32,6 +32,7 @@ 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 { createChannelRecordFromServer } = findByPropsLazy("createChannelRecordFromServer"); interface UserProps { discordId: string; @@ -125,19 +126,7 @@ export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: { const inputType = ChatInputTypes.FORM; inputType.disableAutoFocus = true; - const channel = { - flags_: 256, - guild_id_: null, - id: "0", - getGuildId: () => null, - isPrivate: () => true, - isActiveThread: () => false, - isArchivedLockedThread: () => false, - isDM: () => true, - roles: { "0": { permissions: 0n } }, - getRecipientId: () => "0", - hasFlag: () => false, - }; + const channel = createChannelRecordFromServer({ id: "0", type: 1 }); return ( <> From 5e7b4e9c92ac52e2a3b839390643af0db2fb919e Mon Sep 17 00:00:00 2001 From: Av32000 <59660601+Av32000@users.noreply.github.com> Date: Tue, 27 Feb 2024 11:27:34 +0100 Subject: [PATCH 41/70] SpotifyControls: export album cover as CSS variable for themes (#2197) --vc-spotify-track-image --- src/plugins/spotifyControls/PlayerComponent.tsx | 6 +++++- src/plugins/spotifyControls/index.tsx | 2 +- src/utils/constants.ts | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx index f2370906b..8b3f04bf2 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/plugins/spotifyControls/PlayerComponent.tsx @@ -371,6 +371,10 @@ export function Player() { if (!track || !device?.is_active || shouldHide) return null; + const exportTrackImageStyle = { + "--vc-spotify-track-image": `url(${track?.album?.image?.url || ""})`, + } as React.CSSProperties; + return ( (
@@ -378,7 +382,7 @@ export function Player() {

Check the console for errors

)}> -
+
diff --git a/src/plugins/spotifyControls/index.tsx b/src/plugins/spotifyControls/index.tsx index cfb352efe..d7e4f6454 100644 --- a/src/plugins/spotifyControls/index.tsx +++ b/src/plugins/spotifyControls/index.tsx @@ -31,7 +31,7 @@ function toggleHoverControls(value: boolean) { export default definePlugin({ name: "SpotifyControls", description: "Adds a Spotify player above the account panel", - authors: [Devs.Ven, Devs.afn, Devs.KraXen72], + authors: [Devs.Ven, Devs.afn, Devs.KraXen72, Devs.Av32000], options: { hoverControls: { description: "Show controls on hover", diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 4b8caf825..d66bdc826 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -414,6 +414,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ coolelectronics: { name: "coolelectronics", id: 696392247205298207n, + }, + Av32000: { + name: "Av32000", + id: 593436735380127770n, } } satisfies Record); From b9d0a1c563dfdc0a7b06da5eb7b037314c8260e1 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 27 Feb 2024 11:47:28 +0100 Subject: [PATCH 42/70] SpotifyControls: fix seekbar grabber alignment --- src/plugins/spotifyControls/spotifyStyles.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/plugins/spotifyControls/spotifyStyles.css index 9e585ebec..72383c3e8 100644 --- a/src/plugins/spotifyControls/spotifyStyles.css +++ b/src/plugins/spotifyControls/spotifyStyles.css @@ -170,9 +170,16 @@ /* these importants are necessary, it applies a width and height through inline styles */ height: 10px !important; width: 10px !important; + margin-top: 4px; background-color: var(--interactive-normal); border-color: var(--interactive-normal); color: var(--interactive-normal); + opacity: 0; + transition: opacity 0.1s; +} + +#vc-spotify-progress-bar:hover > [class^="slider"] [class^="grabber"] { + opacity: 1; } #vc-spotify-progress-text { From 27696ed62a8744bbf3f31c02497f238946106153 Mon Sep 17 00:00:00 2001 From: WackyModer <78763021+WackyModer@users.noreply.github.com> Date: Tue, 27 Feb 2024 03:31:51 -0800 Subject: [PATCH 43/70] whoReacted: fix reaction count being off by one (#2209) Co-authored-by: V --- src/plugins/whoReacted/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index 4a2bdeeda..6d994be16 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -69,14 +69,14 @@ function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) { function makeRenderMoreUsers(users: User[]) { return function renderMoreUsers(_label: string, _count: number) { return ( - u.username).join(", ")} > + u.username).join(", ")} > {({ onMouseEnter, onMouseLeave }) => (
- +{users.length - 5} + +{users.length - 4}
)}
From ed5e1be7a4d50d612908fff35fa901e81330ce86 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 27 Feb 2024 09:19:05 -0300 Subject: [PATCH 44/70] Add permissions checks for FakeNitro actions (#2160) Co-authored-by: Vendicated --- src/api/MessageEvents.ts | 10 +- src/plugins/_api/messageEvents.ts | 9 +- src/plugins/fakeNitro/{index.ts => index.tsx} | 121 +++++++++++++----- src/webpack/common/types/utils.d.ts | 1 + 4 files changed, 105 insertions(+), 36 deletions(-) rename src/plugins/fakeNitro/{index.ts => index.tsx} (89%) diff --git a/src/api/MessageEvents.ts b/src/api/MessageEvents.ts index 341b4e678..d6eba748f 100644 --- a/src/api/MessageEvents.ts +++ b/src/api/MessageEvents.ts @@ -74,7 +74,7 @@ export interface MessageExtra { } export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable; -export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable; +export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable; const sendListeners = new Set(); const editListeners = new Set(); @@ -84,7 +84,7 @@ export async function _handlePreSend(channelId: string, messageObj: MessageObjec for (const listener of sendListeners) { try { const result = await listener(channelId, messageObj, extra); - if (result && result.cancel === true) { + if (result?.cancel) { return true; } } catch (e) { @@ -97,11 +97,15 @@ export async function _handlePreSend(channelId: string, messageObj: MessageObjec export async function _handlePreEdit(channelId: string, messageId: string, messageObj: MessageObject) { for (const listener of editListeners) { try { - await listener(channelId, messageId, messageObj); + const result = await listener(channelId, messageId, messageObj); + if (result?.cancel) { + return true; + } } catch (e) { MessageEventsLogger.error("MessageEditHandler: Listener encountered an unknown error\n", e); } } + return false; } /** diff --git a/src/plugins/_api/messageEvents.ts b/src/plugins/_api/messageEvents.ts index bc5f5abf2..1b4a2d15a 100644 --- a/src/plugins/_api/messageEvents.ts +++ b/src/plugins/_api/messageEvents.ts @@ -25,10 +25,13 @@ export default definePlugin({ authors: [Devs.Arjix, Devs.hunt, Devs.Ven], patches: [ { - find: '"MessageActionCreators"', + find: ".Messages.EDIT_TEXTAREA_HELP", replacement: { - match: /async editMessage\(.+?\)\{/, - replace: "$&await Vencord.Api.MessageEvents._handlePreEdit(...arguments);" + match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/, + replace: (match, args) => "" + + `async ${match}` + + `if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` + + "return Promise.resolve({shoudClear:true,shouldRefocus:true});" } }, { diff --git a/src/plugins/fakeNitro/index.ts b/src/plugins/fakeNitro/index.tsx similarity index 89% rename from src/plugins/fakeNitro/index.ts rename to src/plugins/fakeNitro/index.tsx index 560cae381..b9932d291 100644 --- a/src/plugins/fakeNitro/index.ts +++ b/src/plugins/fakeNitro/index.tsx @@ -17,14 +17,14 @@ */ import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents"; -import { definePluginSettings, Settings } from "@api/Settings"; +import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies"; import { getCurrentGuild } from "@utils/discord"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; -import { ChannelStore, EmojiStore, FluxDispatcher, lodash, Parser, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; +import { Alerts, ChannelStore, EmojiStore, FluxDispatcher, Forms, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; import type { Message } from "discord-types/general"; import { applyPalette, GIFEncoder, quantize } from "gifenc"; import type { ReactElement, ReactNode } from "react"; @@ -51,8 +51,6 @@ const PreloadedUserSettingsActionCreators = proxyLazyWebpack(() => UserSettingsA const AppearanceSettingsActionCreators = proxyLazyWebpack(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass)); const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators)); -const USE_EXTERNAL_EMOJIS = 1n << 18n; -const USE_EXTERNAL_STICKERS = 1n << 37n; const enum EmojiIntentions { REACTION = 0, @@ -162,8 +160,23 @@ const settings = definePluginSettings({ description: "Whether to use hyperlinks when sending fake emojis and stickers", type: OptionType.BOOLEAN, default: true - } -}); + }, +}).withPrivateSettings<{ + disableEmbedPermissionCheck: boolean; +}>(); + +function hasPermission(channelId: string, permission: bigint) { + const channel = ChannelStore.getChannel(channelId); + + if (!channel || channel.isPrivate()) return true; + + return PermissionStore.can(permission, channel); +} + +const hasExternalEmojiPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.USE_EXTERNAL_EMOJIS); +const hasExternalStickerPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.USE_EXTERNAL_STICKERS); +const hasEmbedPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.EMBED_LINKS); +const hasAttachmentPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.ATTACH_FILES); export default definePlugin({ name: "FakeNitro", @@ -696,22 +709,6 @@ export default definePlugin({ } }, - hasPermissionToUseExternalEmojis(channelId: string): boolean { - const channel = ChannelStore.getChannel(channelId); - - if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true; - - return PermissionStore.can(USE_EXTERNAL_EMOJIS, channel); - }, - - hasPermissionToUseExternalStickers(channelId: string) { - const channel = ChannelStore.getChannel(channelId); - - if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true; - - return PermissionStore.can(USE_EXTERNAL_STICKERS, channel); - }, - getStickerLink(stickerId: string) { return `https://media.discordapp.net/stickers/${stickerId}.png?size=${settings.store.stickerSize}`; }, @@ -722,7 +719,7 @@ export default definePlugin({ const { frames, width, height } = await parseURL(stickerLink); const gif = GIFEncoder(); - const resolution = Settings.plugins.FakeNitro.stickerSize; + const resolution = settings.store.stickerSize; const canvas = document.createElement("canvas"); canvas.width = resolution; @@ -783,9 +780,38 @@ export default definePlugin({ return (!origStr[offset] || /\s/.test(origStr[offset])) ? "" : " "; } - this.preSend = addPreSendListener((channelId, messageObj, extra) => { + function cannotEmbedNotice() { + return new Promise(resolve => { + Alerts.show({ + 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. + Are you sure you want to send this message? Your FakeNitro items will appear as a link only. + + + You can disable this notice in the plugin settings. + +
, + confirmText: "Send Anyway", + cancelText: "Cancel", + secondaryConfirmText: "Do not show again", + onConfirm: () => resolve(true), + onCloseCallback: () => setImmediate(() => resolve(false)), + onConfirmSecondary() { + settings.store.disableEmbedPermissionCheck = true; + resolve(true); + } + }); + }); + } + + this.preSend = addPreSendListener(async (channelId, messageObj, extra) => { const { guildId } = this; + let hasBypass = false; + stickerBypass: { if (!s.enableStickerBypass) break stickerBypass; @@ -798,7 +824,7 @@ export default definePlugin({ if ("pack_id" in sticker) break stickerBypass; - const canUseStickers = this.canUseStickers && this.hasPermissionToUseExternalStickers(channelId); + const canUseStickers = this.canUseStickers && hasExternalStickerPerms(channelId); if (sticker.available !== false && (canUseStickers || sticker.guild_id === guildId)) break stickerBypass; @@ -812,9 +838,24 @@ export default definePlugin({ } if (sticker.format_type === StickerType.APNG) { - this.sendAnimatedSticker(link, sticker.id, channelId); + if (!hasAttachmentPerms(channelId)) { + Alerts.show({ + title: "Hold on!", + body:
+ + You cannot send this message because it contains an animated FakeNitro sticker, + and you do not have permissions to attach files in the current channel. Please remove the sticker to proceed. + +
+ }); + } else { + this.sendAnimatedSticker(link, sticker.id, channelId); + } + return { cancel: true }; } else { + hasBypass = true; + const url = new URL(link); url.searchParams.set("name", sticker.name); @@ -824,13 +865,15 @@ export default definePlugin({ } if (s.enableEmojiBypass) { - const canUseEmotes = this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId); + const canUseEmotes = this.canUseEmotes && hasExternalEmojiPerms(channelId); for (const emoji of messageObj.validNonShortcutEmojis) { if (!emoji.require_colons) continue; if (emoji.available !== false && canUseEmotes) continue; if (emoji.guildId === guildId && !emoji.animated) continue; + hasBypass = true; + const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`; const url = new URL(emoji.url); @@ -843,16 +886,24 @@ export default definePlugin({ } } + if (hasBypass && !s.disableEmbedPermissionCheck && !hasEmbedPerms(channelId)) { + if (!await cannotEmbedNotice()) { + return { cancel: true }; + } + } + return { cancel: false }; }); - this.preEdit = addPreEditListener((channelId, __, messageObj) => { + this.preEdit = addPreEditListener(async (channelId, __, messageObj) => { if (!s.enableEmojiBypass) return; - const canUseEmotes = this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId); - const { guildId } = this; + let hasBypass = false; + + const canUseEmotes = this.canUseEmotes && hasExternalEmojiPerms(channelId); + messageObj.content = messageObj.content.replace(/(?/ig, (emojiStr, emojiId, offset, origStr) => { const emoji = EmojiStore.getCustomEmojiById(emojiId); if (emoji == null) return emojiStr; @@ -860,12 +911,22 @@ export default definePlugin({ if (emoji.available !== false && canUseEmotes) return emojiStr; if (emoji.guildId === guildId && !emoji.animated) return emojiStr; + hasBypass = true; + const url = new URL(emoji.url); url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("name", emoji.name); return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`; }); + + if (hasBypass && !s.disableEmbedPermissionCheck && !hasEmbedPerms(channelId)) { + if (!await cannotEmbedNotice()) { + return { cancel: true }; + } + } + + return { cancel: false }; }); }, diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts index 2005581a1..3d1c0eea6 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/src/webpack/common/types/utils.d.ts @@ -59,6 +59,7 @@ export interface Alerts { onCancel?(): void; onConfirm?(): void; onConfirmSecondary?(): void; + onCloseCallback?(): void; }): void; /** This is a noop, it does nothing. */ close(): void; From 76de8c424e13c60d63d78894c1894a0ab2390e81 Mon Sep 17 00:00:00 2001 From: Lualt <58912038+LualtOfficial@users.noreply.github.com> Date: Tue, 27 Feb 2024 13:27:37 +0100 Subject: [PATCH 45/70] feat(plugin) FakeNitro: Allow customising hyperlink text (#2192) Co-authored-by: Vendicated --- src/plugins/fakeNitro/index.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index b9932d291..20125c7d1 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -161,6 +161,11 @@ const settings = definePluginSettings({ type: OptionType.BOOLEAN, default: true }, + hyperLinkText: { + description: "What text the hyperlink should use. {{NAME}} will be replaced with the emoji name.", + type: OptionType.STRING, + default: "{{NAME}}" + } }).withPrivateSettings<{ disableEmbedPermissionCheck: boolean; }>(); @@ -880,8 +885,10 @@ export default definePlugin({ url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("name", emoji.name); + const linkText = s.hyperLinkText.replaceAll("{{NAME}}", emoji.name); + messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => { - return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`; + return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${linkText}](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`; }); } } @@ -917,7 +924,9 @@ export default definePlugin({ url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("name", emoji.name); - return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`; + const linkText = s.hyperLinkText.replaceAll("{{NAME}}", emoji.name); + + return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${linkText}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`; }); if (hasBypass && !s.disableEmbedPermissionCheck && !hasEmbedPerms(channelId)) { From 1afa185f578dc54bb3a01a7fcc03d6016870971a Mon Sep 17 00:00:00 2001 From: Andrei Neacsu <58575812+prycaustic@users.noreply.github.com> Date: Tue, 27 Feb 2024 06:30:27 -0600 Subject: [PATCH 46/70] LastfmRichPresence: Add an option to hide the Last.fm logo (#2189) Co-authored-by: V --- src/plugins/lastfm/index.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/plugins/lastfm/index.tsx b/src/plugins/lastfm/index.tsx index 179b8260c..5dfec8a32 100644 --- a/src/plugins/lastfm/index.tsx +++ b/src/plugins/lastfm/index.tsx @@ -170,6 +170,11 @@ const settings = definePluginSettings({ } ], }, + showLastFmLogo: { + description: "show the Last.fm logo by the album cover", + type: OptionType.BOOLEAN, + default: true, + } }); export default definePlugin({ @@ -276,8 +281,10 @@ export default definePlugin({ { large_image: await getApplicationAsset(largeImage), large_text: trackData.album || undefined, - small_image: await getApplicationAsset("lastfm-small"), - small_text: "Last.fm", + ...(settings.store.showLastFmLogo && { + small_image: await getApplicationAsset("lastfm-small"), + small_text: "Last.fm" + }), } : { large_image: await getApplicationAsset("lastfm-large"), large_text: trackData.album || undefined, From 8ccd731aee3a7c797b70a285c6023bcdc8d7d2b1 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 27 Feb 2024 13:41:55 +0100 Subject: [PATCH 47/70] bump to v1.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ffac7ff5..dde55d311 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.6.9", + "version": "1.7.0", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From e0166ef1e6f3962c14e7f5b902cb03675919eec3 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 27 Feb 2024 23:42:57 -0300 Subject: [PATCH 48/70] Fix FakeNitro patch and message content patch error --- src/plugins/fakeNitro/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 20125c7d1..a864a3a67 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -369,8 +369,8 @@ export default definePlugin({ predicate: () => settings.store.transformEmojis, replacement: { // Add the fake nitro emoji notice - match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.*?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/, - replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)` + match: /(?<=emojiDescription:)(\i)(?<=\1=\i\((\i)\).+?)/, + replace: (_, reactNode, props) => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!${props}?.fakeNitroNode?.fake)` } }, // Allow using custom app icons @@ -474,7 +474,7 @@ export default definePlugin({ if (typeof firstContent === "string") { content[0] = firstContent.trimStart(); content[0] || content.shift(); - } else if (firstContent?.type === "span") { + } else if (typeof firstContent?.props.children === "string") { firstContent.props.children = firstContent.props.children.trimStart(); firstContent.props.children || content.shift(); } @@ -484,7 +484,7 @@ export default definePlugin({ if (typeof lastContent === "string") { content[lastIndex] = lastContent.trimEnd(); content[lastIndex] || content.pop(); - } else if (lastContent?.type === "span") { + } else if (typeof firstContent?.props.children === "string") { lastContent.props.children = lastContent.props.children.trimEnd(); lastContent.props.children || content.pop(); } From 7de54a294f0e64c314c7feac087eb4fe6bd03b88 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 27 Feb 2024 23:43:03 -0300 Subject: [PATCH 49/70] Fix NotificationVolume --- src/plugins/notificationVolume/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/notificationVolume/index.ts b/src/plugins/notificationVolume/index.ts index 50eabee73..bc3c7539d 100644 --- a/src/plugins/notificationVolume/index.ts +++ b/src/plugins/notificationVolume/index.ts @@ -27,8 +27,8 @@ export default definePlugin({ { find: "_ensureAudio(){", replacement: { - match: /onloadeddata=\(\)=>\{.\.volume=/, - replace: "$&$self.settings.store.notificationVolume/100*" + match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/, + replace: "$self.settings.store.notificationVolume/100*" }, }, ], From da50c7a19b1755c1827d4a3bb6c4651fe69551c7 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 28 Feb 2024 20:02:37 +0100 Subject: [PATCH 50/70] MemberCount: Also add to server tooltip; refactor code --- src/plugins/memberCount/MemberCount.tsx | 66 +++++++++++ .../memberCount/OnlineMemberCountStore.ts | 52 +++++++++ src/plugins/memberCount/index.tsx | 107 +++++------------- src/plugins/memberCount/style.css | 44 +++++++ 4 files changed, 189 insertions(+), 80 deletions(-) create mode 100644 src/plugins/memberCount/MemberCount.tsx create mode 100644 src/plugins/memberCount/OnlineMemberCountStore.ts create mode 100644 src/plugins/memberCount/style.css diff --git a/src/plugins/memberCount/MemberCount.tsx b/src/plugins/memberCount/MemberCount.tsx new file mode 100644 index 000000000..50665353e --- /dev/null +++ b/src/plugins/memberCount/MemberCount.tsx @@ -0,0 +1,66 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { getCurrentChannel } from "@utils/discord"; +import { SelectedChannelStore, Tooltip, useEffect, useStateFromStores } from "@webpack/common"; + +import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat } from "."; +import { OnlineMemberCountStore } from "./OnlineMemberCountStore"; + +export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) { + const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel()); + + const guildId = isTooltip ? tooltipGuildId! : currentChannel.guild_id; + + const totalCount = useStateFromStores( + [GuildMemberCountStore], + () => GuildMemberCountStore.getMemberCount(guildId) + ); + + let onlineCount = useStateFromStores( + [OnlineMemberCountStore], + () => OnlineMemberCountStore.getCount(guildId) + ); + + const { groups } = useStateFromStores( + [ChannelMemberStore], + () => ChannelMemberStore.getProps(guildId, currentChannel.id) + ); + + if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) { + onlineCount = groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0); + } + + useEffect(() => { + OnlineMemberCountStore.ensureCount(guildId); + }, [guildId]); + + if (totalCount == null) + return null; + + const formattedOnlineCount = onlineCount != null ? numberFormat(onlineCount) : "?"; + + return ( +
+ + {props => ( +
+ + {formattedOnlineCount} +
+ )} +
+ + {props => ( +
+ + {numberFormat(totalCount)} +
+ )} +
+
+ ); +} diff --git a/src/plugins/memberCount/OnlineMemberCountStore.ts b/src/plugins/memberCount/OnlineMemberCountStore.ts new file mode 100644 index 000000000..8790f5e29 --- /dev/null +++ b/src/plugins/memberCount/OnlineMemberCountStore.ts @@ -0,0 +1,52 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { proxyLazy } from "@utils/lazy"; +import { sleep } from "@utils/misc"; +import { Queue } from "@utils/Queue"; +import { Flux, FluxDispatcher, GuildChannelStore, PrivateChannelsStore } from "@webpack/common"; + +export const OnlineMemberCountStore = proxyLazy(() => { + const preloadQueue = new Queue(); + + const onlineMemberMap = new Map(); + + class OnlineMemberCountStore extends Flux.Store { + getCount(guildId: string) { + return onlineMemberMap.get(guildId); + } + + async _ensureCount(guildId: string) { + if (onlineMemberMap.has(guildId)) return; + + await PrivateChannelsStore.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id); + } + + ensureCount(guildId: string) { + if (onlineMemberMap.has(guildId)) return; + + preloadQueue.push(() => + this._ensureCount(guildId) + .then( + () => sleep(200), + () => sleep(200) + ) + ); + } + } + + return new OnlineMemberCountStore(FluxDispatcher, { + GUILD_MEMBER_LIST_UPDATE({ guildId, groups }: { guildId: string, groups: { count: number; id: string; }[]; }) { + onlineMemberMap.set( + guildId, + groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0) + ); + }, + ONLINE_GUILD_MEMBER_COUNT_UPDATE({ guildId, count }) { + onlineMemberMap.set(guildId, count); + } + }); +}); diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx index d9cd548e9..eb4ce372c 100644 --- a/src/plugins/memberCount/index.tsx +++ b/src/plugins/memberCount/index.tsx @@ -16,101 +16,48 @@ * along with this program. If not, see . */ +import "./style.css"; + +import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; -import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; -import { getCurrentChannel } from "@utils/discord"; import definePlugin from "@utils/types"; import { findStoreLazy } from "@webpack"; -import { SelectedChannelStore, Tooltip, useStateFromStores } from "@webpack/common"; import { FluxStore } from "@webpack/types"; -const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; }; -const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & { +import { MemberCount } from "./MemberCount"; + +export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; }; +export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & { getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; }; }; const sharedIntlNumberFormat = new Intl.NumberFormat(); -const numberFormat = (value: number) => sharedIntlNumberFormat.format(value); - -function MemberCount() { - const { id: channelId, guild_id: guildId } = useStateFromStores([SelectedChannelStore], () => getCurrentChannel()); - const { groups } = useStateFromStores( - [ChannelMemberStore], - () => ChannelMemberStore.getProps(guildId, channelId) - ); - const total = useStateFromStores( - [GuildMemberCountStore], - () => GuildMemberCountStore.getMemberCount(guildId) - ); - - if (total == null) - return null; - - const online = - (groups.length === 1 && groups[0].id === "unknown") - ? 0 - : groups.reduce((count, curr) => count + (curr.id === "offline" ? 0 : curr.count), 0); - - return ( - - - {props => ( -
- - {numberFormat(online)} -
- )} -
- - {props => ( -
- - {numberFormat(total)} -
- )} -
-
- ); -} +export const numberFormat = (value: number) => sharedIntlNumberFormat.format(value); +export const cl = classNameFactory("vc-membercount-"); export default definePlugin({ name: "MemberCount", - description: "Shows the amount of online & total members in the server member list", + description: "Shows the amount of online & total members in the server member list and tooltip", authors: [Devs.Ven, Devs.Commandtechno], - patches: [{ - find: "{isSidebarVisible:", - replacement: { - match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, - replace: ":[$1?.startsWith('members')?$self.render():null,$2" + patches: [ + { + find: "{isSidebarVisible:", + replacement: { + match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/, + replace: ":[$1?.startsWith('members')?$self.render():null,$2" + } + }, + { + find: ".invitesDisabledTooltip", + replacement: { + match: /(?<=\.VIEW_AS_ROLES_MENTIONS_WARNING.{0,100})]/, + replace: ",$self.renderTooltip(arguments[0].guild)]" + } } - }], + ], - render: ErrorBoundary.wrap(MemberCount, { noop: true }) + render: ErrorBoundary.wrap(MemberCount, { noop: true }), + renderTooltip: ErrorBoundary.wrap(guild => , { noop: true }) }); diff --git a/src/plugins/memberCount/style.css b/src/plugins/memberCount/style.css new file mode 100644 index 000000000..f43bff830 --- /dev/null +++ b/src/plugins/memberCount/style.css @@ -0,0 +1,44 @@ +.vc-membercount-widget { + display: flex; + align-content: center; + + --color-online: var(--green-360); + --color-total: var(--primary-400); +} + +.vc-membercount-tooltip { + margin-top: 0.25em; + margin-left: 2px; +} + +.vc-membercount-member-list { + justify-content: center; + margin-top: 1em; + padding-inline: 1em; +} + +.vc-membercount-online { + color: var(--color-online); +} + +.vc-membercount-total { + color: var(--color-total); +} + +.vc-membercount-online-dot { + background-color: var(--color-online); + display: inline-block; + width: 12px; + height: 12px; + border-radius: 50%; + margin-right: 0.5em; +} + +.vc-membercount-total-dot { + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + border: 3px solid var(--color-total); + margin: 0 0.5em 0 1em; +} From 3ebde1aae8d54d96a9548911f4c8ad442092ea82 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 1 Mar 2024 00:18:09 +0100 Subject: [PATCH 51/70] fix some minor bugs --- scripts/generateReport.ts | 3 ++- src/plugins/fakeNitro/index.tsx | 4 ++-- src/webpack/webpack.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 0a17e8d7e..33b099ef8 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -428,10 +428,11 @@ function runTime(token: string) { if (searchType === "findComponent") method = "find"; if (searchType === "findExportedComponent") method = "findByProps"; - if (searchType === "waitFor" || searchType === "waitForComponent" || searchType === "waitForStore") { + if (searchType === "waitFor" || searchType === "waitForComponent") { if (typeof args[0] === "string") method = "findByProps"; else method = "find"; } + if (searchType === "waitForStore") method = "findStore"; try { let result: any; diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index a864a3a67..1cf1e536f 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -474,7 +474,7 @@ export default definePlugin({ if (typeof firstContent === "string") { content[0] = firstContent.trimStart(); content[0] || content.shift(); - } else if (typeof firstContent?.props.children === "string") { + } else if (typeof firstContent?.props?.children === "string") { firstContent.props.children = firstContent.props.children.trimStart(); firstContent.props.children || content.shift(); } @@ -484,7 +484,7 @@ export default definePlugin({ if (typeof lastContent === "string") { content[lastIndex] = lastContent.trimEnd(); content[lastIndex] || content.pop(); - } else if (typeof firstContent?.props.children === "string") { + } else if (typeof lastContent?.props?.children === "string") { lastContent.props.children = lastContent.props.children.trimEnd(); lastContent.props.children || content.pop(); } diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index d65f57fcb..a68890a83 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -83,8 +83,8 @@ export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) { return true; } +let devToolsOpen = false; if (IS_DEV && IS_DISCORD_DESKTOP) { - var devToolsOpen = false; // At this point in time, DiscordNative has not been exposed yet, so setImmediate is needed setTimeout(() => { DiscordNative/* just to make sure */?.window.setDevtoolsCallbacks(() => devToolsOpen = true, () => devToolsOpen = false); From 9179f55bf241453cca020e2d8d7e59929a2028b2 Mon Sep 17 00:00:00 2001 From: "[object Object]" Date: Thu, 29 Feb 2024 16:26:32 -0800 Subject: [PATCH 52/70] fix Vencloud not working on UserScript (#2213) Co-authored-by: V --- browser/GMPolyfill.js | 2 +- src/components/VencordSettings/CloudTab.tsx | 4 +--- src/utils/cloud.tsx | 2 +- src/utils/settingsSync.ts | 12 +++++------- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/browser/GMPolyfill.js b/browser/GMPolyfill.js index f8801551e..387389ce6 100644 --- a/browser/GMPolyfill.js +++ b/browser/GMPolyfill.js @@ -62,7 +62,7 @@ function GM_fetch(url, opt) { resp.arrayBuffer = () => blobTo("arrayBuffer", blob); resp.text = () => blobTo("text", blob); resp.json = async () => JSON.parse(await blobTo("text", blob)); - resp.headers = new Headers(parseHeaders(resp.responseHeaders)); + resp.headers = parseHeaders(resp.responseHeaders); resp.ok = resp.status >= 200 && resp.status < 300; resolve(resp); }; diff --git a/src/components/VencordSettings/CloudTab.tsx b/src/components/VencordSettings/CloudTab.tsx index 0392a451c..080dd8dd9 100644 --- a/src/components/VencordSettings/CloudTab.tsx +++ b/src/components/VencordSettings/CloudTab.tsx @@ -39,9 +39,7 @@ function validateUrl(url: string) { async function eraseAllData() { const res = await fetch(new URL("/v1/", getCloudUrl()), { method: "DELETE", - headers: new Headers({ - Authorization: await getCloudAuth() - }) + headers: { Authorization: await getCloudAuth() } }); if (!res.ok) { diff --git a/src/utils/cloud.tsx b/src/utils/cloud.tsx index f56c78dc5..508b1c7ef 100644 --- a/src/utils/cloud.tsx +++ b/src/utils/cloud.tsx @@ -106,7 +106,7 @@ export async function authorizeCloud() { try { const res = await fetch(location, { - headers: new Headers({ Accept: "application/json" }) + headers: { Accept: "application/json" } }); const { secret } = await res.json(); if (secret) { diff --git a/src/utils/settingsSync.ts b/src/utils/settingsSync.ts index ec32e2b1e..9a0f260af 100644 --- a/src/utils/settingsSync.ts +++ b/src/utils/settingsSync.ts @@ -118,10 +118,10 @@ export async function putCloudSettings(manual?: boolean) { try { const res = await fetch(new URL("/v1/settings", getCloudUrl()), { method: "PUT", - headers: new Headers({ + headers: { Authorization: await getCloudAuth(), "Content-Type": "application/octet-stream" - }), + }, body: deflateSync(new TextEncoder().encode(settings)) }); @@ -162,11 +162,11 @@ export async function getCloudSettings(shouldNotify = true, force = false) { try { const res = await fetch(new URL("/v1/settings", getCloudUrl()), { method: "GET", - headers: new Headers({ + headers: { Authorization: await getCloudAuth(), Accept: "application/octet-stream", "If-None-Match": Settings.cloud.settingsSyncVersion.toString() - }), + }, }); if (res.status === 404) { @@ -251,9 +251,7 @@ export async function deleteCloudSettings() { try { const res = await fetch(new URL("/v1/settings", getCloudUrl()), { method: "DELETE", - headers: new Headers({ - Authorization: await getCloudAuth() - }), + headers: { Authorization: await getCloudAuth() }, }); if (!res.ok) { From 1a1156e1ed73f71a79cbb8444990928731dbfb44 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 1 Mar 2024 01:09:55 -0300 Subject: [PATCH 53/70] Add more settings to IgnoreActivities (#2153) --- src/plugins/ignoreActivities/index.tsx | 148 +++++++++++++++++++++++-- 1 file changed, 138 insertions(+), 10 deletions(-) diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index 4e747f363..c04ce1c56 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -5,12 +5,14 @@ */ import * as DataStore from "@api/DataStore"; -import { definePluginSettings } from "@api/Settings"; +import { definePluginSettings, Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; +import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; +import { Margins } from "@utils/margins"; +import definePlugin, { OptionType } from "@utils/types"; import { findStoreLazy } from "@webpack"; -import { StatusSettingsStores, Tooltip } from "webpack/common"; +import { Button, Forms, showToast, StatusSettingsStores, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common"; const enum ActivitiesTypes { Game, @@ -69,7 +71,113 @@ function handleActivityToggle(e: React.MouseEvent StatusSettingsStores.ShowCurrentGame.updateSetting(old => old); } -const settings = definePluginSettings({}).withPrivateSettings<{ +function ImportCustomRPCComponent() { + return ( + + Import the application id of the CustomRPC plugin to the allowed list +
+ +
+
+ ); +} + +let allowedIdsPushID: ((id: string) => boolean) | null = null; + +function AllowedIdsComponent(props: { setValue: (value: string) => void; }) { + const [allowedIds, setAllowedIds] = useState(settings.store.allowedIds ?? ""); + + allowedIdsPushID = (id: string) => { + const currentIds = new Set(allowedIds.split(",").map(id => id.trim()).filter(Boolean)); + + const isAlreadyAdded = currentIds.has(id) || (currentIds.add(id), false); + + const ids = Array.from(currentIds).join(", "); + setAllowedIds(ids); + props.setValue(ids); + + return isAlreadyAdded; + }; + + useEffect(() => () => { + allowedIdsPushID = null; + }, []); + + function handleChange(newValue: string) { + setAllowedIds(newValue); + props.setValue(newValue); + } + + return ( + + Allowed List + Comma separated list of activity IDs to allow (Useful for allowing RPC activities and CustomRPC) + + + ); +} + +const settings = definePluginSettings({ + importCustomRPC: { + type: OptionType.COMPONENT, + description: "", + component: () => + }, + allowedIds: { + type: OptionType.COMPONENT, + description: "", + default: "", + onChange(newValue: string) { + const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean)); + settings.store.allowedIds = Array.from(ids).join(", "); + }, + component: props => + }, + ignorePlaying: { + type: OptionType.BOOLEAN, + description: "Ignore all playing activities (These are usually game and RPC activities)", + default: false + }, + ignoreStreaming: { + type: OptionType.BOOLEAN, + description: "Ignore all streaming activities", + default: false + }, + ignoreListening: { + type: OptionType.BOOLEAN, + description: "Ignore all listening activities (These are usually spotify activities)", + default: false + }, + ignoreWatching: { + type: OptionType.BOOLEAN, + description: "Ignore all watching activities", + default: false + }, + ignoreCompeting: { + type: OptionType.BOOLEAN, + description: "Ignore all competing activities (These are normally special game activities)", + default: false + } +}).withPrivateSettings<{ ignoredActivities: IgnoredActivity[]; }>(); @@ -77,10 +185,26 @@ function getIgnoredActivities() { return settings.store.ignoredActivities ??= []; } +function isActivityTypeIgnored(type: number, id?: string) { + if (id && settings.store.allowedIds.includes(id)) { + return false; + } + + switch (type) { + case 0: return settings.store.ignorePlaying; + case 1: return settings.store.ignoreStreaming; + case 2: return settings.store.ignoreListening; + case 3: return settings.store.ignoreWatching; + case 5: return settings.store.ignoreCompeting; + } + + return false; +} + export default definePlugin({ name: "IgnoreActivities", authors: [Devs.Nuckyz], - description: "Ignore activities from showing up on your status ONLY. You can configure which ones are ignored from the Registered Games and Activities tabs.", + description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.", settings, @@ -141,13 +265,17 @@ export default definePlugin({ }, isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) { - if (props.type === 0 || props.type === 3) { - if (props.application_id != null) return !getIgnoredActivities().some(activity => activity.id === props.application_id); - else { - const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath; - if (exePath) return !getIgnoredActivities().some(activity => activity.id === exePath); + if (isActivityTypeIgnored(props.type, props.application_id)) return false; + + if (props.application_id != null) { + return !getIgnoredActivities().some(activity => activity.id === props.application_id) || settings.store.allowedIds.includes(props.application_id); + } else { + const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath; + if (exePath) { + return !getIgnoredActivities().some(activity => activity.id === exePath); } } + return true; }, 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 54/70] 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 55/70] 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 56/70] 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 57/70] 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 58/70] 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 59/70] 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 60/70] 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 61/70] 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 62/70] 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 63/70] 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 64/70] 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 65/70] 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 66/70] 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 67/70] 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 68/70] 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 69/70] 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 70/70] 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": {