From 537fc5e33dbc284ab6e3c3fc93eb7aaabf8d73b7 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 29 May 2024 04:57:18 -0300 Subject: [PATCH] feat(API): updateMessage API for forcing re-renders --- src/api/MessageUpdater.ts | 29 ++++++++++++++++ src/api/index.ts | 6 ++++ src/plugins/_api/messageUpdater.ts | 37 +++++++++++++++++++++ src/plugins/fakeNitro/index.tsx | 2 +- src/plugins/invisibleChat.desktop/index.tsx | 14 +++----- src/plugins/messageLinkEmbeds/index.tsx | 12 ++----- src/webpack/common/types/stores.d.ts | 25 ++++++++++++++ src/webpack/common/utils.ts | 1 + 8 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 src/api/MessageUpdater.ts create mode 100644 src/plugins/_api/messageUpdater.ts diff --git a/src/api/MessageUpdater.ts b/src/api/MessageUpdater.ts new file mode 100644 index 00000000..5cac8052 --- /dev/null +++ b/src/api/MessageUpdater.ts @@ -0,0 +1,29 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { MessageCache, MessageStore } from "@webpack/common"; +import { FluxStore } from "@webpack/types"; +import { Message } from "discord-types/general"; + +/** + * Update and re-render a message + * @param channelId The channel id of the message + * @param messageId The message id + * @param fields The fields of the message to change. Leave empty if you just want to re-render + */ +export function updateMessage(channelId: string, messageId: string, fields?: Partial) { + const channelMessageCache = MessageCache.getOrCreate(channelId); + if (!channelMessageCache.has(messageId)) return; + + // To cause a message to re-render, we basically need to create a new instance of the message and obtain a new reference + // If we have fields to modify we can use the merge method of the class, otherwise we just create a new instance with the old fields + const newChannelMessageCache = channelMessageCache.update(messageId, (oldMessage: any) => { + return fields ? oldMessage.merge(fields) : new oldMessage.constructor(oldMessage); + }); + + MessageCache.commit(newChannelMessageCache); + (MessageStore as unknown as FluxStore).emitChange(); +} diff --git a/src/api/index.ts b/src/api/index.ts index 5dca6310..02c70008 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -26,6 +26,7 @@ import * as $MessageAccessories from "./MessageAccessories"; import * as $MessageDecorations from "./MessageDecorations"; import * as $MessageEventsAPI from "./MessageEvents"; import * as $MessagePopover from "./MessagePopover"; +import * as $MessageUpdater from "./MessageUpdater"; import * as $Notices from "./Notices"; import * as $Notifications from "./Notifications"; import * as $ServerList from "./ServerList"; @@ -110,3 +111,8 @@ export const ContextMenu = $ContextMenu; * An API allowing you to add buttons to the chat input */ export const ChatButtons = $ChatButtons; + +/** + * An API allowing you to update and re-render messages + */ +export const MessageUpdater = $MessageUpdater; diff --git a/src/plugins/_api/messageUpdater.ts b/src/plugins/_api/messageUpdater.ts new file mode 100644 index 00000000..8f6cca26 --- /dev/null +++ b/src/plugins/_api/messageUpdater.ts @@ -0,0 +1,37 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 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 { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "MessageUpdaterAPI", + description: "API for updating and re-rendering messages.", + authors: [Devs.Nuckyz], + + patches: [ + { + // Message accessories have a custom logic to decide if they should render again, so we need to make it not ignore changed message reference + find: "}renderEmbeds(", + replacement: { + match: /(?<=this.props,\i,\[)"message",/, + replace: "" + } + } + ] +}); diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 427f36ce..737406cf 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -333,7 +333,7 @@ export default definePlugin({ ] }, { - find: "renderEmbeds(", + find: "}renderEmbeds(", replacement: [ { // Call our function to decide whether the embed should be ignored or not diff --git a/src/plugins/invisibleChat.desktop/index.tsx b/src/plugins/invisibleChat.desktop/index.tsx index 3dfe51e7..884ffafe 100644 --- a/src/plugins/invisibleChat.desktop/index.tsx +++ b/src/plugins/invisibleChat.desktop/index.tsx @@ -18,12 +18,13 @@ import { addChatBarButton, ChatBarButton } from "@api/ChatButtons"; import { addButton, removeButton } from "@api/MessagePopover"; +import { updateMessage } from "@api/MessageUpdater"; 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 { ChannelStore, Constants, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common"; +import { ChannelStore, Constants, RestAPI, Tooltip } from "@webpack/common"; import { Message } from "discord-types/general"; import { buildDecModal } from "./components/DecryptionModal"; @@ -103,7 +104,7 @@ export default definePlugin({ name: "InvisibleChat", description: "Encrypt your Messages in a non-suspicious way!", authors: [Devs.SammCheese], - dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI"], + dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI", "MessageUpdaterAPI"], patches: [ { // Indicator @@ -180,14 +181,7 @@ export default definePlugin({ message.embeds.push(embed); } - this.updateMessage(message); - }, - - updateMessage: (message: any) => { - FluxDispatcher.dispatch({ - type: "MESSAGE_UPDATE", - message, - }); + updateMessage(message.channel_id, message.id, { embeds: message.embeds }); }, popOverIcon: () => , diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index 6c8fd83e..e76d53e4 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -17,6 +17,7 @@ */ import { addAccessory, removeAccessory } from "@api/MessageAccessories"; +import { updateMessage } from "@api/MessageUpdater"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants.js"; @@ -28,7 +29,6 @@ import { Button, ChannelStore, Constants, - FluxDispatcher, GuildStore, IconUtils, MessageStore, @@ -250,15 +250,9 @@ function MessageEmbedAccessory({ message }: { message: Message; }) { if (linkedMessage) { messageCache.set(messageID, { message: linkedMessage, fetched: true }); } else { - const msg = { ...message } as any; - delete msg.embeds; - delete msg.interaction; messageFetchQueue.unshift(() => fetchMessage(channelID, messageID) - .then(m => m && FluxDispatcher.dispatch({ - type: "MESSAGE_UPDATE", - message: msg - })) + .then(m => m && updateMessage(message.channel_id, message.id)) ); continue; } @@ -367,7 +361,7 @@ export default definePlugin({ name: "MessageLinkEmbeds", description: "Adds a preview to messages that link another message", authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev], - dependencies: ["MessageAccessoriesAPI"], + dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI"], settings, diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts index 083ec269..f1fc68e8 100644 --- a/src/webpack/common/types/stores.d.ts +++ b/src/webpack/common/types/stores.d.ts @@ -41,8 +41,33 @@ export class FluxStore { __getLocalVars(): Record; } +export class FluxEmitter { + constructor(); + + changeSentinel: number; + changedStores: Set; + isBatchEmitting: boolean; + isDispatching: boolean; + isPaused: boolean; + pauseTimer: NodeJS.Timeout | null; + reactChangedStores: Set; + + batched(batch: (...args: any[]) => void): void; + destroy(): void; + emit(): void; + emitNonReactOnce(): void; + emitReactOnce(): void; + getChangeSentinel(): number; + getIsPaused(): boolean; + injectBatchEmitChanges(batch: (...args: any[]) => void): void; + markChanged(store: FluxStore): void; + pause(): void; + resume(): void; +} + export interface Flux { Store: typeof FluxStore; + Emitter: FluxEmitter; } export class WindowStore extends FluxStore { diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 72a71f31..bb96861f 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -144,6 +144,7 @@ const persistFilter = filters.byCode("[zustand persist middleware]"); export const { persist: zustandPersist } = findLazy(m => m.persist && persistFilter(m.persist)); export const MessageActions = findByPropsLazy("editMessage", "sendMessage"); +export const MessageCache = findByPropsLazy("clearCache", "_channelMessages"); export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal"); export const InviteActions = findByPropsLazy("resolveInvite");