From 17b90beee1af377becbf92764cf954910037f140 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sun, 31 Aug 2025 05:18:40 +0200 Subject: [PATCH 1/4] add context menu options to vencord badges --- src/api/Badges.ts | 39 +++++++++++++++------- src/plugins/_api/badges/index.tsx | 54 +++++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/api/Badges.ts b/src/api/Badges.ts index f79ca0995..1345b26d6 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -34,7 +34,9 @@ export interface ProfileBadge { image?: string; link?: string; /** Action to perform when you click the badge */ - onClick?(event: React.MouseEvent, props: BadgeUserArgs): void; + onClick?(event: React.MouseEvent, props: ProfileBadge & BadgeUserArgs): void; + /** Action to perform when you right click the badge */ + onContextMenu?(event: React.MouseEvent, props: BadgeUserArgs & BadgeUserArgs): void; /** Should the user display this badge? */ shouldShow?(userInfo: BadgeUserArgs): boolean; /** Optional props (e.g. style) for the badge, ignored for component badges */ @@ -76,21 +78,34 @@ export function removeProfileBadge(badge: ProfileBadge) { export function _getBadges(args: BadgeUserArgs) { const badges = [] as ProfileBadge[]; for (const badge of Badges) { - if (!badge.shouldShow || badge.shouldShow(args)) { - const b = badge.getBadges - ? badge.getBadges(args).map(b => { - b.component &&= ErrorBoundary.wrap(b.component, { noop: true }); - return b; - }) - : [{ ...badge, ...args }]; + if (badge.shouldShow && !badge.shouldShow(args)) { + continue; + } - badge.position === BadgePosition.START - ? badges.unshift(...b) - : badges.push(...b); + const b = badge.getBadges + ? badge.getBadges(args).map(badge => ({ + ...args, + ...badge, + component: badge.component && ErrorBoundary.wrap(badge.component, { noop: true }) + })) + : [{ ...args, ...badge }]; + + if (badge.position === BadgePosition.START) { + badges.unshift(...b); + } else { + badges.push(...b); } } + const donorBadges = BadgeAPIPlugin.getDonorBadges(args.userId); - if (donorBadges) badges.unshift(...donorBadges); + if (donorBadges) { + badges.unshift( + ...donorBadges.map(badge => ({ + ...args, + ...badge, + })) + ); + } return badges; } diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 433c30807..4118435af 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -27,11 +27,11 @@ import { openContributorModal } from "@components/settings/tabs"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; -import { shouldShowContributorBadge } from "@utils/misc"; +import { copyWithToast, shouldShowContributorBadge } from "@utils/misc"; import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import definePlugin from "@utils/types"; import { User } from "@vencord/discord-types"; -import { Forms, Toasts, UserStore } from "@webpack/common"; +import { ContextMenuApi, Forms, Menu, Toasts, UserStore } from "@webpack/common"; const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64"; @@ -56,9 +56,35 @@ async function loadBadges(noCache = false) { let intervalId: any; +function BadgeContextMenu({ badge }: { badge: ProfileBadge & BadgeUserArgs; }) { + console.log(badge); + return ( + + {badge.description && ( + copyWithToast(badge.description!)} + /> + )} + {badge.image && ( + copyWithToast(badge.image!)} + /> + )} + + ); +} + export default definePlugin({ name: "BadgeAPI", - description: "API to add badges to users.", + description: "API to add badges to users", authors: [Devs.Megu, Devs.Ven, Devs.TheSun], required: true, patches: [ @@ -80,10 +106,10 @@ export default definePlugin({ match: /(?<="aria-label":(\i)\.description,.{0,200})children:/, replace: "children:$1.component?$self.renderBadgeComponent({...$1}) :" }, - // conditionally override their onClick with badge.onClick if it exists + // handle onClick and onContextMenu { match: /href:(\i)\.link/, - replace: "...($1.onClick&&{onClick:vcE=>$1.onClick(vcE,$1)}),$&" + replace: "...$self.getBadgeMouseEventHandlers($1),$&" } ] } @@ -137,6 +163,19 @@ export default definePlugin({ }, { noop: true }), + getBadgeMouseEventHandlers(badge: ProfileBadge & BadgeUserArgs) { + const handlers = {} as Record void>; + + if (!badge) return handlers; // sanity check + + const { onClick, onContextMenu } = badge; + + if (onClick) handlers.onClick = e => onClick(e, badge); + if (onContextMenu) handlers.onContextMenu = e => onContextMenu(e, badge); + + return handlers; + }, + getDonorBadges(userId: string) { return DonorBadges[userId]?.map(badge => ({ image: badge.badge, @@ -148,6 +187,9 @@ export default definePlugin({ transform: "scale(0.9)" // The image is a bit too big compared to default badges } }, + onContextMenu(event, badge) { + ContextMenuApi.openContextMenu(event, () => ); + }, onClick() { const modalKey = openModal(props => ( { @@ -203,6 +245,6 @@ export default definePlugin({ )); }, - })); + } satisfies ProfileBadge)); } }); From 8ebfd9a19073037ed8c714ddb429b6ced8ed09c0 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 1 Sep 2025 20:53:38 -0300 Subject: [PATCH 2/4] NoTypingAnimation: Fix not working due to broken patches --- src/plugins/noTypingAnimation/index.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/plugins/noTypingAnimation/index.ts b/src/plugins/noTypingAnimation/index.ts index d4aab9004..74bcdd50e 100644 --- a/src/plugins/noTypingAnimation/index.ts +++ b/src/plugins/noTypingAnimation/index.ts @@ -11,11 +11,13 @@ export default definePlugin({ name: "NoTypingAnimation", authors: [Devs.AutumnVN], description: "Disables the CPU-intensive typing dots animation", - patches: [{ - find: "dotCycle", - replacement: { - match: /document.hasFocus\(\)/, - replace: "false" + patches: [ + { + find: "dotCycle", + replacement: { + match: /focused:(\i)/g, + replace: (_, focused) => `_focused:${focused}=false` + } } - }] + ] }); From f0f75aa91877ce2b6dec426684ad96f36ebb862e Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Mon, 1 Sep 2025 19:59:15 -0400 Subject: [PATCH 3/4] AlwaysAnimate: Add nameplates support (#3641) --- src/plugins/alwaysAnimate/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/plugins/alwaysAnimate/index.ts b/src/plugins/alwaysAnimate/index.ts index 4064de9f6..1635781c2 100644 --- a/src/plugins/alwaysAnimate/index.ts +++ b/src/plugins/alwaysAnimate/index.ts @@ -54,6 +54,14 @@ export default definePlugin({ match: /(\.headerContent.+?guildBanner:\i,animate:)\i/, replace: "$1!0" } + }, + { + // Nameplates + find: ".MINI_PREVIEW,[", + replacement: { + match: /animate:\i,loop:/, + replace: "animate:true,loop:true,_loop:" + } } ] }); From 9b0ae0fd9068e3e6763c1d1e258ef4c7580a48d5 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Tue, 2 Sep 2025 03:20:56 +0100 Subject: [PATCH 4/4] Translate: support automod & forwarded messages (#3367) Co-authored-by: V --- src/plugins/translate/index.tsx | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/plugins/translate/index.tsx b/src/plugins/translate/index.tsx index 363897d1b..1bcc8b3fa 100644 --- a/src/plugins/translate/index.tsx +++ b/src/plugins/translate/index.tsx @@ -21,6 +21,7 @@ import "./styles.css"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { Message } from "@vencord/discord-types"; import { ChannelStore, Menu } from "@webpack/common"; import { settings } from "./settings"; @@ -28,8 +29,9 @@ import { setShouldShowTranslateEnabledTooltip, TranslateChatBarIcon, TranslateIc import { handleTranslate, TranslationAccessory } from "./TranslationAccessory"; import { translate } from "./utils"; -const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => { - if (!message.content) return; +const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => { + const content = getMessageContent(message); + if (!content) return; const group = findGroupChildrenByChildId("copy-text", children); if (!group) return; @@ -40,13 +42,23 @@ const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => label="Translate" icon={TranslateIcon} action={async () => { - const trans = await translate("received", message.content); + const trans = await translate("received", content); handleTranslate(message.id, trans); }} /> )); }; + +function getMessageContent(message: Message) { + // Message snapshots is an array, which allows for nested snapshots, which Discord does not do yet. + // no point collecting content or rewriting this to render in a certain way that makes sense + // for something currently impossible. + return message.content + || message.messageSnapshots?.[0]?.message.content + || message.embeds?.find(embed => embed.type === "auto_moderation_message")?.rawDescription || ""; +} + let tooltipTimeout: any; export default definePlugin({ @@ -64,8 +76,9 @@ export default definePlugin({ renderChatBarButton: TranslateChatBarIcon, - renderMessagePopoverButton(message) { - if (!message.content) return null; + renderMessagePopoverButton(message: Message) { + const content = getMessageContent(message); + if (!content) return null; return { label: "Translate", @@ -73,7 +86,7 @@ export default definePlugin({ message, channel: ChannelStore.getChannel(message.channel_id), onClick: async () => { - const trans = await translate("received", message.content); + const trans = await translate("received", content); handleTranslate(message.id, trans); } };