diff --git a/package.json b/package.json index ac4859593..cfd4bd010 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.9.4", + "version": "1.9.5", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/src/plugins/ctrlEnterSend/index.ts b/src/plugins/ctrlEnterSend/index.ts index 6c9b7ac1d..4a1b73765 100644 --- a/src/plugins/ctrlEnterSend/index.ts +++ b/src/plugins/ctrlEnterSend/index.ts @@ -39,6 +39,15 @@ export default definePlugin({ } }), patches: [ + // Only one of the two patches will be at effect; Discord often updates to switch between them. + // See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673 + { + find: ".ENTER&&(!", + replacement: { + match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/, + replace: "$self.shouldSubmit($1, $2)" + } + }, { find: "!this.hasOpenCodeBlock()", replacement: { diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index 4cf8439bc..33c32b1a0 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -88,8 +88,8 @@ export default definePlugin({ { find: "useCanFavoriteChannel", replacement: { - match: /!\(\i\.isDM\(\)\|\|\i\.isThread\(\)\)/, - replace: "true", + match: /\i\.isDM\(\)\|\|\i\.isThread\(\)/, + replace: "false", } } ], diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index ddcabcbdf..efc194954 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -24,7 +24,7 @@ import { getCurrentGuild } from "@utils/discord"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; -import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; +import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; import type { Emoji } from "@webpack/types"; import type { Message } from "discord-types/general"; import { applyPalette, GIFEncoder, quantize } from "gifenc"; @@ -818,7 +818,14 @@ export default definePlugin({ if (isUnusableRoleSubscriptionEmoji(e, this.guildId, true)) return false; - if (this.canUseEmotes) + let isUsableTwitchSubEmote = false; + if (e.managed && e.guildId) { + // @ts-ignore outdated type + const myRoles = GuildMemberStore.getSelfMember(e.guildId)?.roles ?? []; + isUsableTwitchSubEmote = e.roles.some(r => myRoles.includes(r)); + } + + if (this.canUseEmotes || isUsableTwitchSubEmote) return e.guildId === this.guildId || hasExternalEmojiPerms(channelId); else return !e.animated && e.guildId === this.guildId; diff --git a/src/plugins/friendsSince/index.tsx b/src/plugins/friendsSince/index.tsx index 629e8e719..717bd754c 100644 --- a/src/plugins/friendsSince/index.tsx +++ b/src/plugins/friendsSince/index.tsx @@ -17,7 +17,7 @@ const container = findByPropsLazy("memberSince"); const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"'); const locale = findByPropsLazy("getLocale"); const lastSection = findByPropsLazy("lastSection"); -const section = findLazy((m: any) => m.section !== void 0 && Object.values(m).length === 1); +const section = findLazy((m: any) => m.section !== void 0 && m.heading !== void 0 && Object.values(m).length === 2); export default definePlugin({ name: "FriendsSince", diff --git a/src/plugins/invisibleChat.desktop/index.tsx b/src/plugins/invisibleChat.desktop/index.tsx index 01199d999..c7eb29e7e 100644 --- a/src/plugins/invisibleChat.desktop/index.tsx +++ b/src/plugins/invisibleChat.desktop/index.tsx @@ -133,10 +133,12 @@ export default definePlugin({ message: message, channel: ChannelStore.getChannel(message.channel_id), onClick: async () => { - await iteratePasswords(message).then((res: string | false) => { - if (res) return void this.buildEmbed(message, res); - return void buildDecModal({ message }); - }); + const res = await iteratePasswords(message); + + if (res) + this.buildEmbed(message, res); + else + buildDecModal({ message }); } } : null; @@ -169,9 +171,9 @@ export default definePlugin({ message.embeds.push({ type: "rich", - title: "Decrypted Message", + rawTitle: "Decrypted Message", color: "0x45f5f5", - description: revealed, + rawDescription: revealed, footer: { text: "Made with ❤️ by c0dine and Sammy!", }, diff --git a/src/plugins/mentionAvatars/README.md b/src/plugins/mentionAvatars/README.md new file mode 100644 index 000000000..912b51916 --- /dev/null +++ b/src/plugins/mentionAvatars/README.md @@ -0,0 +1,5 @@ +# MentionAvatars + +Shows user avatars inside mentions + +![](https://github.com/user-attachments/assets/fc76ea47-5e19-4063-a592-c57785a75cc7) diff --git a/src/plugins/mentionAvatars/index.tsx b/src/plugins/mentionAvatars/index.tsx new file mode 100644 index 000000000..549693142 --- /dev/null +++ b/src/plugins/mentionAvatars/index.tsx @@ -0,0 +1,44 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; +import { SelectedGuildStore, useState } from "@webpack/common"; +import { User } from "discord-types/general"; + +export default definePlugin({ + name: "MentionAvatars", + description: "Shows user avatars inside mentions", + authors: [Devs.Ven], + + patches: [{ + find: ".USER_MENTION)", + replacement: { + match: /children:"@"\.concat\((null!=\i\?\i:\i)\)(?<=\.useName\((\i)\).+?)/, + replace: "children:$self.renderUsername({username:$1,user:$2})" + } + }], + + renderUsername: ErrorBoundary.wrap((props: { user: User, username: string; }) => { + const { user, username } = props; + const [isHovering, setIsHovering] = useState(false); + + if (!user) return <>@{username}; + + return ( + setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + > + + @{username} + + ); + }, { noop: true }) +}); diff --git a/src/plugins/mentionAvatars/styles.css b/src/plugins/mentionAvatars/styles.css new file mode 100644 index 000000000..33404d7b5 --- /dev/null +++ b/src/plugins/mentionAvatars/styles.css @@ -0,0 +1,8 @@ +.vc-mentionAvatars-avatar { + vertical-align: middle; + width: 1em; + height: 1em; + margin: 0 4px 0.2rem 2px; + border-radius: 50%; + box-sizing: border-box; +} diff --git a/src/plugins/messageLogger/HistoryModal.tsx b/src/plugins/messageLogger/HistoryModal.tsx new file mode 100644 index 000000000..d1b5bf29c --- /dev/null +++ b/src/plugins/messageLogger/HistoryModal.tsx @@ -0,0 +1,91 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { classNameFactory } from "@api/Styles"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Margins } from "@utils/margins"; +import { classes } from "@utils/misc"; +import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { findByPropsLazy } from "@webpack"; +import { TabBar, Text, Timestamp, TooltipContainer, useState } from "@webpack/common"; + +import { parseEditContent } from "."; + +const CodeContainerClasses = findByPropsLazy("markup", "codeContainer"); +const MiscClasses = findByPropsLazy("messageContent", "markupRtl"); + +const cl = classNameFactory("vc-ml-modal-"); + +export function openHistoryModal(message: any) { + openModal(props => + + + + ); +} + +export function HistoryModal({ modalProps, message }: { modalProps: ModalProps; message: any; }) { + const [currentTab, setCurrentTab] = useState(message.editHistory.length); + const timestamps = [message.firstEditTimestamp, ...message.editHistory.map(m => m.timestamp)]; + const contents = [...message.editHistory.map(m => m.content), message.content]; + + return ( + + + Message Edit History + + + + + + {message.firstEditTimestamp.getTime() !== message.timestamp.getTime() && ( + + + + + + )} + + {timestamps.map((timestamp, index) => ( + + + + ))} + + +
+ {parseEditContent(contents[currentTab], message)} +
+
+
+ ); +} diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 6969f0909..9181306ad 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -24,21 +24,26 @@ import { Settings } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; +import { proxyLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; +import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; -import { ChannelStore, FluxDispatcher, i18n, Menu, MessageStore, Parser, Timestamp, UserStore, useStateFromStores } from "@webpack/common"; +import { findByCodeLazy, findByPropsLazy } from "@webpack"; +import { ChannelStore, FluxDispatcher, i18n, Menu, MessageStore, Parser, SelectedChannelStore, Timestamp, UserStore, useStateFromStores } from "@webpack/common"; import { Message } from "discord-types/general"; import overlayStyle from "./deleteStyleOverlay.css?managed"; import textStyle from "./deleteStyleText.css?managed"; +import { openHistoryModal } from "./HistoryModal"; interface MLMessage extends Message { deleted?: boolean; editHistory?: { timestamp: Date; content: string; }[]; + firstEditTimestamp?: Date; } const styles = findByPropsLazy("edited", "communicationDisabled", "isSystemMessage"); +const getMessage = findByCodeLazy('replace(/^\\n+|\\n+$/g,"")'); function addDeleteStyle() { if (Settings.plugins.MessageLogger.deleteStyle === "text") { @@ -125,10 +130,22 @@ const patchChannelContextMenu: NavContextMenuPatchCallback = (children, { channe ); }; +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(), + }); +} + export default definePlugin({ name: "MessageLogger", description: "Temporarily logs deleted and edited messages.", - authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN, Devs.Nickyux], + authors: [Devs.rushii, Devs.Ven, Devs.AutumnVN, Devs.Nickyux, Devs.Kyuuhachi], dependencies: ["MessageUpdaterAPI"], contextMenus: { @@ -150,11 +167,11 @@ export default definePlugin({ (oldMsg, newMsg) => oldMsg?.editHistory === newMsg?.editHistory ); - return ( + return Settings.plugins.MessageLogger.inlineEdits && ( <> {message.editHistory?.map(edit => (
- {Parser.parse(edit.content)} + {parseEditContent(edit.content, message)} 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: [ { // MessageStore @@ -324,7 +368,8 @@ export default definePlugin({ match: /this\.customRenderedContent=(\i)\.customRenderedContent,/, replace: "this.customRenderedContent = $1.customRenderedContent," + "this.deleted = $1.deleted || false," + - "this.editHistory = $1.editHistory || []," + "this.editHistory = $1.editHistory || []," + + "this.firstEditTimestamp = $1.firstEditTimestamp || this.editedTimestamp || this.timestamp," } ] }, @@ -337,7 +382,7 @@ export default definePlugin({ // 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 })" + "Object.assign($&,{ deleted:$1.deleted, editHistory:$1.editHistory, firstEditTimestamp:$1.firstEditTimestamp })" }, { @@ -356,7 +401,8 @@ export default definePlugin({ " return $2;" + "})())," + "deleted: arguments[1]?.deleted," + - "editHistory: arguments[1]?.editHistory" + "editHistory: arguments[1]?.editHistory," + + "firstEditTimestamp: new Date(arguments[1]?.firstEditTimestamp ?? $2.editedTimestamp ?? $2.timestamp)" }, { // Preserve deleted attribute on attachments @@ -404,6 +450,11 @@ export default definePlugin({ // 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," } ] }, @@ -433,6 +484,30 @@ export default definePlugin({ replace: "children:arguments[0].message.deleted?[]:$1" } ] + }, + { + // 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"||', + }, + { + 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/messageLogger/messageLogger.css b/src/plugins/messageLogger/messageLogger.css index a112b1961..2759129d9 100644 --- a/src/plugins/messageLogger/messageLogger.css +++ b/src/plugins/messageLogger/messageLogger.css @@ -38,3 +38,17 @@ .theme-light .messagelogger-edited { opacity: 0.5; } + +.messagelogger-edit-marker { + cursor: pointer; +} + +.vc-ml-modal-timestamp { + cursor: unset; + height: unset; +} + +.vc-ml-modal-tab-bar { + flex-wrap: wrap; + gap: 16px; +} diff --git a/src/plugins/moreUserTags/index.tsx b/src/plugins/moreUserTags/index.tsx index be81a8a89..45538fb66 100644 --- a/src/plugins/moreUserTags/index.tsx +++ b/src/plugins/moreUserTags/index.tsx @@ -256,6 +256,7 @@ export default definePlugin({ // in profiles { find: ",overrideDiscriminator:", + group: true, replacement: [ { // prevent channel id from getting ghosted @@ -263,7 +264,7 @@ export default definePlugin({ match: /user:\i,nick:\i,/, replace: "$&moreTags_channelId," }, { - match: /,botType:(\i\((\i)\)),/g, + match: /,botType:(\i),(?<=user:(\i).+?)/g, replace: ",botType:$self.getTag({user:$2,channelId:moreTags_channelId,origType:$1,location:'not-chat'})," } ] diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 37177caad..3e7d216b7 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -60,7 +60,7 @@ export default definePlugin({ find: 'location:"UserMention', replacement: [ { - match: /user:(\i),channel:(\i).{0,400}?"@"\.concat\(.+?\)/, + match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/, replace: "$&,color:$self.getUserColor($1?.id,{channelId:$2?.id})" } ], diff --git a/src/plugins/showHiddenThings/index.ts b/src/plugins/showHiddenThings/index.ts index 599bcd36d..90bb345ef 100644 --- a/src/plugins/showHiddenThings/index.ts +++ b/src/plugins/showHiddenThings/index.ts @@ -18,34 +18,21 @@ import { definePluginSettings, migratePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin, { OptionType, PluginSettingDef } from "@utils/types"; + +const opt = (description: string) => ({ + type: OptionType.BOOLEAN, + description, + default: true, + restartNeeded: true +} satisfies PluginSettingDef); const settings = definePluginSettings({ - showTimeouts: { - type: OptionType.BOOLEAN, - description: "Show member timeout icons in chat.", - default: true, - }, - showInvitesPaused: { - type: OptionType.BOOLEAN, - description: "Show the invites paused tooltip in the server list.", - default: true, - }, - showModView: { - type: OptionType.BOOLEAN, - description: "Show the member mod view context menu item in all servers.", - default: true, - }, - disableDiscoveryFilters: { - type: OptionType.BOOLEAN, - description: "Disable filters in Server Discovery search that hide servers that don't meet discovery criteria.", - default: true, - }, - disableDisallowedDiscoveryFilters: { - type: OptionType.BOOLEAN, - description: "Disable filters in Server Discovery search that hide NSFW & disallowed servers.", - default: true, - }, + showTimeouts: opt("Show member timeout icons in chat."), + showInvitesPaused: opt("Show the invites paused tooltip in the server list."), + showModView: opt("Show the member mod view context menu item in all servers."), + disableDiscoveryFilters: opt("Disable filters in Server Discovery search that hide servers that don't meet discovery criteria."), + disableDisallowedDiscoveryFilters: opt("Disable filters in Server Discovery search that hide NSFW & disallowed servers."), }); migratePluginSettings("ShowHiddenThings", "ShowTimeouts"); diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 8d1504e1a..4f9fcf304 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -67,7 +67,7 @@ export default definePlugin({ const { nick } = author; const prefix = withMentionPrefix ? "@" : ""; - if (isRepliedMessage && !settings.store.inReplies || username === nick.toLowerCase()) + if (isRepliedMessage && !settings.store.inReplies || username.toLowerCase() === nick.toLowerCase()) return <>{prefix}{nick}; if (settings.store.mode === "user-nick") diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index 112f9f755..6bde04be0 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -183,14 +183,22 @@ export default definePlugin({ }, patches: [ - // Profiles Modal pfp - ...[".MODAL,hasProfileEffect", ".FULL_SIZE,hasProfileEffect:"].map(find => ({ - find, + // Avatar component used in User DMs "User Profile" popup in the right and Profiles Modal pfp + { + find: ".overlay:void 0,status:", + replacement: { + match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/, + replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openImage($1)}," + } + }, + // Old Profiles Modal pfp + { + find: ".MODAL,hasProfileEffect", replacement: { match: /\{src:(\i)(?=,avatarDecoration)/, replace: "{src:$1,onClick:()=>$self.openImage($1)" } - })), + }, // Banners ...[".NITRO_BANNER,", "=!1,canUsePremiumCustomization:"].map(find => ({ find, @@ -202,7 +210,7 @@ export default definePlugin({ 'onClick:ev=>$1&&ev.target.style.backgroundImage&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,' } })), - // User DMs "User Profile" popup in the right + // Old User DMs "User Profile" popup in the right { find: ".avatarPositionPanel", replacement: { @@ -210,14 +218,6 @@ export default definePlugin({ replace: "$1style:($2)?{cursor:\"pointer\"}:{},onClick:$2?()=>{$self.openImage($3)}" } }, - { - find: ".canUsePremiumProfileCustomization,{avatarSrc:", - replacement: { - match: /\.avatar,\i\.clickable\),onClick:\i,(?<=avatarSrc:(\i).+?)/, - replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openImage($1)}," - - } - }, // Group DMs top small & large icon { find: /\.recipients\.length>=2(?! delete a.deleted); return clone;