From a72fe615939578e9f979d5fa4abcd9b11d65a296 Mon Sep 17 00:00:00 2001 From: 5xk Date: Sat, 13 Jun 2026 14:52:28 -0300 Subject: [PATCH 1/9] VcNarrator: don't narrate twice when using Overlay (#4300) Fixes #2717 --- src/plugins/vcNarrator/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/vcNarrator/index.tsx b/src/plugins/vcNarrator/index.tsx index cd05ebfaa..311371680 100644 --- a/src/plugins/vcNarrator/index.tsx +++ b/src/plugins/vcNarrator/index.tsx @@ -43,7 +43,8 @@ interface VoiceStateChangeEvent { // not say the second mute, which would lead you to believe they're unmuted function speak(text: string) { - if (!text) return; + // Don't narrate in the overlay window, otherwise everything is said twice + if (!text || window.__OVERLAY__) return; const { volume, rate } = settings.store; From 6a1ba61b29d96a6850cf9ddc627bda7e8375ac3d Mon Sep 17 00:00:00 2001 From: Clov <273623891+chloricacid@users.noreply.github.com> Date: Sun, 14 Jun 2026 14:21:04 +0300 Subject: [PATCH 2/9] SupportHelper: add "Run Snippet" to all issue posts (#4289) --- src/plugins/_core/supportHelper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 91819048d..b88643a33 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -317,7 +317,7 @@ export default definePlugin({ } } - if (props.message.author.id === VENBOT_USER_ID && (props.channel.parent_id === KNOWN_ISSUES_CHANNEL_ID || props.channel.parent_id === SUPPORT_CATEGORY_ID)) { + if (props.channel.parent_id === KNOWN_ISSUES_CHANNEL_ID || (props.channel.parent_id === SUPPORT_CATEGORY_ID && props.message.author.id === VENBOT_USER_ID)) { const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || ""); if (match) { buttons.push( From bd4559b8947197c11b7cbae95febacacfc5bc046 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sun, 14 Jun 2026 12:24:12 +0100 Subject: [PATCH 3/9] AppleMusicRichPresence: make links customizable (#4298) --- src/plugins/appleMusic.desktop/index.tsx | 62 ++++++++++++++++++++++-- src/plugins/appleMusic.desktop/native.ts | 2 + 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/plugins/appleMusic.desktop/index.tsx b/src/plugins/appleMusic.desktop/index.tsx index d1b32c029..5dddb1d55 100644 --- a/src/plugins/appleMusic.desktop/index.tsx +++ b/src/plugins/appleMusic.desktop/index.tsx @@ -5,11 +5,12 @@ */ import { definePluginSettings } from "@api/Settings"; +import { Paragraph } from "@components/Paragraph"; import { Devs, IS_MAC } from "@utils/constants"; import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; import { Activity, ActivityAssets, ActivityButton } from "@vencord/discord-types"; import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "@vencord/discord-types/enums"; -import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common"; +import { ApplicationAssetUtils, FluxDispatcher } from "@webpack/common"; const Native = VencordNative.pluginHelpers.AppleMusicRichPresence as PluginNative; @@ -19,6 +20,7 @@ export interface TrackData { artist?: string; appleMusicLink?: string; + appleMusicArtistLink?: string; songLink?: string; albumArtwork?: string; @@ -34,6 +36,12 @@ const enum AssetImageType { Disabled = "Disabled" } +const enum LinkType { + Album = "Album", + Artist = "Artist", + Disabled = "Disabled" +} + const applicationId = "1239490006054207550"; function setActivity(activity: Activity | null) { @@ -104,6 +112,24 @@ const settings = definePluginSettings({ description: "Activity state format string", default: "{artist} · {album}" }, + detailsLink: { + type: OptionType.SELECT, + description: "Activity details link", + options: [ + { label: "Album", value: LinkType.Album, default: true }, + { label: "Artist", value: LinkType.Artist }, + { label: "Disabled", value: LinkType.Disabled } + ], + }, + stateLink: { + type: OptionType.SELECT, + description: "Activity state link", + options: [ + { label: "Album", value: LinkType.Album }, + { label: "Artist", value: LinkType.Artist, default: true }, + { label: "Disabled", value: LinkType.Disabled } + ], + }, largeImageType: { type: OptionType.SELECT, description: "Activity assets large image type", @@ -118,6 +144,15 @@ const settings = definePluginSettings({ description: "Activity assets large text format string", default: "{album}" }, + largeImageLink: { + type: OptionType.SELECT, + description: "Activity assets large image link", + options: [ + { label: "Album", value: LinkType.Album, default: true }, + { label: "Artist", value: LinkType.Artist }, + { label: "Disabled", value: LinkType.Disabled } + ], + }, smallImageType: { type: OptionType.SELECT, description: "Activity assets small image type", @@ -132,6 +167,15 @@ const settings = definePluginSettings({ description: "Activity assets small text format string", default: "{artist}" }, + smallImageLink: { + type: OptionType.SELECT, + description: "Activity assets small image link", + options: [ + { label: "Album", value: LinkType.Album }, + { label: "Artist", value: LinkType.Artist, default: true }, + { label: "Disabled", value: LinkType.Disabled } + ], + }, }); function customFormat(formatStr: string, data: TrackData) { @@ -141,6 +185,14 @@ function customFormat(formatStr: string, data: TrackData) { .replaceAll("{artist}", data.artist ?? ""); } +function getLink(type: LinkType, data: TrackData) { + return type === LinkType.Album + ? data.appleMusicLink + : type === LinkType.Artist + ? data.appleMusicArtistLink + : undefined; +} + function getImageAsset(type: AssetImageType, data: TrackData) { const source = type === AssetImageType.Album ? data.albumArtwork @@ -161,10 +213,10 @@ export default definePlugin({ settingsAboutComponent() { return <> - + For the customizable activity format strings, you can use several special strings to include track data in activities!{" "} {"{name}"} is replaced with the track name; {"{artist}"} is replaced with the artist(s)' name(s); and {"{album}"} is replaced with the album name. - + ; }, @@ -200,11 +252,13 @@ export default definePlugin({ if (settings.store.largeImageType !== AssetImageType.Disabled) { assets.large_image = largeImageAsset; if (!isRadio) assets.large_text = customFormat(settings.store.largeTextString, trackData); + assets.large_url = getLink(settings.store.largeImageLink, trackData); } if (settings.store.smallImageType !== AssetImageType.Disabled) { assets.small_image = smallImageAsset; if (!isRadio) assets.small_text = customFormat(settings.store.smallTextString, trackData); + assets.small_url = getLink(settings.store.smallImageLink, trackData); } const buttons: ActivityButton[] = []; @@ -229,6 +283,8 @@ export default definePlugin({ name: customFormat(settings.store.nameString, trackData), details: customFormat(settings.store.detailsString, trackData), state: isRadio ? undefined : customFormat(settings.store.stateString, trackData), + details_url: getLink(settings.store.detailsLink, trackData), + state_url: getLink(settings.store.stateLink, trackData), timestamps: (trackData.playerPosition && trackData.duration && settings.store.enableTimestamps) ? { start: Date.now() - (trackData.playerPosition * 1000), diff --git a/src/plugins/appleMusic.desktop/native.ts b/src/plugins/appleMusic.desktop/native.ts index 0cbddb949..0c137e953 100644 --- a/src/plugins/appleMusic.desktop/native.ts +++ b/src/plugins/appleMusic.desktop/native.ts @@ -19,6 +19,7 @@ async function applescript(cmds: string[]) { interface RemoteData { appleMusicLink?: string, + appleMusicArtistLink?: string; songLink?: string, albumArtwork?: string, artistArtwork?: string; @@ -58,6 +59,7 @@ async function fetchRemoteData({ id, name, artist, album }: { id: string, name: id, data: { appleMusicLink: songData.trackViewUrl, + appleMusicArtistLink: songData.artistViewUrl, songLink: `https://song.link/i/${new URL(songData.trackViewUrl).searchParams.get("i")}`, albumArtwork: (songData.artworkUrl100).replace("100x100", "512x512"), artistArtwork: artistArtworkURL From 278a10432dcb1d5bdebb725a834704faf32226e7 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sun, 14 Jun 2026 13:50:00 +0200 Subject: [PATCH 4/9] ViewRaw: improve UI --- src/components/CodeBlock.tsx | 9 +++++---- src/plugins/viewRaw/index.tsx | 34 +++++++++++++++++----------------- src/plugins/viewRaw/style.css | 6 ++++++ 3 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 src/plugins/viewRaw/style.css diff --git a/src/components/CodeBlock.tsx b/src/components/CodeBlock.tsx index e85e7ed7d..b4a1ccbb8 100644 --- a/src/components/CodeBlock.tsx +++ b/src/components/CodeBlock.tsx @@ -4,6 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { classes } from "@utils/misc"; import { findCssClassesLazy } from "@webpack"; import { Parser } from "@webpack/common"; @@ -12,9 +13,9 @@ const CodeContainerClasses = findCssClassesLazy("markup", "codeContainer"); /** * Renders code in a Discord codeblock */ -export function CodeBlock(props: { content?: string, lang: string; }) { +export function CodeBlock({ className, ...props }: { content?: string, lang: string; className?: string; }) { return ( -
+
{Parser.defaultRules.codeBlock.react(props, null, {})}
); @@ -23,9 +24,9 @@ export function CodeBlock(props: { content?: string, lang: string; }) { /** * Renders inline code like `this` */ -export function InlineCode({ children }: { children: React.ReactNode; }) { +export function InlineCode({ children, className }: { children: React.ReactNode; className?: string; }) { return ( - + {children} ); diff --git a/src/plugins/viewRaw/index.tsx b/src/plugins/viewRaw/index.tsx index 8d9809988..9d3617812 100644 --- a/src/plugins/viewRaw/index.tsx +++ b/src/plugins/viewRaw/index.tsx @@ -16,22 +16,24 @@ * along with this program. If not, see . */ +import "./style.css"; + import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; import { CodeBlock } from "@components/CodeBlock"; -import { Divider } from "@components/Divider"; import ErrorBoundary from "@components/ErrorBoundary"; +import { HeadingSecondary } from "@components/Heading"; +import { Margins } from "@components/margins"; import { Devs } from "@utils/constants"; import { copyWithToast, getCurrentGuild, getIntlMessage } from "@utils/discord"; import { isTruthy } from "@utils/guards"; -import { Margins } from "@utils/margins"; import definePlugin, { IconComponent, OptionType } from "@utils/types"; import { Message } from "@vencord/discord-types"; -import { ChannelStore, Forms, GuildRoleStore, Menu, Modal, openModal, UserProfileStore } from "@webpack/common"; +import { ChannelStore, GuildRoleStore, Menu, Modal, openModal, UserProfileStore } from "@webpack/common"; import { MouseEventHandler } from "react"; -const CopyIcon: IconComponent = ({ height = 20, width = 20, className }) => { +const CopyRawIcon: IconComponent = ({ height = 20, width = 20, className }) => { return ( copyWithToast(json, `${type} data copied to clipboard!`) }, msgContent && { @@ -92,14 +94,12 @@ function openViewRawModal(json: string, type: string, msgContent?: string) { > {!!msgContent && ( <> - Content - - + Message Content + + Message Data )} - - {type} Data - + )); @@ -150,7 +150,7 @@ function MakeContextCallback(name: "Guild" | "Role" | "User" | "Channel" | "Mess id={id} label="View Raw" action={action} - icon={CopyIcon} + icon={CopyRawIcon} /> ); }; @@ -168,7 +168,7 @@ const devContextCallback: NavContextMenuPatchCallback = (children, { id }: { id: id={"vc-view-role-raw"} label="View Raw" action={() => openViewRawModal(JSON.stringify(role, null, 4), "Role")} - icon={CopyIcon} + icon={CopyRawIcon} /> ); }; @@ -193,7 +193,7 @@ export default definePlugin({ }, messagePopoverButton: { - icon: CopyIcon, + icon: CopyRawIcon, render(msg) { const handleClick = () => { if (settings.store.clickMethod === "Right") { @@ -221,7 +221,7 @@ export default definePlugin({ return { label, - icon: CopyIcon, + icon: CopyRawIcon, message: msg, channel: ChannelStore.getChannel(msg.channel_id), onClick: handleClick, diff --git a/src/plugins/viewRaw/style.css b/src/plugins/viewRaw/style.css new file mode 100644 index 000000000..a283f7405 --- /dev/null +++ b/src/plugins/viewRaw/style.css @@ -0,0 +1,6 @@ +.vc-viewRaw-codeBlock, +.vc-viewRaw-codeBlock :is(pre, code, [class*="codeContainer"]) { + max-width: unset; + width: 100%; + box-sizing: border-box; +} \ No newline at end of file From d564fc40250ae7ba5663c0e29d066bf3d5c6fa4c Mon Sep 17 00:00:00 2001 From: NightmareSan7 Date: Sun, 14 Jun 2026 15:12:06 +0200 Subject: [PATCH 5/9] BetterRoleContext: support role headers in member list (#4276) Co-authored-by: Vendicated --- src/components/Icons.tsx | 25 +++++++ src/plugins/betterRoleContext/README.md | 2 +- src/plugins/betterRoleContext/index.tsx | 67 +++++++++++++++---- .../components/RolesAndUsersPermissions.tsx | 13 +--- src/utils/constants.ts | 4 ++ 5 files changed, 86 insertions(+), 25 deletions(-) diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index eb36ede95..47a52a82c 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -81,6 +81,31 @@ export function CopyIcon(props: IconProps) { ); } +/** + * Discord's ID icon, as seen in Developer Mode "Copy ID" context menu items + */ +export function CopyIdIcon(props: IconProps) { + return ( + + + + + ); +} + + /** * Discord's open external icon, as seen in the user profile connections */ diff --git a/src/plugins/betterRoleContext/README.md b/src/plugins/betterRoleContext/README.md index e54e1e313..adac2f5d5 100644 --- a/src/plugins/betterRoleContext/README.md +++ b/src/plugins/betterRoleContext/README.md @@ -1,6 +1,6 @@ # BetterRoleContext -Adds options to copy role color, edit role and view role icon when right clicking roles in the user profile +Adds options to copy role color, edit role and view role icon when right clicking roles in the user profile or in the member list ![](https://github.com/Vendicated/Vencord/assets/45497981/354220a4-09f3-4c5f-a28e-4b19ca775190) diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx index d0c06c4f9..5344da5b3 100644 --- a/src/plugins/betterRoleContext/index.tsx +++ b/src/plugins/betterRoleContext/index.tsx @@ -6,16 +6,16 @@ import { definePluginSettings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; -import { ImageIcon } from "@components/Icons"; +import { CopyIdIcon, ImageIcon } from "@components/Icons"; import { copyToClipboard } from "@utils/clipboard"; import { Devs } from "@utils/constants"; -import { getCurrentChannel, getCurrentGuild, openImageModal } from "@utils/discord"; +import { getCurrentChannel, getCurrentGuild, getIntlMessage, openImageModal } from "@utils/discord"; import { isTruthy } from "@utils/guards"; import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; import { Guild, PopoutProps, Role } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy, findCssClassesLazy } from "@webpack"; -import { GuildRoleStore, Menu, PermissionStore, Popout, useRef } from "@webpack/common"; +import { ContextMenuApi, GuildRoleStore, Menu, PermissionStore, Popout, useRef } from "@webpack/common"; import { ComponentType } from "react"; const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild"); @@ -171,22 +171,63 @@ export function buildExtraRoleContextMenuItems(role: Role, guild: Guild, popoutR return { before, after }; } +export function openRoleContextMenu(event: React.MouseEvent, { guildId, id: roleId }: { guildId: string; id: string; }) { + const guild = getCurrentGuild(); + if (!guild || guild.id !== guildId) return; + + const role = GuildRoleStore.getRole(guildId, roleId); + if (!role) return; + + ContextMenuApi.openContextMenu(event, () => { + const popoutRef = useRef(null); + const { before, after } = buildExtraRoleContextMenuItems(role, guild, popoutRef); + + return ( + + {before} + {after} + copyToClipboard(role.id)} + /> + + ); + }); +} + 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", + description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile or in the member list", tags: ["Roles", "Appearance"], - authors: [Devs.Ven, Devs.goodbee], + authors: [Devs.Ven, Devs.goodbee, Devs.nightmaresan], dependencies: ["UserSettingsAPI"], - settings, - - patches: [{ - find: ".ROLE_MENTION)", - replacement: { - match: /function (\i)(?=.+?renderPopout:.{0,20}\1,\{guildId:\i,channelId:\i)/, - replace: "$self.RoleMembers=$1;$&" + openRoleContextMenu, + patches: [ + { + find: ".ROLE_MENTION)", + replacement: { + match: /function (\i)(?=.+?renderPopout:.{0,20}\1,\{guildId:\i,channelId:\i)/, + replace: "$self.RoleMembers=$1;$&" + } + }, + // Conflicts with RoleColorEverywhere which changes the code at the end of our match. (and also uses same find & similar match) + // However, BetterRoleContext applies first (alphabetic order), so it's not an issue + { + find: 'tutorialId:"whos-online', + replacement: { + match: /(?<=#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.{0,200}?"aria-hidden":!0,)children:.{0,200}?(?:—|\\u2014) ",\i\]\}\)\]/, + replace: "onContextMenu:e=>$self.openRoleContextMenu(e,arguments[0]),$&" + } } - }], + ], start() { // DeveloperMode needs to be enabled for the context menu to be shown diff --git a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx index d49aacca9..b8098ae62 100644 --- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx +++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx @@ -18,7 +18,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; -import { InfoIcon, OwnerCrownIcon } from "@components/Icons"; +import { CopyIdIcon, InfoIcon, OwnerCrownIcon } from "@components/Icons"; import { buildExtraRoleContextMenuItems } from "@plugins/betterRoleContext"; import { cl, getGuildPermissionSpecMap, loadGetGuildPermissionSpecMap } from "@plugins/permissionsViewer/utils"; import { copyToClipboard } from "@utils/clipboard"; @@ -198,15 +198,6 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea ); } -function IDIcon() { - return ( - - - - - ); -} - function ViewAsRoleIcon() { return ( @@ -231,7 +222,7 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str copyToClipboard(roleId)} /> diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 751a72f9d..8629d51d4 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -638,6 +638,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "prism", id: 390884143749136386n, }, + nightmaresan: { + name: "NightmareSan", + id: 304239816466235392n + }, } satisfies Record); // iife so #__PURE__ works correctly From d8abc65d3c12f23b2d6c94d6fea2b4edec482ebf Mon Sep 17 00:00:00 2001 From: Suffocate <70031311+lolsuffocate@users.noreply.github.com> Date: Sun, 14 Jun 2026 16:35:10 +0100 Subject: [PATCH 6/9] Add Display Names for Settings (#3212) --- .../settings/tabs/plugins/components/BooleanSetting.tsx | 2 +- src/components/settings/tabs/plugins/components/Common.tsx | 7 ++++--- .../settings/tabs/plugins/components/NumberSetting.tsx | 2 +- .../settings/tabs/plugins/components/SelectSetting.tsx | 2 +- .../settings/tabs/plugins/components/SliderSetting.tsx | 2 +- .../settings/tabs/plugins/components/TextSetting.tsx | 2 +- src/plugins/consoleJanitor/index.tsx | 2 +- src/plugins/ircColors/index.ts | 1 + src/plugins/lastfmRichPresence/index.tsx | 2 ++ src/plugins/mentionAvatars/index.tsx | 1 + src/plugins/messageLinkEmbeds/index.tsx | 1 + src/plugins/openInApp/index.ts | 3 +++ src/plugins/pinDms/index.tsx | 2 ++ src/plugins/replaceGoogleSearch/index.tsx | 1 + src/plugins/serverListIndicators/index.tsx | 2 +- src/plugins/shikiCodeblocks.desktop/settings.ts | 1 + src/plugins/silentTyping/index.tsx | 2 +- src/plugins/translate/settings.tsx | 1 + src/plugins/viewIcons/index.tsx | 1 + src/plugins/xsOverlay/index.tsx | 3 +++ src/utils/types.ts | 3 ++- 21 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/components/settings/tabs/plugins/components/BooleanSetting.tsx b/src/components/settings/tabs/plugins/components/BooleanSetting.tsx index 76a887ea1..f3e117e75 100644 --- a/src/components/settings/tabs/plugins/components/BooleanSetting.tsx +++ b/src/components/settings/tabs/plugins/components/BooleanSetting.tsx @@ -41,7 +41,7 @@ export function BooleanSetting({ setting, pluginSettings, definedSettings, id, o } return ( - +
- {name && {wordsToTitle(wordsFromCamel(name))}} + {name ?? wordsToTitle(wordsFromCamel(id))} {description && {description}}
{children} diff --git a/src/components/settings/tabs/plugins/components/NumberSetting.tsx b/src/components/settings/tabs/plugins/components/NumberSetting.tsx index fa348a108..36837abc9 100644 --- a/src/components/settings/tabs/plugins/components/NumberSetting.tsx +++ b/src/components/settings/tabs/plugins/components/NumberSetting.tsx @@ -50,7 +50,7 @@ export function NumberSetting({ setting, pluginSettings, definedSettings, id, on } return ( - + +