diff --git a/src/equicordplugins/voiceChannelLog/index.tsx b/src/equicordplugins/voiceChannelLog/index.tsx index 585178f9..77f072e0 100644 --- a/src/equicordplugins/voiceChannelLog/index.tsx +++ b/src/equicordplugins/voiceChannelLog/index.tsx @@ -66,7 +66,6 @@ function getMessageFlags(selfInChannel: boolean) { } function sendVoiceStatusMessage(channelId: string, content: string, userId: string, selfInChannel: boolean): Message | null { - if (settings.store.mode === 1) return null; if (!channelId) return null; const message: Message = createBotMessage({ channelId, content, embeds: [] }); @@ -109,7 +108,7 @@ let clientOldChannelId: string | undefined; export default definePlugin({ name: "VoiceChannelLog", description: "Logs who joins and leaves voice channels", - authors: [Devs.Sqaaakoi, EquicordDevs.thororen], + authors: [Devs.Sqaaakoi, EquicordDevs.thororen, EquicordDevs.nyx], contextMenus: { "channel-context": patchChannelContextMenu }, @@ -139,10 +138,14 @@ export default definePlugin({ timestamp: new Date() }; - addLogEntry(logEntry, oldChannelId); - addLogEntry(logEntry, channelId); + + if (settings.store.mode !== 2) { + addLogEntry(logEntry, oldChannelId); + addLogEntry(logEntry, channelId); + } if (!settings.store.voiceChannelChatSelf && userId === clientUserId) return; + if (settings.store.mode === 1) return; // Join / Leave if ((!oldChannelId && channelId) || (oldChannelId && !channelId)) { // empty string is to make type checker shut up diff --git a/src/plugins/_api/imageModal.ts b/src/plugins/_api/imageModal.ts new file mode 100644 index 00000000..c4163b31 --- /dev/null +++ b/src/plugins/_api/imageModal.ts @@ -0,0 +1,24 @@ +/* + * 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: "ImageModalAPI", + authors: [Devs.sadan, Devs.Nuckyz], + description: "Allows you to open Image Modals", + patches: [ + { + find: "SCALE_DOWN:", + replacement: { + match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/, + replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))` + } + } + ] +}); diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index ae5d3653..dd039cd3 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -23,7 +23,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { Link } from "@components/Link"; import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; -import { Devs, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_SUPPORT_CHANNEL_ID } from "@utils/constants"; +import { Devs, GUILD_ID, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_GUILD_ID, VC_SUPPORT_CHANNEL_ID } from "@utils/constants"; import { sendMessage } from "@utils/discord"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; @@ -40,24 +40,17 @@ import plugins, { PluginMeta } from "~plugins"; import SettingsPlugin from "./settings"; -const VENCORD_GUILD_ID = "1015060230222131221"; -const EQUICORD_GUILD_ID = "1015060230222131221"; const VENBOT_USER_ID = "1017176847865352332"; const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920"; const CodeBlockRe = /```js\n(.+?)```/s; -const AllowedChannelIds = [ - SUPPORT_CHANNEL_ID, - "1173659827881390160", // Equicord > #dev - "1297590739911573585", // Equicord > #support -]; - const TrustedRolesIds = [ "1026534353167208489", // contributor "1026504932959977532", // regular "1042507929485586532", // donor "1173520023239786538", // Equicord Team "1222677964760682556", // Equicord Contributor + "1287079931645263968", // Equibop Contributor "1173343399470964856", // Vencord Contributor ]; @@ -165,13 +158,15 @@ export default definePlugin({ { name: "equicord-debug", description: "Send Equicord debug info", - predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || isEquicordPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id), + // @ts-ignore + predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || isEquicordPluginDev(UserStore.getCurrentUser()?.id) || GUILD_ID === ctx?.guild?.id, execute: async () => ({ content: await generateDebugInfoMessage() }) }, { name: "equicord-plugins", description: "Send Equicord plugin list", - predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || isEquicordPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id), + // @ts-ignore + predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || isEquicordPluginDev(UserStore.getCurrentUser()?.id) || GUILD_ID === ctx?.guild?.id, execute: () => ({ content: generatePluginList() }) } ], @@ -223,7 +218,7 @@ export default definePlugin({ } // @ts-ignore outdated type - const roles = GuildMemberStore.getSelfMember(VENCORD_GUILD_ID)?.roles || GuildMemberStore.getSelfMember(EQUICORD_GUILD_ID)?.roles; + const roles = GuildMemberStore.getSelfMember(VC_GUILD_ID)?.roles || GuildMemberStore.getSelfMember(GUILD_ID)?.roles; if (!roles || TrustedRolesIds.some(id => roles.includes(id))) return; if (!IS_WEB && IS_UPDATER_DISABLED) { diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx index f9c06110..dc4e68fe 100644 --- a/src/plugins/betterRoleContext/index.tsx +++ b/src/plugins/betterRoleContext/index.tsx @@ -8,7 +8,7 @@ import { definePluginSettings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; import { ImageIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; -import { getCurrentGuild, openMediaModal } from "@utils/discord"; +import { getCurrentGuild, openImageModal } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; import { Clipboard, GuildStore, Menu, PermissionStore } from "@webpack/common"; @@ -65,7 +65,7 @@ export default definePlugin({ name: "BetterRoleContext", description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile", authors: [Devs.Ven, Devs.goodbee], - dependencies: ["UserSettingsAPI"], + dependencies: ["UserSettingsAPI", "ImageModalAPI"], settings, @@ -99,7 +99,7 @@ export default definePlugin({ id="vc-view-role-icon" label="View Role Icon" action={() => { - openMediaModal(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`); + openImageModal(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`); }} icon={ImageIcon} /> diff --git a/src/plugins/biggerStreamPreview/index.tsx b/src/plugins/biggerStreamPreview/index.tsx index de0ca55e..17296789 100644 --- a/src/plugins/biggerStreamPreview/index.tsx +++ b/src/plugins/biggerStreamPreview/index.tsx @@ -19,7 +19,7 @@ import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { ScreenshareIcon } from "@components/Icons"; import { Devs } from "@utils/constants"; -import { openMediaModal } from "@utils/discord"; +import { openImageModal } from "@utils/discord"; import definePlugin from "@utils/types"; import { Menu } from "@webpack/common"; import { Channel, User } from "discord-types/general"; @@ -57,7 +57,7 @@ export const handleViewPreview = async ({ guildId, channelId, ownerId }: Applica const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId); if (!previewUrl) return; - openMediaModal(previewUrl); + openImageModal(previewUrl); }; export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => { @@ -89,6 +89,7 @@ export default definePlugin({ name: "BiggerStreamPreview", description: "This plugin allows you to enlarge stream previews", authors: [Devs.phil], + dependencies: ["ImageModalAPI"], contextMenus: { "user-context": userContextPatch, "stream-context": streamContextPatch diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index 273d022e..c789157c 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -158,10 +158,6 @@ export default definePlugin({ { find: ".contain,SCALE_DOWN:", replacement: { - // there are 2 image thingies. one for carosuel and one for the single image. - // so thats why i added global flag. - // also idk if this patch is good, should it be more specific? - // https://regex101.com/r/xfvNvV/1 match: /\.slide,\i\),/g, replace: `$&id:"${ELEMENT_ID}",` } diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 9bc3388f..81787676 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -1,26 +1,14 @@ /* - * 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 . + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later */ import "./messageLogger.css"; import { - findGroupChildrenByChildId, - NavContextMenuPatchCallback, + findGroupChildrenByChildId, + NavContextMenuPatchCallback, } from "@api/ContextMenu"; import { updateMessage } from "@api/MessageUpdater"; import { Settings } from "@api/Settings"; @@ -33,16 +21,16 @@ import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { - ChannelStore, - FluxDispatcher, - i18n, - Menu, - MessageStore, - Parser, - SelectedChannelStore, - Timestamp, - UserStore, - useStateFromStores, + ChannelStore, + FluxDispatcher, + i18n, + Menu, + MessageStore, + Parser, + SelectedChannelStore, + Timestamp, + UserStore, + useStateFromStores, } from "@webpack/common"; import { Message } from "discord-types/general"; @@ -51,538 +39,538 @@ import textStyle from "./deleteStyleText.css?managed"; import { openHistoryModal } from "./HistoryModal"; interface MLMessage extends Message { - deleted?: boolean; - editHistory?: { timestamp: Date; content: string }[]; - firstEditTimestamp?: Date; + deleted?: boolean; + editHistory?: { timestamp: Date; content: string; }[]; + firstEditTimestamp?: Date; } const styles = findByPropsLazy( - "edited", - "communicationDisabled", - "isSystemMessage", + "edited", + "communicationDisabled", + "isSystemMessage", ); const getMessage = findByCodeLazy('replace(/^\\n+|\\n+$/g,"")'); function addDeleteStyle() { - if (Settings.plugins.MessageLogger.deleteStyle === "text") { - enableStyle(textStyle); - disableStyle(overlayStyle); - } else { - disableStyle(textStyle); - enableStyle(overlayStyle); - } + if (Settings.plugins.MessageLogger.deleteStyle === "text") { + enableStyle(textStyle); + disableStyle(overlayStyle); + } else { + disableStyle(textStyle); + enableStyle(overlayStyle); + } } const REMOVE_HISTORY_ID = "ml-remove-history"; const TOGGLE_DELETE_STYLE_ID = "ml-toggle-style"; const patchMessageContextMenu: NavContextMenuPatchCallback = ( - children, - props, + children, + props, ) => { - const { message } = props; - const { deleted, editHistory, id, channel_id } = message; + const { message } = props; + const { deleted, editHistory, id, channel_id } = message; - if (!deleted && !editHistory?.length) return; + if (!deleted && !editHistory?.length) return; - toggle: { - if (!deleted) break toggle; + toggle: { + if (!deleted) break toggle; - const domElement = document.getElementById( - `chat-messages-${channel_id}-${id}`, - ); - if (!domElement) break toggle; + const domElement = document.getElementById( + `chat-messages-${channel_id}-${id}`, + ); + if (!domElement) break toggle; + + children.push( + domElement.classList.toggle("messagelogger-deleted")} + />, + ); + } children.push( - domElement.classList.toggle("messagelogger-deleted")} - />, + { + if (deleted) { + FluxDispatcher.dispatch({ + type: "MESSAGE_DELETE", + channelId: channel_id, + id, + mlDeleted: true, + }); + } else { + message.editHistory = []; + } + }} + />, ); - } - - children.push( - { - if (deleted) { - FluxDispatcher.dispatch({ - type: "MESSAGE_DELETE", - channelId: channel_id, - id, - mlDeleted: true, - }); - } else { - message.editHistory = []; - } - }} - />, - ); }; const patchChannelContextMenu: NavContextMenuPatchCallback = ( - children, - { channel }, + children, + { channel }, ) => { - const messages = MessageStore.getMessages(channel?.id) as MLMessage[]; - if (!messages?.some((msg) => msg.deleted || msg.editHistory?.length)) return; + const messages = MessageStore.getMessages(channel?.id) as MLMessage[]; + if (!messages?.some(msg => msg.deleted || msg.editHistory?.length)) return; - const group = - findGroupChildrenByChildId("mark-channel-read", children) ?? children; - group.push( - { - messages.forEach((msg) => { - if (msg.deleted) - FluxDispatcher.dispatch({ - type: "MESSAGE_DELETE", - channelId: channel.id, - id: msg.id, - mlDeleted: true, - }); - else - updateMessage(channel.id, msg.id, { - editHistory: [], - }); - }); - }} - />, - ); + const group = + findGroupChildrenByChildId("mark-channel-read", children) ?? children; + group.push( + { + messages.forEach(msg => { + if (msg.deleted) + FluxDispatcher.dispatch({ + type: "MESSAGE_DELETE", + channelId: channel.id, + id: msg.id, + mlDeleted: true, + }); + else + updateMessage(channel.id, msg.id, { + editHistory: [], + }); + }); + }} + />, + ); }; export function parseEditContent(content: string, message: Message) { - return Parser.parse(content, true, { - channelId: message.channel_id, - messageId: message.id, - allowLinks: true, - allowHeading: true, - allowList: true, - allowEmojiLinks: true, - viewingChannelId: SelectedChannelStore.getChannelId(), - }); + return Parser.parse(content, true, { + channelId: message.channel_id, + messageId: message.id, + allowLinks: true, + allowHeading: true, + allowList: true, + allowEmojiLinks: true, + viewingChannelId: SelectedChannelStore.getChannelId(), + }); } export default definePlugin({ - name: "MessageLogger", - description: "Temporarily logs deleted and edited messages.", - authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN, Devs.Nickyux, Devs.Kyuuhachi], - dependencies: ["MessageUpdaterAPI"], + name: "MessageLogger", + description: "Temporarily logs deleted and edited messages.", + authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN, Devs.Nickyux, Devs.Kyuuhachi], + dependencies: ["MessageUpdaterAPI"], - contextMenus: { - message: patchMessageContextMenu, - "channel-context": patchChannelContextMenu, - "thread-context": patchChannelContextMenu, - "user-context": patchChannelContextMenu, - "gdm-context": patchChannelContextMenu, - }, - - start() { - addDeleteStyle(); - }, - - renderEdits: ErrorBoundary.wrap( - ({ - message: { id: messageId, channel_id: channelId }, - }: { - message: Message; - }) => { - const message = useStateFromStores( - [MessageStore], - () => MessageStore.getMessage(channelId, messageId) as MLMessage, - null, - (oldMsg, newMsg) => oldMsg?.editHistory === newMsg?.editHistory, - ); - - return ( - Settings.plugins.MessageLogger.inlineEdits && ( - <> - {message.editHistory?.map((edit) => ( -
- {parseEditContent(edit.content, message)} - - - {" "} - ({i18n.Messages.MESSAGE_EDITED}) - - -
- ))} - - ) - ); + contextMenus: { + message: patchMessageContextMenu, + "channel-context": patchChannelContextMenu, + "thread-context": patchChannelContextMenu, + "user-context": patchChannelContextMenu, + "gdm-context": patchChannelContextMenu, }, - { noop: true }, - ), - makeEdit(newMessage: any, oldMessage: any): any { - return { - timestamp: new Date(newMessage.edited_timestamp), - content: oldMessage.content, - }; - }, - - options: { - deleteStyle: { - type: OptionType.SELECT, - description: "The style of deleted messages", - default: "text", - options: [ - { label: "Red text", value: "text", default: true }, - { label: "Red overlay", value: "overlay" }, - ], - onChange: () => addDeleteStyle(), + start() { + addDeleteStyle(); }, - logDeletes: { - type: OptionType.BOOLEAN, - description: "Whether to log deleted messages", - default: true, - }, - collapseDeleted: { - type: OptionType.BOOLEAN, - description: - "Whether to collapse deleted messages, similar to blocked messages", - default: false, - }, - logEdits: { - type: OptionType.BOOLEAN, - description: "Whether to log edited messages", - default: true, - }, - inlineEdits: { - type: OptionType.BOOLEAN, - description: "Whether to display edit history as part of message content", - default: true, - }, - ignoreBots: { - type: OptionType.BOOLEAN, - description: "Whether to ignore messages by bots", - default: false, - }, - ignoreSelf: { - type: OptionType.BOOLEAN, - description: "Whether to ignore messages by yourself", - default: false, - }, - ignoreUsers: { - type: OptionType.STRING, - description: "Comma-separated list of user IDs to ignore", - default: "", - }, - ignoreChannels: { - type: OptionType.STRING, - description: "Comma-separated list of channel IDs to ignore", - default: "", - }, - ignoreGuilds: { - type: OptionType.STRING, - description: "Comma-separated list of guild IDs to ignore", - default: "", - }, - }, - handleDelete( - cache: any, - data: { ids: string[]; id: string; mlDeleted?: boolean }, - isBulk: boolean, - ) { - try { - if (cache == null || (!isBulk && !cache.has(data.id))) return cache; + renderEdits: ErrorBoundary.wrap( + ({ + message: { id: messageId, channel_id: channelId }, + }: { + message: Message; + }) => { + const message = useStateFromStores( + [MessageStore], + () => MessageStore.getMessage(channelId, messageId) as MLMessage, + null, + (oldMsg, newMsg) => oldMsg?.editHistory === newMsg?.editHistory, + ); - const mutate = (id: string) => { - const msg = cache.get(id); - if (!msg) return; - - const EPHEMERAL = 64; - const shouldIgnore = - data.mlDeleted || - (msg.flags & EPHEMERAL) === EPHEMERAL || - this.shouldIgnore(msg); - - if (shouldIgnore) { - cache = cache.remove(id); - } else { - cache = cache.update(id, (m) => - m.set("deleted", true).set( - "attachments", - m.attachments.map((a) => ((a.deleted = true), a)), - ), - ); - } - }; - - if (isBulk) { - data.ids.forEach(mutate); - } else { - mutate(data.id); - } - } catch (e) { - new Logger("MessageLogger").error("Error during handleDelete", e); - } - return cache; - }, - - shouldIgnore(message: any, isEdit = false) { - const { - ignoreBots, - ignoreSelf, - ignoreUsers, - ignoreChannels, - ignoreGuilds, - logEdits, - logDeletes, - } = Settings.plugins.MessageLogger; - const myId = UserStore.getCurrentUser().id; - - return ( - (ignoreBots && message.author?.bot) || - (ignoreSelf && message.author?.id === myId) || - ignoreUsers.includes(message.author?.id) || - ignoreChannels.includes(message.channel_id) || - ignoreChannels.includes( - ChannelStore.getChannel(message.channel_id)?.parent_id, - ) || - (isEdit ? !logEdits : !logDeletes) || - ignoreGuilds.includes( - ChannelStore.getChannel(message.channel_id)?.guild_id, - ) || - // Ignore Venbot in the support channel - (message.channel_id === "1026515880080842772" && - message.author?.id === "1017176847865352332") || - // Ignore VOT on dev-playground - (message.channel_id === "1297239805972709521" && - message.author?.id === "1199905841004937257") - ); - }, - - EditMarker({ message, className, children, ...props }: any) { - return ( - openHistoryModal(message)} - aria-role="button" - > - {children} - - ); - }, - - Messages: proxyLazy(() => ({ - DELETED_MESSAGE_COUNT: getMessage( - "{count, plural, =0 {No deleted messages} one {{count} deleted message} other {{count} deleted messages}}", + return ( + Settings.plugins.MessageLogger.inlineEdits && ( + <> + {message.editHistory?.map(edit => ( +
+ {parseEditContent(edit.content, message)} + + + {" "} + ({i18n.Messages.MESSAGE_EDITED}) + + +
+ ))} + + ) + ); + }, + { noop: true }, ), - })), - patches: [ - { - // MessageStore - find: '"MessageStore"', - replacement: [ - { - // Add deleted=true to all target messages in the MESSAGE_DELETE event - match: - /MESSAGE_DELETE:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/, - replace: - "MESSAGE_DELETE:function($1){" + - " var cache = $2getOrCreate($1.channelId);" + - " cache = $self.handleDelete(cache, $1, false);" + - " $2commit(cache);" + - "},", - }, - { - // Add deleted=true to all target messages in the MESSAGE_DELETE_BULK event - match: - /MESSAGE_DELETE_BULK:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/, - replace: - "MESSAGE_DELETE_BULK:function($1){" + - " var cache = $2getOrCreate($1.channelId);" + - " cache = $self.handleDelete(cache, $1, true);" + - " $2commit(cache);" + - "},", - }, - { - // Add current cached content + new edit time to cached message's editHistory - match: /(MESSAGE_UPDATE:function\((\i)\).+?)\.update\((\i)/, - replace: - "$1" + - ".update($3,m =>" + - " (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message, true)) ? m :" + - " $2.message.edited_timestamp && $2.message.content !== m.content ?" + - " m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" + - " m" + - ")" + - ".update($3", - }, - { - // fix up key (edit last message) attempting to edit a deleted message - match: /(?<=getLastEditableMessage\(\i\)\{.{0,200}\.find\((\i)=>)/, - replace: "!$1.deleted &&", - }, - ], + makeEdit(newMessage: any, oldMessage: any): any { + return { + timestamp: new Date(newMessage.edited_timestamp), + content: oldMessage.content, + }; }, - { - // Message domain model - find: "}addReaction(", - replacement: [ - { - match: /this\.customRenderedContent=(\i)\.customRenderedContent,/, - replace: - "this.customRenderedContent = $1.customRenderedContent," + - "this.deleted = $1.deleted || false," + - "this.editHistory = $1.editHistory || []," + - "this.firstEditTimestamp = $1.firstEditTimestamp || this.editedTimestamp || this.timestamp,", + options: { + deleteStyle: { + type: OptionType.SELECT, + description: "The style of deleted messages", + default: "text", + options: [ + { label: "Red text", value: "text", default: true }, + { label: "Red overlay", value: "overlay" }, + ], + onChange: () => addDeleteStyle(), + }, + logDeletes: { + type: OptionType.BOOLEAN, + description: "Whether to log deleted messages", + default: true, + }, + collapseDeleted: { + type: OptionType.BOOLEAN, + description: + "Whether to collapse deleted messages, similar to blocked messages", + default: false, + }, + logEdits: { + type: OptionType.BOOLEAN, + description: "Whether to log edited messages", + default: true, + }, + inlineEdits: { + type: OptionType.BOOLEAN, + description: "Whether to display edit history as part of message content", + default: true, + }, + ignoreBots: { + type: OptionType.BOOLEAN, + description: "Whether to ignore messages by bots", + default: false, + }, + ignoreSelf: { + type: OptionType.BOOLEAN, + description: "Whether to ignore messages by yourself", + default: false, + }, + ignoreUsers: { + type: OptionType.STRING, + description: "Comma-separated list of user IDs to ignore", + default: "", + }, + ignoreChannels: { + type: OptionType.STRING, + description: "Comma-separated list of channel IDs to ignore", + default: "", + }, + ignoreGuilds: { + type: OptionType.STRING, + description: "Comma-separated list of guild IDs to ignore", + default: "", }, - ], }, - { - // Updated message transformer(?) - find: "THREAD_STARTER_MESSAGE?null===", - replacement: [ + handleDelete( + cache: any, + data: { ids: string[]; id: string; mlDeleted?: boolean; }, + isBulk: boolean, + ) { + try { + if (cache == null || (!isBulk && !cache.has(data.id))) return cache; + + const mutate = (id: string) => { + const msg = cache.get(id); + if (!msg) return; + + const EPHEMERAL = 64; + const shouldIgnore = + data.mlDeleted || + (msg.flags & EPHEMERAL) === EPHEMERAL || + this.shouldIgnore(msg); + + if (shouldIgnore) { + cache = cache.remove(id); + } else { + cache = cache.update(id, m => + m.set("deleted", true).set( + "attachments", + m.attachments.map(a => ((a.deleted = true), a)), + ), + ); + } + }; + + if (isBulk) { + data.ids.forEach(mutate); + } else { + mutate(data.id); + } + } catch (e) { + new Logger("MessageLogger").error("Error during handleDelete", e); + } + return cache; + }, + + shouldIgnore(message: any, isEdit = false) { + const { + ignoreBots, + ignoreSelf, + ignoreUsers, + ignoreChannels, + ignoreGuilds, + logEdits, + logDeletes, + } = Settings.plugins.MessageLogger; + const myId = UserStore.getCurrentUser().id; + + return ( + (ignoreBots && message.author?.bot) || + (ignoreSelf && message.author?.id === myId) || + ignoreUsers.includes(message.author?.id) || + ignoreChannels.includes(message.channel_id) || + ignoreChannels.includes( + ChannelStore.getChannel(message.channel_id)?.parent_id, + ) || + (isEdit ? !logEdits : !logDeletes) || + ignoreGuilds.includes( + ChannelStore.getChannel(message.channel_id)?.guild_id, + ) || + // Ignore Venbot in the support channel + (message.channel_id === "1026515880080842772" && + message.author?.id === "1017176847865352332") || + // Ignore VOT on dev-playground + (message.channel_id === "1297239805972709521" && + message.author?.id === "1199905841004937257") + ); + }, + + EditMarker({ message, className, children, ...props }: any) { + return ( + openHistoryModal(message)} + aria-role="button" + > + {children} + + ); + }, + + Messages: proxyLazy(() => ({ + DELETED_MESSAGE_COUNT: getMessage( + "{count, plural, =0 {No deleted messages} one {{count} deleted message} other {{count} deleted messages}}", + ), + })), + + patches: [ { - // Pass through editHistory & deleted & original attachments to the "edited message" transformer - match: - /(?<=null!=\i\.edited_timestamp\)return )\i\(\i,\{reactions:(\i)\.reactions.{0,50}\}\)/, - replace: - "Object.assign($&,{ deleted:$1.deleted, editHistory:$1.editHistory, firstEditTimestamp:$1.firstEditTimestamp })", + // MessageStore + find: '"MessageStore"', + replacement: [ + { + // Add deleted=true to all target messages in the MESSAGE_DELETE event + match: + /MESSAGE_DELETE:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/, + replace: + "MESSAGE_DELETE:function($1){" + + " var cache = $2getOrCreate($1.channelId);" + + " cache = $self.handleDelete(cache, $1, false);" + + " $2commit(cache);" + + "},", + }, + { + // Add deleted=true to all target messages in the MESSAGE_DELETE_BULK event + match: + /MESSAGE_DELETE_BULK:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/, + replace: + "MESSAGE_DELETE_BULK:function($1){" + + " var cache = $2getOrCreate($1.channelId);" + + " cache = $self.handleDelete(cache, $1, true);" + + " $2commit(cache);" + + "},", + }, + { + // Add current cached content + new edit time to cached message's editHistory + match: /(MESSAGE_UPDATE:function\((\i)\).+?)\.update\((\i)/, + replace: + "$1" + + ".update($3,m =>" + + " (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message, true)) ? m :" + + " $2.message.edited_timestamp && $2.message.content !== m.content ?" + + " m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" + + " m" + + ")" + + ".update($3", + }, + { + // fix up key (edit last message) attempting to edit a deleted message + match: /(?<=getLastEditableMessage\(\i\)\{.{0,200}\.find\((\i)=>)/, + replace: "!$1.deleted &&", + }, + ], }, { - // Construct new edited message and add editHistory & deleted (ref above) - // Pass in custom data to attachment parser to mark attachments deleted as well - match: /attachments:(\i)\((\i)\)/, - replace: - "attachments: $1((() => {" + - " if ($self.shouldIgnore($2)) return $2;" + - " let old = arguments[1]?.attachments;" + - " if (!old) return $2;" + - " let new_ = $2.attachments?.map(a => a.id) ?? [];" + - " let diff = old.filter(a => !new_.includes(a.id));" + - " old.forEach(a => a.deleted = true);" + - " $2.attachments = [...diff, ...$2.attachments];" + - " return $2;" + - "})())," + - "deleted: arguments[1]?.deleted," + - "editHistory: arguments[1]?.editHistory," + - "firstEditTimestamp: new Date(arguments[1]?.firstEditTimestamp ?? $2.editedTimestamp ?? $2.timestamp)", + // Message domain model + find: "}addReaction(", + replacement: [ + { + match: /this\.customRenderedContent=(\i)\.customRenderedContent,/, + replace: + "this.customRenderedContent = $1.customRenderedContent," + + "this.deleted = $1.deleted || false," + + "this.editHistory = $1.editHistory || []," + + "this.firstEditTimestamp = $1.firstEditTimestamp || this.editedTimestamp || this.timestamp,", + }, + ], }, - { - // Preserve deleted attribute on attachments - match: /(\((\i)\){return null==\2\.attachments.+?)spoiler:/, - replace: "$1deleted: arguments[0]?.deleted," + "spoiler:", - }, - ], - }, - { - // Attachment renderer - find: ".removeMosaicItemHoverButton", - group: true, - replacement: [ { - match: /(className:\i,item:\i),/, - replace: "$1,item: deleted,", - }, - { - match: /\[\i\.obscured\]:.+?,/, - replace: "$& 'messagelogger-deleted-attachment': deleted,", - }, - ], - }, + // Updated message transformer(?) + find: "THREAD_STARTER_MESSAGE?null===", + replacement: [ + { + // Pass through editHistory & deleted & original attachments to the "edited message" transformer + match: + /(?<=null!=\i\.edited_timestamp\)return )\i\(\i,\{reactions:(\i)\.reactions.{0,50}\}\)/, + replace: + "Object.assign($&,{ deleted:$1.deleted, editHistory:$1.editHistory, firstEditTimestamp:$1.firstEditTimestamp })", + }, - { - // Base message component renderer - find: "Message must not be a thread starter message", - replacement: [ - { - // Append messagelogger-deleted to classNames if deleted - match: /\)\("li",\{(.+?),className:/, - replace: - ')("li",{$1,className:(arguments[0].message.deleted ? "messagelogger-deleted " : "")+', + { + // Construct new edited message and add editHistory & deleted (ref above) + // Pass in custom data to attachment parser to mark attachments deleted as well + match: /attachments:(\i)\((\i)\)/, + replace: + "attachments: $1((() => {" + + " if ($self.shouldIgnore($2)) return $2;" + + " let old = arguments[1]?.attachments;" + + " if (!old) return $2;" + + " let new_ = $2.attachments?.map(a => a.id) ?? [];" + + " let diff = old.filter(a => !new_.includes(a.id));" + + " old.forEach(a => a.deleted = true);" + + " $2.attachments = [...diff, ...$2.attachments];" + + " return $2;" + + "})())," + + "deleted: arguments[1]?.deleted," + + "editHistory: arguments[1]?.editHistory," + + "firstEditTimestamp: new Date(arguments[1]?.firstEditTimestamp ?? $2.editedTimestamp ?? $2.timestamp)", + }, + { + // Preserve deleted attribute on attachments + match: /(\((\i)\){return null==\2\.attachments.+?)spoiler:/, + replace: "$1deleted: arguments[0]?.deleted," + "spoiler:", + }, + ], }, - ], - }, - { - // Message content renderer - find: 'Messages.MESSAGE_EDITED,")"', - replacement: [ { - // Render editHistory in the deepest div for message content - match: /(\)\("div",\{id:.+?children:\[)/, - replace: - "$1 (!!arguments[0].message.editHistory?.length && $self.renderEdits(arguments[0])),", + // Attachment renderer + find: ".removeMosaicItemHoverButton", + group: true, + replacement: [ + { + match: /(className:\i,item:\i),/, + replace: "$1,item: deleted,", + }, + { + match: /\[\i\.obscured\]:.+?,/, + replace: "$& 'messagelogger-deleted-attachment': deleted,", + }, + ], }, - { - // Make edit marker clickable - match: /"span",\{(?=className:\i\.edited,)/, - replace: "$self.EditMarker,{message:arguments[0].message,", - }, - ], - }, - { - // ReferencedMessageStore - find: '"ReferencedMessageStore"', - replacement: [ { - match: /MESSAGE_DELETE:function\((\i)\).+?},/, - replace: "MESSAGE_DELETE:function($1){},", + // Base message component renderer + find: "Message must not be a thread starter message", + replacement: [ + { + // Append messagelogger-deleted to classNames if deleted + match: /\)\("li",\{(.+?),className:/, + replace: + ')("li",{$1,className:(arguments[0].message.deleted ? "messagelogger-deleted " : "")+', + }, + ], }, - { - match: /MESSAGE_DELETE_BULK:function\((\i)\).+?},/, - replace: "MESSAGE_DELETE_BULK:function($1){},", - }, - ], - }, - { - // Message context base menu - find: "useMessageMenu:", - replacement: [ { - // Remove the first section if message is deleted - match: /children:(\[""===.+?\])/, - replace: "children:arguments[0].message.deleted?[]:$1", + // Message content renderer + find: 'Messages.MESSAGE_EDITED,")"', + replacement: [ + { + // Render editHistory in the deepest div for message content + match: /(\)\("div",\{id:.+?children:\[)/, + replace: + "$1 (!!arguments[0].message.editHistory?.length && $self.renderEdits(arguments[0])),", + }, + { + // Make edit marker clickable + match: /"span",\{(?=className:\i\.edited,)/, + replace: "$self.EditMarker,{message:arguments[0].message,", + }, + ], }, - ], - }, - { - // Message grouping - find: "NON_COLLAPSIBLE.has(", - replacement: { - match: /if\((\i)\.blocked\)return \i\.\i\.MESSAGE_GROUP_BLOCKED;/, - replace: '$&else if($1.deleted) return"MESSAGE_GROUP_DELETED";', - }, - predicate: () => Settings.plugins.MessageLogger.collapseDeleted, - }, - { - // Message group rendering - find: "Messages.NEW_MESSAGES_ESTIMATED_WITH_DATE", - replacement: [ + { - match: /(\i).type===\i\.\i\.MESSAGE_GROUP_BLOCKED\|\|/, - replace: '$&$1.type==="MESSAGE_GROUP_DELETED"||', + // ReferencedMessageStore + find: '"ReferencedMessageStore"', + replacement: [ + { + match: /MESSAGE_DELETE:function\((\i)\).+?},/, + replace: "MESSAGE_DELETE:function($1){},", + }, + { + match: /MESSAGE_DELETE_BULK:function\((\i)\).+?},/, + replace: "MESSAGE_DELETE_BULK:function($1){},", + }, + ], + }, + + { + // Message context base menu + find: "useMessageMenu:", + replacement: [ + { + // Remove the first section if message is deleted + match: /children:(\[""===.+?\])/, + replace: "children:arguments[0].message.deleted?[]:$1", + }, + ], }, { - match: /(\i).type===\i\.\i\.MESSAGE_GROUP_BLOCKED\?.*?:/, - replace: - '$&$1.type==="MESSAGE_GROUP_DELETED"?$self.Messages.DELETED_MESSAGE_COUNT:', + // Message grouping + find: "NON_COLLAPSIBLE.has(", + replacement: { + match: /if\((\i)\.blocked\)return \i\.\i\.MESSAGE_GROUP_BLOCKED;/, + replace: '$&else if($1.deleted) return"MESSAGE_GROUP_DELETED";', + }, + predicate: () => Settings.plugins.MessageLogger.collapseDeleted, }, - ], - predicate: () => Settings.plugins.MessageLogger.collapseDeleted, - }, - ], + { + // Message group rendering + find: "Messages.NEW_MESSAGES_ESTIMATED_WITH_DATE", + replacement: [ + { + match: /(\i).type===\i\.\i\.MESSAGE_GROUP_BLOCKED\|\|/, + replace: '$&$1.type==="MESSAGE_GROUP_DELETED"||', + }, + { + match: /(\i).type===\i\.\i\.MESSAGE_GROUP_BLOCKED\?.*?:/, + replace: + '$&$1.type==="MESSAGE_GROUP_DELETED"?$self.Messages.DELETED_MESSAGE_COUNT:', + }, + ], + predicate: () => Settings.plugins.MessageLogger.collapseDeleted, + }, + ], }); diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index e2007dfe..d0ede9c5 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -7,7 +7,7 @@ import "./styles.css"; import { classNameFactory } from "@api/Styles"; -import { openMediaModal, openUserProfile } from "@utils/discord"; +import { openImageModal, openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; @@ -80,7 +80,7 @@ function GuildInfoModal({ guild }: GuildProps) { className={cl("banner")} src={bannerUrl} alt="" - onClick={() => openMediaModal(bannerUrl)} + onClick={() => openImageModal(bannerUrl)} /> )} @@ -89,7 +89,9 @@ function GuildInfoModal({ guild }: GuildProps) { ? openMediaModal(iconUrl)} + onClick={() => openImageModal(iconUrl, { + width: 256 + })} /> :
{guild.acronym}
} @@ -151,7 +153,7 @@ function Owner(guildId: string, owner: User) { return (
- openMediaModal(ownerAvatarUrl)} /> + openImageModal(ownerAvatarUrl)} /> {Parser.parse(`<@${owner.id}>`)}
); diff --git a/src/plugins/serverInfo/index.tsx b/src/plugins/serverInfo/index.tsx index be3172f0..7a6fa62c 100644 --- a/src/plugins/serverInfo/index.tsx +++ b/src/plugins/serverInfo/index.tsx @@ -31,6 +31,7 @@ export default definePlugin({ description: "Allows you to view info about a server", authors: [Devs.Ven, Devs.Nuckyz], tags: ["guild", "info", "ServerProfile"], + dependencies: ["ImageModalAPI"], contextMenus: { "guild-context": Patch, "guild-header-popout": Patch diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx index 72b63733..aef0c736 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/plugins/spotifyControls/PlayerComponent.tsx @@ -21,7 +21,7 @@ import "./spotifyStyles.css"; import { Flex } from "@components/Flex"; import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons"; import { debounce } from "@shared/debounce"; -import { openMediaModal } from "@utils/discord"; +import { openImageModal } from "@utils/discord"; import { classes, copyWithToast } from "@utils/misc"; import { ContextMenuApi, FluxDispatcher, Forms, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common"; @@ -229,7 +229,7 @@ function AlbumContextMenu({ track }: { track: Track; }) { id="view-cover" label="View Album Cover" // trolley - action={() => openMediaModal(track.album.image.url)} + action={() => openImageModal(track.album.image.url)} icon={ImageIcon} /> . */ +import "./discord.css"; + import { MessageObject } from "@api/MessageEvents"; -import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, InviteActions, MessageActions, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; +import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { Channel, Guild, Message, User } from "discord-types/general"; -import { MediaData, MediaModal, openModal } from "./modal"; +import { ImageModal, openModal } from "./modal"; /** * Open the invite modal @@ -108,21 +110,23 @@ export function sendMessage( return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, extra); } -/** - * - * @param media The url of the media or its data - * @param mediaModalProps Additional props for the image modal - */ -export function openMediaModal(media: string | MediaData, mediaModalProps?: Partial>): string { - media = typeof media === "string" ? { url: media } : media; - media.original ??= media.url; - media.type ??= "IMAGE"; +const FIX_CLASS_NAME = "vc-imagemodal-fix"; + +export function openImageModal(url: string, props?: Partial>): string { return openModal(modalProps => ( - } + // Don't render forward message button scaleDown_f97a12 contain_f97a12 + renderForwardComponent={() => null} + shouldHideMediaOptions={false} + shouldAnimate={true} + fit={FIX_CLASS_NAME} + items={[{ + ...props, + type: "IMAGE", + url, + }]} /> )); } diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index 78e86be2..8c6a68ce 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -101,31 +101,26 @@ export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as { }>; }; -// @TODO Type this -export type MediaData = { - url: string; - original?: string; - type?: string; - alt?: string; +// FIXME: type this +export type ImageModal = any & ComponentType<{ + className?: string; + src: string; + placeholder: string; + original: string; width?: number; height?: number; + animated?: boolean; + responsive?: boolean; + renderLinkComponent(props: any): ReactNode; + renderForwardComponent(props: any): ReactNode; maxWidth?: number; maxHeight?: number; -} & Record; - -export type MediaModal = ComponentType<{ - onClose?: () => void; - items: MediaData[]; - startingIndex?: number; - onIndexChange?: (...args: any[]) => void; - fit?: any; - shouldRedactExplicitContent?: boolean; + shouldAnimate?: boolean; + onClose?(): void; shouldHideMediaOptions?: boolean; - shouldAnimateCarousel?: boolean; - className?: string; }>; -export const MediaModal = findComponentByCodeLazy(".MEDIA_MODAL_CLOSE") as MediaModal; +export const ImageModal = findComponentByCodeLazy(".MEDIA_MODAL_CLOSE") as ImageModal; export const ModalRoot = LazyComponent(() => Modals.ModalRoot); export const ModalHeader = LazyComponent(() => Modals.ModalHeader); diff --git a/src/webpack/common/classes.ts b/src/webpack/common/classes.ts index ca3d75f5..fd4f36ee 100644 --- a/src/webpack/common/classes.ts +++ b/src/webpack/common/classes.ts @@ -16,9 +16,8 @@ * along with this program. If not, see . */ -import { findByPropsLazy, findLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import * as t from "./types/classes"; -export const ModalImageClasses: t.ImageModalClasses = findLazy(m => m.image && m.modal && !m.applicationIcon); export const ButtonWrapperClasses: t.ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent"); diff --git a/src/webpack/common/types/classes.d.ts b/src/webpack/common/types/classes.d.ts index b6066177..e30e25af 100644 --- a/src/webpack/common/types/classes.d.ts +++ b/src/webpack/common/types/classes.d.ts @@ -16,11 +16,6 @@ * along with this program. If not, see . */ -export interface ImageModalClasses { - image: string, - modal: string, -} - export interface ButtonWrapperClasses { hoverScale: string; buttonWrapper: string;