playSample(s, t)}>
+ playSample(t)}>
{wordsToTitle([t])}
))}
diff --git a/src/plugins/volumeBooster/index.ts b/src/plugins/volumeBooster/index.ts
index 62d2a2c1..664a59dd 100644
--- a/src/plugins/volumeBooster/index.ts
+++ b/src/plugins/volumeBooster/index.ts
@@ -17,9 +17,8 @@
*/
import { definePluginSettings } from "@api/Settings";
-import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
-import definePlugin, { OptionType } from "@utils/types";
+import definePlugin, { makeRange, OptionType } from "@utils/types";
const settings = definePluginSettings({
multiplier: {
diff --git a/src/plugins/webKeybinds.web/index.ts b/src/plugins/webKeybinds.web/index.ts
index a7435c95..6a0da357 100644
--- a/src/plugins/webKeybinds.web/index.ts
+++ b/src/plugins/webKeybinds.web/index.ts
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import { Devs } from "@utils/constants";
+import { Devs, IS_MAC } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { ComponentDispatch, FluxDispatcher, NavigationRouter, SelectedGuildStore, SettingsRouter } from "@webpack/common";
@@ -30,7 +30,7 @@ export default definePlugin({
enabledByDefault: true,
onKey(e: KeyboardEvent) {
- const hasCtrl = e.ctrlKey || (e.metaKey && navigator.platform.includes("Mac"));
+ const hasCtrl = e.ctrlKey || (e.metaKey && IS_MAC);
if (hasCtrl) switch (e.key) {
case "t":
diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx
index a2826a2c..af6d8fdb 100644
--- a/src/plugins/whoReacted/index.tsx
+++ b/src/plugins/whoReacted/index.tsx
@@ -23,10 +23,9 @@ import { Queue } from "@utils/Queue";
import { useForceUpdater } from "@utils/react";
import definePlugin from "@utils/types";
import { CustomEmoji, Message, ReactionEmoji, User } from "@vencord/discord-types";
-import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
-import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip, useEffect, useLayoutEffect } from "@webpack/common";
+import { findByPropsLazy } from "@webpack";
+import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip, useEffect, useLayoutEffect, UserSummaryItem } from "@webpack/common";
-const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
let Scroll: any = null;
const queue = new Queue();
diff --git a/src/plugins/xsOverlay/index.tsx b/src/plugins/xsOverlay/index.tsx
index 27faac93..ba184942 100644
--- a/src/plugins/xsOverlay/index.tsx
+++ b/src/plugins/xsOverlay/index.tsx
@@ -5,10 +5,9 @@
*/
import { definePluginSettings } from "@api/Settings";
-import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
-import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
+import definePlugin, { makeRange, OptionType, PluginNative, ReporterTestable } from "@utils/types";
import type { Channel, Embed, GuildMember, MessageAttachment, User } from "@vencord/discord-types";
import { findByCodeLazy, findLazy } from "@webpack";
import { Button, ChannelStore, GuildRoleStore, GuildStore, UserStore } from "@webpack/common";
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index afe1651f..962ea789 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -26,6 +26,11 @@ export const SUPPORT_CHANNEL_ID = "1026515880080842772";
export const SUPPORT_CATEGORY_ID = "1108135649699180705";
export const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
+const platform = navigator.platform.toLowerCase();
+export const IS_WINDOWS = platform.startsWith("win");
+export const IS_MAC = platform.startsWith("mac");
+export const IS_LINUX = platform.startsWith("linux");
+
export interface Dev {
name: string;
id: bigint;
diff --git a/src/utils/react.tsx b/src/utils/react.tsx
index f1c0f8f8..146725c5 100644
--- a/src/utils/react.tsx
+++ b/src/utils/react.tsx
@@ -145,3 +145,10 @@ export function useTimer({ interval = 1000, deps = [] }: TimerOpts) {
return time;
}
+
+export function useCleanupEffect(
+ effect: () => void,
+ deps?: React.DependencyList
+): void {
+ useEffect(() => effect, deps);
+}
diff --git a/src/utils/types.ts b/src/utils/types.ts
index 1c53b971..2f1d6396 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -26,11 +26,18 @@ import { MessageClickListener, MessageEditListener, MessageSendListener } from "
import { MessagePopoverButtonFactory } from "@api/MessagePopover";
import { Command, FluxEvents } from "@vencord/discord-types";
import { ReactNode } from "react";
-import { Promisable } from "type-fest";
// exists to export default definePlugin({...})
-export default function definePlugin(p: P & Record) {
- return p;
+export default function definePlugin(p: P & Record) {
+ return p as typeof p & Plugin;
+}
+
+export function makeRange(start: number, end: number, step = 1) {
+ const ranges: number[] = [];
+ for (let value = start; value <= end; value += step) {
+ ranges.push(Math.round(value * 100) / 100);
+ }
+ return ranges;
}
export type ReplaceFn = (match: string, ...groups: string[]) => string;
@@ -135,18 +142,11 @@ export interface PluginDef {
* Optionally provide settings that the user can configure in the Plugins tab of settings.
*/
settings?: DefinedSettings;
- /**
- * Check that this returns true before allowing a save to complete.
- * If a string is returned, show the error to the user.
- */
- beforeSave?(options: Record): Promisable;
/**
* Allows you to specify a custom Component that will be rendered in your
* plugin's settings page
*/
- settingsAboutComponent?: React.ComponentType<{
- tempSettings?: Record;
- }>;
+ settingsAboutComponent?: React.ComponentType<{}>;
/**
* Allows you to subscribe to Flux events
*/
@@ -322,13 +322,6 @@ export interface IPluginOptionComponentProps {
* NOTE: The user will still need to click save to apply these changes.
*/
setValue(newValue: any): void;
- /**
- * Set to true to prevent the user from saving.
- *
- * NOTE: This will not show the error to the user. It will only stop them saving.
- * Make sure to show the error in your component.
- */
- setError(error: boolean): void;
/**
* The options object
*/
diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts
index a392a02f..b25f3d4b 100644
--- a/src/webpack/common/components.ts
+++ b/src/webpack/common/components.ts
@@ -67,6 +67,7 @@ export const Avatar = waitForComponent("Avatar", filters.componentByCo
export const ColorPicker = waitForComponent("ColorPicker", filters.componentByCode("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", "showEyeDropper"));
+export const UserSummaryItem = waitForComponent("UserSummaryItem", filters.componentByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
export let createScroller: (scrollbarClassName: string, fadeClassName: string, customThemeClassName: string) => t.ScrollerThin;
export let scrollerClasses: Record;
From 828358bd2e28372ece3c19366c2446643e8ae24e Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Tue, 15 Jul 2025 15:52:48 -0300
Subject: [PATCH 018/116] IrcColors: Fix chat colors broken patch
---
src/plugins/ircColors/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/ircColors/index.ts b/src/plugins/ircColors/index.ts
index d0d020af..b6f18572 100644
--- a/src/plugins/ircColors/index.ts
+++ b/src/plugins/ircColors/index.ts
@@ -67,7 +67,7 @@ export default definePlugin({
find: '="SYSTEM_TAG"',
replacement: {
// Override colorString with our custom color and disable gradients if applying the custom color.
- match: /(?<=colorString:\i,colorStrings:\i,colorRoleName:\i}=)(\i),/,
+ match: /(?<=colorString:\i,colorStrings:\i,colorRoleName:\i.*?}=)(\i),/,
replace: "$self.wrapMessageColorProps($1, arguments[0]),"
}
},
From d0869c41cd9668909f65e257d204a5591c79095d Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Wed, 16 Jul 2025 18:15:52 -0300
Subject: [PATCH 019/116] Settings: Improve layout of a setting section and
error
---
.../settings/tabs/plugins/PluginModal.tsx | 6 +++-
.../plugins/components/BooleanSetting.tsx | 23 ++++--------
.../tabs/plugins/components/Common.tsx | 26 +++++++++-----
.../tabs/plugins/components/NumberSetting.tsx | 2 +-
.../tabs/plugins/components/SelectSetting.tsx | 4 +--
.../tabs/plugins/components/SliderSetting.tsx | 2 +-
.../tabs/plugins/components/TextSetting.tsx | 4 +--
.../settings/tabs/plugins/components/index.ts | 2 ++
.../tabs/plugins/components/styles.css | 35 +++++++++++++++++++
.../settings/tabs/plugins/styles.css | 6 ++++
10 files changed, 77 insertions(+), 33 deletions(-)
create mode 100644 src/components/settings/tabs/plugins/components/styles.css
diff --git a/src/components/settings/tabs/plugins/PluginModal.tsx b/src/components/settings/tabs/plugins/PluginModal.tsx
index d34be261..55912d9d 100644
--- a/src/components/settings/tabs/plugins/PluginModal.tsx
+++ b/src/components/settings/tabs/plugins/PluginModal.tsx
@@ -116,7 +116,11 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
);
});
- return {options} ;
+ return (
+
+ {options}
+
+ );
}
function renderMoreUsers(_label: string, count: number) {
diff --git a/src/components/settings/tabs/plugins/components/BooleanSetting.tsx b/src/components/settings/tabs/plugins/components/BooleanSetting.tsx
index c022fa2d..c5408cb9 100644
--- a/src/components/settings/tabs/plugins/components/BooleanSetting.tsx
+++ b/src/components/settings/tabs/plugins/components/BooleanSetting.tsx
@@ -16,11 +16,11 @@
* along with this program. If not, see .
*/
-import { wordsFromCamel, wordsToTitle } from "@utils/text";
+import { Switch } from "@components/settings/Switch";
import { PluginOptionBoolean } from "@utils/types";
-import { Forms, React, Switch, useState } from "@webpack/common";
+import { React, useState } from "@webpack/common";
-import { resolveError, SettingProps } from "./Common";
+import { resolveError, SettingProps, SettingsSection } from "./Common";
export function BooleanSetting({ option, pluginSettings, definedSettings, id, onChange }: SettingProps) {
const def = pluginSettings[id] ?? option.default;
@@ -40,20 +40,9 @@ export function BooleanSetting({ option, pluginSettings, definedSettings, id, on
}
return (
-
-
- {wordsToTitle(wordsFromCamel(id))}
-
- {error && {error} }
-
+
+
+
);
}
diff --git a/src/components/settings/tabs/plugins/components/Common.tsx b/src/components/settings/tabs/plugins/components/Common.tsx
index 06083767..3d84859b 100644
--- a/src/components/settings/tabs/plugins/components/Common.tsx
+++ b/src/components/settings/tabs/plugins/components/Common.tsx
@@ -4,12 +4,15 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
-import { Margins } from "@utils/margins";
+import { classNameFactory } from "@api/Styles";
+import { classes } from "@utils/misc";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { DefinedSettings, PluginOptionBase } from "@utils/types";
-import { Forms } from "@webpack/common";
+import { Text } from "@webpack/common";
import { PropsWithChildren } from "react";
+export const cl = classNameFactory("vc-plugins-setting-");
+
interface SettingBaseProps {
option: T;
onChange(newValue: any): void;
@@ -34,15 +37,20 @@ interface SettingsSectionProps extends PropsWithChildren {
name: string;
description: string;
error: string | null;
+ inlineSetting?: boolean;
}
-export function SettingsSection({ name, description, error, children }: SettingsSectionProps) {
+export function SettingsSection({ name, description, error, inlineSetting, children }: SettingsSectionProps) {
return (
-
- {wordsToTitle(wordsFromCamel(name))}
- {description}
- {children}
- {error && {error} }
-
+
+
+
+ {name && {wordsToTitle(wordsFromCamel(name))} }
+ {description && {description} }
+
+ {children}
+
+ {error &&
{error} }
+
);
}
diff --git a/src/components/settings/tabs/plugins/components/NumberSetting.tsx b/src/components/settings/tabs/plugins/components/NumberSetting.tsx
index 1b2e01a9..108ddc3e 100644
--- a/src/components/settings/tabs/plugins/components/NumberSetting.tsx
+++ b/src/components/settings/tabs/plugins/components/NumberSetting.tsx
@@ -53,9 +53,9 @@ export function NumberSetting({ option, pluginSettings, definedSettings, id, onC
diff --git a/src/components/settings/tabs/plugins/components/SelectSetting.tsx b/src/components/settings/tabs/plugins/components/SelectSetting.tsx
index 35512a21..4c912c51 100644
--- a/src/components/settings/tabs/plugins/components/SelectSetting.tsx
+++ b/src/components/settings/tabs/plugins/components/SelectSetting.tsx
@@ -41,14 +41,14 @@ export function SelectSetting({ option, pluginSettings, definedSettings, onChang
return (
v === state}
serialize={v => String(v)}
+ isDisabled={option.disabled?.call(definedSettings) ?? false}
{...option.componentProps}
/>
diff --git a/src/components/settings/tabs/plugins/components/SliderSetting.tsx b/src/components/settings/tabs/plugins/components/SliderSetting.tsx
index 936d8363..85a52da2 100644
--- a/src/components/settings/tabs/plugins/components/SliderSetting.tsx
+++ b/src/components/settings/tabs/plugins/components/SliderSetting.tsx
@@ -39,7 +39,6 @@ export function SliderSetting({ option, pluginSettings, definedSettings, id, onC
return (
String(v.toFixed(2))}
stickToMarkers={option.stickToMarkers ?? true}
+ disabled={option.disabled?.call(definedSettings) ?? false}
{...option.componentProps}
/>
diff --git a/src/components/settings/tabs/plugins/components/TextSetting.tsx b/src/components/settings/tabs/plugins/components/TextSetting.tsx
index 759137f1..7eb4b9f9 100644
--- a/src/components/settings/tabs/plugins/components/TextSetting.tsx
+++ b/src/components/settings/tabs/plugins/components/TextSetting.tsx
@@ -40,11 +40,11 @@ export function TextSetting({ option, pluginSettings, definedSettings, id, onCha
diff --git a/src/components/settings/tabs/plugins/components/index.ts b/src/components/settings/tabs/plugins/components/index.ts
index 6068dfef..0f1dd40c 100644
--- a/src/components/settings/tabs/plugins/components/index.ts
+++ b/src/components/settings/tabs/plugins/components/index.ts
@@ -16,6 +16,8 @@
* along with this program. If not, see .
*/
+import "./styles.css";
+
import { OptionType } from "@utils/types";
import { ComponentType } from "react";
diff --git a/src/components/settings/tabs/plugins/components/styles.css b/src/components/settings/tabs/plugins/components/styles.css
new file mode 100644
index 00000000..9d72554e
--- /dev/null
+++ b/src/components/settings/tabs/plugins/components/styles.css
@@ -0,0 +1,35 @@
+.vc-plugins-setting-section {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5em;
+}
+
+.vc-plugins-setting-content {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5em;
+}
+
+.vc-plugins-setting-inline {
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.vc-plugins-setting-label {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25em;
+}
+
+.vc-plugins-setting-title {
+ color: var(--header-primary);
+}
+
+.vc-plugins-setting-description {
+ color: var(--header-secondary);
+}
+
+.vc-plugins-setting-error {
+ color: var(--text-danger);
+}
diff --git a/src/components/settings/tabs/plugins/styles.css b/src/components/settings/tabs/plugins/styles.css
index ff026192..7500054c 100644
--- a/src/components/settings/tabs/plugins/styles.css
+++ b/src/components/settings/tabs/plugins/styles.css
@@ -74,3 +74,9 @@
.vc-plugins-info-icon:not(:hover, :focus) {
color: var(--text-muted);
}
+
+.vc-plugins-settings {
+ display: flex;
+ flex-direction: column;
+ gap: 1.25em;
+}
From cdda1224ff6375d7b24014aaf1b40d57a1dff13f Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Wed, 16 Jul 2025 20:23:06 -0300
Subject: [PATCH 020/116] UserVoiceShow: Improve icon in User Profile Modal V2
---
src/plugins/userVoiceShow/components.tsx | 10 +++++++---
src/plugins/userVoiceShow/index.tsx | 2 +-
src/plugins/userVoiceShow/style.css | 5 +++++
3 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/plugins/userVoiceShow/components.tsx b/src/plugins/userVoiceShow/components.tsx
index d4d554ce..9d1c7211 100644
--- a/src/plugins/userVoiceShow/components.tsx
+++ b/src/plugins/userVoiceShow/components.tsx
@@ -27,6 +27,7 @@ const ActionButtonClasses = findByPropsLazy("actionButton", "highlight");
interface IconProps extends React.ComponentPropsWithoutRef<"div"> {
size?: number;
+ iconClassName?: string;
}
function SpeakerIcon(props: IconProps) {
@@ -39,6 +40,7 @@ function SpeakerIcon(props: IconProps) {
className={classes(cl("speaker"), props.onClick != null ? cl("clickable") : undefined, props.className)}
>
;
-export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
+export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined);
const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId);
@@ -179,8 +182,9 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isActionButto
{props => {
const iconProps: IconProps = {
...props,
- className: classes(isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight),
- size: isActionButton ? 20 : undefined,
+ className: classes(isActionButton && ActionButtonClasses.actionButton, isActionButton && shouldHighlight && ActionButtonClasses.highlight),
+ iconClassName: classes(isProfile && cl("profile-speaker")),
+ size: isActionButton ? 20 : 16,
onClick
};
diff --git a/src/plugins/userVoiceShow/index.tsx b/src/plugins/userVoiceShow/index.tsx
index 0ee41414..bc1ce30b 100644
--- a/src/plugins/userVoiceShow/index.tsx
+++ b/src/plugins/userVoiceShow/index.tsx
@@ -60,7 +60,7 @@ export default definePlugin({
find: "#{intl::USER_PROFILE_LOAD_ERROR}",
replacement: {
match: /(\.fetchError.+?\?)null/,
- replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId})`
+ replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId,isProfile:true})`
},
predicate: () => settings.store.showInUserProfileModal
},
diff --git a/src/plugins/userVoiceShow/style.css b/src/plugins/userVoiceShow/style.css
index f9fd56fb..32b42208 100644
--- a/src/plugins/userVoiceShow/style.css
+++ b/src/plugins/userVoiceShow/style.css
@@ -13,6 +13,11 @@
color: var(--interactive-hover);
}
+.vc-uvs-profile-speaker {
+ width: var(--custom-nickname-icon-size);
+ height: var(--custom-nickname-icon-size);
+}
+
.vc-uvs-tooltip-container {
max-width: 300px;
}
From f6f0624e52ea460fbff0ad5294fb31c4090366c6 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 17 Jul 2025 22:05:42 -0300
Subject: [PATCH 021/116] Fix broken Decor style classes find
---
src/plugins/decor/ui/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/decor/ui/index.ts b/src/plugins/decor/ui/index.ts
index c7846364..f8ac3746 100644
--- a/src/plugins/decor/ui/index.ts
+++ b/src/plugins/decor/ui/index.ts
@@ -8,7 +8,7 @@ import { classNameFactory } from "@api/Styles";
import { extractAndLoadChunksLazy, findByPropsLazy } from "@webpack";
export const cl = classNameFactory("vc-decor-");
-export const DecorationModalStyles = findByPropsLazy("modalFooterShopButton");
+export const DecorationModalStyles = findByPropsLazy("modalPreview", "modalCloseButton", "spinner", "modal");
export const requireAvatarDecorationModal = extractAndLoadChunksLazy([".COLLECTIBLES_SHOP_FULLSCREEN&&"]);
export const requireCreateStickerModal = extractAndLoadChunksLazy(["stickerInspected]:"]);
From 25cd6b7069e21bb1a50d54d4402ff8e051a4a9f3 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 17 Jul 2025 22:06:22 -0300
Subject: [PATCH 022/116] Remove unused and non unique webpack common style
classes find
---
packages/discord-types/src/classes.d.ts | 5 -----
src/webpack/common/classes.ts | 3 +--
2 files changed, 1 insertion(+), 7 deletions(-)
diff --git a/packages/discord-types/src/classes.d.ts b/packages/discord-types/src/classes.d.ts
index 752ba3f0..a7260048 100644
--- a/packages/discord-types/src/classes.d.ts
+++ b/packages/discord-types/src/classes.d.ts
@@ -1,8 +1,3 @@
-export interface ImageModalClasses {
- image: string,
- modal: string,
-}
-
export interface ButtonWrapperClasses {
hoverScale: string;
buttonWrapper: string;
diff --git a/src/webpack/common/classes.ts b/src/webpack/common/classes.ts
index 85b90537..daaf023c 100644
--- a/src/webpack/common/classes.ts
+++ b/src/webpack/common/classes.ts
@@ -17,7 +17,6 @@
*/
import * as t from "@vencord/discord-types";
-import { findByPropsLazy, findLazy } from "@webpack";
+import { findByPropsLazy } from "@webpack";
-export const ModalImageClasses: t.ImageModalClasses = findLazy(m => m.image && m.modal && !m.applicationIcon);
export const ButtonWrapperClasses: t.ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent");
From 113a1f4b863bbddf522ba7265e9cb56b04ca9c2e Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Mon, 21 Jul 2025 12:09:36 +0200
Subject: [PATCH 023/116] fix ShowMeYourName
---
src/plugins/showMeYourName/index.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx
index b9b10714..5affa885 100644
--- a/src/plugins/showMeYourName/index.tsx
+++ b/src/plugins/showMeYourName/index.tsx
@@ -50,8 +50,8 @@ export default definePlugin({
{
find: '="SYSTEM_TAG"',
replacement: {
- match: /(?<=onContextMenu:\i,children:)\i/,
- replace: "$self.renderUsername(arguments[0])"
+ match: /(?<=onContextMenu:\i,children:)\i\?/,
+ replace: "$self.renderUsername(arguments[0]),_oldChildren:$&"
}
},
],
From 4ab084c0aca819f29c5397ea57387caa51a95695 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Mon, 21 Jul 2025 12:13:13 +0200
Subject: [PATCH 024/116] make ShowMeYourName patch safer
---
src/plugins/showMeYourName/index.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx
index 5affa885..018fa305 100644
--- a/src/plugins/showMeYourName/index.tsx
+++ b/src/plugins/showMeYourName/index.tsx
@@ -50,7 +50,8 @@ export default definePlugin({
{
find: '="SYSTEM_TAG"',
replacement: {
- match: /(?<=onContextMenu:\i,children:)\i\?/,
+ // The field is named "userName", but as this is unusual casing, the regex also matches username, in case they change it
+ match: /(?<=onContextMenu:\i,children:)\i\?(?=.{0,100}?user[Nn]ame:)/,
replace: "$self.renderUsername(arguments[0]),_oldChildren:$&"
}
},
From 50e2ad776baec3144dabd9ac6fd91e9890f52abd Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Mon, 21 Jul 2025 12:32:14 +0200
Subject: [PATCH 025/116] Plugin Settings: improve headings
---
.../settings/tabs/plugins/PluginModal.tsx | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/src/components/settings/tabs/plugins/PluginModal.tsx b/src/components/settings/tabs/plugins/PluginModal.tsx
index 55912d9d..3332b1fc 100644
--- a/src/components/settings/tabs/plugins/PluginModal.tsx
+++ b/src/components/settings/tabs/plugins/PluginModal.tsx
@@ -27,7 +27,7 @@ import { debounce } from "@shared/debounce";
import { gitRemote } from "@shared/vencordUserAgent";
import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins";
-import { isObjectEmpty } from "@utils/misc";
+import { classes, isObjectEmpty } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { OptionType, Plugin } from "@utils/types";
import { User } from "@vencord/discord-types";
@@ -147,11 +147,12 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
return (
-
- {plugin.name}
+
+ {plugin.name}
-
+
+
{plugin.description}
@@ -168,8 +169,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
)}
- Authors
-
+
Authors
+
{!!plugin.settingsAboutComponent && (
-
+
@@ -204,8 +205,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
)}
-
- Settings
+
+ Settings
{renderSettings()}
From 8b3601326430deb60179b80d4399600a15550e4a Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Tue, 22 Jul 2025 16:35:35 -0400
Subject: [PATCH 026/116] fix GreetStickerPicker (#3561)
---
src/plugins/greetStickerPicker/index.tsx | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/src/plugins/greetStickerPicker/index.tsx b/src/plugins/greetStickerPicker/index.tsx
index b2454f9c..4ea1f25d 100644
--- a/src/plugins/greetStickerPicker/index.tsx
+++ b/src/plugins/greetStickerPicker/index.tsx
@@ -23,13 +23,6 @@ import { Channel, Message } from "@vencord/discord-types";
import { findLazy } from "@webpack";
import { ContextMenuApi, FluxDispatcher, Menu, MessageActions } from "@webpack/common";
-interface Sticker {
- id: string;
- format_type: number;
- description: string;
- name: string;
-}
-
enum GreetMode {
Greet = "Greet",
NormalMessage = "Message"
@@ -168,7 +161,7 @@ export default definePlugin({
{
find: "#{intl::WELCOME_CTA_LABEL}",
replacement: {
- match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).{0,400}?)/,
+ match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).+?)/,
replace: "$&onContextMenu:(vcEvent)=>$self.pickSticker(vcEvent, $1),"
}
}
From 6fb685b9595e88de42d09a97a722b0aeb2a407fa Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Tue, 22 Jul 2025 22:43:31 +0200
Subject: [PATCH 027/116] Fix Plugin settings page
Co-Authored-By: sadan <117494111+sadan4@users.noreply.github.com>
---
src/webpack/common/components.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts
index b25f3d4b..bdf0b2c4 100644
--- a/src/webpack/common/components.ts
+++ b/src/webpack/common/components.ts
@@ -51,7 +51,7 @@ const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", {
export const Tooltip = LazyComponent(() => Tooltips.Tooltip);
export const TooltipContainer = LazyComponent(() => Tooltips.TooltipContainer);
-export const TextInput = waitForComponent("TextInput", filters.componentByCode(".error]:this.hasError()"));
+export const TextInput = waitForComponent("TextInput", filters.componentByCode("#{intl::MAXIMUM_LENGTH_ERROR}", '"input"'));
export const TextArea = waitForComponent("TextArea", filters.componentByCode("this.getPaddingRight()},id:"));
export const Text = waitForComponent("Text", filters.componentByCode('case"always-white"'));
export const Heading = waitForComponent("Heading", filters.componentByCode(">6?{", "variant:"));
From 03fe7d15cf9c51f02922e23f3071b84c865d327c Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Tue, 22 Jul 2025 22:45:28 +0200
Subject: [PATCH 028/116] bump to v1.12.7
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 577bf39d..b3401cc5 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
- "version": "1.12.6",
+ "version": "1.12.7",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
From 29a2bcbf0749618b2bc4d469bb1d8830df3e8521 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 26 Jul 2025 11:31:30 -0300
Subject: [PATCH 029/116] Fix BetterUploadButton not working
---
src/plugins/betterUploadButton/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/betterUploadButton/index.ts b/src/plugins/betterUploadButton/index.ts
index 29827a5e..bfdb3afe 100644
--- a/src/plugins/betterUploadButton/index.ts
+++ b/src/plugins/betterUploadButton/index.ts
@@ -34,7 +34,7 @@ export default definePlugin({
noWarn: true
},
{
- match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/,
+ match: /\.attachButtonInner,.+?onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/,
replace: "$&,onClick:$1,onContextMenu:$2.onClick,",
},
]
From cb36cf5706b68e28aa5857e21269ca9f81c1634e Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Thu, 31 Jul 2025 16:31:33 -0400
Subject: [PATCH 030/116] fix Plugins broken by recent Discord changes (#3569)
Co-authored-by: Vendicated
---
.../src/stores/GuildRoleStore.d.ts | 5 +++--
src/plugins/ircColors/index.ts | 4 ++--
.../components/RolesAndUsersPermissions.tsx | 2 +-
.../components/UserPermissions.tsx | 4 ++--
src/plugins/permissionsViewer/index.tsx | 6 +++---
src/plugins/permissionsViewer/utils.ts | 13 ++++++-------
src/plugins/serverInfo/GuildInfoModal.tsx | 2 +-
src/plugins/showHiddenChannels/index.tsx | 6 +++---
src/plugins/showHiddenThings/index.ts | 17 ++++++++++++++---
src/plugins/typingIndicator/index.tsx | 2 +-
src/webpack/common/stores.ts | 7 ++-----
11 files changed, 38 insertions(+), 30 deletions(-)
diff --git a/packages/discord-types/src/stores/GuildRoleStore.d.ts b/packages/discord-types/src/stores/GuildRoleStore.d.ts
index bf0d4042..faf586a0 100644
--- a/packages/discord-types/src/stores/GuildRoleStore.d.ts
+++ b/packages/discord-types/src/stores/GuildRoleStore.d.ts
@@ -1,7 +1,8 @@
import { FluxStore, Role } from "..";
+// TODO: add the rest of the methods for GuildRoleStore
export class GuildRoleStore extends FluxStore {
getRole(guildId: string, roleId: string): Role;
- getRoles(guildId: string): Record;
- getAllGuildRoles(): Record>;
+ getSortedRoles(guildId: string): Role[];
+ getRolesSnapshot(guildId: string): Record;
}
diff --git a/src/plugins/ircColors/index.ts b/src/plugins/ircColors/index.ts
index b6f18572..ce007a57 100644
--- a/src/plugins/ircColors/index.ts
+++ b/src/plugins/ircColors/index.ts
@@ -74,8 +74,8 @@ export default definePlugin({
{
find: "#{intl::GUILD_OWNER}),children:",
replacement: {
- match: /(?<=roleName:\i,)color:/,
- replace: "color:$self.calculateNameColorForListContext(arguments[0]),originalColor:"
+ match: /(?<=roleName:\i,)(colorString:)/,
+ replace: "$1$self.calculateNameColorForListContext(arguments[0]),originalColor:"
},
predicate: () => settings.store.memberListColors
}
diff --git a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx
index 28beabe1..608aa74f 100644
--- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx
+++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx
@@ -84,7 +84,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
const [selectedItemIndex, selectItem] = useState(0);
const selectedItem = permissions[selectedItemIndex];
- const roles = GuildRoleStore.getRoles(guild.id);
+ const roles = GuildRoleStore.getRolesSnapshot(guild.id);
return (
{
const userPermissions: UserPermissions = [];
- const userRoles = getSortedRoles(guild, guildMember);
+ const userRoles = getSortedRolesForMember(guild, guildMember);
const rolePermissions: Array = userRoles.map(role => ({
type: PermissionType.Role,
diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx
index ed2471fb..1f96e66e 100644
--- a/src/plugins/permissionsViewer/index.tsx
+++ b/src/plugins/permissionsViewer/index.tsx
@@ -31,7 +31,7 @@ import { Button, ChannelStore, Dialog, GuildMemberStore, GuildRoleStore, GuildSt
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions";
import UserPermissions from "./components/UserPermissions";
-import { getSortedRoles, sortPermissionOverwrites } from "./utils";
+import { getSortedRolesForMember, sortPermissionOverwrites } from "./utils";
const PopoutClasses = findByPropsLazy("container", "scroller", "list");
const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "banner");
@@ -73,7 +73,7 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
.with(MenuItemParentType.User, () => {
const member = GuildMemberStore.getMember(guildId, id!)!;
- const permissions: RoleOrUserPermission[] = getSortedRoles(guild, member)
+ const permissions: RoleOrUserPermission[] = getSortedRolesForMember(guild, member)
.map(role => ({
type: PermissionType.Role,
...role
@@ -107,7 +107,7 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
};
})
.otherwise(() => {
- const permissions = Object.values(GuildRoleStore.getRoles(guild.id)).map(role => ({
+ const permissions = GuildRoleStore.getSortedRoles(guild.id).map(role => ({
type: PermissionType.Role,
...role
}));
diff --git a/src/plugins/permissionsViewer/utils.ts b/src/plugins/permissionsViewer/utils.ts
index 022dca6e..27e7494b 100644
--- a/src/plugins/permissionsViewer/utils.ts
+++ b/src/plugins/permissionsViewer/utils.ts
@@ -28,12 +28,11 @@ export const { getGuildPermissionSpecMap } = findByPropsLazy("getGuildPermission
export const cl = classNameFactory("vc-permviewer-");
-export function getSortedRoles({ id }: Guild, member: GuildMember) {
- const roles = GuildRoleStore.getRoles(id);
-
- return [...member.roles, id]
- .map(id => roles[id])
- .sort((a, b) => b.position - a.position);
+export function getSortedRolesForMember({ id: guildId }: Guild, member: GuildMember) {
+ // the guild id is the @everyone role
+ return GuildRoleStore
+ .getSortedRoles(guildId)
+ .filter(role => role.id === guildId || member.roles.includes(role.id));
}
export function sortUserRoles(roles: Role[]) {
@@ -48,7 +47,7 @@ export function sortUserRoles(roles: Role[]) {
}
export function sortPermissionOverwrites(overwrites: T[], guildId: string) {
- const roles = GuildRoleStore.getRoles(guildId);
+ const roles = GuildRoleStore.getRolesSnapshot(guildId);
return overwrites.sort((a, b) => {
if (a.type !== PermissionType.Role || b.type !== PermissionType.Role) return 0;
diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx
index 2fa687e8..ac57e976 100644
--- a/src/plugins/serverInfo/GuildInfoModal.tsx
+++ b/src/plugins/serverInfo/GuildInfoModal.tsx
@@ -200,7 +200,7 @@ function ServerInfoTab({ guild }: GuildProps) {
"Verification Level": ["None", "Low", "Medium", "High", "Highest"][guild.verificationLevel] || "?",
"Server Boosts": `${guild.premiumSubscriberCount ?? 0} (Level ${guild.premiumTier ?? 0})`,
"Channels": GuildChannelStore.getChannels(guild.id)?.count - 1 || "?", // - null category
- "Roles": Object.keys(GuildRoleStore.getRoles(guild.id)).length - 1, // - @everyone
+ "Roles": GuildRoleStore.getSortedRoles(guild.id).length - 1, // - @everyone
};
return (
diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx
index a0ef38a6..d582c8c7 100644
--- a/src/plugins/showHiddenChannels/index.tsx
+++ b/src/plugins/showHiddenChannels/index.tsx
@@ -178,7 +178,7 @@ export default definePlugin({
},
// Add the hidden eye icon if the channel is hidden
{
- match: /\.name,{.{0,140}\.children.+?:null(?<=,channel:(\i).+?)/,
+ match: /\.Children\.count.+?:null(?<=,channel:(\i).+?)/,
replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null`
},
// Make voice channels also appear as muted if they are muted
@@ -302,8 +302,8 @@ export default definePlugin({
},
{
// Include the @everyone role in the allowed roles list for Hidden Channels
- match: /sortBy.{0,30}?\.filter\(\i=>(?<=channel:(\i).+?)/,
- replace: (m, channel) => `${m}$self.isHiddenChannel(${channel})?true:`
+ match: /getSortedRoles.+?\.filter\(\i=>(?=!)/,
+ replace: m => `${m}$self.isHiddenChannel(arguments[0].channel)?true:`
},
{
// If the @everyone role has the required permissions, make the array only contain it
diff --git a/src/plugins/showHiddenThings/index.ts b/src/plugins/showHiddenThings/index.ts
index d80691fe..96ebf169 100644
--- a/src/plugins/showHiddenThings/index.ts
+++ b/src/plugins/showHiddenThings/index.ts
@@ -18,7 +18,9 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
+import { Logger } from "@utils/Logger";
import definePlugin, { OptionType, PluginSettingDef } from "@utils/types";
+import { GuildMember, Role } from "@vencord/discord-types";
const opt = (description: string) => ({
type: OptionType.BOOLEAN,
@@ -70,8 +72,8 @@ export default definePlugin({
find: "#{intl::GUILD_MEMBER_MOD_VIEW_PERMISSION_GRANTED_BY_ARIA_LABEL}),allowOverflow:",
predicate: () => settings.store.showModView,
replacement: {
- match: /(role:)\i(?=,guildId.{0,100}role:(\i\[))/,
- replace: "$1$2arguments[0].member.highestRoleId]",
+ match: /(role:)\i(?<=\i\.roles,\i\.highestRoleId,(\i)\].+)/,
+ replace: "$1$self.findHighestRole(arguments[0],$2)",
}
},
// allows you to open mod view on yourself
@@ -83,5 +85,14 @@ export default definePlugin({
replace: "false"
}
}
- ]
+ ],
+
+ findHighestRole({ member }: { member: GuildMember; }, roles: Role[]): Role | undefined {
+ try {
+ return roles.find(role => role.id === member.highestRoleId);
+ } catch (e) {
+ new Logger("ShowHiddenThings").error("Failed to find highest role", e);
+ return undefined;
+ }
+ }
});
diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx
index 29cc3c21..0a0cd53a 100644
--- a/src/plugins/typingIndicator/index.tsx
+++ b/src/plugins/typingIndicator/index.tsx
@@ -173,7 +173,7 @@ export default definePlugin({
{
find: "UNREAD_IMPORTANT:",
replacement: {
- match: /\.name,{.{0,140}\.children.+?:null(?<=,channel:(\i).+?)/,
+ match: /\.Children\.count.+?:null(?<=,channel:(\i).+?)/,
replace: "$&,$self.TypingIndicator($1.id,$1.getGuildId())"
}
},
diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts
index 4165af8e..71abd729 100644
--- a/src/webpack/common/stores.ts
+++ b/src/webpack/common/stores.ts
@@ -17,7 +17,7 @@
*/
import * as t from "@vencord/discord-types";
-import { findByCodeLazy, findByPropsLazy, waitFor } from "@webpack";
+import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { waitForStore } from "./internal";
@@ -71,6 +71,7 @@ waitForStore("PermissionStore", m => PermissionStore = m);
waitForStore("PresenceStore", m => PresenceStore = m);
waitForStore("ReadStateStore", m => ReadStateStore = m);
waitForStore("GuildChannelStore", m => GuildChannelStore = m);
+waitForStore("GuildRoleStore", m => GuildRoleStore = m);
waitForStore("MessageStore", m => MessageStore = m);
waitForStore("WindowStore", m => WindowStore = m);
waitForStore("EmojiStore", m => EmojiStore = m);
@@ -79,7 +80,3 @@ waitForStore("ThemeStore", m => {
// Importing this directly can easily cause circular imports. For this reason, use a non import access here.
Vencord.QuickCss.initQuickCssThemeStore();
});
-
-// GuildRoleStore is new, this code is for stable + canary compatibility
-// TODO: Change to waitForStore once GuildRoleStore is on stable
-waitFor(["getRole", "getRoles"], m => GuildRoleStore = m);
From fa672a347d06ac56582db1fd319481e421940059 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Fri, 1 Aug 2025 01:51:29 +0200
Subject: [PATCH 031/116] BetterSessions: fix ui
---
src/plugins/betterSessions/index.tsx | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/plugins/betterSessions/index.tsx b/src/plugins/betterSessions/index.tsx
index b40c3c1e..14a25577 100644
--- a/src/plugins/betterSessions/index.tsx
+++ b/src/plugins/betterSessions/index.tsx
@@ -92,7 +92,7 @@ export default definePlugin({
{title}
{(savedSession == null || savedSession.isNew) && (
-
+
);
From 22d3fb10e9c0527a112b7f9f804b21a6f9302898 Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Sun, 3 Aug 2025 09:05:26 -0400
Subject: [PATCH 032/116] fix plugins broken by latest Discord update (#3574)
Co-authored-by: V
---
src/plugins/_api/dynamicImageModalApi.ts | 5 +++--
src/plugins/_api/messageEvents.ts | 2 +-
src/plugins/decor/index.tsx | 2 +-
src/plugins/favGifSearch/index.tsx | 4 ++--
src/plugins/showHiddenChannels/index.tsx | 14 ++++++++++++--
5 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/src/plugins/_api/dynamicImageModalApi.ts b/src/plugins/_api/dynamicImageModalApi.ts
index d91a5a93..114b4836 100644
--- a/src/plugins/_api/dynamicImageModalApi.ts
+++ b/src/plugins/_api/dynamicImageModalApi.ts
@@ -16,8 +16,9 @@ export default definePlugin({
{
find: ".dimensionlessImage,",
replacement: {
- match: /(?<="IMAGE"===\i&&\(\i=)\i(?=\?)/,
- replace: "true"
+ // widthAndHeightPassed = w != null && w !== 0 && h == null || h === 0
+ match: /(?<=\i=)(null!=\i&&0!==\i)&&(null!=\i&&0!==\i)/,
+ replace: "($1)||($2)"
}
}
]
diff --git a/src/plugins/_api/messageEvents.ts b/src/plugins/_api/messageEvents.ts
index 9dfc55e2..e5192763 100644
--- a/src/plugins/_api/messageEvents.ts
+++ b/src/plugins/_api/messageEvents.ts
@@ -27,7 +27,7 @@ export default definePlugin({
{
find: "#{intl::EDIT_TEXTAREA_HELP}",
replacement: {
- match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
+ match: /(?<=,channel:\i\}\)\.then\().+?\i\.content!==this\.props\.message\.content&&\i\((.+?)\)\}(?=return)/,
replace: (match, args) => "" +
`async ${match}` +
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx
index 0a6dd85d..a141f2b5 100644
--- a/src/plugins/decor/index.tsx
+++ b/src/plugins/decor/index.tsx
@@ -71,7 +71,7 @@ export default definePlugin({
},
// Remove NEW label from decor avatar decorations
{
- match: /(?<=\.\i\.PREMIUM_PURCHASE&&\i)(?<=avatarDecoration:(\i).+?)/,
+ match: /(?<=\.\i\.PURCHASE)(?=,)(?<=avatarDecoration:(\i).+?)/,
replace: "||$1.skuId===$self.SKU_ID"
}
]
diff --git a/src/plugins/favGifSearch/index.tsx b/src/plugins/favGifSearch/index.tsx
index 5e302912..d88ced34 100644
--- a/src/plugins/favGifSearch/index.tsx
+++ b/src/plugins/favGifSearch/index.tsx
@@ -35,7 +35,7 @@ interface SearchBarComponentProps {
}
type TSearchBarComponent =
- React.FC & { Sizes: Record<"SMALL" | "MEDIUM" | "LARGE", string>; };
+ React.FC;
interface Gif {
format: number;
@@ -182,7 +182,7 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc
ref={ref}
autoFocus={true}
className={containerClasses.searchBar}
- size={SearchBarComponent.Sizes.MEDIUM}
+ size="md"
onChange={onChange}
onClear={() => {
setQuery("");
diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx
index d582c8c7..2d0ea368 100644
--- a/src/plugins/showHiddenChannels/index.tsx
+++ b/src/plugins/showHiddenChannels/index.tsx
@@ -292,8 +292,8 @@ export default definePlugin({
replacement: [
{
// Change the role permission check to CONNECT if the channel is locked
- match: /ADMINISTRATOR\)\|\|(?<=context:(\i)}.+?)(?=(.+?)VIEW_CHANNEL)/,
- replace: (m, channel, permCheck) => `${m}!Vencord.Webpack.Common.PermissionStore.can(${CONNECT}n,${channel})?${permCheck}CONNECT):`
+ match: /\i\.\i\(\i\.\i\.ADMINISTRATOR,\i\.\i\.VIEW_CHANNEL\)(?<=context:(\i)}.+?)/,
+ replace: (m, channel) => `$self.fixPermCheck(${m},${channel})`
},
{
// Change the permissionOverwrite check to CONNECT if the channel is locked
@@ -492,6 +492,16 @@ export default definePlugin({
}
],
+
+ fixPermCheck(originalPerms: bigint, channel: Channel) {
+ if (!PermissionStore.can(PermissionsBits.CONNECT, channel)) {
+ originalPerms &= ~PermissionsBits.VIEW_CHANNEL;
+ originalPerms |= PermissionsBits.CONNECT;
+ }
+
+ return originalPerms;
+ },
+
isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) {
try {
if (channel == null || Object.hasOwn(channel, "channelId") && channel.channelId == null) return false;
From 38e46f89cfb67e321fe5aeeb178ca674671647cd Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Sun, 3 Aug 2025 16:24:00 +0200
Subject: [PATCH 033/116] BetterUploadButton: fix right click action not
working
---
src/plugins/betterUploadButton/index.ts | 12 +++---------
1 file changed, 3 insertions(+), 9 deletions(-)
diff --git a/src/plugins/betterUploadButton/index.ts b/src/plugins/betterUploadButton/index.ts
index bfdb3afe..788721a9 100644
--- a/src/plugins/betterUploadButton/index.ts
+++ b/src/plugins/betterUploadButton/index.ts
@@ -25,17 +25,11 @@ export default definePlugin({
description: "Upload with a single click, open menu with right click",
patches: [
{
- find: '"ChannelAttachButton"',
+ find: ".CHAT_INPUT_BUTTON_NOTIFICATION,",
replacement: [
{
- // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
- match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/,
- replace: "$&onClick:$1,onContextMenu:$2.onClick,",
- noWarn: true
- },
- {
- match: /\.attachButtonInner,.+?onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/,
- replace: "$&,onClick:$1,onContextMenu:$2.onClick,",
+ match: /onClick:(\i\?void 0:\i)(?=,onDoubleClick:(\i\?void 0:\i))/,
+ replace: "onContextMenu:$1,onClick:$2",
},
]
},
From a02d1afdf095bb8c58a74b29df1cb08bd500c1a8 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Sun, 3 Aug 2025 16:26:08 +0200
Subject: [PATCH 034/116] bump to v1.12.8
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index b3401cc5..57aa36f0 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
- "version": "1.12.7",
+ "version": "1.12.8",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
From 1ebd4123925cfbbd62b805505cc2ad20c0a1646a Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Sun, 3 Aug 2025 17:04:50 +0200
Subject: [PATCH 035/116] BetterUploadButton: don't affect other chat buttons
---
package.json | 2 +-
src/plugins/betterUploadButton/index.ts | 13 +++++++++++--
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/package.json b/package.json
index 57aa36f0..5438d37f 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
- "version": "1.12.8",
+ "version": "1.12.9",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
diff --git a/src/plugins/betterUploadButton/index.ts b/src/plugins/betterUploadButton/index.ts
index 788721a9..9639dc60 100644
--- a/src/plugins/betterUploadButton/index.ts
+++ b/src/plugins/betterUploadButton/index.ts
@@ -28,10 +28,19 @@ export default definePlugin({
find: ".CHAT_INPUT_BUTTON_NOTIFICATION,",
replacement: [
{
- match: /onClick:(\i\?void 0:\i)(?=,onDoubleClick:(\i\?void 0:\i))/,
- replace: "onContextMenu:$1,onClick:$2",
+ match: /onClick:(\i\?void 0:\i)(?=,onDoubleClick:(\i\?void 0:\i),)/,
+ replace: "$&,...$self.getOverrides(arguments[0],$1,$2)",
},
]
},
],
+
+ getOverrides(props: any, onClick: any, onDoubleClick: any) {
+ if (!props?.className?.includes("attachButton")) return {};
+
+ return {
+ onClick: onDoubleClick,
+ onContextMenu: onClick
+ };
+ }
});
From 6380111f326e0778629b62165d14892d661d3f16 Mon Sep 17 00:00:00 2001
From: V
Date: Tue, 5 Aug 2025 19:20:28 +0200
Subject: [PATCH 036/116] Notification Log: fix lag if there are too many
entries (#3584)
Use Discord's lazy list implementation for only rendering what's on screen
---
packages/discord-types/src/components.d.ts | 57 +++++++++++++++++--
.../Notifications/NotificationComponent.tsx | 4 +-
src/api/Notifications/notificationLog.tsx | 41 ++++++-------
src/api/Notifications/styles.css | 35 +++++++++---
src/webpack/common/components.ts | 9 +++
5 files changed, 106 insertions(+), 40 deletions(-)
diff --git a/packages/discord-types/src/components.d.ts b/packages/discord-types/src/components.d.ts
index eff9d73a..1df03735 100644
--- a/packages/discord-types/src/components.d.ts
+++ b/packages/discord-types/src/components.d.ts
@@ -474,19 +474,66 @@ export type MaskedLink = ComponentType>;
-export type ScrollerThin = ComponentType>;
+interface BaseListItem {
+ anchorId: any;
+ listIndex: number;
+ offsetTop: number;
+ section: number;
+}
+interface ListSection extends BaseListItem {
+ type: "section";
+}
+interface ListRow extends BaseListItem {
+ type: "row";
+ row: number;
+ rowIndex: number;
+}
+
+export type ListScrollerThin = ComponentType React.ReactNode;
+ renderRow: (item: ListRow) => React.ReactNode;
+ renderFooter?: (item: any) => React.ReactNode;
+ renderSidebar?: (listVisible: boolean, sidebarVisible: boolean) => React.ReactNode;
+ wrapSection?: (section: number, children: React.ReactNode) => React.ReactNode;
+
+ sectionHeight: number;
+ rowHeight: number;
+ footerHeight?: number;
+ sidebarHeight?: number;
+
+ chunkSize?: number;
+
+ paddingTop?: number;
+ paddingBottom?: number;
+ fade?: boolean;
+ onResize?: Function;
+ getAnchorId?: any;
+
+ innerTag?: string;
+ innerId?: string;
+ innerClassName?: string;
+ innerRole?: string;
+ innerAriaLabel?: string;
+ // Yes, Discord uses this casing
+ innerAriaMultiselectable?: boolean;
+ innerAriaOrientation?: "vertical" | "horizontal";
+}>;
+
export type Clickable = (props: PropsWithChildren> & {
tag?: T;
}) => ReactNode;
diff --git a/src/api/Notifications/NotificationComponent.tsx b/src/api/Notifications/NotificationComponent.tsx
index d07143c4..52e56d5d 100644
--- a/src/api/Notifications/NotificationComponent.tsx
+++ b/src/api/Notifications/NotificationComponent.tsx
@@ -104,9 +104,7 @@ export default ErrorBoundary.wrap(function NotificationComponent({
-
+ {richBody ?? {body}
}
{image && }
diff --git a/src/api/Notifications/notificationLog.tsx b/src/api/Notifications/notificationLog.tsx
index a943db07..dca7679e 100644
--- a/src/api/Notifications/notificationLog.tsx
+++ b/src/api/Notifications/notificationLog.tsx
@@ -21,9 +21,9 @@ import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Flex } from "@components/Flex";
import { openNotificationSettingsModal } from "@components/settings/tabs/vencord/NotificationSettings";
-import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
+import { closeModal, ModalCloseButton, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react";
-import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
+import { Alerts, Button, Forms, ListScrollerThin, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
import { nanoid } from "nanoid";
import type { DispatchWithoutAction } from "react";
@@ -103,21 +103,9 @@ export function useLogs() {
function NotificationEntry({ data }: { data: PersistentNotificationData; }) {
const [removing, setRemoving] = useState(false);
- const ref = React.useRef(null);
-
- useEffect(() => {
- const div = ref.current!;
-
- const setHeight = () => {
- if (div.clientHeight === 0) return requestAnimationFrame(setHeight);
- div.style.height = `${div.clientHeight}px`;
- };
-
- setHeight();
- }, []);
return (
-
+
deleteNotification(data.timestamp), 200);
}}
richBody={
-
+
);
}
@@ -151,9 +139,14 @@ export function NotificationLog({ log, pending }: { log: PersistentNotificationD
);
return (
-
- {log.map(n => )}
-
+
null}
+ renderRow={item => }
+ />
);
}
@@ -161,15 +154,15 @@ function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void
const [log, pending] = useLogs();
return (
-
+
Notification Log
-
+
-
+
diff --git a/src/api/Notifications/styles.css b/src/api/Notifications/styles.css
index 10d3c0cf..52cbc805 100644
--- a/src/api/Notifications/styles.css
+++ b/src/api/Notifications/styles.css
@@ -32,6 +32,7 @@
.vc-notification-content {
width: 100%;
+ overflow: hidden;
}
.vc-notification-header {
@@ -81,6 +82,11 @@
width: 100%;
}
+.vc-notification-log-modal {
+ max-width: 962px;
+ width: clamp(var(--modal-width-large, 800px), 962px, 85vw);
+}
+
.vc-notification-log-empty {
height: 218px;
background: url("/assets/b36de980b174d7b798c89f35c116e5c6.svg") center no-repeat;
@@ -88,19 +94,23 @@
}
.vc-notification-log-container {
- display: flex;
- flex-direction: column;
padding: 1em;
- overflow: hidden;
+ max-height: min(750px, 75vh);
+ width: 100%;
}
.vc-notification-log-wrapper {
+ height: 120px;
+ width: 100%;
+ padding-bottom: 16px;
+ box-sizing: border-box;
transition: 200ms ease;
transition-property: height, opacity;
-}
-.vc-notification-log-wrapper:not(:last-child) {
- margin-bottom: 1em;
+ /* stylelint-disable-next-line no-descending-specificity */
+ .vc-notification-root {
+ height: 104px;
+ }
}
.vc-notification-log-removing {
@@ -109,9 +119,18 @@
margin-bottom: 1em;
}
-.vc-notification-log-body {
+.vc-notification-log-body-wrapper {
display: flex;
flex-direction: column;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.vc-notification-log-body {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ line-height: 1.2em;
}
.vc-notification-log-timestamp {
@@ -123,4 +142,4 @@
.vc-notification-log-danger-btn {
color: var(--white-500);
background-color: var(--button-danger-background);
-}
+}
\ No newline at end of file
diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts
index bdf0b2c4..bf7c9dd1 100644
--- a/src/webpack/common/components.ts
+++ b/src/webpack/common/components.ts
@@ -70,14 +70,23 @@ export const ColorPicker = waitForComponent("ColorPicker", filter
export const UserSummaryItem = waitForComponent("UserSummaryItem", filters.componentByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
export let createScroller: (scrollbarClassName: string, fadeClassName: string, customThemeClassName: string) => t.ScrollerThin;
+export let createListScroller: (scrollBarClassName: string, fadeClassName: string, someOtherClassIdkMan: string, resizeObserverClass: typeof ResizeObserver) => t.ListScrollerThin;
export let scrollerClasses: Record;
+export let listScrollerClasses: Record;
+
waitFor(filters.byCode('="ltr",orientation:', "customTheme:", "forwardRef"), m => createScroller = m);
+waitFor(filters.byCode("getScrollerNode:", "resizeObserver:", "sectionHeight:"), m => createListScroller = m);
waitFor(["thin", "auto", "customTheme"], m => scrollerClasses = m);
+waitFor(m => m.thin && m.auto && !m.customTheme, m => listScrollerClasses = m);
export const ScrollerNone = LazyComponent(() => createScroller(scrollerClasses.none, scrollerClasses.fade, scrollerClasses.customTheme));
export const ScrollerThin = LazyComponent(() => createScroller(scrollerClasses.thin, scrollerClasses.fade, scrollerClasses.customTheme));
export const ScrollerAuto = LazyComponent(() => createScroller(scrollerClasses.auto, scrollerClasses.fade, scrollerClasses.customTheme));
+export const ListScrollerNone = LazyComponent(() => createListScroller(listScrollerClasses.none, listScrollerClasses.fade, "", ResizeObserver));
+export const ListScrollerThin = LazyComponent(() => createListScroller(listScrollerClasses.thin, listScrollerClasses.fade, "", ResizeObserver));
+export const ListScrollerAuto = LazyComponent(() => createListScroller(listScrollerClasses.auto, listScrollerClasses.fade, "", ResizeObserver));
+
const { FocusLock_ } = mapMangledModuleLazy('document.getElementById("app-mount"))', {
FocusLock_: filters.componentByCode(".containerRef")
}) as {
From a2253cb4aeec1c4e6a69a95d2e3dacb0f0c1fa48 Mon Sep 17 00:00:00 2001
From: V
Date: Tue, 5 Aug 2025 23:57:51 +0200
Subject: [PATCH 037/116] remove old discord ui workarounds & legacy code
(#3585)
Co-authored-by: sadan <117494111+sadan4@users.noreply.github.com>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
---
.vscode/settings.json | 9 ++-
src/api/Notifications/styles.css | 6 +-
src/components/CheckedTextInput.tsx | 1 -
src/components/settings/AddonCard.css | 16 +---
src/components/settings/QuickAction.css | 14 +---
.../tabs/plugins/ContributorModal.css | 6 +-
.../settings/tabs/plugins/LinkIconButton.css | 4 +-
src/components/settings/tabs/styles.css | 4 +-
src/plugins/_api/chatButtons.ts | 7 +-
src/plugins/_core/settings.tsx | 3 +-
src/plugins/betterFolders/style.css | 4 +-
src/plugins/clientTheme/utils/styleUtils.ts | 4 +-
src/plugins/ctrlEnterSend/index.ts | 5 +-
src/plugins/decor/index.tsx | 12 ---
src/plugins/fakeNitro/index.tsx | 7 +-
src/plugins/ircColors/index.ts | 4 +-
src/plugins/memberCount/index.tsx | 6 --
src/plugins/messageTags/index.ts | 9 ---
src/plugins/openInApp/index.ts | 5 +-
src/plugins/permissionFreeWill/index.ts | 5 +-
src/plugins/permissionsViewer/utils.ts | 2 +-
src/plugins/pinDms/data.ts | 29 -------
src/plugins/showHiddenChannels/index.tsx | 38 ++++-----
src/plugins/showHiddenThings/index.ts | 6 +-
.../spotifyControls/PlayerComponent.tsx | 1 -
src/plugins/spotifyControls/spotifyStyles.css | 60 +++++++++++----
.../visualRefreshSpotifyStyles.css | 77 -------------------
src/plugins/startupTimings/index.tsx | 10 +--
src/plugins/textReplace/index.tsx | 16 ----
src/plugins/userMessagesPronouns/index.ts | 7 --
src/plugins/viewIcons/index.tsx | 6 --
src/utils/constants.ts | 6 +-
src/webpack/common/stores.ts | 2 -
33 files changed, 105 insertions(+), 286 deletions(-)
delete mode 100644 src/plugins/spotifyControls/visualRefreshSpotifyStyles.css
diff --git a/.vscode/settings.json b/.vscode/settings.json
index fa543b38..fe123188 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -13,11 +13,14 @@
"typescript.format.semicolons": "insert",
"typescript.preferences.quoteStyle": "double",
"javascript.preferences.quoteStyle": "double",
-
"gitlens.remotes": [
{
"domain": "codeberg.org",
"type": "Gitea"
}
- ]
-}
+ ],
+ "css.format.spaceAroundSelectorSeparator": true,
+ "[css]": {
+ "editor.defaultFormatter": "vscode.css-language-features"
+ }
+}
\ No newline at end of file
diff --git a/src/api/Notifications/styles.css b/src/api/Notifications/styles.css
index 52cbc805..471fbf01 100644
--- a/src/api/Notifications/styles.css
+++ b/src/api/Notifications/styles.css
@@ -4,17 +4,13 @@
display: flex;
flex-direction: column;
color: var(--text-default);
- background-color: var(--background-base-lower-alt);
+ background-color: var(--background-base-low);
border-radius: 6px;
overflow: hidden;
cursor: pointer;
width: 100%;
}
-.visual-refresh .vc-notification-root {
- background-color: var(--background-base-low);
-}
-
.vc-notification-root:not(.vc-notification-log-wrapper > .vc-notification-root) {
position: absolute;
z-index: 2147483647;
diff --git a/src/components/CheckedTextInput.tsx b/src/components/CheckedTextInput.tsx
index cf4aa119..8d37a560 100644
--- a/src/components/CheckedTextInput.tsx
+++ b/src/components/CheckedTextInput.tsx
@@ -18,7 +18,6 @@
import { React, TextInput } from "@webpack/common";
-// TODO: Refactor settings to use this as well
interface TextInputProps {
/**
* WARNING: Changing this between renders will have no effect!
diff --git a/src/components/settings/AddonCard.css b/src/components/settings/AddonCard.css
index c8a2a81e..d5e3b4d6 100644
--- a/src/components/settings/AddonCard.css
+++ b/src/components/settings/AddonCard.css
@@ -1,6 +1,7 @@
.vc-addon-card {
- background-color: var(--background-base-lower-alt);
+ background-color: var(--card-primary-bg);
color: var(--interactive-active);
+ border: 1px solid var(--border-subtle);
border-radius: 8px;
display: block;
height: 100%;
@@ -11,26 +12,15 @@
box-sizing: border-box;
}
-.visual-refresh .vc-addon-card {
- background-color: var(--card-primary-bg);
- border: 1px solid var(--border-subtle);
-}
-
.vc-addon-card-disabled {
opacity: 0.6;
}
.vc-addon-card:hover {
- background-color: var(--background-tertiary);
transform: translateY(-1px);
box-shadow: var(--elevation-high);
}
-.visual-refresh .vc-addon-card:hover {
- /* same as non-hover, here to overwrite the non-refresh hover background */
- background-color: var(--card-primary-bg);
-}
-
.vc-addon-header {
margin-top: auto;
display: flex;
@@ -104,4 +94,4 @@
.vc-addon-title:hover {
overflow: visible;
animation: vc-addon-title var(--duration) linear infinite;
-}
+}
\ No newline at end of file
diff --git a/src/components/settings/QuickAction.css b/src/components/settings/QuickAction.css
index f65fba21..8b94bd2d 100644
--- a/src/components/settings/QuickAction.css
+++ b/src/components/settings/QuickAction.css
@@ -14,7 +14,7 @@
.vc-settings-quickActions-pill {
all: unset;
- background: var(--background-base-lower);
+ background: var(--button-secondary-background);
color: var(--header-secondary);
display: flex;
align-items: center;
@@ -26,7 +26,7 @@
}
.vc-settings-quickActions-pill:hover {
- background: var(--background-base-lower-alt);
+ background: var(--button-secondary-background-hover);
transform: translateY(-1px);
box-shadow: var(--elevation-high);
}
@@ -36,15 +36,7 @@
outline-offset: 2px;
}
-.visual-refresh .vc-settings-quickActions-pill {
- background: var(--button-secondary-background);
-}
-
-.visual-refresh .vc-settings-quickActions-pill:hover {
- background: var(--button-secondary-background-hover);
-}
-
.vc-settings-quickActions-img {
width: 24px;
height: 24px;
-}
+}
\ No newline at end of file
diff --git a/src/components/settings/tabs/plugins/ContributorModal.css b/src/components/settings/tabs/plugins/ContributorModal.css
index 3a698e2c..ef3b2dde 100644
--- a/src/components/settings/tabs/plugins/ContributorModal.css
+++ b/src/components/settings/tabs/plugins/ContributorModal.css
@@ -11,7 +11,7 @@
.vc-author-modal-name {
text-transform: none;
flex-grow: 0;
- background: var(--background-tertiary);
+ background: var(--background-base-lowest);
border-radius: 0 9999px 9999px 0;
padding: 6px 0.8em 6px 0.5em;
font-size: 20px;
@@ -26,7 +26,7 @@
position: absolute;
height: 100%;
width: 32px;
- background: var(--background-tertiary);
+ background: var(--background-base-lowest);
z-index: -1;
left: -32px;
top: 0;
@@ -48,4 +48,4 @@
display: grid;
gap: 0.5em;
margin-top: 0.75em;
-}
+}
\ No newline at end of file
diff --git a/src/components/settings/tabs/plugins/LinkIconButton.css b/src/components/settings/tabs/plugins/LinkIconButton.css
index 1055d6c7..97db0780 100644
--- a/src/components/settings/tabs/plugins/LinkIconButton.css
+++ b/src/components/settings/tabs/plugins/LinkIconButton.css
@@ -2,11 +2,11 @@
height: 32px;
width: 32px;
border-radius: 50%;
- border: 4px solid var(--background-tertiary);
+ border: 4px solid var(--background-base-lowest);
box-sizing: border-box
}
.vc-settings-modal-links {
display: flex;
gap: 0.2em;
-}
+}
\ No newline at end of file
diff --git a/src/components/settings/tabs/styles.css b/src/components/settings/tabs/styles.css
index c92d588f..2ff8cb9f 100644
--- a/src/components/settings/tabs/styles.css
+++ b/src/components/settings/tabs/styles.css
@@ -54,7 +54,7 @@
}
.vc-settings-theme-links:focus {
- background-color: var(--background-tertiary);
+ background-color: var(--background-base-lowest);
}
.vc-cloud-settings-sync-grid {
@@ -74,4 +74,4 @@
.vc-updater-modal-close-button {
float: right;
-}
+}
\ No newline at end of file
diff --git a/src/plugins/_api/chatButtons.ts b/src/plugins/_api/chatButtons.ts
index d4e1ea8b..26312253 100644
--- a/src/plugins/_api/chatButtons.ts
+++ b/src/plugins/_api/chatButtons.ts
@@ -16,11 +16,8 @@ export default definePlugin({
{
find: '"sticker")',
replacement: {
- // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
- match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(.+?(\i)\.push)/,
- replace: (m, not, children) => not
- ? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&`
- : `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||`
+ match: /return\(\i\.\i\|\|(?=\(.+?(\i)\.push)/,
+ replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),false)||"
}
}
]
diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx
index 1f942d7d..4a0f297e 100644
--- a/src/plugins/_core/settings.tsx
+++ b/src/plugins/_core/settings.tsx
@@ -59,8 +59,7 @@ export default definePlugin({
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
},
{
- // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
- match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?(?:function\(\){return |\(\)=>))\2/,
+ match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?\(\)=>)\2/,
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
}
]
diff --git a/src/plugins/betterFolders/style.css b/src/plugins/betterFolders/style.css
index a3c82dcb..a0a04966 100644
--- a/src/plugins/betterFolders/style.css
+++ b/src/plugins/betterFolders/style.css
@@ -3,11 +3,11 @@
}
/* These area names need to be hardcoded. Only betterFoldersSidebar is added by the plugin. */
-.visual-refresh .vc-betterFolders-sidebar-grid {
+.vc-betterFolders-sidebar-grid {
/* stylelint-disable-next-line value-keyword-case */
grid-template-columns: [start] min-content [guildsEnd] min-content [sidebarEnd] min-content [channelsEnd] 1fr [end];
grid-template-areas:
"titleBar titleBar titleBar titleBar"
"guildsList betterFoldersSidebar notice notice"
"guildsList betterFoldersSidebar channelsList page";
-}
+}
\ No newline at end of file
diff --git a/src/plugins/clientTheme/utils/styleUtils.ts b/src/plugins/clientTheme/utils/styleUtils.ts
index bc6169d4..ca36730a 100644
--- a/src/plugins/clientTheme/utils/styleUtils.ts
+++ b/src/plugins/clientTheme/utils/styleUtils.ts
@@ -75,8 +75,8 @@ function createColorsOverrides(styles: string) {
const darkThemeBaseLightness = visualRefreshColorsLightness["--neutral-69-hsl"];
createOrUpdateStyle(OVERRIDES_STYLE_ID, [
- `.visual-refresh.theme-light {\n ${generateNewColorVars(visualRefreshColorsLightness, lightThemeBaseLightness)} \n}`,
- `.visual-refresh.theme-dark {\n ${generateNewColorVars(visualRefreshColorsLightness, darkThemeBaseLightness)} \n}`,
+ `.theme-light {\n ${generateNewColorVars(visualRefreshColorsLightness, lightThemeBaseLightness)} \n}`,
+ `.theme-dark {\n ${generateNewColorVars(visualRefreshColorsLightness, darkThemeBaseLightness)} \n}`,
].join("\n\n"));
}
diff --git a/src/plugins/ctrlEnterSend/index.ts b/src/plugins/ctrlEnterSend/index.ts
index 57a70d52..7b0b26b9 100644
--- a/src/plugins/ctrlEnterSend/index.ts
+++ b/src/plugins/ctrlEnterSend/index.ts
@@ -44,9 +44,8 @@ export default definePlugin({
{
find: ".selectPreviousCommandOption(",
replacement: {
- // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
- match: /(?<=(\i)\.which(?:!==|===)\i\.\i.ENTER(\|\||&&)).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=(?:\|\||&&)\(\i\.preventDefault)/,
- replace: (_, event, condition, codeblock) => `${condition === "||" ? "!" : ""}$self.shouldSubmit(${event},${codeblock})`
+ match: /(?<=(\i)\.which!==\i\.\i.ENTER\|\|).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=\|\|\(\i\.preventDefault)/,
+ replace: "!$self.shouldSubmit($1,$2)"
}
},
{
diff --git a/src/plugins/decor/index.tsx b/src/plugins/decor/index.tsx
index a141f2b5..528e9154 100644
--- a/src/plugins/decor/index.tsx
+++ b/src/plugins/decor/index.tsx
@@ -49,18 +49,6 @@ export default definePlugin({
{
find: ".decorationGridItem,",
replacement: [
- {
- // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
- match: /(?<==)\i=>{let{children.{20,200}decorationGridItem/,
- replace: "$self.DecorationGridItem=$&",
- noWarn: true
- },
- {
- // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
- match: /(?<==)\i=>{let{user:\i,avatarDecoration/,
- replace: "$self.DecorationGridDecoration=$&",
- noWarn: true
- },
{
match: /(?<==)\i=>{var{children.{20,200}decorationGridItem/,
replace: "$self.DecorationGridItem=$&",
diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx
index 380147fe..f697c3fd 100644
--- a/src/plugins/fakeNitro/index.tsx
+++ b/src/plugins/fakeNitro/index.tsx
@@ -255,11 +255,8 @@ export default definePlugin({
},
{
// Disallow the emoji for premium locked if the intention doesn't allow it
- // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
- match: /(!)?(\i\.\i\.canUseEmojisEverywhere\(\i\))/,
- replace: (m, not) => not
- ? `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
- : `(${m}||${IS_BYPASSEABLE_INTENTION})`
+ match: /!(\i\.\i\.canUseEmojisEverywhere\(\i\))/,
+ replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
},
{
// Allow animated emojis to be used if the intention allows it
diff --git a/src/plugins/ircColors/index.ts b/src/plugins/ircColors/index.ts
index ce007a57..ebfa735e 100644
--- a/src/plugins/ircColors/index.ts
+++ b/src/plugins/ircColors/index.ts
@@ -74,8 +74,8 @@ export default definePlugin({
{
find: "#{intl::GUILD_OWNER}),children:",
replacement: {
- match: /(?<=roleName:\i,)(colorString:)/,
- replace: "$1$self.calculateNameColorForListContext(arguments[0]),originalColor:"
+ match: /(?<=roleName:\i,)colorString:/,
+ replace: "colorString:$self.calculateNameColorForListContext(arguments[0]),originalColor:"
},
predicate: () => settings.store.memberListColors
}
diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx
index 97f6f23c..7fa6fbcd 100644
--- a/src/plugins/memberCount/index.tsx
+++ b/src/plugins/memberCount/index.tsx
@@ -66,12 +66,6 @@ export default definePlugin({
{
find: "{isSidebarVisible:",
replacement: [
- {
- // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
- match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
- replace: ":[$1?.startsWith('members')?$self.render():null,$2",
- noWarn: true
- },
{
match: /(?<=var\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
replace: ":[$1?.startsWith('members')?$self.render():null,$2",
diff --git a/src/plugins/messageTags/index.ts b/src/plugins/messageTags/index.ts
index 90ce0ceb..08b035b1 100644
--- a/src/plugins/messageTags/index.ts
+++ b/src/plugins/messageTags/index.ts
@@ -17,7 +17,6 @@
*/
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands";
-import * as DataStore from "@api/DataStore";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
@@ -89,14 +88,6 @@ export default definePlugin({
settings,
async start() {
- // TODO(OptionType.CUSTOM Related): Remove DataStore tags migration once enough time has passed
- const oldTags = await DataStore.get(DATA_KEY);
- if (oldTags != null) {
- // @ts-expect-error
- settings.store.tagsList = Object.fromEntries(oldTags.map(oldTag => (delete oldTag.enabled, [oldTag.name, oldTag])));
- await DataStore.del(DATA_KEY);
- }
-
const tags = getTags();
for (const tagName in tags) {
createTagCommand(tags[tagName]);
diff --git a/src/plugins/openInApp/index.ts b/src/plugins/openInApp/index.ts
index e1133234..04c4f441 100644
--- a/src/plugins/openInApp/index.ts
+++ b/src/plugins/openInApp/index.ts
@@ -100,9 +100,8 @@ export default definePlugin({
replace: "true"
},
{
- // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
- match: /(!)?\(0,\i\.isDesktop\)\(\)/,
- replace: (_, not) => not ? "false" : "true"
+ match: /\(0,\i\.isDesktop\)\(\)/,
+ replace: "true"
}
]
},
diff --git a/src/plugins/permissionFreeWill/index.ts b/src/plugins/permissionFreeWill/index.ts
index 8a613514..767d4f98 100644
--- a/src/plugins/permissionFreeWill/index.ts
+++ b/src/plugins/permissionFreeWill/index.ts
@@ -46,9 +46,8 @@ export default definePlugin({
find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}",
replacement: [
{
- // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
- match: /{(?:\i:(?:function\(\){return |\(\)=>)\i}?,?){2}}/,
- replace: m => m.replaceAll(canonicalizeMatch(/(function\(\){return |\(\)=>)\i/g), "$1()=>Promise.resolve(true)")
+ match: /{(?:\i:\(\)=>\i,?){2}}/,
+ replace: m => m.replaceAll(canonicalizeMatch(/\(\)=>\i/g), "()=>Promise.resolve(true)")
}
],
predicate: () => settings.store.onboarding
diff --git a/src/plugins/permissionsViewer/utils.ts b/src/plugins/permissionsViewer/utils.ts
index 27e7494b..c3f61672 100644
--- a/src/plugins/permissionsViewer/utils.ts
+++ b/src/plugins/permissionsViewer/utils.ts
@@ -29,7 +29,7 @@ export const { getGuildPermissionSpecMap } = findByPropsLazy("getGuildPermission
export const cl = classNameFactory("vc-permviewer-");
export function getSortedRolesForMember({ id: guildId }: Guild, member: GuildMember) {
- // the guild id is the @everyone role
+ // The guild id is the @everyone role
return GuildRoleStore
.getSortedRoles(guildId)
.filter(role => role.id === guildId || member.roles.includes(role.id));
diff --git a/src/plugins/pinDms/data.ts b/src/plugins/pinDms/data.ts
index d689bd2a..18782299 100644
--- a/src/plugins/pinDms/data.ts
+++ b/src/plugins/pinDms/data.ts
@@ -4,8 +4,6 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
-import * as DataStore from "@api/DataStore";
-import { Settings } from "@api/Settings";
import { useForceUpdater } from "@utils/react";
import { UserStore } from "@webpack/common";
@@ -28,8 +26,6 @@ let forceUpdateDms: (() => void) | undefined = undefined;
export let currentUserCategories: Category[] = [];
export async function init() {
- await migrateData();
-
const userId = UserStore.getCurrentUser()?.id;
if (userId == null) return;
@@ -154,28 +150,3 @@ export function moveChannel(channelId: string, direction: -1 | 1) {
swapElementsInArray(category.channels, a, b);
}
-
-// TODO(OptionType.CUSTOM Related): Remove DataStore PinnedDms migration once enough time has passed
-async function migrateData() {
- if (Settings.plugins.PinDMs.dmSectioncollapsed != null) {
- settings.store.dmSectionCollapsed = Settings.plugins.PinDMs.dmSectioncollapsed;
- delete Settings.plugins.PinDMs.dmSectioncollapsed;
- }
-
- const dataStoreKeys = await DataStore.keys();
- const pinDmsKeys = dataStoreKeys.map(key => String(key)).filter(key => key.startsWith(CATEGORY_BASE_KEY));
-
- if (pinDmsKeys.length === 0) return;
-
- for (const pinDmsKey of pinDmsKeys) {
- const categories = await DataStore.get(pinDmsKey);
- if (categories == null) continue;
-
- const userId = pinDmsKey.replace(CATEGORY_BASE_KEY, "");
- settings.store.userBasedCategoryList[userId] = categories;
-
- await DataStore.del(pinDmsKey);
- }
-
- await Promise.all([DataStore.del(CATEGORY_MIGRATED_PINDMS_KEY), DataStore.del(CATEGORY_MIGRATED_KEY), DataStore.del(OLD_CATEGORY_KEY)]);
-}
diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx
index 2d0ea368..43cd677e 100644
--- a/src/plugins/showHiddenChannels/index.tsx
+++ b/src/plugins/showHiddenChannels/index.tsx
@@ -112,11 +112,8 @@ export default definePlugin({
},
{
// Prevent Discord from trying to connect to hidden voice channels
- // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
- match: /(?=(\|\||&&)\i\.\i\.selectVoiceChannel\((\i)\.id\))/,
- replace: (_, condition, channel) => condition === "||"
- ? `||$self.isHiddenChannel(${channel})`
- : `&&!$self.isHiddenChannel(${channel})`
+ match: /(?=\|\|\i\.\i\.selectVoiceChannel\((\i)\.id\))/,
+ replace: (_, channel) => `||$self.isHiddenChannel(${channel})`
},
{
// Make Discord show inside the channel if clicking on a hidden or locked channel
@@ -129,11 +126,8 @@ export default definePlugin({
{
find: ".AUDIENCE),{isSubscriptionGated",
replacement: {
- // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
- match: /(!)?(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/,
- replace: (m, not, channel) => not
- ? `${m}&&!$self.isHiddenChannel(${channel})`
- : `${m}||$self.isHiddenChannel(${channel})`
+ match: /(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/,
+ replace: (m, channel) => `${m}||$self.isHiddenChannel(${channel})`
}
},
{
@@ -183,11 +177,8 @@ export default definePlugin({
},
// Make voice channels also appear as muted if they are muted
{
- // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
- match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)(if\()?(\i)(?:\)return |\?)(\i\.MUTED)/,
- replace: (_, otherClasses, isIf, isMuted, mutedClassExpression) => isIf
- ? `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return ""`
- : `${isMuted}?${mutedClassExpression}:"",${otherClasses}${isMuted}?""`
+ match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)if\((\i)(?:\)return |\?)(\i\.MUTED)/,
+ replace: (_, otherClasses, isMuted, mutedClassExpression) => `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return ""`
}
]
},
@@ -197,8 +188,7 @@ export default definePlugin({
{
// Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden
predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle,
- // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
- match: /(?<=\.LOCKED(?:;if\(|:))(?<={channel:(\i).+?)/,
+ match: /(?<=\.LOCKED;if\()(?<={channel:(\i).+?)/,
replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&`
},
{
@@ -292,8 +282,8 @@ export default definePlugin({
replacement: [
{
// Change the role permission check to CONNECT if the channel is locked
- match: /\i\.\i\(\i\.\i\.ADMINISTRATOR,\i\.\i\.VIEW_CHANNEL\)(?<=context:(\i)}.+?)/,
- replace: (m, channel) => `$self.fixPermCheck(${m},${channel})`
+ match: /(forceRoles:.+?)(\i\.\i\(\i\.\i\.ADMINISTRATOR,\i\.\i\.VIEW_CHANNEL\))(?<=context:(\i)}.+?)/,
+ replace: (_, rest, mergedPermissions, channel) => `${rest}$self.swapViewChannelWithConnectPermission(${mergedPermissions},${channel})`
},
{
// Change the permissionOverwrite check to CONNECT if the channel is locked
@@ -303,7 +293,7 @@ export default definePlugin({
{
// Include the @everyone role in the allowed roles list for Hidden Channels
match: /getSortedRoles.+?\.filter\(\i=>(?=!)/,
- replace: m => `${m}$self.isHiddenChannel(arguments[0].channel)?true:`
+ replace: m => `${m}$self.isHiddenChannel(arguments[0]?.channel)?true:`
},
{
// If the @everyone role has the required permissions, make the array only contain it
@@ -493,13 +483,13 @@ export default definePlugin({
],
- fixPermCheck(originalPerms: bigint, channel: Channel) {
+ swapViewChannelWithConnectPermission(mergedPermissions: bigint, channel: Channel) {
if (!PermissionStore.can(PermissionsBits.CONNECT, channel)) {
- originalPerms &= ~PermissionsBits.VIEW_CHANNEL;
- originalPerms |= PermissionsBits.CONNECT;
+ mergedPermissions &= ~PermissionsBits.VIEW_CHANNEL;
+ mergedPermissions |= PermissionsBits.CONNECT;
}
- return originalPerms;
+ return mergedPermissions;
},
isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) {
diff --git a/src/plugins/showHiddenThings/index.ts b/src/plugins/showHiddenThings/index.ts
index 96ebf169..c39dccc4 100644
--- a/src/plugins/showHiddenThings/index.ts
+++ b/src/plugins/showHiddenThings/index.ts
@@ -72,8 +72,8 @@ export default definePlugin({
find: "#{intl::GUILD_MEMBER_MOD_VIEW_PERMISSION_GRANTED_BY_ARIA_LABEL}),allowOverflow:",
predicate: () => settings.store.showModView,
replacement: {
- match: /(role:)\i(?<=\i\.roles,\i\.highestRoleId,(\i)\].+)/,
- replace: "$1$self.findHighestRole(arguments[0],$2)",
+ match: /(?<=\.highestRole\),)role:\i(?<=\[\i\.roles,\i\.highestRoleId,(\i)\].+)/,
+ replace: "role:$self.getHighestRole(arguments[0],$2)",
}
},
// allows you to open mod view on yourself
@@ -87,7 +87,7 @@ export default definePlugin({
}
],
- findHighestRole({ member }: { member: GuildMember; }, roles: Role[]): Role | undefined {
+ getHighestRole({ member }: { member: GuildMember; }, roles: Role[]): Role | undefined {
try {
return roles.find(role => role.id === member.highestRoleId);
} catch (e) {
diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx
index 78a69a14..32c78c36 100644
--- a/src/plugins/spotifyControls/PlayerComponent.tsx
+++ b/src/plugins/spotifyControls/PlayerComponent.tsx
@@ -17,7 +17,6 @@
*/
import "./spotifyStyles.css";
-import "./visualRefreshSpotifyStyles.css"; // TODO: merge with spotifyStyles.css and remove when old UI is discontinued
import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/plugins/spotifyControls/spotifyStyles.css
index d4afbc83..e9dfeffd 100644
--- a/src/plugins/spotifyControls/spotifyStyles.css
+++ b/src/plugins/spotifyControls/spotifyStyles.css
@@ -1,8 +1,13 @@
#vc-spotify-player {
- padding: 0.375rem 0.5rem;
+ padding: 12px;
+ background: var(--bg-overlay-floating, var(--background-base-low, var(--background-base-lower-alt)));
+ margin: 0;
+ border-top-left-radius: 10px;
+ border-top-right-radius: 10px;
border-bottom: 1px solid var(--border-subtle);
- --vc-spotify-green: var(--spotify, #1db954); /* so custom themes can easily change it */
+ /* so custom themes can easily change it */
+ --vc-spotify-green: var(--spotify, #1db954);
--vc-spotify-green-90: color-mix(in hsl, var(--vc-spotify-green), transparent 90%);
--vc-spotify-green-80: color-mix(in hsl, var(--vc-spotify-green), transparent 80%);
}
@@ -16,12 +21,13 @@
}
.vc-spotify-button {
+ margin: 0 2px;
+ border-radius: var(--radius-sm);
background: none;
color: var(--interactive-normal);
padding: 0;
width: 32px;
height: 32px;
- border-radius: 100%;
display: flex;
justify-content: center;
align-items: center;
@@ -47,13 +53,16 @@
filter: brightness(1.3);
} */
-.vc-spotify-shuffle-on,
.vc-spotify-repeat-context,
.vc-spotify-repeat-track,
-.vc-spotify-shuffle-on:hover,
+.vc-spotify-shuffle-on {
+ background-color: var(--vc-spotify-green-90);
+}
+
.vc-spotify-repeat-context:hover,
-.vc-spotify-repeat-track:hover {
- color: var(--vc-spotify-green);
+.vc-spotify-repeat-track:hover,
+.vc-spotify-shuffle-on:hover {
+ background-color: var(--vc-spotify-green-80);
}
.vc-spotify-tooltip-text {
@@ -74,6 +83,15 @@
.vc-spotify-button-row {
justify-content: center;
+ margin-top: 14px;
+}
+
+.vc-spotify-secondary-song-info {
+ font-size: 12px;
+}
+
+.vc-spotify-song-info-prefix {
+ display: none;
}
#vc-spotify-info-wrapper {
@@ -127,7 +145,7 @@
.vc-spotify-album {
font-size: 12px;
text-decoration: none;
- color: var(--header-secondary);
+ color: var(--header-primary);
}
.vc-spotify-comma {
@@ -155,16 +173,26 @@
padding: 0 !important;
}
-#vc-spotify-progress-bar > [class^="slider"] [class^="bar-"] {
- height: 4px !important;
+#vc-spotify-progress-bar > [class^="slider"] [class^="bar"] {
+ height: 3px !important;
+ top: calc(12px - 4px / 2 + var(--bar-offset));
+}
+
+#vc-spotify-progress-bar > [class^="slider"] [class^="barFill"] {
+ background-color: var(--interactive-active);
+}
+
+#vc-spotify-progress-bar > [class^="slider"]:hover [class^="barFill"] {
+ background-color: var(--vc-spotify-green);
}
#vc-spotify-progress-bar > [class^="slider"] [class^="grabber"] {
/* these importants are necessary, it applies a width and height through inline styles */
- height: 10px !important;
- width: 10px !important;
- margin-top: 4px;
- background-color: var(--interactive-normal);
+ height: 16px !important;
+ width: 16px !important;
+ margin-top: calc(17px/-2 + var(--bar-offset)/2);
+ margin-left: -0.5px;
+ background-color: var(--interactive-active);
border-color: var(--interactive-normal);
color: var(--interactive-normal);
opacity: 0;
@@ -183,6 +211,8 @@
font-size: 12px;
top: 10px;
position: absolute;
+ margin-top: 8px;
+ font-family: var(--font-code);
}
.vc-spotify-time-left {
@@ -196,4 +226,4 @@
.vc-spotify-fallback {
padding: 0.5em;
color: var(--text-default);
-}
+}
\ No newline at end of file
diff --git a/src/plugins/spotifyControls/visualRefreshSpotifyStyles.css b/src/plugins/spotifyControls/visualRefreshSpotifyStyles.css
deleted file mode 100644
index 0d6d30ba..00000000
--- a/src/plugins/spotifyControls/visualRefreshSpotifyStyles.css
+++ /dev/null
@@ -1,77 +0,0 @@
-/* TODO: merge with spotifyStyles.css and remove when old UI is discontinued */
-.visual-refresh {
- #vc-spotify-player {
- padding: 12px;
- background: var(--bg-overlay-floating, var(--background-base-low, var(--background-base-lower-alt)));
- margin: 0;
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
- }
-
- .vc-spotify-song-info-prefix {
- display: none;
- }
-
- .vc-spotify-artist, .vc-spotify-album {
- color: var(--header-primary);
- }
-
- .vc-spotify-secondary-song-info {
- font-size: 12px;
- }
-
- #vc-spotify-progress-bar {
- position: relative;
- color: var(--text-default);
- width: 100%;
- }
-
- #vc-spotify-progress-bar > [class^="slider"] {
- flex-grow: 1;
- width: 100%;
- padding: 0 !important;
- }
-
- #vc-spotify-progress-bar > [class^="slider"] [class^="bar"] {
- height: 3px !important;
- top: calc(12px - 4px / 2 + var(--bar-offset));
- }
-
- #vc-spotify-progress-bar > [class^="slider"] [class^="barFill"] {
- background-color: var(--interactive-active);
- }
-
- #vc-spotify-progress-bar > [class^="slider"]:hover [class^="barFill"] {
- background-color: var(--vc-spotify-green);
- }
-
- #vc-spotify-progress-bar > [class^="slider"] [class^="grabber"] {
- background-color: var(--interactive-active);
- width: 16px !important;
- height: 16px !important;
- margin-top: calc(17px/-2 + var(--bar-offset)/2);
- margin-left: -0.5px;
- }
-
- .vc-spotify-progress-time {
- margin-top: 8px;
- font-family: var(--font-code);
- }
-
- .vc-spotify-button-row {
- margin-top: 14px;
- }
-
- .vc-spotify-button {
- margin: 0 2px;
- border-radius: var(--radius-sm);
- }
-
- .vc-spotify-repeat-context, .vc-spotify-repeat-track, .vc-spotify-shuffle-on {
- background-color: var(--vc-spotify-green-90);
- }
-
- .vc-spotify-repeat-context:hover, .vc-spotify-repeat-track:hover, .vc-spotify-shuffle-on:hover {
- background-color: var(--vc-spotify-green-80);
- }
-}
diff --git a/src/plugins/startupTimings/index.tsx b/src/plugins/startupTimings/index.tsx
index ac7f3f0c..747850f1 100644
--- a/src/plugins/startupTimings/index.tsx
+++ b/src/plugins/startupTimings/index.tsx
@@ -25,17 +25,10 @@ export default definePlugin({
name: "StartupTimings",
description: "Adds Startup Timings to the Settings menu",
authors: [Devs.Megu],
+
patches: [{
find: "#{intl::ACTIVITY_SETTINGS}",
replacement: [
- {
- // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
- match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/,
- replace: (_, commaOrSemi, settings, elements) => "" +
- `${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
- `&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`,
- noWarn: true
- },
{
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+\)\)\}\))(?=\)\})/,
replace: (_, commaOrSemi, settings, elements) => "" +
@@ -44,5 +37,6 @@ export default definePlugin({
},
]
}],
+
StartupTimingPage
});
diff --git a/src/plugins/textReplace/index.tsx b/src/plugins/textReplace/index.tsx
index d5d6f4dc..756541cc 100644
--- a/src/plugins/textReplace/index.tsx
+++ b/src/plugins/textReplace/index.tsx
@@ -16,7 +16,6 @@
* along with this program. If not, see .
*/
-import { DataStore } from "@api/index";
import { definePluginSettings } from "@api/Settings";
import { Flex } from "@components/Flex";
import { DeleteIcon } from "@components/Icons";
@@ -241,20 +240,5 @@ export default definePlugin({
// Channel used for sharing rules, applying rules here would be messy
if (channelId === TEXT_REPLACE_RULES_CHANNEL_ID) return;
msg.content = applyRules(msg.content);
- },
-
- async start() {
- // TODO(OptionType.CUSTOM Related): Remove DataStore rules migrations once enough time has passed
- const oldStringRules = await DataStore.get(STRING_RULES_KEY);
- if (oldStringRules != null) {
- settings.store.stringRules = oldStringRules;
- await DataStore.del(STRING_RULES_KEY);
- }
-
- const oldRegexRules = await DataStore.get(REGEX_RULES_KEY);
- if (oldRegexRules != null) {
- settings.store.regexRules = oldRegexRules;
- await DataStore.del(REGEX_RULES_KEY);
- }
}
});
diff --git a/src/plugins/userMessagesPronouns/index.ts b/src/plugins/userMessagesPronouns/index.ts
index 1699251e..9eaab235 100644
--- a/src/plugins/userMessagesPronouns/index.ts
+++ b/src/plugins/userMessagesPronouns/index.ts
@@ -42,13 +42,6 @@ export default definePlugin({
{
find: '="SYSTEM_TAG"',
replacement: [
- {
- // Add next to username (compact mode)
- // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
- match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g,
- replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),",
- noWarn: true
- },
{
// Add next to username (compact mode)
match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\)\),(?=\i)/g,
diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx
index 3b8485fc..a523a681 100644
--- a/src/plugins/viewIcons/index.tsx
+++ b/src/plugins/viewIcons/index.tsx
@@ -199,12 +199,6 @@ export default definePlugin({
{
find: ".overlay:void 0,status:",
replacement: [
- {
- // FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
- match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/,
- replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},",
- noWarn: true
- },
{
match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",.{0,100}className:\i,/,
replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},",
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 962ea789..eb19bf86 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -93,9 +93,9 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "Mai",
id: 722647978577363026n
},
- echo: {
- name: "ECHO",
- id: 712639419785412668n
+ amy: {
+ name: "Amy",
+ id: 603229858612510720n
},
katlyn: {
name: "katlyn",
diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts
index 71abd729..ca867c70 100644
--- a/src/webpack/common/stores.ts
+++ b/src/webpack/common/stores.ts
@@ -31,8 +31,6 @@ export let MessageStore: Omit & GenericStore & {
getMessages(chanId: string): any;
};
-// TODO: The correct name for this is ChannelActionCreators and it has already been exported again from utils. Remove this export once enough time has passed
-export const PrivateChannelsStore = findByPropsLazy("openPrivateChannel");
export let PermissionStore: GenericStore;
export let GuildChannelStore: GenericStore;
export let ReadStateStore: GenericStore;
From 36c15d619e99645b0f8c93a9b0fae9ae8a54a2f0 Mon Sep 17 00:00:00 2001
From: Zordan <92729699+zordythezordan@users.noreply.github.com>
Date: Wed, 6 Aug 2025 17:26:00 +0300
Subject: [PATCH 038/116] HideAttachments: correctly support forwarded Messages
(#3587)
Co-authored-by: V
---
.../discord-types/src/common/messages/Message.d.ts | 3 +++
src/plugins/hideAttachments/index.tsx | 11 ++++-------
src/plugins/unsuppressEmbeds/index.tsx | 11 ++++++-----
3 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/packages/discord-types/src/common/messages/Message.d.ts b/packages/discord-types/src/common/messages/Message.d.ts
index 38010caf..41de8816 100644
--- a/packages/discord-types/src/common/messages/Message.d.ts
+++ b/packages/discord-types/src/common/messages/Message.d.ts
@@ -83,6 +83,9 @@ export class Message extends DiscordRecord {
channel_id: string;
message_id: string;
} | undefined;
+ messageSnapshots: {
+ message: Message;
+ }[];
nick: unknown; // probably a string
nonce: string | undefined;
pinned: boolean;
diff --git a/src/plugins/hideAttachments/index.tsx b/src/plugins/hideAttachments/index.tsx
index 0198a3e1..38213057 100644
--- a/src/plugins/hideAttachments/index.tsx
+++ b/src/plugins/hideAttachments/index.tsx
@@ -25,7 +25,7 @@ import { ImageInvisible, ImageVisible } from "@components/Icons";
import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
import definePlugin from "@utils/types";
-import { MessageSnapshot } from "@vencord/discord-types";
+import { Message } from "@vencord/discord-types";
import { ChannelStore } from "@webpack/common";
const KEY = "HideAttachments_HiddenIds";
@@ -41,6 +41,8 @@ const saveHiddenMessages = (ids: Set) => set(KEY, ids);
migratePluginSettings("HideMedia", "HideAttachments");
+const hasMedia = (msg: Message) => msg.attachments.length > 0 || msg.embeds.length > 0 || msg.stickerItems.length > 0;
+
export default definePlugin({
name: "HideMedia",
description: "Hide attachments and embeds for individual messages via hover button",
@@ -56,12 +58,7 @@ export default definePlugin({
}],
renderMessagePopoverButton(msg) {
- // @ts-expect-error - discord-types lags behind discord.
- const hasAttachmentsInShapshots = msg.messageSnapshots.some(
- (snapshot: MessageSnapshot) => snapshot?.message.attachments.length
- );
-
- if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length && !hasAttachmentsInShapshots) return null;
+ if (!hasMedia(msg) && !msg.messageSnapshots.some(s => hasMedia(s.message))) return null;
const isHidden = hiddenMessages.has(msg.id);
diff --git a/src/plugins/unsuppressEmbeds/index.tsx b/src/plugins/unsuppressEmbeds/index.tsx
index e5f8557f..9eecd86d 100644
--- a/src/plugins/unsuppressEmbeds/index.tsx
+++ b/src/plugins/unsuppressEmbeds/index.tsx
@@ -20,17 +20,18 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co
import { ImageInvisible, ImageVisible } from "@components/Icons";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
-import { MessageSnapshot } from "@vencord/discord-types";
+import { Channel, Message } from "@vencord/discord-types";
import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common";
const EMBED_SUPPRESSED = 1 << 2;
-const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, messageSnapshots, embeds, flags, id: messageId } }) => {
+const messageContextMenuPatch: NavContextMenuPatchCallback = (
+ children,
+ { channel, message: { author, messageSnapshots, embeds, flags, id: messageId } }: { channel: Channel; message: Message; }
+) => {
const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0;
- const hasEmbedsInSnapshots = messageSnapshots.some(
- (snapshot: MessageSnapshot) => snapshot?.message.embeds.length
- );
+ const hasEmbedsInSnapshots = messageSnapshots.some(s => s.message.embeds.length);
if (!isEmbedSuppressed && !embeds.length && !hasEmbedsInSnapshots) return;
From 6a66b7f54f3ef6693941c024004a163b6639a44c Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Wed, 6 Aug 2025 16:16:07 -0400
Subject: [PATCH 039/116] fix plugins broken by Discord update (#3583)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: V
---
src/plugins/_api/notices.ts | 4 ++--
src/plugins/reviewDB/index.tsx | 7 -------
src/plugins/showAllMessageButtons/index.ts | 5 +++--
src/plugins/showHiddenChannels/index.tsx | 12 +++---------
src/plugins/superReactionTweaks/index.ts | 4 ++--
src/plugins/vencordToolbox/index.tsx | 4 ++--
6 files changed, 12 insertions(+), 24 deletions(-)
diff --git a/src/plugins/_api/notices.ts b/src/plugins/_api/notices.ts
index f0445924..89aa1a46 100644
--- a/src/plugins/_api/notices.ts
+++ b/src/plugins/_api/notices.ts
@@ -33,8 +33,8 @@ export default definePlugin({
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
},
{
- match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
- replace: "if($1?.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
+ match: /(?<=function (\i)\(\i\){)return null!=(\i)(?=.+?NOTICE_DISMISS:\1)/,
+ replace: (m, _, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}`
}
]
}
diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx
index d8374e6f..c5c102ae 100644
--- a/src/plugins/reviewDB/index.tsx
+++ b/src/plugins/reviewDB/index.tsx
@@ -83,13 +83,6 @@ export default definePlugin({
replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user}),"
}
},
- {
- find: ".MODAL,user:",
- replacement: {
- match: /children:\[(?=[^[]+?shouldShowTooltip:)/,
- replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user}),"
- }
- },
{
find: ".SIDEBAR,shouldShowTooltip:",
replacement: {
diff --git a/src/plugins/showAllMessageButtons/index.ts b/src/plugins/showAllMessageButtons/index.ts
index 1d689013..6ac17e5d 100644
--- a/src/plugins/showAllMessageButtons/index.ts
+++ b/src/plugins/showAllMessageButtons/index.ts
@@ -28,8 +28,9 @@ export default definePlugin({
{
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
replacement: {
- match: /isExpanded:\i&&(.+?),/,
- replace: "isExpanded:$1,"
+ // isExpanded = isShiftPressed && other conditions...
+ match: /(?<=(\i)=)\i(?=&&.+?isExpanded:\1)/,
+ replace: "true"
}
}
]
diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx
index 43cd677e..78c81d00 100644
--- a/src/plugins/showHiddenChannels/index.tsx
+++ b/src/plugins/showHiddenChannels/index.tsx
@@ -319,20 +319,14 @@ export default definePlugin({
replacement: [
{
// Create a variable for the channel prop
- match: /users:\i,maxUsers:\i.+?}=(\i).*?;/,
- replace: (m, props) => `${m}let{shcChannel}=${props};`
+ match: /(function \i\(\i\)\{)([^}]+?hideOverflowCount)/,
+ replace: "$1let {shcChannel}=arguments[0];$2"
},
{
// Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen
- match: /\i>0(?=&&.{0,30}Math.min)/,
+ match: /\i>0(?=&&!\i&&!\i)/,
replace: m => `($self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)?true:${m})`
},
- {
- // Prevent Discord from overwriting the last children with the plus button
- // if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen
- match: /(?<=\i\.length-)1(?=\]=.{0,60}renderPopout)(?<=(\i)=\i\.length-\i.+?)/,
- replace: (_, amount) => `($self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)&&${amount}<=0?0:1)`
- },
{
// Show only the plus text without overflowed children amount
// if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen
diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts
index 188f868a..b8aed478 100644
--- a/src/plugins/superReactionTweaks/index.ts
+++ b/src/plugins/superReactionTweaks/index.ts
@@ -42,8 +42,8 @@ export default definePlugin({
{
find: ",BURST_REACTION_EFFECT_PLAY",
replacement: {
- match: /(BURST_REACTION_EFFECT_PLAY:\i=>{.{50,100})(\i\(\i,\i\))>=\d+/,
- replace: "$1!$self.shouldPlayBurstReaction($2)"
+ match: /(?<=(\i)=\i=>{.+?)(\i\(\i,\i\))>=\i(?=\).+BURST_REACTION_EFFECT_PLAY:\1)/,
+ replace: "!$self.shouldPlayBurstReaction($2)"
}
},
{
diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx
index 2f671a23..82cd0284 100644
--- a/src/plugins/vencordToolbox/index.tsx
+++ b/src/plugins/vencordToolbox/index.tsx
@@ -140,9 +140,9 @@ export default definePlugin({
patches: [
{
- find: "toolbar:function",
+ find: ".controlButtonWrapper,",
replacement: {
- match: /(?<=toolbar:function.{0,100}\()\i.Fragment,/,
+ match: /(?<=function (\i).{0,100}\()\i.Fragment,(?=.+?toolbar:\1\(\))/,
replace: "$self.ToolboxFragmentWrapper,"
}
}
From 74d78d89ed087cf25ad2c1e565f0736b92d89539 Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Wed, 6 Aug 2025 20:11:38 -0400
Subject: [PATCH 040/116] fix TypingTweaks (#3586)
Co-authored-by: V
---
.../src/stores/AuthenticationStore.d.ts | 11 +++
.../src/stores/RelationshipStore.d.ts | 5 ++
.../discord-types/src/stores/TypingStore.d.ts | 9 +++
packages/discord-types/src/stores/index.d.ts | 2 +
src/plugins/typingIndicator/index.tsx | 7 +-
src/plugins/typingTweaks/index.tsx | 67 +++++++++++++------
src/webpack/common/stores.ts | 5 ++
7 files changed, 83 insertions(+), 23 deletions(-)
create mode 100644 packages/discord-types/src/stores/AuthenticationStore.d.ts
create mode 100644 packages/discord-types/src/stores/TypingStore.d.ts
diff --git a/packages/discord-types/src/stores/AuthenticationStore.d.ts b/packages/discord-types/src/stores/AuthenticationStore.d.ts
new file mode 100644
index 00000000..3ad2354f
--- /dev/null
+++ b/packages/discord-types/src/stores/AuthenticationStore.d.ts
@@ -0,0 +1,11 @@
+import { FluxStore } from "..";
+
+export class AuthenticationStore extends FluxStore {
+ /**
+ * Gets the id of the current user
+ */
+ getId(): string;
+
+ // This Store has a lot more methods related to everything Auth, but they really should
+ // not be needed, so they are not typed
+}
diff --git a/packages/discord-types/src/stores/RelationshipStore.d.ts b/packages/discord-types/src/stores/RelationshipStore.d.ts
index 5d1a08af..9aceac47 100644
--- a/packages/discord-types/src/stores/RelationshipStore.d.ts
+++ b/packages/discord-types/src/stores/RelationshipStore.d.ts
@@ -15,6 +15,11 @@ export class RelationshipStore extends FluxStore {
isFriend(userId: string): boolean;
isBlocked(userId: string): boolean;
isIgnored(userId: string): boolean;
+ /**
+ * @see {@link isBlocked}
+ * @see {@link isIgnored}
+ */
+ isBlockedOrIgnored(userId: string): boolean;
getSince(userId: string): string;
getMutableRelationships(): Map;
diff --git a/packages/discord-types/src/stores/TypingStore.d.ts b/packages/discord-types/src/stores/TypingStore.d.ts
new file mode 100644
index 00000000..0ebe994f
--- /dev/null
+++ b/packages/discord-types/src/stores/TypingStore.d.ts
@@ -0,0 +1,9 @@
+import { FluxStore } from "..";
+
+export class TypingStore extends FluxStore {
+ /**
+ * returns a map of user ids to timeout ids
+ */
+ getTypingUsers(channelId: string): Record;
+ isTyping(channelId: string, userId: string): boolean;
+}
diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts
index 23045832..16a329c5 100644
--- a/packages/discord-types/src/stores/index.d.ts
+++ b/packages/discord-types/src/stores/index.d.ts
@@ -1,4 +1,5 @@
// please keep in alphabetical order
+export * from "./AuthenticationStore";
export * from "./ChannelStore";
export * from "./DraftStore";
export * from "./EmojiStore";
@@ -11,6 +12,7 @@ export * from "./RelationshipStore";
export * from "./SelectedChannelStore";
export * from "./SelectedGuildStore";
export * from "./ThemeStore";
+export * from "./TypingStore";
export * from "./UserProfileStore";
export * from "./UserStore";
export * from "./WindowStore";
diff --git a/src/plugins/typingIndicator/index.tsx b/src/plugins/typingIndicator/index.tsx
index 0a0cd53a..47a0fbeb 100644
--- a/src/plugins/typingIndicator/index.tsx
+++ b/src/plugins/typingIndicator/index.tsx
@@ -24,13 +24,12 @@ import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
-import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, UserSummaryItem, useStateFromStores } from "@webpack/common";
+import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, TypingStore, UserStore, UserSummaryItem, useStateFromStores } from "@webpack/common";
import { buildSeveralUsers } from "../typingTweaks";
const ThreeDots = findComponentByCodeLazy(".dots,", "dotRadius:");
-const TypingStore = findStoreLazy("TypingStore");
const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore");
const enum IndicatorMode {
@@ -46,7 +45,7 @@ function getDisplayName(guildId: string, userId: string) {
function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: string; }) {
const typingUsers: Record = useStateFromStores(
[TypingStore],
- () => ({ ...TypingStore.getTypingUsers(channelId) as Record }),
+ () => ({ ...TypingStore.getTypingUsers(channelId) }),
null,
(old, current) => {
const oldKeys = Object.keys(old);
@@ -90,7 +89,7 @@ function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: s
}
default: {
tooltipText = Settings.plugins.TypingTweaks.enabled
- ? buildSeveralUsers({ a: UserStore.getUser(a), b: UserStore.getUser(b), count: typingUsersArray.length - 2, guildId })
+ ? buildSeveralUsers({ users: [a, b].map(UserStore.getUser), count: typingUsersArray.length - 2, guildId })
: getIntlMessage("SEVERAL_USERS_TYPING");
break;
}
diff --git a/src/plugins/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx
index bcfea898..8369497c 100644
--- a/src/plugins/typingTweaks/index.tsx
+++ b/src/plugins/typingTweaks/index.tsx
@@ -20,9 +20,11 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { openUserProfile } from "@utils/discord";
+import { isNonNullish } from "@utils/guards";
+import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
-import { User } from "@vencord/discord-types";
-import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common";
+import { Channel, User } from "@vencord/discord-types";
+import { AuthenticationStore, Avatar, GuildMemberStore, React, RelationshipStore, TypingStore, UserStore, useStateFromStores } from "@webpack/common";
import { PropsWithChildren } from "react";
import managedStyle from "./style.css?managed";
@@ -45,24 +47,26 @@ const settings = definePluginSettings({
}
});
-export const buildSeveralUsers = ErrorBoundary.wrap(({ a, b, count, guildId }: { a: User, b: User, count: number; guildId: string; }) => {
+export const buildSeveralUsers = ErrorBoundary.wrap(function buildSeveralUsers({ users, count, guildId }: { users: User[], count: number; guildId: string; }) {
return (
<>
-
- {", "}
-
- {", "}
+ {users.slice(0, count).map(user => (
+
+
+ {", "}
+
+ ))}
and {count} others are typing...
>
);
}, { noop: true });
-interface Props {
+interface TypingUserProps {
user: User;
guildId: string;
}
-const TypingUser = ErrorBoundary.wrap(function ({ user, guildId }: Props) {
+const TypingUser = ErrorBoundary.wrap(function TypingUser({ user, guildId }: TypingUserProps) {
return (
0.{0,300}?"aria-atomic":!0,children:)\i/,
- replace: "$self.renderTypingUsers({ users: $1, guildId: arguments[0]?.channel?.guild_id, children: $& })"
+ match: /(?<="aria-atomic":!0,children:)\i/,
+ replace: "$self.renderTypingUsers({ users: arguments[0]?.typingUserObjects, guildId: arguments[0]?.channel?.guild_id, children: $& })"
},
{
- // Changes the indicator to keep the user object when creating the list of typing users
- match: /\.map\((\i)=>\i\.\i\.getName\(\i(?:\.guild_id)?,\i\.id,\1\)\)/,
- replace: ""
+ match: /(?<=function \i\(\i\)\{)(?=[^}]+?\{channel:\i,isThreadCreation:\i=!1\})/,
+ replace: "let typingUserObjects = $self.useTypingUsers(arguments[0]?.channel);"
+ },
+ {
+ // Get the typing users as user objects instead of names
+ match: /typingUsers:(\i)\?\[\]:\i,/,
+ // check by typeof so if the variable is not defined due to other patch failing, it won't throw a ReferenceError
+ replace: "$&typingUserObjects: $1 || typeof typingUserObjects === 'undefined' ? [] : typingUserObjects,"
},
{
// Adds the alternative formatting for several users typing
- match: /(,{a:(\i),b:(\i),c:\i}\):\i\.length>3&&\(\i=)\i\.\i\.string\(\i\.\i#{intl::SEVERAL_USERS_TYPING}\)(?<=(\i)\.length.+?)/,
- replace: (_, rest, a, b, users) =>
- `${rest}$self.buildSeveralUsers({ a: ${a}, b: ${b}, count: ${users}.length - 2, guildId: arguments[0]?.channel?.guild_id })`,
+ // users.length > 3 && (component = intl(key))
+ match: /(&&\(\i=)\i\.\i\.format\(\i\.\i#{intl::SEVERAL_USERS_TYPING_STRONG},\{\}\)/,
+ replace: "$1$self.buildSeveralUsers({ users: arguments[0]?.typingUserObjects, count: arguments[0]?.typingUserObjects?.length - 2, guildId: arguments[0]?.channel?.guild_id })",
predicate: () => settings.store.alternativeFormatting
}
]
}
],
+ useTypingUsers(channel: Channel | undefined): User[] {
+ try {
+ if (!channel) {
+ throw new Error("No channel");
+ }
+
+ const typingUsers = useStateFromStores([TypingStore], () => TypingStore.getTypingUsers(channel.id));
+ const myId = useStateFromStores([AuthenticationStore], () => AuthenticationStore.getId());
+
+ return Object.keys(typingUsers)
+ .filter(id => id && id !== myId && !RelationshipStore.isBlockedOrIgnored(id))
+ .map(id => UserStore.getUser(id))
+ .filter(isNonNullish);
+ } catch (e) {
+ new Logger("TypingTweaks").error("Failed to get typing users:", e);
+ return [];
+ }
+ },
+
+
buildSeveralUsers,
renderTypingUsers: ErrorBoundary.wrap(({ guildId, users, children }: PropsWithChildren<{ guildId: string, users: User[]; }>) => {
@@ -140,7 +169,7 @@ export default definePlugin({
return ;
});
} catch (e) {
- console.error(e);
+ new Logger("TypingTweaks").error("Failed to render typing users:", e);
}
return children;
diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts
index ca867c70..221218c6 100644
--- a/src/webpack/common/stores.ts
+++ b/src/webpack/common/stores.ts
@@ -40,10 +40,12 @@ export let GuildStore: t.GuildStore;
export let GuildRoleStore: t.GuildRoleStore;
export let GuildMemberStore: t.GuildMemberStore;
export let UserStore: t.UserStore;
+export let AuthenticationStore: t.AuthenticationStore;
export let UserProfileStore: t.UserProfileStore;
export let SelectedChannelStore: t.SelectedChannelStore;
export let SelectedGuildStore: t.SelectedGuildStore;
export let ChannelStore: t.ChannelStore;
+export let TypingStore: t.TypingStore;
export let RelationshipStore: t.RelationshipStore;
export let EmojiStore: t.EmojiStore;
@@ -51,11 +53,13 @@ export let ThemeStore: t.ThemeStore;
export let WindowStore: t.WindowStore;
export let DraftStore: t.DraftStore;
+
/**
* @see jsdoc of {@link t.useStateFromStores}
*/
export const useStateFromStores: t.useStateFromStores = findByCodeLazy("useStateFromStores");
+waitForStore("AuthenticationStore", s => AuthenticationStore = s);
waitForStore("DraftStore", s => DraftStore = s);
waitForStore("UserStore", s => UserStore = s);
waitForStore("UserProfileStore", m => UserProfileStore = m);
@@ -73,6 +77,7 @@ waitForStore("GuildRoleStore", m => GuildRoleStore = m);
waitForStore("MessageStore", m => MessageStore = m);
waitForStore("WindowStore", m => WindowStore = m);
waitForStore("EmojiStore", m => EmojiStore = m);
+waitForStore("TypingStore", m => TypingStore = m);
waitForStore("ThemeStore", m => {
ThemeStore = m;
// Importing this directly can easily cause circular imports. For this reason, use a non import access here.
From fe2ed0776f983ab79c6079c0a660d6091b57ba55 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Wed, 6 Aug 2025 21:46:02 -0300
Subject: [PATCH 041/116] ShowHiddenChannels: Fix showing allowed roles
---
src/plugins/showHiddenChannels/index.tsx | 26 +++++++++++++-----------
1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx
index 78c81d00..b8075d8c 100644
--- a/src/plugins/showHiddenChannels/index.tsx
+++ b/src/plugins/showHiddenChannels/index.tsx
@@ -23,7 +23,6 @@ import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
-import { canonicalizeMatch } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types";
import type { Channel, Role } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack";
@@ -302,15 +301,18 @@ export default definePlugin({
},
{
// Patch the header to only return allowed users and roles if it's a hidden channel or locked channel (Like when it's used on the HiddenChannelLockScreen)
- match: /MANAGE_ROLES.{0,90}?return(?=\(.+?(\(0,\i\.jsxs\)\("div",{className:\i\.members.+?guildId:(\i)\.guild_id.+?roleColor.+?\]}\)))/,
- replace: (m, component, channel) => {
- // Export the channel for the users allowed component patch
- component = component.replace(canonicalizeMatch(/(?<=users:\i)/), `,shcChannel:${channel}`);
- // Always render the component for multiple allowed users
- component = component.replace(canonicalizeMatch(/1!==\i\.length/), "true");
-
- return `${m} $self.isHiddenChannel(${channel},true)?${component}:`;
- }
+ match: /\.members.+?\]}\),(?<=channel:(\i).+?)/,
+ replace: (m, channel) => `${m}$self.isHiddenChannel(${channel},true)?null:`
+ },
+ {
+ // Export the channel for the users allowed component patch
+ match: /maxUsers:\i,users:\i(?<=channel:(\i).+?)/,
+ replace: (m, channel) => `${m},shcChannel:${channel}`
+ },
+ {
+ // Always render the component for multiple allowed users
+ match: /1!==\i\.length(?=\|\|)/,
+ replace: "true"
}
]
},
@@ -319,8 +321,8 @@ export default definePlugin({
replacement: [
{
// Create a variable for the channel prop
- match: /(function \i\(\i\)\{)([^}]+?hideOverflowCount)/,
- replace: "$1let {shcChannel}=arguments[0];$2"
+ match: /let{users:\i,maxUsers:\i,/,
+ replace: "let{shcChannel}=arguments[0];$&"
},
{
// Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen
From 164fd43cc441413a6ecf479b510d837201898c1e Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Thu, 7 Aug 2025 02:28:34 +0200
Subject: [PATCH 042/116] Fix IgnoreActivities
---
src/plugins/ignoreActivities/index.tsx | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx
index b0889186..94334f5a 100644
--- a/src/plugins/ignoreActivities/index.tsx
+++ b/src/plugins/ignoreActivities/index.tsx
@@ -256,8 +256,10 @@ export default definePlugin({
{
find: "#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}",
replacement: {
- match: /#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}.+?}\(\),(?<={overlay:\i,.+?=(\i),.+?)(?=!(\i))/,
- replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`
+ // let { ... nowPlaying: a = !1 ...
+ // let { overlay: b ... } = Props
+ match: /#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}.+?}\(\),(?<=nowPlaying:(\i)=!1,.+?overlay:\i,[^}]+?\}=(\i).+?)/,
+ replace: (m, nowPlaying, props) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`
}
},
// Activities from the apps launcher in the bottom right of the chat bar
From 98efe13b97b43d1eb09c9d3e3c983d50dc410f0c Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Thu, 7 Aug 2025 04:24:34 +0200
Subject: [PATCH 043/116] ShowHiddenThings: fix crash when viewing Mod View
---
src/plugins/showHiddenThings/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/showHiddenThings/index.ts b/src/plugins/showHiddenThings/index.ts
index c39dccc4..6d2ca713 100644
--- a/src/plugins/showHiddenThings/index.ts
+++ b/src/plugins/showHiddenThings/index.ts
@@ -73,7 +73,7 @@ export default definePlugin({
predicate: () => settings.store.showModView,
replacement: {
match: /(?<=\.highestRole\),)role:\i(?<=\[\i\.roles,\i\.highestRoleId,(\i)\].+)/,
- replace: "role:$self.getHighestRole(arguments[0],$2)",
+ replace: "role:$self.getHighestRole(arguments[0],$1)",
}
},
// allows you to open mod view on yourself
From c7e799e9352637bad2086f64553e08b7ff6f460c Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Thu, 7 Aug 2025 09:29:33 -0300
Subject: [PATCH 044/116] ShowHiddenChannels: Fix incorrect allowed users and
roles component
---
src/plugins/showHiddenChannels/index.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx
index b8075d8c..33f44657 100644
--- a/src/plugins/showHiddenChannels/index.tsx
+++ b/src/plugins/showHiddenChannels/index.tsx
@@ -301,8 +301,8 @@ export default definePlugin({
},
{
// Patch the header to only return allowed users and roles if it's a hidden channel or locked channel (Like when it's used on the HiddenChannelLockScreen)
- match: /\.members.+?\]}\),(?<=channel:(\i).+?)/,
- replace: (m, channel) => `${m}$self.isHiddenChannel(${channel},true)?null:`
+ match: /return\(0,\i\.jsxs?\)\(\i\.\i,{channelId:(\i)\.id(?=.+?(\(0,\i\.jsxs?\)\("div",{className:\i\.members.+?\]}\)),)/,
+ replace: (m, channel, allowedUsersAndRolesComponent) => `if($self.isHiddenChannel(${channel},true)){return${allowedUsersAndRolesComponent};}${m}`
},
{
// Export the channel for the users allowed component patch
From 7e028267f1ea6ad64dc8e2d62c171c28330ecdb9 Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Thu, 7 Aug 2025 15:04:59 -0400
Subject: [PATCH 045/116] SpotifyShareCommands: correctly handle local tracks
(#3592)
---
src/plugins/spotifyShareCommands/index.ts | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/plugins/spotifyShareCommands/index.ts b/src/plugins/spotifyShareCommands/index.ts
index e11864ae..d00d4a2c 100644
--- a/src/plugins/spotifyShareCommands/index.ts
+++ b/src/plugins/spotifyShareCommands/index.ts
@@ -46,7 +46,7 @@ interface Artist {
}
interface Track {
- id: string;
+ id: string | null;
album: Album;
artists: Artist[];
duration: number;
@@ -71,6 +71,13 @@ function makeCommand(name: string, formatUrl: (track: Track) => string): Command
});
}
+ // local tracks have an id of null
+ if (track.id == null) {
+ return sendBotMessage(channel.id, {
+ content: "Failed to find the track on spotify."
+ });
+ }
+
const data = formatUrl(track);
const message = findOption(options, "message");
From 4403aee3c1436b88d14b245e9d8e29d6b527eb84 Mon Sep 17 00:00:00 2001
From: byeoon <47872200+byeoon@users.noreply.github.com>
Date: Thu, 7 Aug 2025 16:24:13 -0400
Subject: [PATCH 046/116] New plugin CopyStickerLinks: adds Copy/Open Link
option to stickers (#3191)
Co-authored-by: V
---
packages/discord-types/enums/index.ts | 1 +
packages/discord-types/enums/messages.ts | 13 +++
.../src/common/messages/Message.d.ts | 3 +-
.../src/common/messages/Sticker.d.ts | 35 +++++++
.../src/common/messages/index.d.ts | 1 +
.../src/stores/StickersStore.d.ts | 15 +++
packages/discord-types/src/stores/index.d.ts | 1 +
src/plugins/copyStickerLinks/README.MD | 5 +
src/plugins/copyStickerLinks/index.tsx | 94 +++++++++++++++++++
src/plugins/expressionCloner/index.tsx | 16 +---
src/plugins/fakeNitro/index.tsx | 68 +++-----------
src/utils/constants.ts | 2 +-
src/webpack/common/stores.ts | 3 +-
13 files changed, 188 insertions(+), 69 deletions(-)
create mode 100644 packages/discord-types/enums/messages.ts
create mode 100644 packages/discord-types/src/common/messages/Sticker.d.ts
create mode 100644 packages/discord-types/src/stores/StickersStore.d.ts
create mode 100644 src/plugins/copyStickerLinks/README.MD
create mode 100644 src/plugins/copyStickerLinks/index.tsx
diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts
index 62074d46..e2227d09 100644
--- a/packages/discord-types/enums/index.ts
+++ b/packages/discord-types/enums/index.ts
@@ -1 +1,2 @@
export * from "./commands";
+export * from "./messages";
diff --git a/packages/discord-types/enums/messages.ts b/packages/discord-types/enums/messages.ts
new file mode 100644
index 00000000..9c0025b7
--- /dev/null
+++ b/packages/discord-types/enums/messages.ts
@@ -0,0 +1,13 @@
+export const enum StickerType {
+ /** an official sticker in a pack */
+ STANDARD = 1,
+ /** a sticker uploaded to a guild for the guild's members */
+ GUILD = 2
+}
+
+export const enum StickerFormatType {
+ PNG = 1,
+ APNG = 2,
+ LOTTIE = 3,
+ GIF = 4
+}
diff --git a/packages/discord-types/src/common/messages/Message.d.ts b/packages/discord-types/src/common/messages/Message.d.ts
index 41de8816..e3586255 100644
--- a/packages/discord-types/src/common/messages/Message.d.ts
+++ b/packages/discord-types/src/common/messages/Message.d.ts
@@ -2,6 +2,7 @@ import { CommandOption } from './Commands';
import { User, UserJSON } from '../User';
import { Embed, EmbedJSON } from './Embed';
import { DiscordRecord } from "../Record";
+import { StickerFormatType } from "../../../enums";
/**
* TODO: looks like discord has moved over to Date instead of Moment;
@@ -92,7 +93,7 @@ export class Message extends DiscordRecord {
reactions: MessageReaction[];
state: string;
stickerItems: {
- format_type: number;
+ format_type: StickerFormatType;
id: string;
name: string;
}[];
diff --git a/packages/discord-types/src/common/messages/Sticker.d.ts b/packages/discord-types/src/common/messages/Sticker.d.ts
new file mode 100644
index 00000000..744b0623
--- /dev/null
+++ b/packages/discord-types/src/common/messages/Sticker.d.ts
@@ -0,0 +1,35 @@
+import { StickerFormatType, StickerType } from "../../../enums";
+
+interface BaseSticker {
+ asset: string;
+ available: boolean;
+ description: string;
+ format_type: StickerFormatType;
+ id: string;
+ name: string;
+ sort_value?: number;
+ /** a comma separated string */
+ tags: string;
+}
+
+export interface PackSticker extends BaseSticker {
+ pack_id: string;
+ type: StickerType.STANDARD;
+}
+
+export interface GuildSticker extends BaseSticker {
+ guild_id: string;
+ type: StickerType.GUILD;
+}
+
+export type Sticker = PackSticker | GuildSticker;
+
+export interface PremiumStickerPack {
+ banner_asset_id?: string;
+ cover_sticker_id?: string;
+ description: string;
+ id: string;
+ name: string;
+ sku_id: string;
+ stickers: PackSticker[];
+}
diff --git a/packages/discord-types/src/common/messages/index.d.ts b/packages/discord-types/src/common/messages/index.d.ts
index 245e971e..23987546 100644
--- a/packages/discord-types/src/common/messages/index.d.ts
+++ b/packages/discord-types/src/common/messages/index.d.ts
@@ -2,3 +2,4 @@ export * from "./Commands";
export * from "./Message";
export * from "./Embed";
export * from "./Emoji";
+export * from "./Sticker";
diff --git a/packages/discord-types/src/stores/StickersStore.d.ts b/packages/discord-types/src/stores/StickersStore.d.ts
new file mode 100644
index 00000000..fdfb012c
--- /dev/null
+++ b/packages/discord-types/src/stores/StickersStore.d.ts
@@ -0,0 +1,15 @@
+import { FluxStore, GuildSticker, PremiumStickerPack, Sticker } from "..";
+
+export type StickerGuildMap = Map;
+
+export class StickersStore extends FluxStore {
+ getAllGuildStickers(): StickerGuildMap;
+ getRawStickersByGuild(): StickerGuildMap;
+ getPremiumPacks(): PremiumStickerPack[];
+
+ getStickerById(id: string): Sticker | undefined;
+ getStickerPack(id: string): PremiumStickerPack | undefined;
+ getStickersByGuildId(guildId: string): Sticker[] | undefined;
+
+ isPremiumPack(id: string): boolean;
+}
diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts
index 16a329c5..7a435ff5 100644
--- a/packages/discord-types/src/stores/index.d.ts
+++ b/packages/discord-types/src/stores/index.d.ts
@@ -11,6 +11,7 @@ export * from "./MessageStore";
export * from "./RelationshipStore";
export * from "./SelectedChannelStore";
export * from "./SelectedGuildStore";
+export * from "./StickersStore";
export * from "./ThemeStore";
export * from "./TypingStore";
export * from "./UserProfileStore";
diff --git a/src/plugins/copyStickerLinks/README.MD b/src/plugins/copyStickerLinks/README.MD
new file mode 100644
index 00000000..40e63232
--- /dev/null
+++ b/src/plugins/copyStickerLinks/README.MD
@@ -0,0 +1,5 @@
+# CopyStickerLinks
+
+Adds "Copy Link" and "Open Link" options to the context menu of stickers!
+
+
diff --git a/src/plugins/copyStickerLinks/index.tsx b/src/plugins/copyStickerLinks/index.tsx
new file mode 100644
index 00000000..eaccc9b7
--- /dev/null
+++ b/src/plugins/copyStickerLinks/index.tsx
@@ -0,0 +1,94 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2025 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
+import { Devs } from "@utils/constants";
+import { copyWithToast } from "@utils/misc";
+import definePlugin from "@utils/types";
+import { Message, Sticker } from "@vencord/discord-types";
+import { Menu, React, StickersStore } from "@webpack/common";
+import ExpressionClonerPlugin from "plugins/expressionCloner";
+
+const StickerExt = [, "png", "png", "json", "gif"] as const;
+
+type PartialSticker = Pick;
+
+function getUrl(data: PartialSticker): string {
+ if (data.format_type === 4)
+ return `https:${window.GLOBAL_ENV.MEDIA_PROXY_ENDPOINT}/stickers/${data.id}.gif?size=512&lossless=true`;
+
+ return `https://${window.GLOBAL_ENV.CDN_HOST}/stickers/${data.id}.${StickerExt[data.format_type]}?size=512&lossless=true`;
+}
+
+function buildMenuItem(sticker: PartialSticker, addBottomSeparator: boolean) {
+ return (
+ <>
+
+ copyWithToast(getUrl(sticker), "Link copied!")}
+ />
+
+ VencordNative.native.openExternal(getUrl(sticker))}
+ />
+
+ {addBottomSeparator && }
+ >
+ );
+}
+
+const messageContextMenuPatch: NavContextMenuPatchCallback = (
+ children,
+ { favoriteableId, favoriteableType, message }: { favoriteableId: string; favoriteableType: string; message: Message; }
+) => {
+ if (!favoriteableId || favoriteableType !== "sticker") return;
+
+ const sticker = message.stickerItems.find(s => s.id === favoriteableId);
+ if (!sticker?.format_type) return;
+
+ const idx = children.findIndex(c => Array.isArray(c) && findGroupChildrenByChildId("vc-copy-sticker-url", c) != null);
+
+ children.splice(idx, 0, buildMenuItem(sticker, idx !== -1));
+};
+
+const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { target: HTMLElement; }) => {
+ const id = props?.target?.dataset?.id;
+ if (!id) return;
+ if (props.target.className?.includes("lottieCanvas")) return;
+
+ const sticker = StickersStore.getStickerById(id);
+ if (sticker) {
+ children.push(buildMenuItem(sticker, Vencord.Plugins.isPluginEnabled(ExpressionClonerPlugin.name)));
+ }
+};
+
+export default definePlugin({
+ name: "CopyStickerLinks",
+ description: "Adds the ability to copy & open Sticker links",
+ authors: [Devs.Ven, Devs.Byeoon],
+ contextMenus: {
+ "message": messageContextMenuPatch,
+ "expression-picker": expressionPickerPatch
+ }
+});
diff --git a/src/plugins/expressionCloner/index.tsx b/src/plugins/expressionCloner/index.tsx
index 60a95336..45022e60 100644
--- a/src/plugins/expressionCloner/index.tsx
+++ b/src/plugins/expressionCloner/index.tsx
@@ -25,25 +25,17 @@ import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
import definePlugin from "@utils/types";
-import { Guild } from "@vencord/discord-types";
-import { findByCodeLazy, findStoreLazy } from "@webpack";
-import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, IconUtils, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
+import { Guild, GuildSticker } from "@vencord/discord-types";
+import { findByCodeLazy } from "@webpack";
+import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, IconUtils, Menu, PermissionsBits, PermissionStore, React, RestAPI, StickersStore, Toasts, Tooltip, UserStore } from "@webpack/common";
import { Promisable } from "type-fest";
-const StickersStore = findStoreLazy("StickersStore");
const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START");
const getGuildMaxEmojiSlots = findByCodeLazy(".additionalEmojiSlots") as (guild: Guild) => number;
-interface Sticker {
+interface Sticker extends GuildSticker {
t: "Sticker";
- description: string;
- format_type: number;
- guild_id: string;
- id: string;
- name: string;
- tags: string;
- type: number;
}
interface Emoji {
diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx
index f697c3fd..981a7da8 100644
--- a/src/plugins/fakeNitro/index.tsx
+++ b/src/plugins/fakeNitro/index.tsx
@@ -24,17 +24,12 @@ import { getCurrentGuild, getEmojiURL } from "@utils/discord";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType, Patch } from "@utils/types";
import type { Emoji, Message } from "@vencord/discord-types";
+import { StickerFormatType } from "@vencord/discord-types/enums";
import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
-import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
+import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, lodash, Parser, PermissionsBits, PermissionStore, StickersStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import { applyPalette, GIFEncoder, quantize } from "gifenc";
import type { ReactElement, ReactNode } from "react";
-const StickerStore = findStoreLazy("StickersStore") as {
- getPremiumPacks(): StickerPack[];
- getAllGuildStickers(): Map;
- getStickerById(id: string): Sticker | undefined;
-};
-
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
const BINARY_READ_OPTIONS = findByPropsLazy("readerFactory");
@@ -70,41 +65,6 @@ const enum EmojiIntentions {
const IS_BYPASSEABLE_INTENTION = `[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`;
-const enum StickerType {
- PNG = 1,
- APNG = 2,
- LOTTIE = 3,
- // don't think you can even have gif stickers but the docs have it
- GIF = 4
-}
-
-interface BaseSticker {
- available: boolean;
- description: string;
- format_type: number;
- id: string;
- name: string;
- tags: string;
- type: number;
-}
-interface GuildSticker extends BaseSticker {
- guild_id: string;
-}
-interface DiscordSticker extends BaseSticker {
- pack_id: string;
-}
-type Sticker = GuildSticker | DiscordSticker;
-
-interface StickerPack {
- id: string;
- name: string;
- sku_id: string;
- description: string;
- cover_sticker_id: string;
- banner_asset_id: string;
- stickers: Sticker[];
-}
-
const enum FakeNoticeType {
Sticker,
Emoji
@@ -549,8 +509,8 @@ export default definePlugin({
const gifMatch = child.props.href.match(fakeNitroGifStickerRegex);
if (gifMatch) {
- // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickerStore contains the id of the fake sticker
- if (StickerStore.getStickerById(gifMatch[1])) return null;
+ // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickersStore contains the id of the fake sticker
+ if (StickersStore.getStickerById(gifMatch[1])) return null;
}
}
@@ -638,7 +598,7 @@ export default definePlugin({
url = new URL(item);
} catch { }
- const stickerName = StickerStore.getStickerById(imgMatch[1])?.name ?? url?.searchParams.get("name") ?? "FakeNitroSticker";
+ const stickerName = StickersStore.getStickerById(imgMatch[1])?.name ?? url?.searchParams.get("name") ?? "FakeNitroSticker";
stickers.push({
format_type: 1,
id: imgMatch[1],
@@ -651,9 +611,9 @@ export default definePlugin({
const gifMatch = item.match(fakeNitroGifStickerRegex);
if (gifMatch) {
- if (!StickerStore.getStickerById(gifMatch[1])) continue;
+ if (!StickersStore.getStickerById(gifMatch[1])) continue;
- const stickerName = StickerStore.getStickerById(gifMatch[1])?.name ?? "FakeNitroSticker";
+ const stickerName = StickersStore.getStickerById(gifMatch[1])?.name ?? "FakeNitroSticker";
stickers.push({
format_type: 2,
id: gifMatch[1],
@@ -689,8 +649,8 @@ export default definePlugin({
const gifMatch = url.match(fakeNitroGifStickerRegex);
if (gifMatch) {
- // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickerStore contains the id of the fake sticker
- if (StickerStore.getStickerById(gifMatch[1])) return true;
+ // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickersStore contains the id of the fake sticker
+ if (StickersStore.getStickerById(gifMatch[1])) return true;
}
}
@@ -710,8 +670,8 @@ export default definePlugin({
const match = attachment.url.match(fakeNitroGifStickerRegex);
if (match) {
- // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickerStore contains the id of the fake sticker
- if (StickerStore.getStickerById(match[1])) return false;
+ // There is no way to differentiate a regular gif attachment from a fake nitro animated sticker, so we check if the StickersStore contains the id of the fake sticker
+ if (StickersStore.getStickerById(match[1])) return false;
}
return true;
@@ -866,7 +826,7 @@ export default definePlugin({
if (!s.enableStickerBypass)
break stickerBypass;
- const sticker = StickerStore.getStickerById(extra.stickers?.[0]!);
+ const sticker = StickersStore.getStickerById(extra.stickers?.[0]!);
if (!sticker)
break stickerBypass;
@@ -883,11 +843,11 @@ export default definePlugin({
// but will give us a normal non animated png for no reason
// TODO: Remove this workaround when it's not needed anymore
let link = this.getStickerLink(sticker.id);
- if (sticker.format_type === StickerType.GIF && link.includes(".png")) {
+ if (sticker.format_type === StickerFormatType.GIF && link.includes(".png")) {
link = link.replace(".png", ".gif");
}
- if (sticker.format_type === StickerType.APNG) {
+ if (sticker.format_type === StickerFormatType.APNG) {
if (!hasAttachmentPerms(channelId)) {
Alerts.show({
title: "Hold on!",
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index eb19bf86..7c7488b6 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -482,7 +482,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "Sqaaakoi",
id: 259558259491340288n
},
- Byron: {
+ Byeoon: {
name: "byeoon",
id: 1167275288036655133n
},
diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts
index 221218c6..472abd4e 100644
--- a/src/webpack/common/stores.ts
+++ b/src/webpack/common/stores.ts
@@ -49,11 +49,11 @@ export let TypingStore: t.TypingStore;
export let RelationshipStore: t.RelationshipStore;
export let EmojiStore: t.EmojiStore;
+export let StickersStore: t.StickersStore;
export let ThemeStore: t.ThemeStore;
export let WindowStore: t.WindowStore;
export let DraftStore: t.DraftStore;
-
/**
* @see jsdoc of {@link t.useStateFromStores}
*/
@@ -77,6 +77,7 @@ waitForStore("GuildRoleStore", m => GuildRoleStore = m);
waitForStore("MessageStore", m => MessageStore = m);
waitForStore("WindowStore", m => WindowStore = m);
waitForStore("EmojiStore", m => EmojiStore = m);
+waitForStore("StickersStore", m => StickersStore = m);
waitForStore("TypingStore", m => TypingStore = m);
waitForStore("ThemeStore", m => {
ThemeStore = m;
From 0c89314d497bd49a230f72f21763e3c0a1fa7e82 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Thu, 7 Aug 2025 23:38:27 +0200
Subject: [PATCH 047/116] PermissionFreeWill: don't break permission toggles
---
src/plugins/permissionFreeWill/index.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/plugins/permissionFreeWill/index.ts b/src/plugins/permissionFreeWill/index.ts
index 767d4f98..781c64bc 100644
--- a/src/plugins/permissionFreeWill/index.ts
+++ b/src/plugins/permissionFreeWill/index.ts
@@ -46,8 +46,9 @@ export default definePlugin({
find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}",
replacement: [
{
+ // replace export getters with functions that always resolve to true
match: /{(?:\i:\(\)=>\i,?){2}}/,
- replace: m => m.replaceAll(canonicalizeMatch(/\(\)=>\i/g), "()=>Promise.resolve(true)")
+ replace: m => m.replaceAll(canonicalizeMatch(/\(\)=>\i/g), "()=>()=>Promise.resolve(true)")
}
],
predicate: () => settings.store.onboarding
From d9e2732a8defcda19d9142b28524bf3ec6dec3d8 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Mon, 11 Aug 2025 15:35:29 -0300
Subject: [PATCH 048/116] Fix MessageEventsAPI broken patch
---
src/plugins/_api/messageEvents.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/plugins/_api/messageEvents.ts b/src/plugins/_api/messageEvents.ts
index e5192763..3030db03 100644
--- a/src/plugins/_api/messageEvents.ts
+++ b/src/plugins/_api/messageEvents.ts
@@ -35,10 +35,10 @@ export default definePlugin({
}
},
{
- find: ".handleSendMessage,onResize",
+ find: ".handleSendMessage,onResize:",
replacement: {
- // https://regex101.com/r/hBlXpl/1
- match: /let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);(?<=\)\(({.+?})\)\.then.+?)/,
+ // https://regex101.com/r/7iswuk/1
+ match: /let (\i)=\i\.\i\.parse\((\i),.+?\.getSendMessageOptions\(\{.+?\}\);(?=.+?(\i)\.flags=)(?<=\)\(({.+?})\)\.then.+?)/,
replace: (m, parsedMessage, channel, replyOptions, extra) => m +
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
"return{shouldClear:false,shouldRefocus:true};"
From 27b2e97e3fe8bbac8dffbddcff62743f73457d9d Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Tue, 12 Aug 2025 02:55:01 +0200
Subject: [PATCH 049/116] ReviewDB: fix input
---
src/plugins/reviewDB/components/ReviewsView.tsx | 6 +++---
src/plugins/reviewDB/style.css | 3 ++-
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx
index 7a7d8d02..42ab0938 100644
--- a/src/plugins/reviewDB/components/ReviewsView.tsx
+++ b/src/plugins/reviewDB/components/ReviewsView.tsx
@@ -29,8 +29,8 @@ import ReviewComponent from "./ReviewComponent";
const Transforms = findByPropsLazy("insertNodes", "textToText");
const Editor = findByPropsLazy("start", "end", "toSlateRange");
-const ChatInputTypes = findByPropsLazy("FORM");
-const InputComponent = findComponentByCodeLazy("disableThemedBackground", "CHANNEL_TEXT_AREA");
+const ChatInputTypes = findByPropsLazy("FORM", "USER_PROFILE");
+const InputComponent = findComponentByCodeLazy("editorClassName", "CHANNEL_TEXT_AREA");
const createChannelRecordFromServer = findByCodeLazy(".GUILD_TEXT])", "fromServer)");
interface UserProps {
@@ -127,7 +127,7 @@ export function ReviewsInputComponent(
) {
const { token } = Auth;
const editorRef = useRef(null);
- const inputType = ChatInputTypes.FORM;
+ const inputType = ChatInputTypes.USER_PROFILE_REPLY;
inputType.disableAutoFocus = true;
const channel = createChannelRecordFromServer({ id: "0", type: 1 });
diff --git a/src/plugins/reviewDB/style.css b/src/plugins/reviewDB/style.css
index 47ccd091..bf2fed30 100644
--- a/src/plugins/reviewDB/style.css
+++ b/src/plugins/reviewDB/style.css
@@ -8,6 +8,7 @@
}
.vc-rdb-input {
+ padding-left: 12px;
margin-top: 6px;
margin-bottom: 12px;
resize: none;
@@ -132,4 +133,4 @@
.vc-rdb-block-modal-unblock {
cursor: pointer;
-}
+}
\ No newline at end of file
From 72329f901cae0e45feb24d9ff38eb2eb8d3756d0 Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Mon, 11 Aug 2025 21:28:19 -0400
Subject: [PATCH 050/116] AccountPanelServerProfile: fix right click menu
(#3600)
Co-authored-by: Vendicated
---
.../accountPanelServerProfile/index.tsx | 24 +++++++------------
1 file changed, 8 insertions(+), 16 deletions(-)
diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx
index ce9f6b51..57144ec0 100644
--- a/src/plugins/accountPanelServerProfile/index.tsx
+++ b/src/plugins/accountPanelServerProfile/index.tsx
@@ -11,7 +11,7 @@ import { getCurrentChannel } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack";
-import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common";
+import { ContextMenuApi, Menu } from "@webpack/common";
interface UserProfileProps {
popoutProps: Record;
@@ -22,7 +22,7 @@ interface UserProfileProps {
const UserProfile = findComponentByCodeLazy(".POPOUT,user");
let openAlternatePopout = false;
-let accountPanelRef: React.RefObject | null> = { current: null };
+let accountPanelRef: React.RefObject = { current: null };
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
@@ -38,8 +38,7 @@ const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
disabled={getCurrentChannel()?.getGuildId() == null}
action={e => {
openAlternatePopout = true;
- accountPanelRef.current?.props.onMouseDown();
- accountPanelRef.current?.props.onClick(e);
+ accountPanelRef.current?.click();
}}
/>
){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/,
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})`
@@ -84,8 +79,8 @@ export default definePlugin({
replace: "$&$self.onPopoutClose();"
},
{
- match: /(?<=#{intl::SET_STATUS}\),)/,
- replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
+ match: /#{intl::SET_STATUS}\)(?<=innerRef:(\i),style:.+?)/,
+ replace: "$&,onContextMenu:($self.grabRef($1),$self.openAccountPanelContextMenu)"
}
]
}
@@ -95,12 +90,9 @@ export default definePlugin({
return accountPanelRef;
},
- useAccountPanelRef() {
- useEffect(() => () => {
- accountPanelRef.current = null;
- }, []);
-
- return (accountPanelRef = useRef(null));
+ grabRef(ref: React.RefObject) {
+ accountPanelRef = ref;
+ return ref;
},
openAccountPanelContextMenu(event: React.UIEvent) {
From aad88fe9cd5cafd34691e487f39acd4d215d74ed Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Wed, 13 Aug 2025 12:55:24 +0200
Subject: [PATCH 051/116] fix plugins sending messages
---
src/api/MessageEvents.ts | 10 +++++-----
src/utils/discord.tsx | 8 ++++----
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/api/MessageEvents.ts b/src/api/MessageEvents.ts
index 8b1d9e78..6e752c90 100644
--- a/src/api/MessageEvents.ts
+++ b/src/api/MessageEvents.ts
@@ -62,7 +62,7 @@ export interface MessageReplyOptions {
};
}
-export interface MessageExtra {
+export interface MessageOptions {
stickers?: string[];
uploads?: Upload[];
replyOptions: MessageReplyOptions;
@@ -72,17 +72,17 @@ export interface MessageExtra {
openWarningPopout: (props: any) => any;
}
-export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable;
+export type MessageSendListener = (channelId: string, messageObj: MessageObject, options: MessageOptions) => Promisable;
export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable;
const sendListeners = new Set();
const editListeners = new Set();
-export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
- extra.replyOptions = replyOptions;
+export async function _handlePreSend(channelId: string, messageObj: MessageObject, options: MessageOptions, replyOptions: MessageReplyOptions) {
+ options.replyOptions = replyOptions;
for (const listener of sendListeners) {
try {
- const result = await listener(channelId, messageObj, extra);
+ const result = await listener(channelId, messageObj, options);
if (result?.cancel) {
return true;
}
diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx
index d7a38ebb..f236943b 100644
--- a/src/utils/discord.tsx
+++ b/src/utils/discord.tsx
@@ -110,7 +110,7 @@ export function insertTextIntoChatInputBox(text: string) {
});
}
-interface MessageExtra {
+interface MessageOptions {
messageReference: Message["messageReference"];
allowedMentions: {
parse: string[];
@@ -122,8 +122,8 @@ interface MessageExtra {
export function sendMessage(
channelId: string,
data: Partial,
- waitForChannelReady?: boolean,
- extra?: Partial
+ waitForChannelReady = true,
+ options: Partial = {}
) {
const messageData = {
content: "",
@@ -133,7 +133,7 @@ export function sendMessage(
...data
};
- return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, extra);
+ return MessageActions.sendMessage(channelId, messageData, waitForChannelReady, options);
}
/**
From f356f647ff90d1abed69dc47c05e641a94dde7a5 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Wed, 13 Aug 2025 12:58:29 +0200
Subject: [PATCH 052/116] bump to v1.12.10
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 5438d37f..498b8919 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
- "version": "1.12.9",
+ "version": "1.12.10",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
From 204f916b2a1a29198331c1d862998710b7681412 Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Thu, 14 Aug 2025 15:42:58 -0400
Subject: [PATCH 053/116] fix Settings UI and various plugins (#3608)
Co-authored-by: Vendicated
---
packages/discord-types/src/components.d.ts | 7 +-----
src/plugins/betterSettings/index.tsx | 4 +--
src/plugins/consoleJanitor/index.tsx | 2 +-
src/plugins/decor/settings.tsx | 2 +-
.../decor/ui/modals/ChangeDecorationModal.tsx | 2 +-
.../decor/ui/modals/CreateDecorationModal.tsx | 4 +--
src/plugins/fakeNitro/index.tsx | 2 +-
src/plugins/ignoreActivities/index.tsx | 4 +--
src/plugins/showHiddenChannels/index.tsx | 2 +-
src/webpack/common/FormText.tsx | 25 +++++++++++++++++++
src/webpack/common/components.ts | 2 +-
src/webpack/common/utils.ts | 2 +-
12 files changed, 39 insertions(+), 19 deletions(-)
create mode 100644 src/webpack/common/FormText.tsx
diff --git a/packages/discord-types/src/components.d.ts b/packages/discord-types/src/components.d.ts
index 1df03735..f9e778d8 100644
--- a/packages/discord-types/src/components.d.ts
+++ b/packages/discord-types/src/components.d.ts
@@ -43,12 +43,7 @@ export type FormDivider = ComponentType<{
}>;
-export type FormText = ComponentType & TextProps> & { Types: FormTextTypes; };
+export type FormText = ComponentType;
export type Tooltip = ComponentType<{
text: ReactNode | ComponentType;
diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx
index cbf94c2b..8fecb262 100644
--- a/src/plugins/betterSettings/index.tsx
+++ b/src/plugins/betterSettings/index.tsx
@@ -123,8 +123,8 @@ export default definePlugin({
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: [
{
- match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
- replace: "$1$self.wrapMenu($2)"
+ match: /(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
+ replace: "$self.wrapMenu($1)"
},
{
match: /case \i\.\i\.DEVELOPER_OPTIONS:return \i;/,
diff --git a/src/plugins/consoleJanitor/index.tsx b/src/plugins/consoleJanitor/index.tsx
index 8a11cf01..4981ac2c 100644
--- a/src/plugins/consoleJanitor/index.tsx
+++ b/src/plugins/consoleJanitor/index.tsx
@@ -60,7 +60,7 @@ const AllowLevelSettings = ErrorBoundary.wrap(() => {
return (
Filter List
- Always allow loggers of these types
+ Always allow loggers of these types
{Object.keys(settings.store.allowLevel).map(key => (
diff --git a/src/plugins/decor/settings.tsx b/src/plugins/decor/settings.tsx
index 8e82bbae..361ecf89 100644
--- a/src/plugins/decor/settings.tsx
+++ b/src/plugins/decor/settings.tsx
@@ -24,7 +24,7 @@ export const settings = definePluginSettings({
return
-
+
You can also access Decor decorations from the {
diff --git a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx
index d9cfd50c..ed81eaf0 100644
--- a/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx
+++ b/src/plugins/decor/ui/modals/ChangeDecorationModal.tsx
@@ -83,7 +83,7 @@ function SectionHeader({ section }: SectionHeaderProps) {
/>}
{hasSubtitle &&
-
+
{section.subtitle}
}
diff --git a/src/plugins/decor/ui/modals/CreateDecorationModal.tsx b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx
index 4afb7464..d772ac4a 100644
--- a/src/plugins/decor/ui/modals/CreateDecorationModal.tsx
+++ b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx
@@ -97,7 +97,7 @@ function CreateDecorationModal(props: ModalProps) {
filters={[{ name: "Decoration file", extensions: ["png", "apng"] }]}
onFileSelect={setFile}
/>
-
+
File should be APNG or PNG.
@@ -107,7 +107,7 @@ function CreateDecorationModal(props: ModalProps) {
value={name}
onChange={setName}
/>
-
+
This name will be used when referring to this decoration.
diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx
index 981a7da8..8dcb2300 100644
--- a/src/plugins/fakeNitro/index.tsx
+++ b/src/plugins/fakeNitro/index.tsx
@@ -800,7 +800,7 @@ export default definePlugin({
however you do not have permissions to embed links in the current channel.
Are you sure you want to send this message? Your FakeNitro items will appear as a link only.
-
+
You can disable this notice in the plugin settings.
,
diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx
index 94334f5a..88906c38 100644
--- a/src/plugins/ignoreActivities/index.tsx
+++ b/src/plugins/ignoreActivities/index.tsx
@@ -82,7 +82,7 @@ function recalculateActivities() {
function ImportCustomRPCComponent() {
return (
- Import the application id of the CustomRPC plugin to the filter list
+ Import the application id of the CustomRPC plugin to the filter list
{
@@ -133,7 +133,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
return (
Filter List
- Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC
+ Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC
`&&!$self.isHiddenChannel(${channel})`
diff --git a/src/webpack/common/FormText.tsx b/src/webpack/common/FormText.tsx
new file mode 100644
index 00000000..8c371ed4
--- /dev/null
+++ b/src/webpack/common/FormText.tsx
@@ -0,0 +1,25 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import * as t from "@vencord/discord-types";
+
+import { Text } from "./components";
+
+// TODO: replace with our own component soon
+export const FormText: t.FormText = function FormText(props) {
+ const variant = props.variant || "text-sm/normal";
+ return (
+
+ {props.children}
+
+ );
+} as any;
+
+// @ts-expect-error
+FormText.Types = {};
diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts
index bf7c9dd1..d5e8dad1 100644
--- a/src/webpack/common/components.ts
+++ b/src/webpack/common/components.ts
@@ -20,11 +20,11 @@ import { LazyComponent } from "@utils/lazyReact";
import * as t from "@vencord/discord-types";
import { filters, mapMangledModuleLazy, waitFor } from "@webpack";
+import { FormText } from "./FormText";
import { waitForComponent } from "./internal";
const FormTitle = waitForComponent("FormTitle", filters.componentByCode('["defaultMargin".concat', '="h5"'));
-const FormText = waitForComponent("FormText", filters.componentByCode(".SELECTABLE),", ".DISABLED:"));
const FormSection = waitForComponent("FormSection", filters.componentByCode(".titleId)"));
const FormDivider = waitForComponent("FormDivider", filters.componentByCode(".divider,", ",style:", '"div"', /\.divider,\i\),style:/));
diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts
index 1d653dea..862f3843 100644
--- a/src/webpack/common/utils.ts
+++ b/src/webpack/common/utils.ts
@@ -203,6 +203,6 @@ export const DisplayProfileUtils: t.DisplayProfileUtils = mapMangledModuleLazy(/
export const DateUtils: t.DateUtils = mapMangledModuleLazy("millisecondsInUnit:", {
calendarFormat: filters.byCode("sameElse"),
dateFormat: filters.byCode('":'),
- isSameDay: filters.byCode("Math.abs(+"),
+ isSameDay: filters.byCode(/Math\.abs\(\i-\i\)/),
diffAsUnits: filters.byCode("days:0", "millisecondsInUnit")
});
From 93294673de00bbca7610da258128629f6296ee1c Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Thu, 14 Aug 2025 21:48:28 +0200
Subject: [PATCH 054/116] bump to v1.12.11
---
package.json | 2 +-
src/webpack/common/utils.ts | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index 498b8919..50219306 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
- "version": "1.12.10",
+ "version": "1.12.11",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts
index 862f3843..633eceea 100644
--- a/src/webpack/common/utils.ts
+++ b/src/webpack/common/utils.ts
@@ -203,6 +203,7 @@ export const DisplayProfileUtils: t.DisplayProfileUtils = mapMangledModuleLazy(/
export const DateUtils: t.DateUtils = mapMangledModuleLazy("millisecondsInUnit:", {
calendarFormat: filters.byCode("sameElse"),
dateFormat: filters.byCode('":'),
- isSameDay: filters.byCode(/Math\.abs\(\i-\i\)/),
+ // TODO: the +? are for compat with the old version - Remove them once no longer needed
+ isSameDay: filters.byCode(/Math\.abs\(\+?\i-\+?\i\)/),
diffAsUnits: filters.byCode("days:0", "millisecondsInUnit")
});
From 330c3cead76ca39787d0f199e3c566e90fb8f5a8 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sun, 17 Aug 2025 17:33:23 -0300
Subject: [PATCH 055/116] Fix plugins broken by latest Discord update (#3609)
---
src/plugins/_api/notices.ts | 4 ++--
src/plugins/copyStickerLinks/{README.MD => README.md} | 0
src/plugins/customIdle/index.ts | 2 +-
src/plugins/greetStickerPicker/index.tsx | 2 +-
src/plugins/showAllMessageButtons/index.ts | 6 +++---
src/plugins/showHiddenChannels/index.tsx | 2 +-
src/plugins/superReactionTweaks/index.ts | 4 ++--
src/plugins/vencordToolbox/index.tsx | 2 +-
src/plugins/webContextMenus.web/index.ts | 4 ----
9 files changed, 11 insertions(+), 15 deletions(-)
rename src/plugins/copyStickerLinks/{README.MD => README.md} (100%)
diff --git a/src/plugins/_api/notices.ts b/src/plugins/_api/notices.ts
index 89aa1a46..2384a10e 100644
--- a/src/plugins/_api/notices.ts
+++ b/src/plugins/_api/notices.ts
@@ -33,8 +33,8 @@ export default definePlugin({
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
},
{
- match: /(?<=function (\i)\(\i\){)return null!=(\i)(?=.+?NOTICE_DISMISS:\1)/,
- replace: (m, _, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}`
+ match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
+ replace: (m, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}`
}
]
}
diff --git a/src/plugins/copyStickerLinks/README.MD b/src/plugins/copyStickerLinks/README.md
similarity index 100%
rename from src/plugins/copyStickerLinks/README.MD
rename to src/plugins/copyStickerLinks/README.md
diff --git a/src/plugins/customIdle/index.ts b/src/plugins/customIdle/index.ts
index 9a0a47a2..78f4f9d0 100644
--- a/src/plugins/customIdle/index.ts
+++ b/src/plugins/customIdle/index.ts
@@ -40,7 +40,7 @@ export default definePlugin({
replace: "$self.getIdleTimeout()||"
},
{
- match: /Math\.min\((\i\.\i\.getSetting\(\)\*\i\.\i\.\i\.SECOND),\i\.\i\)/,
+ match: /Math\.min\((\i\*\i\.\i\.\i\.SECOND),\i\.\i\)/,
replace: "$1" // Decouple idle from afk (phone notifications will remain at user setting or 10 min maximum)
},
{
diff --git a/src/plugins/greetStickerPicker/index.tsx b/src/plugins/greetStickerPicker/index.tsx
index 4ea1f25d..0cc3e170 100644
--- a/src/plugins/greetStickerPicker/index.tsx
+++ b/src/plugins/greetStickerPicker/index.tsx
@@ -161,7 +161,7 @@ export default definePlugin({
{
find: "#{intl::WELCOME_CTA_LABEL}",
replacement: {
- match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).+?)/,
+ match: /className:\i\.welcomeCTA,(?<={channel:\i,message:\i}=(\i).+?)/,
replace: "$&onContextMenu:(vcEvent)=>$self.pickSticker(vcEvent, $1),"
}
}
diff --git a/src/plugins/showAllMessageButtons/index.ts b/src/plugins/showAllMessageButtons/index.ts
index 6ac17e5d..aa52d34b 100644
--- a/src/plugins/showAllMessageButtons/index.ts
+++ b/src/plugins/showAllMessageButtons/index.ts
@@ -28,9 +28,9 @@ export default definePlugin({
{
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
replacement: {
- // isExpanded = isShiftPressed && other conditions...
- match: /(?<=(\i)=)\i(?=&&.+?isExpanded:\1)/,
- replace: "true"
+ // isExpanded: isShiftPressed && other conditions...
+ match: /isExpanded:\i&&(.+?),/,
+ replace: "isExpanded:$1,"
}
}
]
diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx
index 1e5891ce..b4fcd713 100644
--- a/src/plugins/showHiddenChannels/index.tsx
+++ b/src/plugins/showHiddenChannels/index.tsx
@@ -306,7 +306,7 @@ export default definePlugin({
},
{
// Export the channel for the users allowed component patch
- match: /maxUsers:\i,users:\i(?<=channel:(\i).+?)/,
+ match: /maxUsers:\d+?,users:\i(?<=channel:(\i).+?)/,
replace: (m, channel) => `${m},shcChannel:${channel}`
},
{
diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts
index b8aed478..22e7d6f3 100644
--- a/src/plugins/superReactionTweaks/index.ts
+++ b/src/plugins/superReactionTweaks/index.ts
@@ -42,8 +42,8 @@ export default definePlugin({
{
find: ",BURST_REACTION_EFFECT_PLAY",
replacement: {
- match: /(?<=(\i)=\i=>{.+?)(\i\(\i,\i\))>=\i(?=\).+BURST_REACTION_EFFECT_PLAY:\1)/,
- replace: "!$self.shouldPlayBurstReaction($2)"
+ match: /(BURST_REACTION_EFFECT_PLAY:\i=>{.+?if\()(\(\(\i,\i\)=>.+?\(\i,\i\))>=\d+?(?=\))/,
+ replace: (_, rest, playingCount) => `${rest}!$self.shouldPlayBurstReaction(${playingCount})`
}
},
{
diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx
index 82cd0284..af9fbc87 100644
--- a/src/plugins/vencordToolbox/index.tsx
+++ b/src/plugins/vencordToolbox/index.tsx
@@ -142,7 +142,7 @@ export default definePlugin({
{
find: ".controlButtonWrapper,",
replacement: {
- match: /(?<=function (\i).{0,100}\()\i.Fragment,(?=.+?toolbar:\1\(\))/,
+ match: /(?<=toolbar:function\(.{0,100}\()\i.Fragment,/,
replace: "$self.ToolboxFragmentWrapper,"
}
}
diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts
index 666e51be..be85e33c 100644
--- a/src/plugins/webContextMenus.web/index.ts
+++ b/src/plugins/webContextMenus.web/index.ts
@@ -142,10 +142,6 @@ export default definePlugin({
match: /!\i\.isPlatformEmbedded/,
replace: "false"
},
- {
- match: /return\s*?\[.{0,50}?(?=\?\(0,\i\.jsxs?.{0,100}?id:"copy-image")/,
- replace: "return [true"
- },
{
match: /(?<=#{intl::COPY_IMAGE_MENU_ITEM}\),)action:/,
replace: "action:()=>$self.copyImage(arguments[0]),oldAction:"
From 0bb2ed6b727f7205fe6e5a4f788f6c7e3f83dc23 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Mon, 18 Aug 2025 18:58:40 +0200
Subject: [PATCH 056/116] bump to v1.12.12
---
package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index 50219306..09a82293 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
- "version": "1.12.11",
+ "version": "1.12.12",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@@ -105,4 +105,4 @@
"engines": {
"node": ">=18"
}
-}
+}
\ No newline at end of file
From 643656d798b4d328adaaf0f12dccc59a2c955990 Mon Sep 17 00:00:00 2001
From: fawn
Date: Tue, 19 Aug 2025 17:41:02 +0300
Subject: [PATCH 057/116] new plugin ImageFilename: displays image filename
tooltips on hover (#3617)
Co-authored-by: Vendicated
---
src/plugins/imageFilename/README.md | 5 +++
src/plugins/imageFilename/index.ts | 51 +++++++++++++++++++++++++++++
2 files changed, 56 insertions(+)
create mode 100644 src/plugins/imageFilename/README.md
create mode 100644 src/plugins/imageFilename/index.ts
diff --git a/src/plugins/imageFilename/README.md b/src/plugins/imageFilename/README.md
new file mode 100644
index 00000000..7929a73a
--- /dev/null
+++ b/src/plugins/imageFilename/README.md
@@ -0,0 +1,5 @@
+# ImageFilename
+
+Display the file name of images & GIFs as a tooltip when hovering over them
+
+
diff --git a/src/plugins/imageFilename/index.ts b/src/plugins/imageFilename/index.ts
new file mode 100644
index 00000000..08a446f8
--- /dev/null
+++ b/src/plugins/imageFilename/index.ts
@@ -0,0 +1,51 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { definePluginSettings } from "@api/Settings";
+import { Devs } from "@utils/constants";
+import definePlugin, { OptionType } from "@utils/types";
+
+const ImageExtensionRe = /\.(png|jpg|jpeg|gif|webp|avif)$/i;
+const GifHostRegex = /^(.+?\.)?(tenor|giphy|imgur)\.com$/i;
+
+const settings = definePluginSettings({
+ showFullUrl: {
+ description: "Show the full URL of the image instead of just the file name. Always enabled for GIFs because they usually have no meaningful file name",
+ type: OptionType.BOOLEAN,
+ default: false,
+ },
+});
+
+export default definePlugin({
+ name: "ImageFilename",
+ authors: [Devs.Ven],
+ description: "Display the file name of images & GIFs as a tooltip when hovering over them",
+ settings,
+
+ patches: [
+ {
+ find: ".clickableWrapper",
+ replacement: {
+ match: /\.originalLink,href:(\i)/,
+ replace: "$&,title:$self.getTitle($1)"
+ }
+ },
+ ],
+
+ getTitle(src: string) {
+ try {
+ const url = new URL(src);
+ const isGif = GifHostRegex.test(url.hostname);
+ if (!isGif && !ImageExtensionRe.test(url.pathname)) return undefined;
+
+ return isGif || settings.store.showFullUrl
+ ? src
+ : url.pathname.split("/").pop();
+ } catch {
+ return undefined;
+ }
+ }
+});
From 4a35cf1769f2f6bc178451b5641140ef06e478b5 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Sat, 23 Aug 2025 01:57:27 +0200
Subject: [PATCH 058/116] Toolbox: fix & move to the titlebar
---
src/plugins/vencordToolbox/index.tsx | 31 ++++++++++------------------
1 file changed, 11 insertions(+), 20 deletions(-)
diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx
index af9fbc87..1bc97cf5 100644
--- a/src/plugins/vencordToolbox/index.tsx
+++ b/src/plugins/vencordToolbox/index.tsx
@@ -94,7 +94,7 @@ function VencordPopoutIcon(isShown: boolean) {
);
}
-function VencordPopoutButton() {
+function VencordPopoutButton({ buttonClass }: { buttonClass: string; }) {
const buttonRef = useRef(null);
const [show, setShow] = useState(false);
@@ -111,7 +111,7 @@ function VencordPopoutButton() {
{(_, { isShown }) => (
setShow(v => !v)}
tooltip={isShown ? null : "Vencord Toolbox"}
icon={() => VencordPopoutIcon(isShown)}
@@ -122,33 +122,24 @@ function VencordPopoutButton() {
);
}
-function ToolboxFragmentWrapper({ children }: { children: ReactNode[]; }) {
- children.splice(
- children.length - 1, 0,
-
-
-
- );
-
- return <>{children}>;
-}
-
export default definePlugin({
name: "VencordToolbox",
- description: "Adds a button next to the inbox button in the channel header that houses Vencord quick actions",
+ description: "Adds a button to the titlebar that houses Vencord quick actions",
authors: [Devs.Ven, Devs.AutumnVN],
patches: [
{
- find: ".controlButtonWrapper,",
+ find: '"M9 3v18"',
replacement: {
- match: /(?<=toolbar:function\(.{0,100}\()\i.Fragment,/,
- replace: "$self.ToolboxFragmentWrapper,"
+ match: /focusSectionProps:"HELP".{0,20},className:(\i\.button)\}\),/,
+ replace: "$& $self.renderVencordPopoutButton($1),"
}
}
],
- ToolboxFragmentWrapper: ErrorBoundary.wrap(ToolboxFragmentWrapper, {
- fallback: () => Failed to render :(
- })
+ renderVencordPopoutButton: (buttonClass: string) => (
+
+
+
+ )
});
From abe910d80d2848127e5ee1857cfccedfbdbbbf07 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Sat, 23 Aug 2025 02:02:02 +0200
Subject: [PATCH 059/116] fix not being able to dismiss Vencord Notices
---
src/plugins/_api/notices.ts | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/plugins/_api/notices.ts b/src/plugins/_api/notices.ts
index 2384a10e..3b43c31a 100644
--- a/src/plugins/_api/notices.ts
+++ b/src/plugins/_api/notices.ts
@@ -32,9 +32,16 @@ export default definePlugin({
match: /(?<=!1;)\i=null;(?=.{0,80}getPremiumSubscription\(\))/g,
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
},
+
+ // TODO: remove this compat eventually
{
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
- replace: (m, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}`
+ replace: (m, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}`,
+ noWarn: true,
+ },
+ {
+ match: /(?<=function (\i)\(\i\){)return null!=(\i)(?=.+?NOTICE_DISMISS:\1)/,
+ replace: (m, _, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}`
}
]
}
From 76a60e07e9d299953461640b3409aff13045295d Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Sat, 23 Aug 2025 02:16:32 +0200
Subject: [PATCH 060/116] fix SuperReactionTweaks
---
src/plugins/superReactionTweaks/index.ts | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts
index 22e7d6f3..2edf99e8 100644
--- a/src/plugins/superReactionTweaks/index.ts
+++ b/src/plugins/superReactionTweaks/index.ts
@@ -42,8 +42,13 @@ export default definePlugin({
{
find: ",BURST_REACTION_EFFECT_PLAY",
replacement: {
- match: /(BURST_REACTION_EFFECT_PLAY:\i=>{.+?if\()(\(\(\i,\i\)=>.+?\(\i,\i\))>=\d+?(?=\))/,
- replace: (_, rest, playingCount) => `${rest}!$self.shouldPlayBurstReaction(${playingCount})`
+ /*
+ * var limit = 5
+ * ...
+ * if (calculatePlayingCount(a,b) >= limit) return;
+ */
+ match: /(?<=(\i)=5.+?)if\((.{0,20}?)>=\1\)return;/,
+ replace: "if(!$self.shouldPlayBurstReaction($2))return;"
}
},
{
@@ -58,8 +63,7 @@ export default definePlugin({
shouldPlayBurstReaction(playingCount: number) {
if (settings.store.unlimitedSuperReactionPlaying) return true;
- if (settings.store.superReactionPlayingLimit === 0) return false;
- if (playingCount <= settings.store.superReactionPlayingLimit) return true;
+ if (settings.store.superReactionPlayingLimit > playingCount) return true;
return false;
},
From 5dee7469864f042cd221deff5ab1495b092acd24 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Sat, 23 Aug 2025 11:30:58 -0300
Subject: [PATCH 061/116] Fix IgnoreActivities
---
src/plugins/_api/notices.ts | 2 +-
src/plugins/ignoreActivities/index.tsx | 22 ++++++++++++++----
src/plugins/superReactionTweaks/index.ts | 29 ++++++++++++++++--------
3 files changed, 38 insertions(+), 15 deletions(-)
diff --git a/src/plugins/_api/notices.ts b/src/plugins/_api/notices.ts
index 3b43c31a..43ef2acc 100644
--- a/src/plugins/_api/notices.ts
+++ b/src/plugins/_api/notices.ts
@@ -33,7 +33,7 @@ export default definePlugin({
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
},
- // TODO: remove this compat eventually
+ // FIXME(Bundler minifier change related): Remove the non used compability once enough time has passed
{
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
replace: (m, notice) => `if(${notice}?.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);${m}`,
diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx
index 88906c38..014c1abd 100644
--- a/src/plugins/ignoreActivities/index.tsx
+++ b/src/plugins/ignoreActivities/index.tsx
@@ -56,15 +56,15 @@ function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string
);
}
-const ToggleIconOn = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Disable Activity", "M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z", fill);
-const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Enable Activity", "m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z", fill);
+const ToggleIconOn = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Disable activity", "M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z", fill);
+const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Enable activity", "m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z", fill);
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
const s = settings.use(["ignoredActivities"]);
const { ignoredActivities } = s;
if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
- return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)");
+ return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--interactive-normal)");
}
function handleActivityToggle(e: React.MouseEvent, activity: IgnoredActivity) {
@@ -253,15 +253,29 @@ export default definePlugin({
replace: (m, runningGames) => `${m}${runningGames}=${runningGames}.filter(({id,name})=>$self.isActivityNotIgnored({type:0,application_id:id,name}));`
}
},
+
+ // FIXME(Bundler minifier change related): Remove the non used compability once enough time has passed
{
find: "#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}",
replacement: {
// let { ... nowPlaying: a = !1 ...
// let { overlay: b ... } = Props
match: /#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}.+?}\(\),(?<=nowPlaying:(\i)=!1,.+?overlay:\i,[^}]+?\}=(\i).+?)/,
- replace: (m, nowPlaying, props) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`
+ replace: (m, nowPlaying, props) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`,
+ noWarn: true,
}
},
+ {
+ find: "#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}",
+ replacement: {
+ // let { ... nowPlaying: a = !1 ...
+ // let { overlay: b ... } = Props ...
+ // ToggleOverLayButton(), nowPlaying && ... RemoveGameButton()
+ match: /\.gameNameLastPlayed.+?,\i\(\),(?<=nowPlaying:(\i)=!1,.+?overlay:\i,[^}]+?\}=(\i).+?)(?=\1&&)/,
+ replace: (m, nowPlaying, props) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`,
+ }
+ },
+
// Activities from the apps launcher in the bottom right of the chat bar
{
find: ".promotedLabelWrapperNonBanner,children",
diff --git a/src/plugins/superReactionTweaks/index.ts b/src/plugins/superReactionTweaks/index.ts
index 2edf99e8..a8827903 100644
--- a/src/plugins/superReactionTweaks/index.ts
+++ b/src/plugins/superReactionTweaks/index.ts
@@ -41,21 +41,30 @@ export default definePlugin({
patches: [
{
find: ",BURST_REACTION_EFFECT_PLAY",
- replacement: {
- /*
- * var limit = 5
- * ...
- * if (calculatePlayingCount(a,b) >= limit) return;
- */
- match: /(?<=(\i)=5.+?)if\((.{0,20}?)>=\1\)return;/,
- replace: "if(!$self.shouldPlayBurstReaction($2))return;"
- }
+ replacement: [
+ // FIXME(Bundler minifier change related): Remove the non used compability once enough time has passed
+ {
+ // if (inlinedCalculatePlayingCount(a,b) >= limit) return;
+ match: /(BURST_REACTION_EFFECT_PLAY:\i=>{.+?if\()(\(\(\i,\i\)=>.+?\(\i,\i\))>=5+?(?=\))/,
+ replace: (_, rest, playingCount) => `${rest}!$self.shouldPlayBurstReaction(${playingCount})`,
+ noWarn: true,
+ },
+ {
+ /*
+ * var limit = 5
+ * ...
+ * if (calculatePlayingCount(a,b) >= limit) return;
+ */
+ match: /((\i)=5.+?)if\((.{0,20}?)>=\2\)return;/,
+ replace: (_, rest, playingCount) => `${rest}if(!$self.shouldPlayBurstReaction(${playingCount}))return;`
+ }
+ ]
},
{
find: ".EMOJI_PICKER_CONSTANTS_EMOJI_CONTAINER_PADDING_HORIZONTAL)",
replacement: {
match: /(openPopoutType:void 0(?=.+?isBurstReaction:(\i).+?(\i===\i\.\i.REACTION)).+?\[\2,\i\]=\i\.useState\().+?\)/,
- replace: (_, rest, isBurstReactionVariable, isReactionIntention) => `${rest}$self.shouldSuperReactByDefault&&${isReactionIntention})`
+ replace: (_, rest, _isBurstReactionVariable, isReactionIntention) => `${rest}$self.shouldSuperReactByDefault&&${isReactionIntention})`
}
}
],
From 19c1eaed18cdc484ed0c910c8ce0743cef6d5531 Mon Sep 17 00:00:00 2001
From: thororen <78185467+thororen1234@users.noreply.github.com>
Date: Wed, 27 Aug 2025 21:43:15 -0400
Subject: [PATCH 062/116] fix failing to find the Select component (#3631)
---
src/components/settings/tabs/plugins/index.tsx | 1 -
src/webpack/common/components.ts | 4 ++--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/components/settings/tabs/plugins/index.tsx b/src/components/settings/tabs/plugins/index.tsx
index 6faa6368..4db9699a 100644
--- a/src/components/settings/tabs/plugins/index.tsx
+++ b/src/components/settings/tabs/plugins/index.tsx
@@ -262,7 +262,6 @@ function PluginSettings() {
select={onStatusChange}
isSelected={v => v === searchValue.status}
closeOnSelect={true}
- className={InputStyles.input}
/>
diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts
index d5e8dad1..9715619a 100644
--- a/src/webpack/common/components.ts
+++ b/src/webpack/common/components.ts
@@ -55,8 +55,8 @@ export const TextInput = waitForComponent("TextInput", filters.comp
export const TextArea = waitForComponent("TextArea", filters.componentByCode("this.getPaddingRight()},id:"));
export const Text = waitForComponent("Text", filters.componentByCode('case"always-white"'));
export const Heading = waitForComponent("Heading", filters.componentByCode(">6?{", "variant:"));
-export const Select = waitForComponent("Select", filters.componentByCode('.selectPositionTop]:"top"===', '"Escape"==='));
-export const SearchableSelect = waitForComponent("SearchableSelect", filters.componentByCode('.selectPositionTop]:"top"===', ".multi]:"));
+export const Select = waitForComponent("Select", filters.componentByCode('="bottom",', ".select,", '"Escape"==='));
+export const SearchableSelect = waitForComponent("SearchableSelect", filters.componentByCode(".setSelectionRange(", ".multi]:"));
export const Slider = waitForComponent("Slider", filters.componentByCode('"markDash".concat('));
export const Popout = waitForComponent("Popout", filters.componentByCode("ref:this.ref,", "renderPopout:this.renderPopout,"));
export const Dialog = waitForComponent("Dialog", filters.componentByCode('role:"dialog",tabIndex:-1'));
From c5888c25f70ad85793ba33bead19604d50469265 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Fri, 29 Aug 2025 02:50:32 +0200
Subject: [PATCH 063/116] bump to v1.12.13
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 09a82293..3f0e3371 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
- "version": "1.12.12",
+ "version": "1.12.13",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
From 67aff64fed0422092961f2255691504e92b680e8 Mon Sep 17 00:00:00 2001
From: thororen <78185467+thororen1234@users.noreply.github.com>
Date: Fri, 29 Aug 2025 15:01:27 -0400
Subject: [PATCH 064/116] MutualGroupDMs: Fix broken patch (#3634)
---
src/plugins/mutualGroupDMs/index.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx
index 8d81e43d..28889923 100644
--- a/src/plugins/mutualGroupDMs/index.tsx
+++ b/src/plugins/mutualGroupDMs/index.tsx
@@ -109,14 +109,14 @@ export default definePlugin({
},
// User Profile Modal v2
{
- find: ".tabBarPanel,children:",
+ find: ".tabBarPanel,{[",
replacement: [
{
match: /items:(\i),.+?(?=return\(0,\i\.jsxs?\)\("div)/,
replace: "$&$self.pushSection($1,arguments[0].user);"
},
{
- match: /\.tabBarPanel,children:(?=.+?section:(\i))/,
+ match: /\.tabBarPanel,.+?children:(?=.+?section:(\i))/,
replace: "$&$1==='MUTUAL_GDMS'?$self.renderMutualGDMs(arguments[0]):"
},
// Make the gap between each item smaller so our tab can fit.
From aca30bcb9a37425c4cdecda6f0a5b94a57225112 Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Fri, 29 Aug 2025 15:02:45 -0400
Subject: [PATCH 065/116] ShowHiddenChannels: Fix broken patch (#3635)
---
src/plugins/showHiddenChannels/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx
index b4fcd713..5f83a3f5 100644
--- a/src/plugins/showHiddenChannels/index.tsx
+++ b/src/plugins/showHiddenChannels/index.tsx
@@ -239,7 +239,7 @@ export default definePlugin({
replace: (m, channel) => `${m}if($self.isHiddenChannel(${channel}))break;`
},
{
- match: /(?<="renderHeaderBar",\i=>{.+?hideSearch:(\i)\.isDirectory\(\))/,
+ match: /(?<="renderHeaderBar",\(\)=>{.+?hideSearch:(\i)\.isDirectory\(\))/,
replace: (_, channel) => `||$self.isHiddenChannel(${channel})`
},
{
From 8807564053c7b4cc05c763e2dc7171f5d61e39c7 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Fri, 29 Aug 2025 16:23:23 -0300
Subject: [PATCH 066/116] ConsoleJanitor: Fix outdated settings margin
---
.../settings/tabs/plugins/components/Common.tsx | 2 +-
src/plugins/consoleJanitor/index.tsx | 15 ++++++---------
2 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/src/components/settings/tabs/plugins/components/Common.tsx b/src/components/settings/tabs/plugins/components/Common.tsx
index 3d84859b..61c3da52 100644
--- a/src/components/settings/tabs/plugins/components/Common.tsx
+++ b/src/components/settings/tabs/plugins/components/Common.tsx
@@ -36,7 +36,7 @@ export function resolveError(isValidResult: boolean | string) {
interface SettingsSectionProps extends PropsWithChildren {
name: string;
description: string;
- error: string | null;
+ error?: string | null;
inlineSetting?: boolean;
}
diff --git a/src/plugins/consoleJanitor/index.tsx b/src/plugins/consoleJanitor/index.tsx
index 4981ac2c..fb4117c1 100644
--- a/src/plugins/consoleJanitor/index.tsx
+++ b/src/plugins/consoleJanitor/index.tsx
@@ -6,11 +6,10 @@
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
-import { Flex } from "@components/Flex";
+import { SettingsSection } from "@components/settings/tabs/plugins/components/Common";
import { Devs } from "@utils/constants";
-import { Margins } from "@utils/margins";
import definePlugin, { defineDefault, OptionType, StartAt } from "@utils/types";
-import { Checkbox, Forms, Text } from "@webpack/common";
+import { Checkbox, Text } from "@webpack/common";
const Noop = () => { };
const NoopLogger = {
@@ -58,15 +57,13 @@ function AllowLevelSetting({ settingKey }: AllowLevelSettingProps) {
const AllowLevelSettings = ErrorBoundary.wrap(() => {
return (
-
- Filter List
- Always allow loggers of these types
-
+
+
{Object.keys(settings.store.allowLevel).map(key => (
))}
-
-
+
+
);
});
From 17b90beee1af377becbf92764cf954910037f140 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Sun, 31 Aug 2025 05:18:40 +0200
Subject: [PATCH 067/116] 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 f79ca099..1345b26d 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 433c3080..4118435a 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 068/116] 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 d4aab900..74bcdd50 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 069/116] 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 4064de9f..1635781c 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 070/116] 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 363897d1..1bcc8b3f 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);
}
};
From 75a2506c51993c7e44be8d53cd0055264ae65eab Mon Sep 17 00:00:00 2001
From: Etorix <92535668+EtorixDev@users.noreply.github.com>
Date: Tue, 2 Sep 2025 09:42:17 -0700
Subject: [PATCH 071/116] ViewRaw: Adjust icon size to match other icons
(#3605)
---
src/plugins/viewRaw/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/viewRaw/index.tsx b/src/plugins/viewRaw/index.tsx
index e6781784..1dcef31b 100644
--- a/src/plugins/viewRaw/index.tsx
+++ b/src/plugins/viewRaw/index.tsx
@@ -32,7 +32,7 @@ import { Button, ChannelStore, Forms, GuildRoleStore, Menu, Text } from "@webpac
const CopyIcon = () => {
- return
+ return
;
From 5c69d340d967084a567da6f3d6d276cbe715c9d7 Mon Sep 17 00:00:00 2001
From: thororen <78185467+thororen1234@users.noreply.github.com>
Date: Tue, 2 Sep 2025 19:44:25 -0400
Subject: [PATCH 072/116] Fix MutualGroupDMs & Decor broken patches (#3644)
---
src/plugins/decor/ui/modals/CreateDecorationModal.tsx | 2 +-
src/plugins/mutualGroupDMs/index.tsx | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/plugins/decor/ui/modals/CreateDecorationModal.tsx b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx
index d772ac4a..6feb3b4f 100644
--- a/src/plugins/decor/ui/modals/CreateDecorationModal.tsx
+++ b/src/plugins/decor/ui/modals/CreateDecorationModal.tsx
@@ -17,7 +17,7 @@ import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDeco
import { cl, DecorationModalStyles, requireAvatarDecorationModal, requireCreateStickerModal } from "../";
import { AvatarDecorationModalPreview } from "../components";
-const FileUpload = findComponentByCodeLazy("fileUploadInput,");
+const FileUpload = findComponentByCodeLazy(".fileUpload),");
const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE="positive', {
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING", "INFO"),
diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx
index 28889923..6909fbd2 100644
--- a/src/plugins/mutualGroupDMs/index.tsx
+++ b/src/plugins/mutualGroupDMs/index.tsx
@@ -109,14 +109,14 @@ export default definePlugin({
},
// User Profile Modal v2
{
- find: ".tabBarPanel,{[",
+ find: ".WIDGETS?",
replacement: [
{
match: /items:(\i),.+?(?=return\(0,\i\.jsxs?\)\("div)/,
replace: "$&$self.pushSection($1,arguments[0].user);"
},
{
- match: /\.tabBarPanel,.+?children:(?=.+?section:(\i))/,
+ match: /\.tabBarPanel,.*?children:(?=.+?section:(\i))/,
replace: "$&$1==='MUTUAL_GDMS'?$self.renderMutualGDMs(arguments[0]):"
},
// Make the gap between each item smaller so our tab can fit.
From 4ff3614dc0a96ebfd825dc5fd3ef9e77de7d1da1 Mon Sep 17 00:00:00 2001
From: ayuxia
Date: Tue, 2 Sep 2025 22:19:12 -0300
Subject: [PATCH 073/116] ShowMeYourName: support friend nicknames (#3639)
Co-authored-by: V
---
packages/discord-types/enums/channel.ts | 15 +++++++
packages/discord-types/enums/index.ts | 1 +
src/plugins/noReplyMention/index.tsx | 2 +-
src/plugins/onePingPerDM/index.ts | 6 +--
src/plugins/relationshipNotifier/functions.ts | 3 +-
src/plugins/relationshipNotifier/types.ts | 4 --
src/plugins/relationshipNotifier/utils.ts | 3 +-
src/plugins/showMeYourName/index.tsx | 45 +++++++++++++++----
src/utils/constants.ts | 6 +--
9 files changed, 61 insertions(+), 24 deletions(-)
create mode 100644 packages/discord-types/enums/channel.ts
diff --git a/packages/discord-types/enums/channel.ts b/packages/discord-types/enums/channel.ts
new file mode 100644
index 00000000..7aae546b
--- /dev/null
+++ b/packages/discord-types/enums/channel.ts
@@ -0,0 +1,15 @@
+export const enum ChannelType {
+ GUILD_TEXT = 0,
+ DM = 1,
+ GUILD_VOICE = 2,
+ GROUP_DM = 3,
+ GUILD_CATEGORY = 4,
+ GUILD_ANNOUNCEMENT = 5,
+ ANNOUNCEMENT_THREAD = 10,
+ PUBLIC_THREAD = 11,
+ PRIVATE_THREAD = 12,
+ GUILD_STAGE_VOICE = 13,
+ GUILD_DIRECTORY = 14,
+ GUILD_FORUM = 15,
+ GUILD_MEDIA = 16
+}
\ No newline at end of file
diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts
index e2227d09..0ab49887 100644
--- a/packages/discord-types/enums/index.ts
+++ b/packages/discord-types/enums/index.ts
@@ -1,2 +1,3 @@
export * from "./commands";
export * from "./messages";
+export * from "./channel";
\ No newline at end of file
diff --git a/src/plugins/noReplyMention/index.tsx b/src/plugins/noReplyMention/index.tsx
index b4de573f..4af78ee2 100644
--- a/src/plugins/noReplyMention/index.tsx
+++ b/src/plugins/noReplyMention/index.tsx
@@ -53,7 +53,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "NoReplyMention",
description: "Disables reply pings by default",
- authors: [Devs.DustyAngel47, Devs.axyie, Devs.pylix, Devs.outfoxxed],
+ authors: [Devs.DustyAngel47, Devs.rae, Devs.pylix, Devs.outfoxxed],
settings,
shouldMention(message: Message, isHoldingShift: boolean) {
diff --git a/src/plugins/onePingPerDM/index.ts b/src/plugins/onePingPerDM/index.ts
index 0ef81781..b6192fb6 100644
--- a/src/plugins/onePingPerDM/index.ts
+++ b/src/plugins/onePingPerDM/index.ts
@@ -8,13 +8,9 @@ import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { MessageJSON } from "@vencord/discord-types";
+import { ChannelType } from "@vencord/discord-types/enums";
import { ChannelStore, ReadStateStore, UserStore } from "@webpack/common";
-const enum ChannelType {
- DM = 1,
- GROUP_DM = 3
-}
-
const settings = definePluginSettings({
channelToAffect: {
type: OptionType.SELECT,
diff --git a/src/plugins/relationshipNotifier/functions.ts b/src/plugins/relationshipNotifier/functions.ts
index 5bff474b..19f36086 100644
--- a/src/plugins/relationshipNotifier/functions.ts
+++ b/src/plugins/relationshipNotifier/functions.ts
@@ -17,10 +17,11 @@
*/
import { getUniqueUsername, openUserProfile } from "@utils/discord";
+import { ChannelType } from "@vencord/discord-types/enums";
import { UserUtils } from "@webpack/common";
import settings from "./settings";
-import { ChannelDelete, ChannelType, GuildDelete, RelationshipRemove, RelationshipType } from "./types";
+import { ChannelDelete, GuildDelete, RelationshipRemove, RelationshipType } from "./types";
import { deleteGroup, deleteGuild, getGroup, getGuild, GuildAvailabilityStore, notify } from "./utils";
let manuallyRemovedFriend: string | undefined;
diff --git a/src/plugins/relationshipNotifier/types.ts b/src/plugins/relationshipNotifier/types.ts
index f2e482d6..5d3d12d4 100644
--- a/src/plugins/relationshipNotifier/types.ts
+++ b/src/plugins/relationshipNotifier/types.ts
@@ -52,10 +52,6 @@ export interface SimpleGuild {
iconURL?: string;
}
-export const enum ChannelType {
- GROUP_DM = 3,
-}
-
export const enum RelationshipType {
FRIEND = 1,
BLOCKED = 2,
diff --git a/src/plugins/relationshipNotifier/utils.ts b/src/plugins/relationshipNotifier/utils.ts
index aaef783b..78371f13 100644
--- a/src/plugins/relationshipNotifier/utils.ts
+++ b/src/plugins/relationshipNotifier/utils.ts
@@ -20,11 +20,12 @@ import { DataStore, Notices } from "@api/index";
import { showNotification } from "@api/Notifications";
import { getUniqueUsername, openUserProfile } from "@utils/discord";
import { FluxStore } from "@vencord/discord-types";
+import { ChannelType } from "@vencord/discord-types/enums";
import { findStoreLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, GuildStore, RelationshipStore, UserStore, UserUtils } from "@webpack/common";
import settings from "./settings";
-import { ChannelType, RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types";
+import { RelationshipType, SimpleGroupChannel, SimpleGuild } from "./types";
export const GuildAvailabilityStore = findStoreLazy("GuildAvailabilityStore") as FluxStore & {
totalGuilds: number;
diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx
index 018fa305..623c70ca 100644
--- a/src/plugins/showMeYourName/index.tsx
+++ b/src/plugins/showMeYourName/index.tsx
@@ -10,10 +10,12 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
-import { Message, User } from "@vencord/discord-types";
+import { Channel, Message, User } from "@vencord/discord-types";
+import { RelationshipStore } from "@webpack/common";
interface UsernameProps {
- author: { nick: string; };
+ author: { nick: string; authorId: string; };
+ channel: Channel;
message: Message;
withMentionPrefix?: boolean;
isRepliedMessage: boolean;
@@ -30,6 +32,15 @@ const settings = definePluginSettings({
{ label: "Username only", value: "user" },
],
},
+ friendNicknames: {
+ type: OptionType.SELECT,
+ description: "How to prioritise friend nicknames over server nicknames",
+ options: [
+ { label: "Show friend nicknames only in direct messages", value: "dms", default: true },
+ { label: "Prefer friend nicknames over server nicknames", value: "always" },
+ { label: "Prefer server nicknames over friend nicknames", value: "fallback" }
+ ]
+ },
displayNames: {
type: OptionType.BOOLEAN,
description: "Use display names in place of usernames",
@@ -45,7 +56,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "ShowMeYourName",
description: "Display usernames next to nicks, or no nicks at all",
- authors: [Devs.Rini, Devs.TheKodeToad],
+ authors: [Devs.Rini, Devs.TheKodeToad, Devs.rae],
patches: [
{
find: '="SYSTEM_TAG"',
@@ -58,23 +69,39 @@ export default definePlugin({
],
settings,
- renderUsername: ErrorBoundary.wrap(({ author, message, isRepliedMessage, withMentionPrefix, userOverride }: UsernameProps) => {
+ renderUsername: ErrorBoundary.wrap(({ author, channel, message, isRepliedMessage, withMentionPrefix, userOverride }: UsernameProps) => {
try {
+ const { mode, friendNicknames, displayNames, inReplies } = settings.store;
+
const user = userOverride ?? message.author;
let { username } = user;
- if (settings.store.displayNames)
+
+ if (displayNames)
username = user.globalName || username;
- const { nick } = author;
+ let { nick } = author;
+
+ const friendNickname = RelationshipStore.getNickname(author.authorId);
+
+ if (friendNickname) {
+ const shouldUseFriendNickname =
+ friendNicknames === "always" ||
+ (friendNicknames === "dms" && channel.isPrivate()) ||
+ (friendNicknames === "fallback" && !nick);
+
+ if (shouldUseFriendNickname)
+ nick = friendNickname;
+ }
+
const prefix = withMentionPrefix ? "@" : "";
- if (isRepliedMessage && !settings.store.inReplies || username.toLowerCase() === nick.toLowerCase())
+ if (isRepliedMessage && !inReplies || username.toLowerCase() === nick.toLowerCase())
return <>{prefix}{nick}>;
- if (settings.store.mode === "user-nick")
+ if (mode === "user-nick")
return <>{prefix}{username} {nick} >;
- if (settings.store.mode === "nick-user")
+ if (mode === "nick-user")
return <>{prefix}{nick} {username} >;
return <>{prefix}{username}>;
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 7c7488b6..705b1451 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -197,9 +197,9 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "sunnie",
id: 406028027768733696n
},
- axyie: {
- name: "'ax",
- id: 929877747151548487n,
+ rae: {
+ name: "rae",
+ id: 1398136199503282277n
},
pointy: {
name: "pointy",
From 8789973bf5ee233a68318e80e9223c853ddc86b3 Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Tue, 2 Sep 2025 21:41:46 -0400
Subject: [PATCH 074/116] WhoReacted: remove ugly more users tooltip (#3640)
Co-authored-by: Vendicated
---
src/plugins/whoReacted/index.tsx | 23 ++---------------------
1 file changed, 2 insertions(+), 21 deletions(-)
diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx
index af6d8fdb..2faedfa2 100644
--- a/src/plugins/whoReacted/index.tsx
+++ b/src/plugins/whoReacted/index.tsx
@@ -24,7 +24,7 @@ import { useForceUpdater } from "@utils/react";
import definePlugin from "@utils/types";
import { CustomEmoji, Message, ReactionEmoji, User } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack";
-import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip, useEffect, useLayoutEffect, UserSummaryItem } from "@webpack/common";
+import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, useEffect, useLayoutEffect, UserSummaryItem } from "@webpack/common";
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
let Scroll: any = null;
@@ -73,24 +73,6 @@ function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) {
return cache.users;
}
-function makeRenderMoreUsers(users: User[]) {
- return function renderMoreUsers(_label: string, _count: number) {
- return (
- u.username).join(", ")} >
- {({ onMouseEnter, onMouseLeave }) => (
-
- +{users.length - 4}
-
- )}
-
- );
- };
-}
-
function handleClickAvatar(event: React.UIEvent) {
event.stopPropagation();
}
@@ -163,7 +145,7 @@ export default definePlugin({
-
From 65c85a522202bdd090f9fcf7bd7702b2c5eba165 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Wed, 3 Sep 2025 04:26:33 +0200
Subject: [PATCH 075/116] CallTimer: fix overflow when using aligned chat input
Co-Authored-By: sadan4 <117494111+sadan4@users.noreply.github.com>
Co-Authored-By: God
---
src/plugins/callTimer/alignedChatInputFix.css | 4 ++++
src/plugins/callTimer/index.tsx | 3 +++
2 files changed, 7 insertions(+)
create mode 100644 src/plugins/callTimer/alignedChatInputFix.css
diff --git a/src/plugins/callTimer/alignedChatInputFix.css b/src/plugins/callTimer/alignedChatInputFix.css
new file mode 100644
index 00000000..9bb4a039
--- /dev/null
+++ b/src/plugins/callTimer/alignedChatInputFix.css
@@ -0,0 +1,4 @@
+.align-chat-input [class*="panels"] [class*="inner_"],
+.align-chat-input [class*="rtcConnectionStatus_"] {
+ height: fit-content;
+}
\ No newline at end of file
diff --git a/src/plugins/callTimer/index.tsx b/src/plugins/callTimer/index.tsx
index dcab0551..ae98a787 100644
--- a/src/plugins/callTimer/index.tsx
+++ b/src/plugins/callTimer/index.tsx
@@ -23,6 +23,8 @@ import { useTimer } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { React } from "@webpack/common";
+import alignedChatInputFix from "./alignedChatInputFix.css";
+
function formatDuration(ms: number) {
// here be dragons (moment fucking sucks)
const human = Settings.plugins.CallTimer.format === "human";
@@ -50,6 +52,7 @@ export default definePlugin({
name: "CallTimer",
description: "Adds a timer to vcs",
authors: [Devs.Ven],
+ managedStyle: alignedChatInputFix,
startTime: 0,
interval: void 0 as NodeJS.Timeout | undefined,
From 9700ec9cd289e67a5af5398a2ceaf74737e1b5ef Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Wed, 3 Sep 2025 04:37:38 +0200
Subject: [PATCH 076/116] fix minor bugs
---
src/plugins/_api/badges/fixDiscordBadgePadding.css | 8 ++++----
src/plugins/_api/badges/index.tsx | 1 -
src/plugins/callTimer/index.tsx | 2 +-
3 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/src/plugins/_api/badges/fixDiscordBadgePadding.css b/src/plugins/_api/badges/fixDiscordBadgePadding.css
index eb1d60d5..7e7702a3 100644
--- a/src/plugins/_api/badges/fixDiscordBadgePadding.css
+++ b/src/plugins/_api/badges/fixDiscordBadgePadding.css
@@ -1,5 +1,5 @@
/* the profile popout badge container(s) */
-[class*="biteSize_"] [class*="tags_"] [class*="container_"] {
- /* Discord has padding set to 2px instead of 1px, which causes the 12th badge to wrap to a new line. */
- padding: 0 1px;
-}
+[class*="profile_"] [class*="tags_"] [class*="container_"] {
+ /* Discord has gap set to 2px instead of 1px, which causes the 12th badge to wrap to a new line. */
+ gap: 1px;
+}
\ No newline at end of file
diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx
index 4118435a..8742a4bd 100644
--- a/src/plugins/_api/badges/index.tsx
+++ b/src/plugins/_api/badges/index.tsx
@@ -57,7 +57,6 @@ async function loadBadges(noCache = false) {
let intervalId: any;
function BadgeContextMenu({ badge }: { badge: ProfileBadge & BadgeUserArgs; }) {
- console.log(badge);
return (
Date: Wed, 3 Sep 2025 20:41:06 -0400
Subject: [PATCH 077/116] NoReplyMention: add role whitelist / blacklist
(#2794)
Co-authored-by: V
---
src/plugins/noReplyMention/index.tsx | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/src/plugins/noReplyMention/index.tsx b/src/plugins/noReplyMention/index.tsx
index 4af78ee2..28bfc256 100644
--- a/src/plugins/noReplyMention/index.tsx
+++ b/src/plugins/noReplyMention/index.tsx
@@ -20,6 +20,7 @@ import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import type { Message } from "@vencord/discord-types";
+import { ChannelStore, GuildMemberStore } from "@webpack/common";
const settings = definePluginSettings({
userList: {
@@ -28,16 +29,22 @@ const settings = definePluginSettings({
type: OptionType.STRING,
default: "1234567890123445,1234567890123445",
},
+ roleList: {
+ description:
+ "List of roles to allow or exempt pings for (separated by commas or spaces)",
+ type: OptionType.STRING,
+ default: "1234567890123445,1234567890123445",
+ },
shouldPingListed: {
description: "Behaviour",
type: OptionType.SELECT,
options: [
{
- label: "Do not ping the listed users",
+ label: "Do not ping the listed users / roles",
value: false,
},
{
- label: "Only ping the listed users",
+ label: "Only ping the listed users / roles",
value: true,
default: true,
},
@@ -57,7 +64,14 @@ export default definePlugin({
settings,
shouldMention(message: Message, isHoldingShift: boolean) {
- const isListed = settings.store.userList.includes(message.author.id);
+ let isListed = settings.store.userList.includes(message.author.id);
+
+ const channel = ChannelStore.getChannel(message.channel_id);
+ if (channel?.guild_id && !isListed) {
+ const roles = GuildMemberStore.getMember(channel.guild_id, message.author.id)?.roles;
+ isListed = !!roles && roles.some(role => settings.store.roleList.includes(role));
+ }
+
const isExempt = settings.store.shouldPingListed ? isListed : !isListed;
return settings.store.inverseShiftReply ? isHoldingShift !== isExempt : !isHoldingShift && isExempt;
},
From 77b016de366c827786bdcd45819552696c562875 Mon Sep 17 00:00:00 2001
From: union <43791401+Unionizing@users.noreply.github.com>
Date: Wed, 3 Sep 2025 20:46:02 -0400
Subject: [PATCH 078/116] ReverseImageSearch: add Bing (#2793)
---
src/plugins/reverseImageSearch/index.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/plugins/reverseImageSearch/index.tsx b/src/plugins/reverseImageSearch/index.tsx
index a2e7b7e6..3c1a5402 100644
--- a/src/plugins/reverseImageSearch/index.tsx
+++ b/src/plugins/reverseImageSearch/index.tsx
@@ -28,6 +28,7 @@ const Engines = {
Yandex: "https://yandex.com/images/search?rpt=imageview&url=",
SauceNAO: "https://saucenao.com/search.php?url=",
IQDB: "https://iqdb.org/?url=",
+ Bing: "https://www.bing.com/images/search?view=detailv2&iss=sbi&q=imgurl:",
TinEye: "https://www.tineye.com/search?url=",
ImgOps: "https://imgops.com/start?url="
} as const;
From 1d00ba4161fd1c285d1efe25f489c8bf68c77359 Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Thu, 4 Sep 2025 15:59:30 -0400
Subject: [PATCH 079/116] fix patches for Experiments and Vencord Toolbox
(#3647)
---
src/plugins/experiments/index.tsx | 2 +-
src/plugins/vencordToolbox/index.tsx | 5 +++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx
index e916e158..9b6a3377 100644
--- a/src/plugins/experiments/index.tsx
+++ b/src/plugins/experiments/index.tsx
@@ -80,7 +80,7 @@ export default definePlugin({
},
// Change top right chat toolbar button from the help one to the dev one
{
- find: '"M9 3v18"',
+ find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
match: /hasBugReporterAccess:(\i)/,
replace: "_hasBugReporterAccess:$1=true"
diff --git a/src/plugins/vencordToolbox/index.tsx b/src/plugins/vencordToolbox/index.tsx
index 1bc97cf5..5a7b8cad 100644
--- a/src/plugins/vencordToolbox/index.tsx
+++ b/src/plugins/vencordToolbox/index.tsx
@@ -129,9 +129,10 @@ export default definePlugin({
patches: [
{
- find: '"M9 3v18"',
+ find: '?"BACK_FORWARD_NAVIGATION":',
replacement: {
- match: /focusSectionProps:"HELP".{0,20},className:(\i\.button)\}\),/,
+ // TODO: (?:\.button) is for stable compat and should be removed soon:tm:
+ match: /focusSectionProps:"HELP".{0,20},className:(\i(?:\.button)?)\}\),/,
replace: "$& $self.renderVencordPopoutButton($1),"
}
}
From b6e96a4d3b631c5a2d236ae28179ee0879ae00fe Mon Sep 17 00:00:00 2001
From: Gleb P <63789651+ultard@users.noreply.github.com>
Date: Fri, 5 Sep 2025 04:37:13 +0300
Subject: [PATCH 080/116] MemberCount: also show members in voice (#2937)
Co-authored-by: Vendicated
---
.../src/stores/VoiceStateStore.d.ts | 42 +++++++++++++++++++
packages/discord-types/src/stores/index.d.ts | 1 +
src/plugins/memberCount/MemberCount.tsx | 42 ++++++++++++++++---
src/plugins/memberCount/VoiceIcon.tsx | 14 +++++++
src/plugins/memberCount/index.tsx | 17 +++++---
src/plugins/memberCount/style.css | 21 +++++++++-
src/plugins/userVoiceShow/components.tsx | 9 ++--
src/plugins/vcNarrator/index.tsx | 15 +++----
src/utils/constants.ts | 4 ++
src/webpack/common/stores.ts | 2 +
10 files changed, 140 insertions(+), 27 deletions(-)
create mode 100644 packages/discord-types/src/stores/VoiceStateStore.d.ts
create mode 100644 src/plugins/memberCount/VoiceIcon.tsx
diff --git a/packages/discord-types/src/stores/VoiceStateStore.d.ts b/packages/discord-types/src/stores/VoiceStateStore.d.ts
new file mode 100644
index 00000000..84540d99
--- /dev/null
+++ b/packages/discord-types/src/stores/VoiceStateStore.d.ts
@@ -0,0 +1,42 @@
+import { DiscordRecord } from "../common";
+import { FluxStore } from "./FluxStore";
+
+export type UserVoiceStateRecords = Record;
+export type VoiceStates = Record;
+
+export interface VoiceState extends DiscordRecord {
+ userId: string;
+ channelId: string | null | undefined;
+ sessionId: string | null | undefined;
+ mute: boolean;
+ deaf: boolean;
+ selfMute: boolean;
+ selfDeaf: boolean;
+ selfVideo: boolean;
+ selfStream: boolean | undefined;
+ suppress: boolean;
+ requestToSpeakTimestamp: string | null | undefined;
+ discoverable: boolean;
+
+ isVoiceMuted(): boolean;
+ isVoiceDeafened(): boolean;
+}
+
+export class VoiceStateStore extends FluxStore {
+ getAllVoiceStates(): VoiceStates;
+
+ getVoiceStates(guildId?: string | null): UserVoiceStateRecords;
+ getVoiceStatesForChannel(channelId: string): UserVoiceStateRecords;
+ getVideoVoiceStatesForChannel(channelId: string): UserVoiceStateRecords;
+
+ getVoiceState(guildId: string | null, userId: string): VoiceState | undefined;
+ getUserVoiceChannelId(guildId: string | null, userId: string): string | undefined;
+ getVoiceStateForChannel(channelId: string, userId?: string): VoiceState | undefined;
+ getVoiceStateForUser(userId: string): VoiceState | undefined;
+
+ getCurrentClientVoiceChannelId(guildId: string | null): string | undefined;
+ isCurrentClientInVoiceChannel(): boolean;
+
+ isInChannel(channelId: string, userId?: string): boolean;
+ hasVideo(channelId: string): boolean;
+}
diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts
index 7a435ff5..5906262a 100644
--- a/packages/discord-types/src/stores/index.d.ts
+++ b/packages/discord-types/src/stores/index.d.ts
@@ -16,6 +16,7 @@ export * from "./ThemeStore";
export * from "./TypingStore";
export * from "./UserProfileStore";
export * from "./UserStore";
+export * from "./VoiceStateStore";
export * from "./WindowStore";
/**
diff --git a/src/plugins/memberCount/MemberCount.tsx b/src/plugins/memberCount/MemberCount.tsx
index 0a3f5e62..02033886 100644
--- a/src/plugins/memberCount/MemberCount.tsx
+++ b/src/plugins/memberCount/MemberCount.tsx
@@ -6,16 +6,37 @@
import { getCurrentChannel } from "@utils/discord";
import { isObjectEmpty } from "@utils/misc";
-import { SelectedChannelStore, Tooltip, useEffect, useStateFromStores } from "@webpack/common";
+import { ChannelStore, PermissionsBits, PermissionStore, SelectedChannelStore, Tooltip, useEffect, useStateFromStores, VoiceStateStore } from "@webpack/common";
-import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat, ThreadMemberListStore } from ".";
+import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat, settings, ThreadMemberListStore } from ".";
import { OnlineMemberCountStore } from "./OnlineMemberCountStore";
+import { VoiceIcon } from "./VoiceIcon";
export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) {
- const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
+ const { voiceActivity } = settings.use(["voiceActivity"]);
+ const includeVoice = voiceActivity && !isTooltip;
+ const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
const guildId = isTooltip ? tooltipGuildId! : currentChannel?.guild_id;
+ const voiceActivityCount = useStateFromStores(
+ [VoiceStateStore],
+ () => {
+ if (!includeVoice) return 0;
+
+ const voiceStates = VoiceStateStore.getVoiceStates(guildId);
+ if (!voiceStates) return 0;
+
+ return Object.values(voiceStates)
+ .filter(({ channelId }) => {
+ if (!channelId) return false;
+ const channel = ChannelStore.getChannel(channelId);
+ return channel && PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel);
+ })
+ .length;
+ }
+ );
+
const totalCount = useStateFromStores(
[GuildMemberCountStore],
() => GuildMemberCountStore.getMemberCount(guildId)
@@ -51,13 +72,14 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t
if (totalCount == null)
return null;
+ const formattedVoiceCount = numberFormat(voiceActivityCount ?? 0);
const formattedOnlineCount = onlineCount != null ? numberFormat(onlineCount) : "?";
return (
{props => (
-
+
{formattedOnlineCount}
@@ -65,12 +87,22 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t
{props => (
-
+
{numberFormat(totalCount)}
)}
+ {includeVoice && voiceActivityCount > 0 &&
+
+ {props => (
+
+
+ {formattedVoiceCount}
+
+ )}
+
+ }
);
}
diff --git a/src/plugins/memberCount/VoiceIcon.tsx b/src/plugins/memberCount/VoiceIcon.tsx
new file mode 100644
index 00000000..e76f7151
--- /dev/null
+++ b/src/plugins/memberCount/VoiceIcon.tsx
@@ -0,0 +1,14 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+export function VoiceIcon({ className }: { className?: string; }) {
+ return (
+
+
+
+
+ );
+}
diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx
index 7fa6fbcd..bb48eedb 100644
--- a/src/plugins/memberCount/index.tsx
+++ b/src/plugins/memberCount/index.tsx
@@ -37,18 +37,23 @@ export const ThreadMemberListStore = findStoreLazy("ThreadMemberListStore") as F
};
-const settings = definePluginSettings({
+export const settings = definePluginSettings({
toolTip: {
type: OptionType.BOOLEAN,
- description: "If the member count should be displayed on the server tooltip",
+ description: "Show member count on the server tooltip",
default: true,
restartNeeded: true
},
memberList: {
type: OptionType.BOOLEAN,
- description: "If the member count should be displayed on the member list",
+ description: "Show member count in the member list",
default: true,
restartNeeded: true
+ },
+ voiceActivity: {
+ type: OptionType.BOOLEAN,
+ description: "Show voice activity with member count in the member list",
+ default: true
}
});
@@ -58,8 +63,8 @@ export const cl = classNameFactory("vc-membercount-");
export default definePlugin({
name: "MemberCount",
- description: "Shows the amount of online & total members in the server member list and tooltip",
- authors: [Devs.Ven, Devs.Commandtechno],
+ description: "Shows the number of online members, total members, and users in voice channels on the server — in the member list and tooltip.",
+ authors: [Devs.Ven, Devs.Commandtechno, Devs.Apexo],
settings,
patches: [
@@ -82,6 +87,6 @@ export default definePlugin({
predicate: () => settings.store.toolTip
}
],
- render: ErrorBoundary.wrap(MemberCount, { noop: true }),
+ render: ErrorBoundary.wrap(() => , { noop: true }),
renderTooltip: ErrorBoundary.wrap(guild => , { noop: true })
});
diff --git a/src/plugins/memberCount/style.css b/src/plugins/memberCount/style.css
index f43bff83..2b0e4400 100644
--- a/src/plugins/memberCount/style.css
+++ b/src/plugins/memberCount/style.css
@@ -1,9 +1,11 @@
.vc-membercount-widget {
+ gap: 0.85em;
display: flex;
align-content: center;
--color-online: var(--green-360);
--color-total: var(--primary-400);
+ --color-voice: var(--primary-400);
}
.vc-membercount-tooltip {
@@ -13,10 +15,17 @@
.vc-membercount-member-list {
justify-content: center;
+ flex-wrap: wrap;
margin-top: 1em;
padding-inline: 1em;
}
+.vc-membercount-container {
+ display: flex;
+ align-items: center;
+ gap: 0.5em;
+}
+
.vc-membercount-online {
color: var(--color-online);
}
@@ -25,13 +34,16 @@
color: var(--color-total);
}
+.vc-membercount-voice {
+ color: var(--color-voice);
+}
+
.vc-membercount-online-dot {
background-color: var(--color-online);
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
- margin-right: 0.5em;
}
.vc-membercount-total-dot {
@@ -40,5 +52,10 @@
height: 6px;
border-radius: 50%;
border: 3px solid var(--color-total);
- margin: 0 0.5em 0 1em;
}
+
+.vc-membercount-voice-icon {
+ color: var(--color-voice);
+ width: 15px;
+ height: 15px;
+}
\ No newline at end of file
diff --git a/src/plugins/userVoiceShow/components.tsx b/src/plugins/userVoiceShow/components.tsx
index 9d1c7211..b7907978 100644
--- a/src/plugins/userVoiceShow/components.tsx
+++ b/src/plugins/userVoiceShow/components.tsx
@@ -8,8 +8,8 @@ import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { classes } from "@utils/misc";
import { Channel } from "@vencord/discord-types";
-import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy, mapMangledModuleLazy } from "@webpack";
-import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores } from "@webpack/common";
+import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, mapMangledModuleLazy } from "@webpack";
+import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common";
const cl = classNameFactory("vc-uvs-");
@@ -18,7 +18,6 @@ const { useChannelName } = mapMangledModuleLazy("#{intl::GROUP_DM_ALONE}", {
useChannelName: filters.byCode("()=>null==")
});
const getDMChannelIcon = findByCodeLazy(".getChannelIconURL({");
-const VoiceStateStore = findStoreLazy("VoiceStateStore");
const Avatar = findComponentByCodeLazy(".status)/2):0");
const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL");
@@ -84,7 +83,7 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) {
const voiceStates = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStatesForChannel(channel.id));
const users = useMemo(
- () => Object.values(voiceStates).map(voiceState => UserStore.getUser(voiceState.userId)).filter(user => user != null),
+ () => Object.values(voiceStates).map(voiceState => UserStore.getUser(voiceState.userId)).filter(user => user != null),
[voiceStates]
);
@@ -139,7 +138,7 @@ export interface VoiceChannelIndicatorProps {
const clickTimers = {} as Record;
export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
- const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined);
+ const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId);
const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId);
if (channel == null) return null;
diff --git a/src/plugins/vcNarrator/index.tsx b/src/plugins/vcNarrator/index.tsx
index 8931efe3..5b7b3f82 100644
--- a/src/plugins/vcNarrator/index.tsx
+++ b/src/plugins/vcNarrator/index.tsx
@@ -22,13 +22,12 @@ import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { wordsToTitle } from "@utils/text";
import definePlugin, { ReporterTestable } from "@utils/types";
-import { findByPropsLazy } from "@webpack";
-import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common";
+import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore, VoiceStateStore } from "@webpack/common";
import { ReactElement } from "react";
import { getCurrentVoice, settings } from "./settings";
-interface VoiceState {
+interface VoiceStateChangeEvent {
userId: string;
channelId?: string;
oldChannelId?: string;
@@ -38,8 +37,6 @@ interface VoiceState {
selfMute: boolean;
}
-const VoiceStateStore = findByPropsLazy("getVoiceStatesForChannel", "getCurrentClientVoiceChannelId");
-
// Mute/Deaf for other people than you is commented out, because otherwise someone can spam it and it will be annoying
// Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would
// not say the second mute, which would lead you to believe they're unmuted
@@ -88,7 +85,7 @@ let StatusMap = {} as Record WindowStore = m);
waitForStore("EmojiStore", m => EmojiStore = m);
waitForStore("StickersStore", m => StickersStore = m);
waitForStore("TypingStore", m => TypingStore = m);
+waitForStore("VoiceStateStore", m => VoiceStateStore = m);
waitForStore("ThemeStore", m => {
ThemeStore = m;
// Importing this directly can easily cause circular imports. For this reason, use a non import access here.
From e857f6806fa181596c160ce867b6b90e8a3895eb Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Fri, 5 Sep 2025 03:55:38 +0200
Subject: [PATCH 081/116] ShowMeYourName: respect streamer mode
---
.../discord-types/src/stores/StreamerModeStore.d.ts | 11 +++++++++++
packages/discord-types/src/stores/index.d.ts | 1 +
src/plugins/showMeYourName/index.tsx | 6 ++++--
src/webpack/common/stores.ts | 2 ++
4 files changed, 18 insertions(+), 2 deletions(-)
create mode 100644 packages/discord-types/src/stores/StreamerModeStore.d.ts
diff --git a/packages/discord-types/src/stores/StreamerModeStore.d.ts b/packages/discord-types/src/stores/StreamerModeStore.d.ts
new file mode 100644
index 00000000..efa6cfc9
--- /dev/null
+++ b/packages/discord-types/src/stores/StreamerModeStore.d.ts
@@ -0,0 +1,11 @@
+import { FluxStore } from "@vencord/discord-types";
+
+export class StreamerModeStore extends FluxStore {
+ get autoToggle(): boolean;
+ get disableNotifications(): boolean;
+ get disableSounds(): boolean;
+ get enableContentProtection(): boolean;
+ get enabled(): boolean;
+ get hideInstantInvites(): boolean;
+ get hidePersonalInformation(): boolean;
+}
diff --git a/packages/discord-types/src/stores/index.d.ts b/packages/discord-types/src/stores/index.d.ts
index 5906262a..a843445b 100644
--- a/packages/discord-types/src/stores/index.d.ts
+++ b/packages/discord-types/src/stores/index.d.ts
@@ -12,6 +12,7 @@ export * from "./RelationshipStore";
export * from "./SelectedChannelStore";
export * from "./SelectedGuildStore";
export * from "./StickersStore";
+export * from "./StreamerModeStore";
export * from "./ThemeStore";
export * from "./TypingStore";
export * from "./UserProfileStore";
diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx
index 623c70ca..24a1121d 100644
--- a/src/plugins/showMeYourName/index.tsx
+++ b/src/plugins/showMeYourName/index.tsx
@@ -11,7 +11,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Channel, Message, User } from "@vencord/discord-types";
-import { RelationshipStore } from "@webpack/common";
+import { RelationshipStore, StreamerModeStore } from "@webpack/common";
interface UsernameProps {
author: { nick: string; authorId: string; };
@@ -74,7 +74,9 @@ export default definePlugin({
const { mode, friendNicknames, displayNames, inReplies } = settings.store;
const user = userOverride ?? message.author;
- let { username } = user;
+ let username = StreamerModeStore.enabled
+ ? user.username[0] + "…"
+ : user.username;
if (displayNames)
username = user.globalName || username;
diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts
index b1877b08..1e3e5797 100644
--- a/src/webpack/common/stores.ts
+++ b/src/webpack/common/stores.ts
@@ -54,6 +54,7 @@ export let StickersStore: t.StickersStore;
export let ThemeStore: t.ThemeStore;
export let WindowStore: t.WindowStore;
export let DraftStore: t.DraftStore;
+export let StreamerModeStore: t.StreamerModeStore;
/**
* @see jsdoc of {@link t.useStateFromStores}
@@ -81,6 +82,7 @@ waitForStore("EmojiStore", m => EmojiStore = m);
waitForStore("StickersStore", m => StickersStore = m);
waitForStore("TypingStore", m => TypingStore = m);
waitForStore("VoiceStateStore", m => VoiceStateStore = m);
+waitForStore("StreamerModeStore", m => StreamerModeStore = m);
waitForStore("ThemeStore", m => {
ThemeStore = m;
// Importing this directly can easily cause circular imports. For this reason, use a non import access here.
From 26074b7f185013745da0656adb500b3fbe95fa4f Mon Sep 17 00:00:00 2001
From: u32
Date: Fri, 5 Sep 2025 03:04:08 +0100
Subject: [PATCH 082/116] MessageLatency: fix bot check (#3523)
---
src/plugins/messageLatency/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx
index cd331b9d..b81c55cf 100644
--- a/src/plugins/messageLatency/index.tsx
+++ b/src/plugins/messageLatency/index.tsx
@@ -98,7 +98,7 @@ export default definePlugin({
if (!isNonNullish(nonce)) return null;
// Bots basically never send a nonce, and if someone does do it then it's usually not a snowflake
- if (message.bot) return null;
+ if (message.author.bot) return null;
let isDiscordKotlin = false;
let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds
From 1cfc3fb8f808c5770c0175a2471c7eb5525800fd Mon Sep 17 00:00:00 2001
From: thororen <78185467+thororen1234@users.noreply.github.com>
Date: Sat, 6 Sep 2025 12:27:29 -0400
Subject: [PATCH 083/116] fix OnePingPerDM (#3648)
also removes a now obsolete patch from FakeNitro
---
src/plugins/fakeNitro/index.tsx | 3 +--
src/plugins/onePingPerDM/index.ts | 24 ++++++++++++++----------
2 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx
index 8dcb2300..2988f8fb 100644
--- a/src/plugins/fakeNitro/index.tsx
+++ b/src/plugins/fakeNitro/index.tsx
@@ -159,7 +159,6 @@ function makeBypassPatches(): Omit {
{ func: "canUseHighVideoUploadQuality", predicate: () => settings.store.enableStreamQualityBypass },
{ func: "canStreamQuality", predicate: () => settings.store.enableStreamQualityBypass },
{ func: "canUseClientThemes" },
- { func: "canUseCustomNotificationSounds" },
{ func: "canUsePremiumAppIcons" }
];
@@ -176,7 +175,7 @@ function makeBypassPatches(): Omit {
export default definePlugin({
name: "FakeNitro",
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN],
- description: "Allows you to stream in nitro quality, send fake emojis/stickers, use client themes and custom Discord notifications.",
+ description: "Allows you to stream in nitro quality, send fake emojis/stickers, and use client themes.",
dependencies: ["MessageEventsAPI"],
settings,
diff --git a/src/plugins/onePingPerDM/index.ts b/src/plugins/onePingPerDM/index.ts
index b6192fb6..fae787a8 100644
--- a/src/plugins/onePingPerDM/index.ts
+++ b/src/plugins/onePingPerDM/index.ts
@@ -38,17 +38,21 @@ export default definePlugin({
description: "If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit",
authors: [Devs.ProffDea],
settings,
- patches: [{
- find: ".getDesktopType()===",
- replacement: [{
- match: /(\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\)/,
- replace: "$&if(!$self.isPrivateChannelRead(arguments[0]?.message))return;else "
- },
+ patches: [
{
- match: /sound:(\i\?\i:void 0,soundpack:\i,volume:\i,onClick)/,
- replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
- }]
- }],
+ find: ".getDesktopType()===",
+ replacement: [
+ {
+ match: /(\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\)/,
+ replace: "$&if(!$self.isPrivateChannelRead(arguments[0]?.message))return;else "
+ },
+ {
+ match: /sound:(\i\?\i:void 0,volume:\i,onClick)/,
+ replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
+ }
+ ]
+ }
+ ],
isPrivateChannelRead(message: MessageJSON) {
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
if (
From efecbae75b17f8b1ccb72da8e6c67642f2bb87af Mon Sep 17 00:00:00 2001
From: nin0
Date: Sun, 7 Sep 2025 20:14:38 -0400
Subject: [PATCH 084/116] LastFMRPC: add setting to show artist/song name in
member list (#3629)
---
src/plugins/lastfmRichPresence/index.tsx | 25 ++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/src/plugins/lastfmRichPresence/index.tsx b/src/plugins/lastfmRichPresence/index.tsx
index 77fa2784..abf42d5f 100644
--- a/src/plugins/lastfmRichPresence/index.tsx
+++ b/src/plugins/lastfmRichPresence/index.tsx
@@ -47,6 +47,7 @@ interface Activity {
buttons?: Array;
name: string;
application_id: string;
+ status_display_type?: number;
metadata?: {
button_urls?: Array;
};
@@ -134,6 +135,25 @@ const settings = definePluginSettings({
type: OptionType.STRING,
default: "some music",
},
+ statusDisplayType: {
+ description: "Show the track / artist name in the member list",
+ type: OptionType.SELECT,
+ options: [
+ {
+ label: "Don't show (shows generic listening message)",
+ value: "off",
+ default: true
+ },
+ {
+ label: "Show artist name",
+ value: "artist"
+ },
+ {
+ label: "Show track name",
+ value: "track"
+ }
+ ]
+ },
nameFormat: {
description: "Show name of song and artist in status name",
type: OptionType.SELECT,
@@ -346,6 +366,11 @@ export default definePlugin({
details: trackData.name,
state: trackData.artist,
+ status_display_type: {
+ "off": 0,
+ "artist": 1,
+ "track": 2
+ }[settings.store.statusDisplayType],
assets,
buttons: buttons.length ? buttons.map(v => v.label) : undefined,
From b225f2ec6cbb29f565bc3379254cd846cb259e2e Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Mon, 8 Sep 2025 02:51:04 +0200
Subject: [PATCH 085/116] SpotifyControls: add copy song/artist/album name
options
---
.../spotifyControls/PlayerComponent.tsx | 45 +++++++++----------
1 file changed, 21 insertions(+), 24 deletions(-)
diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx
index 32c78c36..dabd31dc 100644
--- a/src/plugins/spotifyControls/PlayerComponent.tsx
+++ b/src/plugins/spotifyControls/PlayerComponent.tsx
@@ -21,7 +21,7 @@ import "./spotifyStyles.css";
import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Flex } from "@components/Flex";
-import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
+import { CopyIcon, ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
import { debounce } from "@shared/debounce";
import { openImageModal } from "@utils/discord";
import { classes, copyWithToast } from "@utils/misc";
@@ -76,27 +76,28 @@ function Button(props: React.ButtonHTMLAttributes) {
);
}
-function CopyContextMenu({ name, path }: { name: string; path: string; }) {
- const copyId = `spotify-copy-${name}`;
- const openId = `spotify-open-${name}`;
-
+function CopyContextMenu({ name, type, path }: { type: string; name: string; path: string; }) {
return (
FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
- aria-label={`Spotify ${name} Menu`}
+ navId="vc-spotify-menu"
+ onClose={ContextMenuApi.closeContextMenu}
+ aria-label={`Spotify ${type} Menu`}
>
copyWithToast(name)}
+ icon={CopyIcon}
+ />
+ copyWithToast("https://open.spotify.com" + path)}
icon={LinkIcon}
/>
SpotifyStore.openExternal(path)}
icon={OpenExternalIcon}
/>
@@ -104,11 +105,6 @@ function CopyContextMenu({ name, path }: { name: string; path: string; }) {
);
}
-function makeContextMenu(name: string, path: string) {
- return (e: React.MouseEvent) =>
- ContextMenuApi.openContextMenu(e, () => );
-}
-
function Controls() {
const [isPlaying, shuffle, repeat] = useStateFromStores(
[SpotifyStore],
@@ -259,13 +255,14 @@ function AlbumContextMenu({ track }: { track: Track; }) {
);
}
-function makeLinkProps(name: string, condition: unknown, path: string) {
+function makeLinkProps(type: "Song" | "Artist" | "Album", condition: unknown, name: string, path: string) {
if (!condition) return {};
return {
role: "link",
onClick: () => SpotifyStore.openExternal(path),
- onContextMenu: makeContextMenu(name, path)
+ onContextMenu: e =>
+ ContextMenuApi.openContextMenu(e, () => )
} satisfies React.HTMLAttributes;
}
@@ -306,7 +303,7 @@ function Info({ track }: { track: Track; }) {
id={cl("song-title")}
className={cl("ellipoverflow")}
title={track.name}
- {...makeLinkProps("Song", track.id, `/track/${track.id}`)}
+ {...makeLinkProps("Song", track.id, track.name, `/track/${track.id}`)}
>
{track.name}
@@ -319,7 +316,7 @@ function Info({ track }: { track: Track; }) {
className={cl("artist")}
style={{ fontSize: "inherit" }}
title={a.name}
- {...makeLinkProps("Artist", a.id, `/artist/${a.id}`)}
+ {...makeLinkProps("Artist", a.id, a.name, `/artist/${a.id}`)}
>
{a.name}
@@ -336,7 +333,7 @@ function Info({ track }: { track: Track; }) {
className={cl("album")}
style={{ fontSize: "inherit" }}
title={track.album.name}
- {...makeLinkProps("Album", track.album.id, `/album/${track.album.id}`)}
+ {...makeLinkProps("Album", track.album.id, track.album.name, `/album/${track.album.id}`)}
>
{track.album.name}
From a4e1d026ea3d630ec990882fcc225f4e7f28ee03 Mon Sep 17 00:00:00 2001
From: ww <153429803+q1werasd1@users.noreply.github.com>
Date: Mon, 8 Sep 2025 04:30:08 +0300
Subject: [PATCH 086/116] VolumeBooster: make multiplier option more flexible
(#3656)
---
src/plugins/volumeBooster/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/volumeBooster/index.ts b/src/plugins/volumeBooster/index.ts
index 664a59dd..81dcf663 100644
--- a/src/plugins/volumeBooster/index.ts
+++ b/src/plugins/volumeBooster/index.ts
@@ -24,7 +24,7 @@ const settings = definePluginSettings({
multiplier: {
description: "Volume Multiplier",
type: OptionType.SLIDER,
- markers: makeRange(1, 5, 1),
+ markers: makeRange(1, 5, 0.5),
default: 2,
stickToMarkers: true,
}
From 84957b0e887fe1fa68bc6bf3325b44d1ba07a3f1 Mon Sep 17 00:00:00 2001
From: Ryan Cao <70191398+ryanccn@users.noreply.github.com>
Date: Mon, 8 Sep 2025 22:49:24 +0000
Subject: [PATCH 087/116] improve `wordsFromCamel` correctness (#3621)
Co-authored-by: V
---
src/utils/text.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/utils/text.ts b/src/utils/text.ts
index 2e85af4e..a48c140d 100644
--- a/src/utils/text.ts
+++ b/src/utils/text.ts
@@ -21,7 +21,8 @@ import { moment } from "@webpack/common";
// Utils for readable text transformations eg: `toTitle(fromKebab())`
// Case style to words
-export const wordsFromCamel = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase());
+export const wordsFromCamel = (text: string) =>
+ text.split(/(?=[A-Z][a-z])|(?<=[a-z])(?=[A-Z])/).map(w => /^[A-Z]{2,}$/.test(w) ? w : w.toLowerCase());
export const wordsFromSnake = (text: string) => text.toLowerCase().split("_");
export const wordsFromKebab = (text: string) => text.toLowerCase().split("-");
export const wordsFromPascal = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase());
From c38aac23fd1e1f4acf171479fe4beb17d8fe4ae9 Mon Sep 17 00:00:00 2001
From: V
Date: Tue, 9 Sep 2025 01:48:53 +0200
Subject: [PATCH 088/116] improve various types (#3663)
Co-authored-by: V
Co-authored-by: John Davis <70701251+gobuster@users.noreply.github.com>
Co-authored-by: sadan4 <117494111+sadan4@users.noreply.github.com>
---
packages/discord-types/enums/index.ts | 3 +-
packages/discord-types/enums/misc.ts | 4 +
packages/discord-types/src/index.d.ts | 1 +
.../src/modules/CloudUpload.d.ts | 74 +++++++++++++++++++
packages/discord-types/src/modules/index.d.ts | 1 +
src/api/MessageEvents.ts | 28 +------
src/api/{Notices.ts => Notices.tsx} | 11 ++-
src/plugins/anonymiseFileNames/index.tsx | 6 +-
src/plugins/previewMessage/index.tsx | 6 +-
src/plugins/voiceMessages/index.tsx | 8 +-
src/utils/discord.tsx | 16 +++-
src/utils/react.tsx | 10 ++-
12 files changed, 128 insertions(+), 40 deletions(-)
create mode 100644 packages/discord-types/enums/misc.ts
create mode 100644 packages/discord-types/src/modules/CloudUpload.d.ts
create mode 100644 packages/discord-types/src/modules/index.d.ts
rename src/api/{Notices.ts => Notices.tsx} (73%)
diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts
index 0ab49887..3d80895c 100644
--- a/packages/discord-types/enums/index.ts
+++ b/packages/discord-types/enums/index.ts
@@ -1,3 +1,4 @@
+export * from "./channel";
export * from "./commands";
export * from "./messages";
-export * from "./channel";
\ No newline at end of file
+export * from "./misc";
diff --git a/packages/discord-types/enums/misc.ts b/packages/discord-types/enums/misc.ts
new file mode 100644
index 00000000..d1419ba3
--- /dev/null
+++ b/packages/discord-types/enums/misc.ts
@@ -0,0 +1,4 @@
+export const enum CloudUploadPlatform {
+ REACT_NATIVE = 0,
+ WEB = 1,
+}
diff --git a/packages/discord-types/src/index.d.ts b/packages/discord-types/src/index.d.ts
index 6d9356b4..fdbd65f1 100644
--- a/packages/discord-types/src/index.d.ts
+++ b/packages/discord-types/src/index.d.ts
@@ -4,6 +4,7 @@ export * from "./components";
export * from "./flux";
export * from "./fluxEvents";
export * from "./menu";
+export * from "./modules";
export * from "./stores";
export * from "./utils";
export * as Webpack from "../webpack";
diff --git a/packages/discord-types/src/modules/CloudUpload.d.ts b/packages/discord-types/src/modules/CloudUpload.d.ts
new file mode 100644
index 00000000..02192ed5
--- /dev/null
+++ b/packages/discord-types/src/modules/CloudUpload.d.ts
@@ -0,0 +1,74 @@
+import EventEmitter from "events";
+import { CloudUploadPlatform } from "../../enums";
+
+interface BaseUploadItem {
+ platform: CloudUploadPlatform;
+ id?: string;
+ origin?: string;
+ isThumbnail?: boolean;
+ clip?: unknown;
+}
+
+export interface ReactNativeUploadItem extends BaseUploadItem {
+ platform: CloudUploadPlatform.REACT_NATIVE;
+ uri: string;
+ filename?: string;
+ mimeType?: string;
+ durationSecs?: number;
+ waveform?: string;
+ isRemix?: boolean;
+}
+
+export interface WebUploadItem extends BaseUploadItem {
+ platform: CloudUploadPlatform.WEB;
+ file: File;
+}
+
+export type CloudUploadItem = ReactNativeUploadItem | WebUploadItem;
+
+export class CloudUpload extends EventEmitter {
+ constructor(item: CloudUploadItem, channelId: string, showLargeMessageDialog?: boolean, reactNativeFileIndex?: number);
+
+ channelId: string;
+ classification: string;
+ clip: unknown;
+ contentHash: unknown;
+ currentSize: number;
+ description: string | null;
+ durationSecs: number | undefined;
+ etag: string | undefined;
+ error: unknown;
+ filename: string;
+ id: string;
+ isImage: boolean;
+ isRemix: boolean | undefined;
+ isThumbnail: boolean;
+ isVideo: boolean;
+ item: {
+ file: File;
+ platform: CloudUploadPlatform;
+ origin: string;
+ };
+ loaded: number;
+ mimeType: string;
+ origin: string;
+ postCompressionSize: number | undefined;
+ preCompressionSize: number;
+ responseUrl: string;
+ sensitive: boolean;
+ showLargeMessageDialog: boolean;
+ spoiler: boolean;
+ startTime: number;
+ status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED" | "REMOVED_FROM_MSG_DRAFT";
+ uniqueId: string;
+ uploadedFilename: string;
+ waveform: string | undefined;
+
+ // there are many more methods than just these but I didn't find them particularly useful
+ upload(): Promise;
+ cancel(): void;
+ delete(): Promise;
+ getSize(): number;
+ maybeConvertToWebP(): Promise;
+ removeFromMsgDraft(): void;
+}
diff --git a/packages/discord-types/src/modules/index.d.ts b/packages/discord-types/src/modules/index.d.ts
new file mode 100644
index 00000000..fe0c1e24
--- /dev/null
+++ b/packages/discord-types/src/modules/index.d.ts
@@ -0,0 +1 @@
+export * from "./CloudUpload";
diff --git a/src/api/MessageEvents.ts b/src/api/MessageEvents.ts
index 6e752c90..54d40000 100644
--- a/src/api/MessageEvents.ts
+++ b/src/api/MessageEvents.ts
@@ -17,7 +17,7 @@
*/
import { Logger } from "@utils/Logger";
-import type { Channel, CustomEmoji, Message } from "@vencord/discord-types";
+import type { Channel, CloudUpload, CustomEmoji, Message } from "@vencord/discord-types";
import { MessageStore } from "@webpack/common";
import type { Promisable } from "type-fest";
@@ -30,30 +30,6 @@ export interface MessageObject {
tts: boolean;
}
-export interface Upload {
- classification: string;
- currentSize: number;
- description: string | null;
- filename: string;
- id: string;
- isImage: boolean;
- isVideo: boolean;
- item: {
- file: File;
- platform: number;
- };
- loaded: number;
- mimeType: string;
- preCompressionSize: number;
- responseUrl: string;
- sensitive: boolean;
- showLargeMessageDialog: boolean;
- spoiler: boolean;
- status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED";
- uniqueId: string;
- uploadedFilename: string;
-}
-
export interface MessageReplyOptions {
messageReference: Message["messageReference"];
allowedMentions?: {
@@ -64,7 +40,7 @@ export interface MessageReplyOptions {
export interface MessageOptions {
stickers?: string[];
- uploads?: Upload[];
+ uploads?: CloudUpload[];
replyOptions: MessageReplyOptions;
content: string;
channel: Channel;
diff --git a/src/api/Notices.ts b/src/api/Notices.tsx
similarity index 73%
rename from src/api/Notices.ts
rename to src/api/Notices.tsx
index 6d20087a..b4c44b88 100644
--- a/src/api/Notices.ts
+++ b/src/api/Notices.tsx
@@ -16,7 +16,10 @@
* along with this program. If not, see .
*/
+import ErrorBoundary from "@components/ErrorBoundary";
+import { isPrimitiveReactNode } from "@utils/react";
import { waitFor } from "@webpack";
+import { ReactNode } from "react";
let NoticesModule: any;
waitFor(m => m.show && m.dismiss && !m.suppressAll, m => NoticesModule = m);
@@ -36,7 +39,11 @@ export function nextNotice() {
}
}
-export function showNotice(message: string, buttonText: string, onOkClick: () => void) {
- noticesQueue.push(["GENERIC", message, buttonText, onOkClick]);
+export function showNotice(message: ReactNode, buttonText: string, onOkClick: () => void) {
+ const notice = isPrimitiveReactNode(message)
+ ? message
+ : "Error Showing Notice"}>{message} ;
+
+ noticesQueue.push(["GENERIC", notice, buttonText, onOkClick]);
if (!currentNotice) nextNotice();
}
diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx
index 8237be8b..62cd7796 100644
--- a/src/plugins/anonymiseFileNames/index.tsx
+++ b/src/plugins/anonymiseFileNames/index.tsx
@@ -16,11 +16,11 @@
* along with this program. If not, see .
*/
-import { Upload } from "@api/MessageEvents";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
+import { CloudUpload } from "@vencord/discord-types";
import { findByCodeLazy } from "@webpack";
import { useState } from "@webpack/common";
@@ -89,7 +89,7 @@ export default definePlugin({
},
],
- AnonymiseUploadButton: ErrorBoundary.wrap(({ upload }: { upload: Upload; }) => {
+ AnonymiseUploadButton: ErrorBoundary.wrap(({ upload }: { upload: CloudUpload; }) => {
const [anonymise, setAnonymise] = useState(upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault);
function onToggleAnonymise() {
@@ -110,7 +110,7 @@ export default definePlugin({
);
}, { noop: true }),
- anonymise(upload: Upload) {
+ anonymise(upload: CloudUpload) {
if ((upload[ANONYMISE_UPLOAD_SYMBOL] ?? settings.store.anonymiseByDefault) === false) {
return;
}
diff --git a/src/plugins/previewMessage/index.tsx b/src/plugins/previewMessage/index.tsx
index 1f64c714..6bc9d25f 100644
--- a/src/plugins/previewMessage/index.tsx
+++ b/src/plugins/previewMessage/index.tsx
@@ -20,7 +20,7 @@ import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { generateId, sendBotMessage } from "@api/Commands";
import { Devs } from "@utils/constants";
import definePlugin, { StartAt } from "@utils/types";
-import { MessageAttachment } from "@vencord/discord-types";
+import { CloudUpload, MessageAttachment } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack";
import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common";
@@ -45,7 +45,7 @@ const getImageBox = (url: string): Promise<{ width: number, height: number; } |
const getAttachments = async (channelId: string) =>
await Promise.all(
UploadStore.getUploads(channelId, DraftType.ChannelMessage)
- .map(async (upload: any) => {
+ .map(async (upload: CloudUpload) => {
const { isImage, filename, spoiler, item: { file } } = upload;
const url = URL.createObjectURL(file);
const attachment: MessageAttachment = {
@@ -53,7 +53,7 @@ const getAttachments = async (channelId: string) =>
filename: spoiler ? "SPOILER_" + filename : filename,
// weird eh? if i give it the normal content type the preview doenst work
content_type: undefined,
- size: await upload.getSize(),
+ size: upload.getSize(),
spoiler,
// discord adds query params to the url, so we need to add a hash to prevent that
url: url + "#",
diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx
index 74f39ea3..ddb395e4 100644
--- a/src/plugins/voiceMessages/index.tsx
+++ b/src/plugins/voiceMessages/index.tsx
@@ -27,6 +27,8 @@ import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModa
import { useAwaiter } from "@utils/react";
import definePlugin from "@utils/types";
import { chooseFile } from "@utils/web";
+import { CloudUpload as TCloudUpload } from "@vencord/discord-types";
+import { CloudUploadPlatform } from "@vencord/discord-types/enums";
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { Button, Card, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
import { ComponentType } from "react";
@@ -37,7 +39,7 @@ import { cl } from "./utils";
import { VoicePreview } from "./VoicePreview";
import { VoiceRecorderWeb } from "./WebRecorder";
-const CloudUpload = findLazy(m => m.prototype?.trackUploadFinished);
+const CloudUpload: typeof TCloudUpload = findLazy(m => m.prototype?.trackUploadFinished);
const PendingReplyStore = findStoreLazy("PendingReplyStore");
const OptionClasses = findByPropsLazy("optionName", "optionIcon", "optionLabel");
@@ -92,8 +94,8 @@ function sendAudio(blob: Blob, meta: AudioMetadata) {
const upload = new CloudUpload({
file: new File([blob], "voice-message.ogg", { type: "audio/ogg; codecs=opus" }),
isThumbnail: false,
- platform: 1,
- }, channelId, false, 0);
+ platform: CloudUploadPlatform.WEB,
+ }, channelId);
upload.on("complete", () => {
RestAPI.post({
diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx
index f236943b..83713625 100644
--- a/src/utils/discord.tsx
+++ b/src/utils/discord.tsx
@@ -17,7 +17,7 @@
*/
import { MessageObject } from "@api/MessageEvents";
-import { Channel, Guild, GuildFeatures, Message, User } from "@vencord/discord-types";
+import { Channel, CloudUpload, Guild, GuildFeatures, Message, User } from "@vencord/discord-types";
import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
import { Except } from "type-fest";
@@ -117,6 +117,20 @@ interface MessageOptions {
replied_user: boolean;
};
stickerIds: string[];
+ attachmentsToUpload: CloudUpload[];
+ poll: {
+ allow_multiselect: boolean;
+ answers: Array<{
+ poll_media: {
+ text: string;
+ attachment_ids?: unknown;
+ emoji?: { name: string; id?: string; };
+ };
+ }>;
+ duration: number;
+ layout_type: number;
+ question: { text: string; };
+ };
}
export function sendMessage(
diff --git a/src/utils/react.tsx b/src/utils/react.tsx
index 146725c5..8963bdf5 100644
--- a/src/utils/react.tsx
+++ b/src/utils/react.tsx
@@ -17,7 +17,7 @@
*/
import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common";
-import { ActionDispatch } from "react";
+import { ActionDispatch, ReactNode } from "react";
import { checkIntersecting } from "./misc";
@@ -25,6 +25,14 @@ export * from "./lazyReact";
export const NoopComponent = () => null;
+/**
+ * Check if a React node is a primitive (string, number, bigint, boolean, undefined)
+ */
+export function isPrimitiveReactNode(node: ReactNode): boolean {
+ const t = typeof node;
+ return t === "string" || t === "number" || t === "bigint" || t === "boolean" || t === "undefined";
+}
+
/**
* Check if an element is on screen
* @param intersectOnly If `true`, will only update the state when the element comes into view
From 0f29eab3ea668b4d1dca39bee827b3e5e938d97d Mon Sep 17 00:00:00 2001
From: thororen <78185467+thororen1234@users.noreply.github.com>
Date: Mon, 8 Sep 2025 20:03:12 -0400
Subject: [PATCH 089/116] MemberCount: use circle svg instead of css hacks
(#3653)
fixes some deformations
---
src/plugins/memberCount/CircleIcon.tsx | 17 +++++++++++++++++
src/plugins/memberCount/MemberCount.tsx | 5 +++--
src/plugins/memberCount/index.tsx | 1 -
src/plugins/memberCount/style.css | 23 +++++++++++------------
4 files changed, 31 insertions(+), 15 deletions(-)
create mode 100644 src/plugins/memberCount/CircleIcon.tsx
diff --git a/src/plugins/memberCount/CircleIcon.tsx b/src/plugins/memberCount/CircleIcon.tsx
new file mode 100644
index 00000000..bf6604a4
--- /dev/null
+++ b/src/plugins/memberCount/CircleIcon.tsx
@@ -0,0 +1,17 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2025 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+export function CircleIcon({ className }: { className?: string; }) {
+ return (
+
+
+
+ );
+}
diff --git a/src/plugins/memberCount/MemberCount.tsx b/src/plugins/memberCount/MemberCount.tsx
index 02033886..6887af51 100644
--- a/src/plugins/memberCount/MemberCount.tsx
+++ b/src/plugins/memberCount/MemberCount.tsx
@@ -9,6 +9,7 @@ import { isObjectEmpty } from "@utils/misc";
import { ChannelStore, PermissionsBits, PermissionStore, SelectedChannelStore, Tooltip, useEffect, useStateFromStores, VoiceStateStore } from "@webpack/common";
import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat, settings, ThreadMemberListStore } from ".";
+import { CircleIcon } from "./CircleIcon";
import { OnlineMemberCountStore } from "./OnlineMemberCountStore";
import { VoiceIcon } from "./VoiceIcon";
@@ -80,7 +81,7 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t
{props => (
-
+
{formattedOnlineCount}
)}
@@ -88,7 +89,7 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t
{props => (
-
+
{numberFormat(totalCount)}
)}
diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx
index bb48eedb..e2d78e2b 100644
--- a/src/plugins/memberCount/index.tsx
+++ b/src/plugins/memberCount/index.tsx
@@ -36,7 +36,6 @@ export const ThreadMemberListStore = findStoreLazy("ThreadMemberListStore") as F
getMemberListSections(channelId?: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; };
};
-
export const settings = definePluginSettings({
toolTip: {
type: OptionType.BOOLEAN,
diff --git a/src/plugins/memberCount/style.css b/src/plugins/memberCount/style.css
index 2b0e4400..009e7d10 100644
--- a/src/plugins/memberCount/style.css
+++ b/src/plugins/memberCount/style.css
@@ -18,6 +18,7 @@
flex-wrap: wrap;
margin-top: 1em;
padding-inline: 1em;
+ line-height: 1.2em;
}
.vc-membercount-container {
@@ -38,20 +39,18 @@
color: var(--color-voice);
}
-.vc-membercount-online-dot {
- background-color: var(--color-online);
- display: inline-block;
- width: 12px;
- height: 12px;
- border-radius: 50%;
+.vc-membercount-online-count {
+ fill: var(--status-online);
+ width: 18px;
+ height: 18px;
}
-.vc-membercount-total-dot {
- display: inline-block;
- width: 6px;
- height: 6px;
- border-radius: 50%;
- border: 3px solid var(--color-total);
+.vc-membercount-total-count {
+ fill: none;
+ stroke: var(--status-offline);
+ stroke-width: 4px;
+ width: 15px;
+ height: 15px;
}
.vc-membercount-voice-icon {
From 4c7acbbbc79a69416288ce0aef84af9ca5c4d444 Mon Sep 17 00:00:00 2001
From: thororen1234 <78185467+thororen1234@users.noreply.github.com>
Date: Fri, 5 Sep 2025 14:57:43 -0400
Subject: [PATCH 090/116] fix NoPendingCount
---
src/plugins/noPendingCount/index.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/noPendingCount/index.ts b/src/plugins/noPendingCount/index.ts
index 914deaa0..2d860dcf 100644
--- a/src/plugins/noPendingCount/index.ts
+++ b/src/plugins/noPendingCount/index.ts
@@ -87,7 +87,7 @@ export default definePlugin({
replacement: {
// The two groups inside the first group grab the minified names of the variables,
// they are then referenced later to find unviewedTrialCount + unviewedDiscountCount.
- match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,300}\i=)\1\+\2/,
+ match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,300})\1\+\2/,
replace: "0"
}
}
From dc72ee3809d764982059e2fa60948aeb49bd3aa6 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Tue, 9 Sep 2025 02:23:57 +0200
Subject: [PATCH 091/116] GameActivityToggle: fix overflow
---
src/plugins/gameActivityToggle/style.css | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/plugins/gameActivityToggle/style.css b/src/plugins/gameActivityToggle/style.css
index e13e2425..040212ce 100644
--- a/src/plugins/gameActivityToggle/style.css
+++ b/src/plugins/gameActivityToggle/style.css
@@ -1,3 +1,4 @@
-[class^="panels"] [class^="avatarWrapper"] {
- min-width: 88px;
-}
+/* make gap smaller since we're adding an extra button */
+[class^="panels"] [class^="buttons"] {
+ gap: 4px;
+}
\ No newline at end of file
From 51c23ff7968fa9d15863a8fbf806d7ffdbfe5300 Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Mon, 8 Sep 2025 20:51:03 -0400
Subject: [PATCH 092/116] FakeNitro: Add custom client theme color picker
(#3534)
---
src/plugins/fakeNitro/index.tsx | 35 ++++++++++++++++-----------------
1 file changed, 17 insertions(+), 18 deletions(-)
diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx
index 2988f8fb..370d6857 100644
--- a/src/plugins/fakeNitro/index.tsx
+++ b/src/plugins/fakeNitro/index.tsx
@@ -174,8 +174,8 @@ function makeBypassPatches(): Omit {
export default definePlugin({
name: "FakeNitro",
- authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN],
- description: "Allows you to stream in nitro quality, send fake emojis/stickers, and use client themes.",
+ authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN, Devs.sadan],
+ description: "Allows you to send fake emojis/stickers, use nitro themes, and stream in nitro quality",
dependencies: ["MessageEventsAPI"],
settings,
@@ -273,6 +273,14 @@ export default definePlugin({
replace: (_, rest, backgroundGradientPresetId, originalCall, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme},()=>${originalCall});`
}
},
+ // Allow users to use custom client themes
+ {
+ find: "customUserThemeSettings:{",
+ replacement: {
+ match: /(?<=\i=)\(0,\i\.\i\)\(\i\.\i\.TIER_2\)(?=,|;)/g,
+ replace: "true"
+ }
+ },
{
find: '["strong","em","u","text","inlineCode","s","spoiler"]',
replacement: [
@@ -386,24 +394,15 @@ export default definePlugin({
if (premiumType !== 2) {
proto.appearance ??= AppearanceSettingsActionCreators.create();
- if (UserSettingsProtoStore.settings.appearance?.theme != null) {
- const appearanceSettingsDummy = AppearanceSettingsActionCreators.create({
- theme: UserSettingsProtoStore.settings.appearance.theme
- });
+ const protoStoreAppearenceSettings = UserSettingsProtoStore.settings.appearance;
- proto.appearance.theme = appearanceSettingsDummy.theme;
- }
+ const appearanceSettingsOverwrite = AppearanceSettingsActionCreators.create({
+ ...proto.appearance,
+ theme: protoStoreAppearenceSettings?.theme,
+ clientThemeSettings: protoStoreAppearenceSettings?.clientThemeSettings
+ });
- if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null) {
- const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators.create({
- backgroundGradientPresetId: {
- value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value
- }
- });
-
- proto.appearance.clientThemeSettings ??= clientThemeSettingsDummy;
- proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummy.backgroundGradientPresetId;
- }
+ proto.appearance = appearanceSettingsOverwrite;
}
} catch (err) {
new Logger("FakeNitro").error(err);
From 4e8d22b4d56e3646c67d30b692b3f5c1407b5919 Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Mon, 8 Sep 2025 22:01:16 -0300
Subject: [PATCH 093/116] NoPendingCount: Improve slow patch
---
src/plugins/noPendingCount/index.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/plugins/noPendingCount/index.ts b/src/plugins/noPendingCount/index.ts
index 2d860dcf..1e963d9f 100644
--- a/src/plugins/noPendingCount/index.ts
+++ b/src/plugins/noPendingCount/index.ts
@@ -87,8 +87,8 @@ export default definePlugin({
replacement: {
// The two groups inside the first group grab the minified names of the variables,
// they are then referenced later to find unviewedTrialCount + unviewedDiscountCount.
- match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,300})\1\+\2/,
- replace: "0"
+ match: /(\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.+?\i)=\1\+\2/,
+ replace: (_, rest) => `${rest}=0`
}
}
],
From 50eb62045a329a8e1208ebbe26bb4ff232538f4d Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Mon, 8 Sep 2025 22:18:39 -0300
Subject: [PATCH 094/116] Fix FavoriteGifSearch & broken Menu component find
---
src/plugins/favGifSearch/index.tsx | 8 +-------
src/webpack/common/menu.ts | 2 +-
2 files changed, 2 insertions(+), 8 deletions(-)
diff --git a/src/plugins/favGifSearch/index.tsx b/src/plugins/favGifSearch/index.tsx
index d88ced34..7ab0145e 100644
--- a/src/plugins/favGifSearch/index.tsx
+++ b/src/plugins/favGifSearch/index.tsx
@@ -20,13 +20,11 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
-import { findByPropsLazy } from "@webpack";
import { useCallback, useEffect, useRef, useState } from "@webpack/common";
interface SearchBarComponentProps {
- ref?: React.MutableRefObject;
+ ref?: React.RefObject;
autoFocus: boolean;
- className: string;
size: string;
onChange: (query: string) => void;
onClear: () => void;
@@ -59,9 +57,6 @@ interface Instance {
forceUpdate: () => void;
}
-
-const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchBarFullRow");
-
export const settings = definePluginSettings({
searchOption: {
type: OptionType.SELECT,
@@ -181,7 +176,6 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc
{
diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts
index e4c5d0a3..7e5447ce 100644
--- a/src/webpack/common/menu.ts
+++ b/src/webpack/common/menu.ts
@@ -42,7 +42,7 @@ waitFor(m => m.name === "MenuCheckboxItem", (_, id) => {
waitFor(filters.componentByCode('path:["empty"]'), m => Menu.Menu = m);
waitFor(filters.componentByCode("sliderContainer", "slider", "handleSize:16", "=100"), m => Menu.MenuSliderControl = m);
-waitFor(filters.componentByCode('role:"searchbox', "top:2", "query:"), m => Menu.MenuSearchControl = m);
+waitFor(filters.componentByCode(".SEARCH)", ".focus()", "query:"), m => Menu.MenuSearchControl = m);
export const ContextMenuApi: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN', {
closeContextMenu: filters.byCode("CONTEXT_MENU_CLOSE"),
From 98058f0caec5409be6b2f7529f67d20ea2e0801b Mon Sep 17 00:00:00 2001
From: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Date: Mon, 8 Sep 2025 22:27:41 -0300
Subject: [PATCH 095/116] Fix mistakes
---
src/plugins/favGifSearch/index.tsx | 2 +-
src/plugins/noPendingCount/index.ts | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/plugins/favGifSearch/index.tsx b/src/plugins/favGifSearch/index.tsx
index 7ab0145e..2e0ea651 100644
--- a/src/plugins/favGifSearch/index.tsx
+++ b/src/plugins/favGifSearch/index.tsx
@@ -131,7 +131,7 @@ export default definePlugin({
function SearchBar({ instance, SearchBarComponent }: { instance: Instance; SearchBarComponent: TSearchBarComponent; }) {
const [query, setQuery] = useState("");
- const ref = useRef<{ containerRef?: React.MutableRefObject; } | null>(null);
+ const ref = useRef<{ containerRef?: React.RefObject; } | null>(null);
const onChange = useCallback((searchQuery: string) => {
setQuery(searchQuery);
diff --git a/src/plugins/noPendingCount/index.ts b/src/plugins/noPendingCount/index.ts
index 1e963d9f..b1a7ffad 100644
--- a/src/plugins/noPendingCount/index.ts
+++ b/src/plugins/noPendingCount/index.ts
@@ -87,8 +87,8 @@ export default definePlugin({
replacement: {
// The two groups inside the first group grab the minified names of the variables,
// they are then referenced later to find unviewedTrialCount + unviewedDiscountCount.
- match: /(\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.+?\i)=\1\+\2/,
- replace: (_, rest) => `${rest}=0`
+ match: /(\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.+?)\2\+\3/,
+ replace: (_, rest) => `${rest}0`
}
}
],
From 8eabb1112577eef13369c3bf8d78f60937653958 Mon Sep 17 00:00:00 2001
From: thororen <78185467+thororen1234@users.noreply.github.com>
Date: Mon, 8 Sep 2025 21:35:01 -0400
Subject: [PATCH 096/116] ClearURLs: use rules from ClearURLs browser extension
(#3657)
Co-authored-by: V
---
src/plugins/clearURLs/README.md | 11 ++
src/plugins/clearURLs/defaultRules.ts | 159 --------------------------
src/plugins/clearURLs/index.ts | 147 ++++++++++++------------
src/utils/constants.ts | 4 +
4 files changed, 90 insertions(+), 231 deletions(-)
create mode 100644 src/plugins/clearURLs/README.md
delete mode 100644 src/plugins/clearURLs/defaultRules.ts
diff --git a/src/plugins/clearURLs/README.md b/src/plugins/clearURLs/README.md
new file mode 100644
index 00000000..43a453d1
--- /dev/null
+++ b/src/plugins/clearURLs/README.md
@@ -0,0 +1,11 @@
+# ClearURLs
+
+Automatically removes tracking elements from URLs you send.
+
+Uses data from the [ClearURLs browser extension](https://clearurls.xyz/).
+
+## Example
+
+**Before:** `https://www.amazon.com/dp/exampleProduct/ref=sxin_0_pb?__mk_de_DE=ÅMÅŽÕÑ&keywords=tea&pd_rd_i=exampleProduct&pd_rd_r=8d39e4cd-1e4f-43db-b6e7-72e969a84aa5&pd_rd_w=1pcKM&pd_rd_wg=hYrNl&pf_rd_p=50bbfd25-5ef7-41a2-68d6-74d854b30e30&pf_rd_r=0GMWD0YYKA7XFGX55ADP&qid=1517757263&rnid=2914120011`
+
+**After:** `https://www.amazon.com/dp/exampleProduct/`
diff --git a/src/plugins/clearURLs/defaultRules.ts b/src/plugins/clearURLs/defaultRules.ts
deleted file mode 100644
index e4e7d380..00000000
--- a/src/plugins/clearURLs/defaultRules.ts
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * 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 .
-*/
-
-export const defaultRules = [
- "action_object_map",
- "action_type_map",
- "action_ref_map",
- "spm@*.aliexpress.com",
- "scm@*.aliexpress.com",
- "aff_platform",
- "aff_trace_key",
- "algo_expid@*.aliexpress.*",
- "algo_pvid@*.aliexpress.*",
- "btsid",
- "ws_ab_test",
- "pd_rd_*@amazon.*",
- "_encoding@amazon.*",
- "psc@amazon.*",
- "tag@amazon.*",
- "ref_@amazon.*",
- "pf_rd_*@amazon.*",
- "pf@amazon.*",
- "crid@amazon.*",
- "keywords@amazon.*",
- "sprefix@amazon.*",
- "sr@amazon.*",
- "ie@amazon.*",
- "node@amazon.*",
- "qid@amazon.*",
- "callback@bilibili.com",
- "cvid@bing.com",
- "form@bing.com",
- "sk@bing.com",
- "sp@bing.com",
- "sc@bing.com",
- "qs@bing.com",
- "pq@bing.com",
- "sc_cid",
- "mkt_tok",
- "trk",
- "trkCampaign",
- "ga_*",
- "gclid",
- "gclsrc",
- "hmb_campaign",
- "hmb_medium",
- "hmb_source",
- "spReportId",
- "spJobID",
- "spUserID",
- "spMailingID",
- "itm_*",
- "s_cid",
- "elqTrackId",
- "elqTrack",
- "assetType",
- "assetId",
- "recipientId",
- "campaignId",
- "siteId",
- "mc_cid",
- "mc_eid",
- "pk_*",
- "sc_campaign",
- "sc_channel",
- "sc_content",
- "sc_medium",
- "sc_outcome",
- "sc_geo",
- "sc_country",
- "nr_email_referer",
- "vero_conv",
- "vero_id",
- "yclid",
- "_openstat",
- "mbid",
- "cmpid",
- "cid",
- "c_id",
- "campaign_id",
- "Campaign",
- "hash@ebay.*",
- "fb_action_ids",
- "fb_action_types",
- "fb_ref",
- "fb_source",
- "fbclid",
- "refsrc@facebook.com",
- "hrc@facebook.com",
- "gs_l",
- "gs_lcp@google.*",
- "ved@google.*",
- "ei@google.*",
- "sei@google.*",
- "gws_rd@google.*",
- "gs_gbg@google.*",
- "gs_mss@google.*",
- "gs_rn@google.*",
- "_hsenc",
- "_hsmi",
- "__hssc",
- "__hstc",
- "hsCtaTracking",
- "source@sourceforge.net",
- "position@sourceforge.net",
- "t@*.twitter.com",
- "s@*.twitter.com",
- "ref_*@*.twitter.com",
- "t@*.x.com",
- "s@*.x.com",
- "ref_*@*.x.com",
- "t@*.fixupx.com",
- "s@*.fixupx.com",
- "ref_*@*.fixupx.com",
- "t@*.fxtwitter.com",
- "s@*.fxtwitter.com",
- "ref_*@*.fxtwitter.com",
- "t@*.twittpr.com",
- "s@*.twittpr.com",
- "ref_*@*.twittpr.com",
- "t@*.fixvx.com",
- "s@*.fixvx.com",
- "ref_*@*.fixvx.com",
- "tt_medium",
- "tt_content",
- "lr@yandex.*",
- "redircnt@yandex.*",
- "feature@*.youtube.com",
- "kw@*.youtube.com",
- "si@*.youtube.com",
- "pp@*.youtube.com",
- "si@*.youtu.be",
- "wt_zmc",
- "utm_source",
- "utm_content",
- "utm_medium",
- "utm_campaign",
- "utm_term",
- "si@open.spotify.com",
- "igshid",
- "igsh",
- "share_id@reddit.com",
- "si@soundcloud.com",
-];
diff --git a/src/plugins/clearURLs/index.ts b/src/plugins/clearURLs/index.ts
index f00c751d..36270607 100644
--- a/src/plugins/clearURLs/index.ts
+++ b/src/plugins/clearURLs/index.ts
@@ -22,77 +22,74 @@ import {
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
-import { defaultRules } from "./defaultRules";
+const CLEAR_URLS_JSON_URL = "https://raw.githubusercontent.com/ClearURLs/Rules/master/data.min.json";
-// From lodash
-const reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
-const reHasRegExpChar = RegExp(reRegExpChar.source);
+interface Provider {
+ urlPattern: string;
+ completeProvider: boolean;
+ rules?: string[];
+ rawRules?: string[];
+ referralMarketing?: string[];
+ exceptions?: string[];
+ redirections?: string[];
+ forceRedirection?: boolean;
+}
+
+interface ClearUrlsData {
+ providers: Record;
+}
+
+interface RuleSet {
+ name: string;
+ urlPattern: RegExp;
+ rules?: RegExp[];
+ rawRules?: RegExp[];
+ exceptions?: RegExp[];
+}
export default definePlugin({
name: "ClearURLs",
- description: "Removes tracking garbage from URLs",
- authors: [Devs.adryd],
+ description: "Automatically removes tracking elements from URLs you send",
+ authors: [Devs.adryd, Devs.thororen],
- start() {
- this.createRules();
+ rules: [] as RuleSet[],
+
+ async start() {
+ await this.createRules();
+ },
+
+ stop() {
+ this.rules = [];
},
onBeforeMessageSend(_, msg) {
- return this.onSend(msg);
+ return this.cleanMessage(msg);
},
onBeforeMessageEdit(_cid, _mid, msg) {
- return this.onSend(msg);
+ return this.cleanMessage(msg);
},
- escapeRegExp(str: string) {
- return (str && reHasRegExpChar.test(str))
- ? str.replace(reRegExpChar, "\\$&")
- : (str || "");
- },
+ async createRules() {
+ const res = await fetch(CLEAR_URLS_JSON_URL)
+ .then(res => res.json()) as ClearUrlsData;
- createRules() {
- // Can be extended upon once user configs are available
- // Eg. (useDefaultRules: boolean, customRules: Array[string])
- const rules = defaultRules;
+ this.rules = [];
- this.universalRules = new Set();
- this.rulesByHost = new Map();
- this.hostRules = new Map();
+ for (const [name, provider] of Object.entries(res.providers)) {
+ const urlPattern = new RegExp(provider.urlPattern, "i");
- for (const rule of rules) {
- const splitRule = rule.split("@");
- const paramRule = new RegExp(
- "^" +
- this.escapeRegExp(splitRule[0]).replace(/\\\*/, ".+?") +
- "$"
- );
+ const rules = provider.rules?.map(rule => new RegExp(rule, "i"));
+ const rawRules = provider.rawRules?.map(rule => new RegExp(rule, "i"));
+ const exceptions = provider.exceptions?.map(ex => new RegExp(ex, "i"));
- if (!splitRule[1]) {
- this.universalRules.add(paramRule);
- continue;
- }
- const hostRule = new RegExp(
- "^(www\\.)?" +
- this.escapeRegExp(splitRule[1])
- .replace(/\\\./, "\\.")
- .replace(/^\\\*\\\./, "(.+?\\.)?")
- .replace(/\\\*/, ".+?") +
- "$"
- );
- const hostRuleIndex = hostRule.toString();
-
- this.hostRules.set(hostRuleIndex, hostRule);
- if (this.rulesByHost.get(hostRuleIndex) == null) {
- this.rulesByHost.set(hostRuleIndex, new Set());
- }
- this.rulesByHost.get(hostRuleIndex).add(paramRule);
- }
- },
-
- removeParam(rule: string | RegExp, param: string, parent: URLSearchParams) {
- if (param === rule || rule instanceof RegExp && rule.test(param)) {
- parent.delete(param);
+ this.rules.push({
+ name,
+ urlPattern,
+ rules,
+ rawRules,
+ exceptions,
+ });
}
},
@@ -106,34 +103,40 @@ export default definePlugin({
}
// Cheap way to check if there are any search params
- if (url.searchParams.entries().next().done) {
- // If there are none, we don't need to modify anything
- return match;
- }
+ if (url.searchParams.entries().next().done) return match;
- // Check all universal rules
- this.universalRules.forEach(rule => {
- url.searchParams.forEach((_value, param, parent) => {
- this.removeParam(rule, param, parent);
- });
- });
+ // Check rules for each provider that matches
+ this.rules.forEach(({ urlPattern, exceptions, rawRules, rules }) => {
+ if (!urlPattern.test(url.href) || exceptions?.some(ex => ex.test(url.href))) return;
- // Check rules for each hosts that match
- this.hostRules.forEach((regex, hostRuleName) => {
- if (!regex.test(url.hostname)) return;
- this.rulesByHost.get(hostRuleName).forEach(rule => {
- url.searchParams.forEach((_value, param, parent) => {
- this.removeParam(rule, param, parent);
+ const toDelete: string[] = [];
+
+ if (rules) {
+ // Add matched params to delete list
+ url.searchParams.forEach((_, param) => {
+ if (rules.some(rule => rule.test(param))) {
+ toDelete.push(param);
+ }
});
+ }
+
+ // Delete matched params from list
+ toDelete.forEach(param => url.searchParams.delete(param));
+
+ // Match and remove any raw rules
+ let cleanedUrl = url.href;
+ rawRules?.forEach(rawRule => {
+ cleanedUrl = cleanedUrl.replace(rawRule, "");
});
+ url = new URL(cleanedUrl);
});
return url.toString();
},
- onSend(msg: MessageObject) {
+ cleanMessage(msg: MessageObject) {
// Only run on messages that contain URLs
- if (msg.content.match(/http(s)?:\/\//)) {
+ if (/http(s)?:\/\//.test(msg.content)) {
msg.content = msg.content.replace(
/(https?:\/\/[^\s<]+[^<.,:;"'>)|\]\s])/g,
match => this.replacer(match)
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 684f25bb..92de3d91 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -602,6 +602,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "Cootshk",
id: 921605971577548820n
},
+ thororen: {
+ name: "thororen",
+ id: 848339671629299742n
+ },
} satisfies Record);
// iife so #__PURE__ works correctly
From 479d01a1b9417eb4201f8490bfe7cb0eab4bc51a Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Tue, 9 Sep 2025 03:56:53 +0200
Subject: [PATCH 097/116] fix FavGifSearch regression
---
src/plugins/favGifSearch/index.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/plugins/favGifSearch/index.tsx b/src/plugins/favGifSearch/index.tsx
index 2e0ea651..28823a68 100644
--- a/src/plugins/favGifSearch/index.tsx
+++ b/src/plugins/favGifSearch/index.tsx
@@ -30,6 +30,7 @@ interface SearchBarComponentProps {
onClear: () => void;
query: string;
placeholder: string;
+ className?: string;
}
type TSearchBarComponent =
@@ -147,7 +148,7 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc
// scroll back to top
ref.current?.containerRef?.current
- .closest("#gif-picker-tab-panel")
+ ?.closest("#gif-picker-tab-panel")
?.querySelector("[class|=\"content\"]")
?.firstElementChild?.scrollTo(0, 0);
@@ -177,6 +178,7 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc
ref={ref}
autoFocus={true}
size="md"
+ className=""
onChange={onChange}
onClear={() => {
setQuery("");
From 9c0af5adee7d7e64226e5e51c1b723bae6189b69 Mon Sep 17 00:00:00 2001
From: thororen <78185467+thororen1234@users.noreply.github.com>
Date: Tue, 9 Sep 2025 12:28:37 -0400
Subject: [PATCH 098/116] Fix broken MessagePopoverAPI not working (#3661)
---
src/plugins/_api/messagePopover.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/_api/messagePopover.ts b/src/plugins/_api/messagePopover.ts
index a49658fb..af861673 100644
--- a/src/plugins/_api/messagePopover.ts
+++ b/src/plugins/_api/messagePopover.ts
@@ -27,7 +27,7 @@ export default definePlugin({
{
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
replacement: {
- match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
+ match: /(?<=\]\}\)),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" +
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},`
}
From fbc2dbe78189dcfe9dc907058770e951730995bd Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Tue, 9 Sep 2025 22:36:34 -0400
Subject: [PATCH 099/116] FakeNitro: fix nitro themes not working for some
users (#3666)
---
src/plugins/fakeNitro/index.tsx | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx
index 370d6857..bb9a6daa 100644
--- a/src/plugins/fakeNitro/index.tsx
+++ b/src/plugins/fakeNitro/index.tsx
@@ -276,6 +276,8 @@ export default definePlugin({
// Allow users to use custom client themes
{
find: "customUserThemeSettings:{",
+ // Discord has two separate modules for treatments 1 and 2
+ all: true,
replacement: {
match: /(?<=\i=)\(0,\i\.\i\)\(\i\.\i\.TIER_2\)(?=,|;)/g,
replace: "true"
From 56d25b03f918b952172986676ecd4ec6f368359b Mon Sep 17 00:00:00 2001
From: Gabriel <38334104+gabrielmar@users.noreply.github.com>
Date: Tue, 16 Sep 2025 22:35:01 -0300
Subject: [PATCH 100/116] UserVoiceShow: Improve tooltip & add icons for
muted/deafened (#3630)
Co-authored-by: V
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
---
src/plugins/userVoiceShow/components.tsx | 166 ++++++++++++-----------
src/plugins/userVoiceShow/style.css | 4 +-
2 files changed, 87 insertions(+), 83 deletions(-)
diff --git a/src/plugins/userVoiceShow/components.tsx b/src/plugins/userVoiceShow/components.tsx
index b7907978..13dfed60 100644
--- a/src/plugins/userVoiceShow/components.tsx
+++ b/src/plugins/userVoiceShow/components.tsx
@@ -8,8 +8,9 @@ import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { classes } from "@utils/misc";
import { Channel } from "@vencord/discord-types";
-import { filters, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, mapMangledModuleLazy } from "@webpack";
-import { ChannelRouter, ChannelStore, GuildStore, IconUtils, match, P, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common";
+import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
+import { ChannelRouter, ChannelStore, Parser, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common";
+import { PropsWithChildren } from "react";
const cl = classNameFactory("vc-uvs-");
@@ -17,60 +18,71 @@ const { selectVoiceChannel } = findByPropsLazy("selectVoiceChannel", "selectChan
const { useChannelName } = mapMangledModuleLazy("#{intl::GROUP_DM_ALONE}", {
useChannelName: filters.byCode("()=>null==")
});
-const getDMChannelIcon = findByCodeLazy(".getChannelIconURL({");
-
-const Avatar = findComponentByCodeLazy(".status)/2):0");
-const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL");
const ActionButtonClasses = findByPropsLazy("actionButton", "highlight");
-interface IconProps extends React.ComponentPropsWithoutRef<"div"> {
+type IconProps = Omit, "children"> & {
size?: number;
iconClassName?: string;
-}
+};
-function SpeakerIcon(props: IconProps) {
- props.size ??= 16;
+function Icon(props: PropsWithChildren) {
+ const {
+ size = 16,
+ className,
+ iconClassName,
+ ...restProps
+ } = props;
return (
);
}
-function LockedSpeakerIcon(props: IconProps) {
- props.size ??= 16;
-
+function SpeakerIcon(props: IconProps) {
return (
-
+
+
+
+
+ );
+}
+
+function LockedSpeakerIcon(props: IconProps) {
+ return (
+
+
+
+
+ );
+}
+
+function MutedIcon(props: IconProps) {
+ return (
+
+
+
+ );
+}
+
+function DeafIcon(props: IconProps) {
+ return (
+
+
+
);
}
@@ -87,36 +99,13 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) {
[voiceStates]
);
- const guild = channel.getGuildId() == null ? undefined : GuildStore.getGuild(channel.getGuildId());
- const guildIcon = guild?.icon == null ? undefined : IconUtils.getGuildIconURL({
- id: guild.id,
- icon: guild.icon,
- size: 30
- });
-
- const channelIcon = match(channel.type)
- .with(P.union(1, 3), () => {
- return channel.recipients.length >= 2 && channel.icon == null
- ?
- : ;
- })
- .otherwise(() => null);
- const channelName = useChannelName(channel);
-
+ const Icon = isLocked ? LockedSpeakerIcon : SpeakerIcon;
return (
<>
- {guild != null && (
-
- {guildIcon != null &&
}
-
{guild.name}
-
- )}
-
- {channelIcon}
- {channelName}
-
+ In Voice Chat
+ {Parser.parse(`<#${channel.id}>`)}
- {isLocked ?
:
}
+
;
+const clickTimers = new Map();
export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId);
+ const { isMuted, isDeaf } = useStateFromStores([VoiceStateStore], () => {
+ const voiceState = VoiceStateStore.getVoiceStateForUser(userId);
+ return {
+ isMuted: voiceState?.mute || voiceState?.selfMute || false,
+ isDeaf: voiceState?.deaf || voiceState?.selfDeaf || false
+ };
+ });
+
const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId);
if (channel == null) return null;
@@ -154,8 +151,8 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, is
if (channel == null || channelId == null) return;
- clearTimeout(clickTimers[channelId]);
- delete clickTimers[channelId];
+ clearTimeout(clickTimers.get(channelId));
+ clickTimers.delete(channelId);
if (e.detail > 1) {
if (!isDM && !PermissionStore.can(PermissionsBits.CONNECT, channel)) {
@@ -165,32 +162,39 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, is
selectVoiceChannel(channelId);
} else {
- clickTimers[channelId] = setTimeout(() => {
+ const timeoutId = setTimeout(() => {
ChannelRouter.transitionToChannel(channelId);
- delete clickTimers[channelId];
+ clickTimers.delete(channelId);
}, 250);
+ clickTimers.set(channelId, timeoutId);
}
}
+ const IconComponent =
+ isLocked
+ ? LockedSpeakerIcon
+ : isDeaf
+ ? DeafIcon
+ : isMuted
+ ? MutedIcon
+ : SpeakerIcon;
+
return (
}
tooltipClassName={cl("tooltip-container")}
tooltipContentClassName={cl("tooltip-content")}
>
- {props => {
- const iconProps: IconProps = {
- ...props,
- className: classes(isActionButton && ActionButtonClasses.actionButton, isActionButton && shouldHighlight && ActionButtonClasses.highlight),
- iconClassName: classes(isProfile && cl("profile-speaker")),
- size: isActionButton ? 20 : 16,
- onClick
- };
-
- return isLocked ?
-
- : ;
- }}
+ {props => (
+
+ )}
);
}, { noop: true });
diff --git a/src/plugins/userVoiceShow/style.css b/src/plugins/userVoiceShow/style.css
index 32b42208..322b8ed3 100644
--- a/src/plugins/userVoiceShow/style.css
+++ b/src/plugins/userVoiceShow/style.css
@@ -19,7 +19,7 @@
}
.vc-uvs-tooltip-container {
- max-width: 300px;
+ max-width: 50vw;
}
.vc-uvs-tooltip-content {
@@ -42,4 +42,4 @@
.vc-uvs-vc-members {
display: flex;
gap: 6px;
-}
+}
\ No newline at end of file
From 3005906a280c2bfae8fb86d5b14266946f92eb42 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Sat, 20 Sep 2025 20:37:01 +0200
Subject: [PATCH 101/116] CallTimer: fix horizontal text cutoff
---
src/plugins/callTimer/alignedChatInputFix.css | 6 +++---
src/plugins/callTimer/index.tsx | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/plugins/callTimer/alignedChatInputFix.css b/src/plugins/callTimer/alignedChatInputFix.css
index 9bb4a039..33c14fbf 100644
--- a/src/plugins/callTimer/alignedChatInputFix.css
+++ b/src/plugins/callTimer/alignedChatInputFix.css
@@ -1,4 +1,4 @@
-.align-chat-input [class*="panels"] [class*="inner_"],
-.align-chat-input [class*="rtcConnectionStatus_"] {
- height: fit-content;
+[class*="panels"] [class*="inner_"],
+[class*="rtcConnectionStatus_"] {
+ height: fit-content !important;
}
\ No newline at end of file
diff --git a/src/plugins/callTimer/index.tsx b/src/plugins/callTimer/index.tsx
index 76b3efd6..6503568e 100644
--- a/src/plugins/callTimer/index.tsx
+++ b/src/plugins/callTimer/index.tsx
@@ -95,6 +95,6 @@ export default definePlugin({
deps: [channelId]
});
- return Connected for {formatDuration(time)}
;
+ return {formatDuration(time)}
;
}
});
From 8c2dc84f3b3c18e20fc5c8c94ea1b837a1a3b093 Mon Sep 17 00:00:00 2001
From: thororen <78185467+thororen1234@users.noreply.github.com>
Date: Sun, 21 Sep 2025 22:38:57 -0400
Subject: [PATCH 102/116] Settings: fix debug info layout (#3673)
---
src/plugins/_core/settings.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx
index 4a0f297e..4d12f37a 100644
--- a/src/plugins/_core/settings.tsx
+++ b/src/plugins/_core/settings.tsx
@@ -39,7 +39,7 @@ export default definePlugin({
find: ".versionHash",
replacement: [
{
- match: /\[\(0,\i\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/,
+ match: /\.info.+?\[\(0,\i\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/,
replace: (m, component, props) => {
props = props.replace(/children:\[.+\]/, "");
return `${m},$self.makeInfoElements(${component}, ${props})`;
From 80872f4ab9a0184c05d1b34dfec4a052c1c0709b Mon Sep 17 00:00:00 2001
From: quarty <142720982+qwertyquarty@users.noreply.github.com>
Date: Mon, 22 Sep 2025 04:44:13 +0200
Subject: [PATCH 103/116] MessageLatency: add option to ignore own messages
(#3677)
Co-authored-by: V
---
src/plugins/messageLatency/index.tsx | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx
index b81c55cf..437ce644 100644
--- a/src/plugins/messageLatency/index.tsx
+++ b/src/plugins/messageLatency/index.tsx
@@ -11,7 +11,7 @@ import { isNonNullish } from "@utils/guards";
import definePlugin, { OptionType } from "@utils/types";
import { Message } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack";
-import { SnowflakeUtils, Tooltip } from "@webpack/common";
+import { AuthenticationStore, SnowflakeUtils, Tooltip } from "@webpack/common";
type FillValue = ("status-danger" | "status-warning" | "status-positive" | "text-muted");
type Fill = [FillValue, FillValue, FillValue];
@@ -48,6 +48,11 @@ export default definePlugin({
type: OptionType.BOOLEAN,
description: "Show milliseconds",
default: false
+ },
+ ignoreSelf: {
+ type: OptionType.BOOLEAN,
+ description: "Don't add indicator to your own messages",
+ default: false
}
}),
@@ -91,7 +96,7 @@ export default definePlugin({
},
latencyTooltipData(message: Message) {
- const { latency, detectDiscordKotlin, showMillis } = this.settings.store;
+ const { latency, detectDiscordKotlin, showMillis, ignoreSelf } = this.settings.store;
const { id, nonce } = message;
// Message wasn't received through gateway
@@ -100,6 +105,8 @@ export default definePlugin({
// Bots basically never send a nonce, and if someone does do it then it's usually not a snowflake
if (message.author.bot) return null;
+ if (ignoreSelf && message.author.id === AuthenticationStore.getId()) return null;
+
let isDiscordKotlin = false;
let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds
if (!showMillis) {
From edd68fe08e5f929e95e6635ec65ec9fdc54089f7 Mon Sep 17 00:00:00 2001
From: nin0
Date: Sun, 21 Sep 2025 23:08:08 -0400
Subject: [PATCH 104/116] AppleMusicRichPresence: add status display type
(#3669)
Co-authored-by: V
---
packages/discord-types/enums/activity.ts | 30 +++++++++
packages/discord-types/enums/index.ts | 1 +
.../discord-types/src/common/Activity.d.ts | 36 ++++++++++
packages/discord-types/src/common/index.d.ts | 1 +
src/plugins/appleMusic.desktop/index.tsx | 67 ++++++++-----------
src/plugins/customRPC/index.tsx | 38 +----------
src/plugins/lastfmRichPresence/index.tsx | 51 ++------------
7 files changed, 104 insertions(+), 120 deletions(-)
create mode 100644 packages/discord-types/enums/activity.ts
create mode 100644 packages/discord-types/src/common/Activity.d.ts
diff --git a/packages/discord-types/enums/activity.ts b/packages/discord-types/enums/activity.ts
new file mode 100644
index 00000000..513a65de
--- /dev/null
+++ b/packages/discord-types/enums/activity.ts
@@ -0,0 +1,30 @@
+export const enum ActivityType {
+ PLAYING = 0,
+ STREAMING = 1,
+ LISTENING = 2,
+ WATCHING = 3,
+ CUSTOM_STATUS = 4,
+ COMPETING = 5,
+ HANG_STATUS = 6
+}
+
+export const enum ActivityFlags {
+ INSTANCE = 1 << 0,
+ JOIN = 1 << 1,
+ /** @deprecated */
+ SPECTATE = 1 << 2,
+ /** @deprecated */
+ JOIN_REQUEST = 1 << 3,
+ SYNC = 1 << 4,
+ PLAY = 1 << 5,
+ PARTY_PRIVACY_FRIENDS = 1 << 6,
+ PARTY_PRIVACY_VOICE_CHANNEL = 1 << 7,
+ EMBEDDED = 1 << 8,
+ CONTEXTLESS = 1 << 9
+}
+
+export const enum ActivityStatusDisplayType {
+ NAME = 0,
+ STATE = 1,
+ DETAILS = 2
+}
diff --git a/packages/discord-types/enums/index.ts b/packages/discord-types/enums/index.ts
index 3d80895c..a25ff3b5 100644
--- a/packages/discord-types/enums/index.ts
+++ b/packages/discord-types/enums/index.ts
@@ -1,3 +1,4 @@
+export * from "./activity";
export * from "./channel";
export * from "./commands";
export * from "./messages";
diff --git a/packages/discord-types/src/common/Activity.d.ts b/packages/discord-types/src/common/Activity.d.ts
new file mode 100644
index 00000000..d513780a
--- /dev/null
+++ b/packages/discord-types/src/common/Activity.d.ts
@@ -0,0 +1,36 @@
+import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "../../enums";
+
+export interface ActivityAssets {
+ large_image?: string;
+ large_text?: string;
+ small_image?: string;
+ small_text?: string;
+}
+
+export interface ActivityButton {
+ label: string;
+ url: string;
+}
+
+export interface Activity {
+ name: string;
+ application_id: string;
+ type: ActivityType;
+ state?: string;
+ state_url?: string;
+ details?: string;
+ details_url?: string;
+ url?: string;
+ flags: ActivityFlags;
+ status_display_type?: ActivityStatusDisplayType;
+ timestamps?: {
+ start?: number;
+ end?: number;
+ };
+ assets?: ActivityAssets;
+ buttons?: string[];
+ metadata?: {
+ button_urls?: Array;
+ };
+}
+
diff --git a/packages/discord-types/src/common/index.d.ts b/packages/discord-types/src/common/index.d.ts
index 5e50e96e..65ad8856 100644
--- a/packages/discord-types/src/common/index.d.ts
+++ b/packages/discord-types/src/common/index.d.ts
@@ -1,3 +1,4 @@
+export * from "./Activity";
export * from "./Application";
export * from "./Channel";
export * from "./Guild";
diff --git a/src/plugins/appleMusic.desktop/index.tsx b/src/plugins/appleMusic.desktop/index.tsx
index 57e5ae19..f6fc9da9 100644
--- a/src/plugins/appleMusic.desktop/index.tsx
+++ b/src/plugins/appleMusic.desktop/index.tsx
@@ -7,49 +7,12 @@
import { definePluginSettings } from "@api/Settings";
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";
const Native = VencordNative.pluginHelpers.AppleMusicRichPresence as PluginNative;
-interface ActivityAssets {
- large_image?: string;
- large_text?: string;
- small_image?: string;
- small_text?: string;
-}
-
-interface ActivityButton {
- label: string;
- url: string;
-}
-
-interface Activity {
- state?: string;
- details?: string;
- timestamps?: {
- start?: number;
- end?: number;
- };
- assets?: ActivityAssets;
- buttons?: Array;
- name: string;
- application_id: string;
- metadata?: {
- button_urls?: Array;
- };
- type: number;
- flags: number;
-}
-
-const enum ActivityType {
- PLAYING = 0,
- LISTENING = 2,
-}
-
-const enum ActivityFlag {
- INSTANCE = 1 << 0,
-}
-
export interface TrackData {
name: string;
album?: string;
@@ -90,6 +53,25 @@ const settings = definePluginSettings({
{ label: "Listening", value: ActivityType.LISTENING }
],
},
+ statusDisplayType: {
+ description: "Show the track / artist name in the member list",
+ type: OptionType.SELECT,
+ options: [
+ {
+ label: "Don't show (shows generic listening message)",
+ value: "off",
+ default: true
+ },
+ {
+ label: "Show artist name",
+ value: "artist"
+ },
+ {
+ label: "Show track name",
+ value: "track"
+ }
+ ]
+ },
refreshInterval: {
type: OptionType.SLIDER,
description: "The interval between activity refreshes (seconds)",
@@ -258,7 +240,12 @@ export default definePlugin({
metadata: !isRadio && buttons.length ? { button_urls: buttons.map(v => v.url) } : undefined,
type: settings.store.activityType,
- flags: ActivityFlag.INSTANCE,
+ status_display_type: {
+ "off": ActivityStatusDisplayType.NAME,
+ "artist": ActivityStatusDisplayType.STATE,
+ "track": ActivityStatusDisplayType.DETAILS
+ }[settings.store.statusDisplayType],
+ flags: ActivityFlags.INSTANCE,
};
}
});
diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx
index d35dad13..9a5a1cd9 100644
--- a/src/plugins/customRPC/index.tsx
+++ b/src/plugins/customRPC/index.tsx
@@ -27,6 +27,8 @@ import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
+import { Activity } from "@vencord/discord-types";
+import { ActivityType } from "@vencord/discord-types/enums";
import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, React, UserStore } from "@webpack/common";
@@ -39,41 +41,7 @@ async function getApplicationAsset(key: string): Promise {
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
}
-interface ActivityAssets {
- large_image?: string;
- large_text?: string;
- small_image?: string;
- small_text?: string;
-}
-
-interface Activity {
- state?: string;
- details?: string;
- timestamps?: {
- start?: number;
- end?: number;
- };
- assets?: ActivityAssets;
- buttons?: Array;
- name: string;
- application_id: string;
- metadata?: {
- button_urls?: Array;
- };
- type: ActivityType;
- url?: string;
- flags: number;
-}
-
-const enum ActivityType {
- PLAYING = 0,
- STREAMING = 1,
- LISTENING = 2,
- WATCHING = 3,
- COMPETING = 5
-}
-
-const enum TimestampMode {
+export const enum TimestampMode {
NONE,
NOW,
TIME,
diff --git a/src/plugins/lastfmRichPresence/index.tsx b/src/plugins/lastfmRichPresence/index.tsx
index abf42d5f..3c36e9dc 100644
--- a/src/plugins/lastfmRichPresence/index.tsx
+++ b/src/plugins/lastfmRichPresence/index.tsx
@@ -21,40 +21,11 @@ import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
+import { Activity, ActivityAssets, ActivityButton } from "@vencord/discord-types";
+import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "@vencord/discord-types/enums";
import { findByPropsLazy } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
-interface ActivityAssets {
- large_image?: string;
- large_text?: string;
- small_image?: string;
- small_text?: string;
-}
-
-
-interface ActivityButton {
- label: string;
- url: string;
-}
-
-interface Activity {
- state: string;
- details?: string;
- timestamps?: {
- start?: number;
- };
- assets?: ActivityAssets;
- buttons?: Array;
- name: string;
- application_id: string;
- status_display_type?: number;
- metadata?: {
- button_urls?: Array;
- };
- type: number;
- flags: number;
-}
-
interface TrackData {
name: string;
album: string;
@@ -63,16 +34,6 @@ interface TrackData {
imageUrl?: string;
}
-// only relevant enum values
-const enum ActivityType {
- PLAYING = 0,
- LISTENING = 2,
-}
-
-const enum ActivityFlag {
- INSTANCE = 1 << 0,
-}
-
const enum NameFormat {
StatusName = "status-name",
ArtistFirst = "artist-first",
@@ -367,9 +328,9 @@ export default definePlugin({
details: trackData.name,
state: trackData.artist,
status_display_type: {
- "off": 0,
- "artist": 1,
- "track": 2
+ "off": ActivityStatusDisplayType.NAME,
+ "artist": ActivityStatusDisplayType.STATE,
+ "track": ActivityStatusDisplayType.DETAILS
}[settings.store.statusDisplayType],
assets,
@@ -379,7 +340,7 @@ export default definePlugin({
},
type: settings.store.useListeningStatus ? ActivityType.LISTENING : ActivityType.PLAYING,
- flags: ActivityFlag.INSTANCE,
+ flags: ActivityFlags.INSTANCE,
};
}
});
From 2e9d67f0b4dbc59088b22e317c634d96c92a5985 Mon Sep 17 00:00:00 2001
From: thororen <78185467+thororen1234@users.noreply.github.com>
Date: Sun, 21 Sep 2025 23:15:14 -0400
Subject: [PATCH 105/116] add Vencord badges to user settings section (#3667)
Co-authored-by: V
---
src/plugins/_api/badges/index.tsx | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx
index 8742a4bd..8c900038 100644
--- a/src/plugins/_api/badges/index.tsx
+++ b/src/plugins/_api/badges/index.tsx
@@ -111,6 +111,13 @@ export default definePlugin({
replace: "...$self.getBadgeMouseEventHandlers($1),$&"
}
]
+ },
+ {
+ find: "profileCardUsernameRow,children:",
+ replacement: {
+ match: /badges:(\i)(?<=displayProfile:(\i).+?)/,
+ replace: "badges:[...$self.getBadges($2),...$1]"
+ }
}
],
From 228b85a0c8287e72f88b89a821919c51cb982468 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Mon, 22 Sep 2025 05:18:10 +0200
Subject: [PATCH 106/116] bump to v1.13.0
---
package.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index 3f0e3371..4f05e969 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
- "version": "1.12.13",
+ "version": "1.13.0",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@@ -105,4 +105,4 @@
"engines": {
"node": ">=18"
}
-}
\ No newline at end of file
+}
From 746c8240206107dfc40f0995d17cd24730d40b0d Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Tue, 23 Sep 2025 22:24:58 +0200
Subject: [PATCH 107/116] Fix Debug Logging toggle not working
Closes #3268
---
src/plugins/_core/noTrack.ts | 9 +++++++++
src/utils/types.ts | 3 ++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts
index ad1f255b..eada58cb 100644
--- a/src/plugins/_core/noTrack.ts
+++ b/src/plugins/_core/noTrack.ts
@@ -71,6 +71,15 @@ export default definePlugin({
}
],
+ // The TRACK event takes an optional `resolve` property that is called when the tracking event was submitted to the server.
+ // A few spots in Discord await this callback before continuing (most notably the Voice Debug Logging toggle).
+ // Since we NOOP the AnalyticsActionHandlers module, there is no handler for the TRACK event, so we have to handle it ourselves
+ flux: {
+ TRACK(event) {
+ event?.resolve?.();
+ }
+ },
+
startAt: StartAt.Init,
start() {
// Sentry is initialized in its own WebpackInstance.
diff --git a/src/utils/types.ts b/src/utils/types.ts
index 2f1d6396..2293b2ff 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -26,6 +26,7 @@ import { MessageClickListener, MessageEditListener, MessageSendListener } from "
import { MessagePopoverButtonFactory } from "@api/MessagePopover";
import { Command, FluxEvents } from "@vencord/discord-types";
import { ReactNode } from "react";
+import { LiteralUnion } from "type-fest";
// exists to export default definePlugin({...})
export default function definePlugin(p: P & Record) {
@@ -151,7 +152,7 @@ export interface PluginDef {
* Allows you to subscribe to Flux events
*/
flux?: {
- [E in FluxEvents]?: (event: any) => void | Promise;
+ [E in LiteralUnion]?: (event: any) => void | Promise;
};
/**
* Allows you to manipulate context menus
From cb845b522458532479c077ed82279c27e025601e Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Tue, 23 Sep 2025 23:58:38 +0200
Subject: [PATCH 108/116] PlatformIndicators: update indicators in real time if
status changes
Closes #3516
---
packages/discord-types/src/utils.d.ts | 8 +++--
src/plugins/index.ts | 4 +--
src/plugins/platformIndicators/index.tsx | 41 +++++++-----------------
src/utils/types.ts | 6 ++--
4 files changed, 21 insertions(+), 38 deletions(-)
diff --git a/packages/discord-types/src/utils.d.ts b/packages/discord-types/src/utils.d.ts
index 77b6f88b..14b8fa50 100644
--- a/packages/discord-types/src/utils.d.ts
+++ b/packages/discord-types/src/utils.d.ts
@@ -6,13 +6,15 @@ import type { FluxEvents } from "./fluxEvents";
export { FluxEvents };
+type FluxEventsAutoComplete = LiteralUnion;
+
export interface FluxDispatcher {
_actionHandlers: any;
_subscriptions: any;
- dispatch(event: { [key: string]: unknown; type: FluxEvents; }): Promise;
+ dispatch(event: { [key: string]: unknown; type: FluxEventsAutoComplete; }): Promise;
isDispatching(): boolean;
- subscribe(event: FluxEvents, callback: (data: any) => void): void;
- unsubscribe(event: FluxEvents, callback: (data: any) => void): void;
+ subscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void;
+ unsubscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void;
wait(callback: () => void): void;
}
diff --git a/src/plugins/index.ts b/src/plugins/index.ts
index 8288935b..5bb60a13 100644
--- a/src/plugins/index.ts
+++ b/src/plugins/index.ts
@@ -222,7 +222,7 @@ export function subscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Flux
for (const [event, handler] of Object.entries(p.flux)) {
const wrappedHandler = p.flux[event] = function () {
try {
- const res = handler.apply(p, arguments as any);
+ const res = handler!.apply(p, arguments as any);
return res instanceof Promise
? res.catch(e => logger.error(`${p.name}: Error while handling ${event}\n`, e))
: res;
@@ -242,7 +242,7 @@ export function unsubscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Fl
logger.debug("Unsubscribing from flux events of plugin", p.name);
for (const [event, handler] of Object.entries(p.flux)) {
- fluxDispatcher.unsubscribe(event as FluxEvents, handler);
+ fluxDispatcher.unsubscribe(event as FluxEvents, handler!);
}
}
}
diff --git a/src/plugins/platformIndicators/index.tsx b/src/plugins/platformIndicators/index.tsx
index f804e987..add93064 100644
--- a/src/plugins/platformIndicators/index.tsx
+++ b/src/plugins/platformIndicators/index.tsx
@@ -22,12 +22,11 @@ import { addProfileBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeProf
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
import { Settings } from "@api/Settings";
-import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types";
import { filters, findStoreLazy, mapMangledModuleLazy } from "@webpack";
-import { PresenceStore, Tooltip, UserStore } from "@webpack/common";
+import { AuthenticationStore, PresenceStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
export interface Session {
sessionId: string;
@@ -85,7 +84,7 @@ const PlatformIcon = ({ platform, status, small }: { platform: Platform, status:
};
function ensureOwnStatus(user: User) {
- if (user.id === UserStore.getCurrentUser().id) {
+ if (user.id === AuthenticationStore.getId()) {
const sessions = SessionsStore.getSessions();
if (typeof sessions !== "object") return null;
const sortedSessions = Object.values(sessions).sort(({ status: a }, { status: b }) => {
@@ -104,7 +103,7 @@ function ensureOwnStatus(user: User) {
}, {});
const { clientStatuses } = PresenceStore.getState();
- clientStatuses[UserStore.getCurrentUser().id] = ownStatus;
+ clientStatuses[AuthenticationStore.getId()] = ownStatus;
}
}
@@ -115,7 +114,7 @@ function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] {
ensureOwnStatus(user);
- const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record;
+ const status = PresenceStore.getClientStatus(user.id) as Record;
if (!status) return [];
return Object.entries(status).map(([platform, status]) => ({
@@ -134,11 +133,9 @@ function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] {
}
const PlatformIndicator = ({ user, small = false }: { user: User; small?: boolean; }) => {
- if (!user || user.bot) return null;
-
ensureOwnStatus(user);
- const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record;
+ const status = useStateFromStores([PresenceStore], () => PresenceStore.getClientStatus(user.id) as Record);
if (!status) return null;
const icons = Object.entries(status).map(([platform, status]) => (
@@ -170,10 +167,8 @@ const badge: ProfileBadge = {
const indicatorLocations = {
list: {
description: "In the member list",
- onEnable: () => addMemberListDecorator("platform-indicator", props =>
-
-
-
+ onEnable: () => addMemberListDecorator("platform-indicator", ({ user }) =>
+ user && !user.bot ? : null
),
onDisable: () => removeMemberListDecorator("platform-indicator")
},
@@ -184,11 +179,10 @@ const indicatorLocations = {
},
messages: {
description: "Inside messages",
- onEnable: () => addMessageDecoration("platform-indicator", props =>
-
-
-
- ),
+ onEnable: () => addMessageDecoration("platform-indicator", props => {
+ const user = props.message?.author;
+ return user && !user.bot ? : null;
+ }),
onDisable: () => removeMessageDecoration("platform-indicator")
}
};
@@ -201,19 +195,6 @@ export default definePlugin({
start() {
const settings = Settings.plugins.PlatformIndicators;
- const { displayMode } = settings;
-
- // transfer settings from the old ones, which had a select menu instead of booleans
- if (displayMode) {
- if (displayMode !== "both") settings[displayMode] = true;
- else {
- settings.list = true;
- settings.badges = true;
- }
- settings.messages = true;
- delete settings.displayMode;
- }
-
Object.entries(indicatorLocations).forEach(([key, value]) => {
if (settings[key]) value.onEnable();
});
diff --git a/src/utils/types.ts b/src/utils/types.ts
index 2293b2ff..2d21eaff 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -151,9 +151,9 @@ export interface PluginDef {
/**
* Allows you to subscribe to Flux events
*/
- flux?: {
- [E in LiteralUnion]?: (event: any) => void | Promise;
- };
+ flux?: Partial<{
+ [E in LiteralUnion]: (event: any) => void | Promise;
+ }>;
/**
* Allows you to manipulate context menus
*/
From 7c839be64fbef65b4825ec1d17acd0264aadcd64 Mon Sep 17 00:00:00 2001
From: thororen <78185467+thororen1234@users.noreply.github.com>
Date: Wed, 24 Sep 2025 10:49:50 -0400
Subject: [PATCH 109/116] BetterFolders: close folder if the last server is
removed (#3658)
Co-authored-by: V
---
src/plugins/betterFolders/FolderSideBar.tsx | 23 +++++++++++++++++----
src/plugins/betterFolders/index.tsx | 5 ++---
2 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx
index 40329122..0e643958 100644
--- a/src/plugins/betterFolders/FolderSideBar.tsx
+++ b/src/plugins/betterFolders/FolderSideBar.tsx
@@ -21,24 +21,39 @@ import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Animations, useStateFromStores } from "@webpack/common";
import type { CSSProperties } from "react";
-import { ExpandedGuildFolderStore, settings } from ".";
+import { ExpandedGuildFolderStore, settings, SortedGuildStore } from ".";
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
const GuildsBar = findComponentByCodeLazy('("guildsnav")');
+function getExpandedFolderIds() {
+ const expandedFolders = ExpandedGuildFolderStore.getExpandedFolders();
+ const folders = SortedGuildStore.getGuildFolders();
+
+ const expandedFolderIds = new Set();
+
+ for (const folder of folders) {
+ if (expandedFolders.has(folder.folderId) && folder.guildIds?.length) {
+ expandedFolderIds.add(folder.folderId);
+ }
+ }
+
+ return expandedFolderIds;
+}
+
export default ErrorBoundary.wrap(guildsBarProps => {
- const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
+ const expandedFolderIds = useStateFromStores([ExpandedGuildFolderStore, SortedGuildStore], () => getExpandedFolderIds());
const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
const Sidebar = (
);
- const visible = !!expandedFolders.size;
+ const visible = !!expandedFolderIds.size;
const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join(""));
// We need to display none if we are in fullscreen. Yes this seems horrible doing with css, but it's literally how Discord does it.
diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx
index 62567f51..5a1754d3 100644
--- a/src/plugins/betterFolders/index.tsx
+++ b/src/plugins/betterFolders/index.tsx
@@ -22,7 +22,7 @@ import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
-import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
+import { findByPropsLazy, findStoreLazy } from "@webpack";
import { FluxDispatcher } from "@webpack/common";
import { ReactNode } from "react";
@@ -35,8 +35,7 @@ enum FolderIconDisplay {
}
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
-const SortedGuildStore = findStoreLazy("SortedGuildStore");
-const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
+export const SortedGuildStore = findStoreLazy("SortedGuildStore");
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
let lastGuildId = null as string | null;
From c5a1bbd7db2472cfb7cfc62a0cec59f3135e479e Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Thu, 25 Sep 2025 15:19:34 +0200
Subject: [PATCH 110/116] GameActivityToggle: fix background colour when using
nameplate
---
src/plugins/gameActivityToggle/index.tsx | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/plugins/gameActivityToggle/index.tsx b/src/plugins/gameActivityToggle/index.tsx
index 97885177..8959acc2 100644
--- a/src/plugins/gameActivityToggle/index.tsx
+++ b/src/plugins/gameActivityToggle/index.tsx
@@ -60,7 +60,7 @@ function makeIcon(showCurrentGame?: boolean) {
};
}
-function GameActivityToggleButton() {
+function GameActivityToggleButton(props: { nameplate?: any; }) {
const showCurrentGame = ShowCurrentGame.useSetting();
return (
@@ -70,6 +70,7 @@ function GameActivityToggleButton() {
role="switch"
aria-checked={!showCurrentGame}
redGlow={!showCurrentGame}
+ plated={props?.nameplate != null}
onClick={() => ShowCurrentGame.updateSetting(old => !old)}
/>
);
@@ -97,7 +98,7 @@ export default definePlugin({
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
replacement: {
match: /className:\i\.buttons,.{0,50}children:\[/,
- replace: "$&$self.GameActivityToggleButton(),"
+ replace: "$&$self.GameActivityToggleButton(arguments[0]),"
}
}
],
From 79c5cf530403e214ea2cf4f3c3c500d98ad0424c Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Sat, 27 Sep 2025 15:59:18 -0400
Subject: [PATCH 111/116] fix plugins for latest Discord update (#3681)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
---
src/plugins/_core/settings.tsx | 5 +++--
src/plugins/implicitRelationships/index.ts | 2 +-
src/webpack/common/components.ts | 8 +-------
3 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx
index 4d12f37a..a632e9fa 100644
--- a/src/plugins/_core/settings.tsx
+++ b/src/plugins/_core/settings.tsx
@@ -67,8 +67,9 @@ export default definePlugin({
{
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: {
- match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
- replace: "$2.open($1);return;"
+ // Skip the check Discord performs to make sure the section being selected in the user settings context menu is valid
+ match: /(?<=function\((\i),(\i),\i\)\{)(?=let \i=Object.values\(\i\.\i\).+?(\(0,\i\.openUserSettings\))\()/,
+ replace: (_, settingsPanel, section, openUserSettings) => `${openUserSettings}(${settingsPanel},{section:${section}});return;`
}
}
],
diff --git a/src/plugins/implicitRelationships/index.ts b/src/plugins/implicitRelationships/index.ts
index d57370a0..a49635e1 100644
--- a/src/plugins/implicitRelationships/index.ts
+++ b/src/plugins/implicitRelationships/index.ts
@@ -48,7 +48,7 @@ export default definePlugin({
},
// Sections header
{
- find: "#{intl::FRIENDS_SECTION_ONLINE}",
+ find: "#{intl::FRIENDS_SECTION_ONLINE}),className:",
replacement: {
match: /,{id:(\i\.\i)\.PENDING,show:.+?className:(\i\.item)/,
replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}`
diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts
index 9715619a..b975f303 100644
--- a/src/webpack/common/components.ts
+++ b/src/webpack/common/components.ts
@@ -87,13 +87,7 @@ export const ListScrollerNone = LazyComponent(() => createListScroller(listScrol
export const ListScrollerThin = LazyComponent(() => createListScroller(listScrollerClasses.thin, listScrollerClasses.fade, "", ResizeObserver));
export const ListScrollerAuto = LazyComponent(() => createListScroller(listScrollerClasses.auto, listScrollerClasses.fade, "", ResizeObserver));
-const { FocusLock_ } = mapMangledModuleLazy('document.getElementById("app-mount"))', {
- FocusLock_: filters.componentByCode(".containerRef")
-}) as {
- FocusLock_: t.FocusLock;
-};
-
-export const FocusLock = LazyComponent(() => FocusLock_);
+export const FocusLock = waitForComponent("FocusLock", filters.componentByCode(".containerRef,{keyboardModeEnabled:"));
export let useToken: t.useToken;
waitFor(m => {
From 631c763fc44b94bbce78eea15f115b2290f3d62b Mon Sep 17 00:00:00 2001
From: TheRealClarity <68876810+TheRealClarity@users.noreply.github.com>
Date: Sun, 28 Sep 2025 21:46:14 +0100
Subject: [PATCH 112/116] YoutubeAdblock: fix blocking in watch together
activity (#3690)
---
src/plugins/youtubeAdblock.desktop/native.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/plugins/youtubeAdblock.desktop/native.ts b/src/plugins/youtubeAdblock.desktop/native.ts
index ae05d646..02112dac 100644
--- a/src/plugins/youtubeAdblock.desktop/native.ts
+++ b/src/plugins/youtubeAdblock.desktop/native.ts
@@ -13,8 +13,10 @@ app.on("browser-window-created", (_, win) => {
frame?.once("dom-ready", () => {
if (!RendererSettings.store.plugins?.YoutubeAdblock?.enabled) return;
- if (frame.url.includes("youtube.com/embed/") || (frame.url.includes("discordsays") && frame.url.includes("youtube.com"))) {
+ if (frame.url.includes("youtube.com/embed/")) {
frame.executeJavaScript(adguard);
+ } else if (frame.parent?.url.includes("youtube.com/embed/")) {
+ frame.parent.executeJavaScript(adguard);
}
});
});
From f040133412f1edc7ccfa3e4c3cc8309b46ff9aed Mon Sep 17 00:00:00 2001
From: V
Date: Sun, 28 Sep 2025 23:18:23 +0200
Subject: [PATCH 113/116] QuickReply/MessageClickActions: ignore non-replyable
& ephemeral messages (#3692)
Co-authored-by: YashRaj <91825864+YashRajCodes@users.noreply.github.com>
---
packages/discord-types/CONTRIBUTING.md | 1 +
packages/discord-types/enums/messages.ts | 583 ++++++++++++++++++
.../src/common/messages/Message.d.ts | 19 +-
src/plugins/messageClickActions/index.ts | 8 +-
src/plugins/quickReply/index.ts | 4 +-
src/webpack/common/utils.ts | 2 +
6 files changed, 607 insertions(+), 10 deletions(-)
create mode 100644 packages/discord-types/CONTRIBUTING.md
diff --git a/packages/discord-types/CONTRIBUTING.md b/packages/discord-types/CONTRIBUTING.md
new file mode 100644
index 00000000..3bef4092
--- /dev/null
+++ b/packages/discord-types/CONTRIBUTING.md
@@ -0,0 +1 @@
+Hint: https://docs.discord.food is an incredible resource and allows you to copy paste complete enums and interfaces
diff --git a/packages/discord-types/enums/messages.ts b/packages/discord-types/enums/messages.ts
index 9c0025b7..8aa6fbb3 100644
--- a/packages/discord-types/enums/messages.ts
+++ b/packages/discord-types/enums/messages.ts
@@ -11,3 +11,586 @@ export const enum StickerFormatType {
LOTTIE = 3,
GIF = 4
}
+
+export const enum MessageType {
+ /**
+ * A default message (see below)
+ *
+ * Value: 0
+ * Name: DEFAULT
+ * Rendered Content: "{content}"
+ * Deletable: true
+ */
+ DEFAULT = 0,
+ /**
+ * A message sent when a user is added to a group DM or thread
+ *
+ * Value: 1
+ * Name: RECIPIENT_ADD
+ * Rendered Content: "{author} added {mentions [0] } to the {group/thread}."
+ * Deletable: false
+ */
+ RECIPIENT_ADD = 1,
+ /**
+ * A message sent when a user is removed from a group DM or thread
+ *
+ * Value: 2
+ * Name: RECIPIENT_REMOVE
+ * Rendered Content: "{author} removed {mentions [0] } from the {group/thread}."
+ * Deletable: false
+ */
+ RECIPIENT_REMOVE = 2,
+ /**
+ * A message sent when a user creates a call in a private channel
+ *
+ * Value: 3
+ * Name: CALL
+ * Rendered Content: participated ? "{author} started a call{ended ? " that lasted {duration}" : " — Join the call"}." : "You missed a call from {author} that lasted {duration}."
+ * Deletable: false
+ */
+ CALL = 3,
+ /**
+ * A message sent when a group DM or thread's name is changed
+ *
+ * Value: 4
+ * Name: CHANNEL_NAME_CHANGE
+ * Rendered Content: "{author} changed the {is_forum ? "post title" : "channel name"}: {content} "
+ * Deletable: false
+ */
+ CHANNEL_NAME_CHANGE = 4,
+ /**
+ * A message sent when a group DM's icon is changed
+ *
+ * Value: 5
+ * Name: CHANNEL_ICON_CHANGE
+ * Rendered Content: "{author} changed the channel icon."
+ * Deletable: false
+ */
+ CHANNEL_ICON_CHANGE = 5,
+ /**
+ * A message sent when a message is pinned in a channel
+ *
+ * Value: 6
+ * Name: CHANNEL_PINNED_MESSAGE
+ * Rendered Content: "{author} pinned a message to this channel."
+ * Deletable: true
+ */
+ CHANNEL_PINNED_MESSAGE = 6,
+ /**
+ * A message sent when a user joins a guild
+ *
+ * Value: 7
+ * Name: USER_JOIN
+ * Rendered Content: See user join message type , obtained via the formula timestamp_ms % 13
+ * Deletable: true
+ */
+ USER_JOIN = 7,
+ /**
+ * A message sent when a user subscribes to (boosts) a guild
+ *
+ * Value: 8
+ * Name: PREMIUM_GUILD_SUBSCRIPTION
+ * Rendered Content: "{author} just boosted the server{content ? " {content} times"}!"
+ * Deletable: true
+ */
+ PREMIUM_GUILD_SUBSCRIPTION = 8,
+ /**
+ * A message sent when a user subscribes to (boosts) a guild to tier 1
+ *
+ * Value: 9
+ * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_1
+ * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 1! "
+ * Deletable: true
+ */
+ PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9,
+ /**
+ * A message sent when a user subscribes to (boosts) a guild to tier 2
+ *
+ * Value: 10
+ * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_2
+ * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 2! "
+ * Deletable: true
+ */
+ PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10,
+ /**
+ * A message sent when a user subscribes to (boosts) a guild to tier 3
+ *
+ * Value: 11
+ * Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_3
+ * Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 3! "
+ * Deletable: true
+ */
+ PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11,
+ /**
+ * A message sent when a news channel is followed
+ *
+ * Value: 12
+ * Name: CHANNEL_FOLLOW_ADD
+ * Rendered Content: "{author} has added {content} to this channel. Its most important updates will show up here."
+ * Deletable: true
+ */
+ CHANNEL_FOLLOW_ADD = 12,
+ /**
+ * A message sent when a guild is disqualified from discovery
+ *
+ * Value: 14
+ * Name: GUILD_DISCOVERY_DISQUALIFIED
+ * Rendered Content: "This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details."
+ * Deletable: true
+ */
+ GUILD_DISCOVERY_DISQUALIFIED = 14,
+ /**
+ * A message sent when a guild requalifies for discovery
+ *
+ * Value: 15
+ * Name: GUILD_DISCOVERY_REQUALIFIED
+ * Rendered Content: "This server is eligible for Server Discovery again and has been automatically relisted!"
+ * Deletable: true
+ */
+ GUILD_DISCOVERY_REQUALIFIED = 15,
+ /**
+ * A message sent when a guild has failed discovery requirements for a week
+ *
+ * Value: 16
+ * Name: GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING
+ * Rendered Content: "This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery."
+ * Deletable: true
+ */
+ GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16,
+ /**
+ * A message sent when a guild has failed discovery requirements for 3 weeks
+ *
+ * Value: 17
+ * Name: GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING
+ * Rendered Content: "This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery."
+ * Deletable: true
+ */
+ GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17,
+ /**
+ * A message sent when a thread is created
+ *
+ * Value: 18
+ * Name: THREAD_CREATED
+ * Rendered Content: "{author} started a thread: {content} . See all threads."
+ * Deletable: true
+ */
+ THREAD_CREATED = 18,
+ /**
+ * A message sent when a user replies to a message
+ *
+ * Value: 19
+ * Name: REPLY
+ * Rendered Content: "{content}"
+ * Deletable: true
+ */
+ REPLY = 19,
+ /**
+ * A message sent when a user uses a slash command
+ *
+ * Value: 20
+ * Name: CHAT_INPUT_COMMAND
+ * Rendered Content: "{content}"
+ * Deletable: true
+ */
+ CHAT_INPUT_COMMAND = 20,
+ /**
+ * A message sent when a thread starter message is added to a thread
+ *
+ * Value: 21
+ * Name: THREAD_STARTER_MESSAGE
+ * Rendered Content: "{referenced_message?.content}" ?? "Sorry, we couldn't load the first message in this thread"
+ * Deletable: false
+ */
+ THREAD_STARTER_MESSAGE = 21,
+ /**
+ * A message sent to remind users to invite friends to a guild
+ *
+ * Value: 22
+ * Name: GUILD_INVITE_REMINDER
+ * Rendered Content: "Wondering who to invite?\nStart by inviting anyone who can help you build the server!"
+ * Deletable: true
+ */
+ GUILD_INVITE_REMINDER = 22,
+ /**
+ * A message sent when a user uses a context menu command
+ *
+ * Value: 23
+ * Name: CONTEXT_MENU_COMMAND
+ * Rendered Content: "{content}"
+ * Deletable: true
+ */
+ CONTEXT_MENU_COMMAND = 23,
+ /**
+ * A message sent when auto moderation takes an action
+ *
+ * Value: 24
+ * Name: AUTO_MODERATION_ACTION
+ * Rendered Content: Special embed rendered from embeds[0]
+ * Deletable: true 1
+ */
+ AUTO_MODERATION_ACTION = 24,
+ /**
+ * A message sent when a user purchases or renews a role subscription
+ *
+ * Value: 25
+ * Name: ROLE_SUBSCRIPTION_PURCHASE
+ * Rendered Content: "{author} {is_renewal ? "renewed" : "joined"} {role_subscription.tier_name} and has been a subscriber of {guild} for {role_subscription.total_months_subscribed} month(?s)!"
+ * Deletable: true
+ */
+ ROLE_SUBSCRIPTION_PURCHASE = 25,
+ /**
+ * A message sent when a user is upsold to a premium interaction
+ *
+ * Value: 26
+ * Name: INTERACTION_PREMIUM_UPSELL
+ * Rendered Content: "{content}"
+ * Deletable: true
+ */
+ INTERACTION_PREMIUM_UPSELL = 26,
+ /**
+ * A message sent when a stage channel starts
+ *
+ * Value: 27
+ * Name: STAGE_START
+ * Rendered Content: "{author} started {content} "
+ * Deletable: true
+ */
+ STAGE_START = 27,
+ /**
+ * A message sent when a stage channel ends
+ *
+ * Value: 28
+ * Name: STAGE_END
+ * Rendered Content: "{author} ended {content} "
+ * Deletable: true
+ */
+ STAGE_END = 28,
+ /**
+ * A message sent when a user starts speaking in a stage channel
+ *
+ * Value: 29
+ * Name: STAGE_SPEAKER
+ * Rendered Content: "{author} is now a speaker."
+ * Deletable: true
+ */
+ STAGE_SPEAKER = 29,
+ /**
+ * A message sent when a user raises their hand in a stage channel
+ *
+ * Value: 30
+ * Name: STAGE_RAISE_HAND
+ * Rendered Content: "{author} requested to speak."
+ * Deletable: true
+ */
+ STAGE_RAISE_HAND = 30,
+ /**
+ * A message sent when a stage channel's topic is changed
+ *
+ * Value: 31
+ * Name: STAGE_TOPIC
+ * Rendered Content: "{author} changed the Stage topic: {content} "
+ * Deletable: true
+ */
+ STAGE_TOPIC = 31,
+ /**
+ * A message sent when a user purchases an application premium subscription
+ *
+ * Value: 32
+ * Name: GUILD_APPLICATION_PREMIUM_SUBSCRIPTION
+ * Rendered Content: "{author} upgraded {application ?? "a deleted application"} to premium for this server!"
+ * Deletable: true
+ */
+ GUILD_APPLICATION_PREMIUM_SUBSCRIPTION = 32,
+ /**
+ * A message sent when a user gifts a premium (Nitro) referral
+ *
+ * Value: 35
+ * Name: PREMIUM_REFERRAL
+ * Rendered Content: "{content}"
+ * Deletable: true
+ */
+ PREMIUM_REFERRAL = 35,
+ /**
+ * A message sent when a user enabled lockdown for the guild
+ *
+ * Value: 36
+ * Name: GUILD_INCIDENT_ALERT_MODE_ENABLED
+ * Rendered Content: "{author} enabled security actions until {content}."
+ * Deletable: true
+ */
+ GUILD_INCIDENT_ALERT_MODE_ENABLED = 36,
+ /**
+ * A message sent when a user disables lockdown for the guild
+ *
+ * Value: 37
+ * Name: GUILD_INCIDENT_ALERT_MODE_DISABLED
+ * Rendered Content: "{author} disabled security actions."
+ * Deletable: true
+ */
+ GUILD_INCIDENT_ALERT_MODE_DISABLED = 37,
+ /**
+ * A message sent when a user reports a raid for the guild
+ *
+ * Value: 38
+ * Name: GUILD_INCIDENT_REPORT_RAID
+ * Rendered Content: "{author} reported a raid in {guild}."
+ * Deletable: true
+ */
+ GUILD_INCIDENT_REPORT_RAID = 38,
+ /**
+ * A message sent when a user reports a false alarm for the guild
+ *
+ * Value: 39
+ * Name: GUILD_INCIDENT_REPORT_FALSE_ALARM
+ * Rendered Content: "{author} reported a false alarm in {guild}."
+ * Deletable: true
+ */
+ GUILD_INCIDENT_REPORT_FALSE_ALARM = 39,
+ /**
+ * A message sent when no one sends a message in the current channel for 1 hour
+ *
+ * Value: 40
+ * Name: GUILD_DEADCHAT_REVIVE_PROMPT
+ * Rendered Content: "{content}"
+ * Deletable: true
+ */
+ GUILD_DEADCHAT_REVIVE_PROMPT = 40,
+ /**
+ * A message sent when a user buys another user a gift
+ *
+ * Value: 41
+ * Name: CUSTOM_GIFT
+ * Rendered Content: Special embed rendered from embeds[0].url and gift_info
+ * Deletable: true
+ */
+ CUSTOM_GIFT = 41,
+ /**
+ * Value: 42
+ * Name: GUILD_GAMING_STATS_PROMPT
+ * Rendered Content: "{content}"
+ * Deletable: true
+ */
+ GUILD_GAMING_STATS_PROMPT = 42,
+ /**
+ * A message sent when a user purchases a guild product
+ *
+ * Value: 44
+ * Name: PURCHASE_NOTIFICATION
+ * Rendered Content: "{author} has purchased {purchase_notification.guild_product_purchase.product_name}!"
+ * Deletable: true
+ */
+ PURCHASE_NOTIFICATION = 44,
+ /**
+ * A message sent when a poll is finalized
+ *
+ * Value: 46
+ * Name: POLL_RESULT
+ * Rendered Content: Special embed rendered from embeds[0]
+ * Deletable: true
+ */
+ POLL_RESULT = 46,
+ /**
+ * A message sent by the Discord Updates account when a new changelog is posted
+ *
+ * Value: 47
+ * Name: CHANGELOG
+ * Rendered Content: "{content}"
+ * Deletable: true
+ */
+ CHANGELOG = 47,
+ /**
+ * A message sent when a Nitro promotion is triggered
+ *
+ * Value: 48
+ * Name: NITRO_NOTIFICATION
+ * Rendered Content: Special embed rendered from content
+ * Deletable: true
+ */
+ NITRO_NOTIFICATION = 48,
+ /**
+ * A message sent when a voice channel is linked to a lobby
+ *
+ * Value: 49
+ * Name: CHANNEL_LINKED_TO_LOBBY
+ * Rendered Content: "{content}"
+ * Deletable: true
+ */
+ CHANNEL_LINKED_TO_LOBBY = 49,
+ /**
+ * A local-only ephemeral message sent when a user is prompted to gift Nitro to a friend on their friendship anniversary
+ *
+ * Value: 50
+ * Name: GIFTING_PROMPT
+ * Rendered Content: Special embed
+ * Deletable: true
+ */
+ GIFTING_PROMPT = 50,
+ /**
+ * A local-only message sent when a user receives an in-game message NUX
+ *
+ * Value: 51
+ * Name: IN_GAME_MESSAGE_NUX
+ * Rendered Content: "{author} messaged you from {application.name}. In-game chat may not include rich messaging features such as images, polls, or apps. Learn More "
+ * Deletable: true
+ */
+ IN_GAME_MESSAGE_NUX = 51,
+ /**
+ * A message sent when a user accepts a guild join request
+ *
+ * Value: 52
+ * Name: GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION 2
+ * Rendered Content: "{join_request.user}'s application to {content} was approved! Welcome!"
+ * Deletable: true
+ */
+ GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION = 52,
+ /**
+ * A message sent when a user rejects a guild join request
+ *
+ * Value: 53
+ * Name: GUILD_JOIN_REQUEST_REJECT_NOTIFICATION 2
+ * Rendered Content: "{join_request.user}'s application to {content} was rejected."
+ * Deletable: true
+ */
+ GUILD_JOIN_REQUEST_REJECT_NOTIFICATION = 53,
+ /**
+ * A message sent when a user withdraws a guild join request
+ *
+ * Value: 54
+ * Name: GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION 2
+ * Rendered Content: "{join_request.user}'s application to {content} has been withdrawn."
+ * Deletable: true
+ */
+ GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION = 54,
+ /**
+ * A message sent when a user upgrades to HD streaming
+ *
+ * Value: 55
+ * Name: HD_STREAMING_UPGRADED
+ * Rendered Content: "{author} activated HD Splash Potion "
+ * Deletable: true
+ */
+ HD_STREAMING_UPGRADED = 55,
+ /**
+ * A message sent when a user resolves a moderation report by deleting the offending message
+ *
+ * Value: 58
+ * Name: REPORT_TO_MOD_DELETED_MESSAGE
+ * Rendered Content: "{author} deleted the message"
+ * Deletable: true
+ */
+ REPORT_TO_MOD_DELETED_MESSAGE = 58,
+ /**
+ * A message sent when a user resolves a moderation report by timing out the offending user
+ *
+ * Value: 59
+ * Name: REPORT_TO_MOD_TIMEOUT_USER
+ * Rendered Content: "{author} timed out {mentions [0] }"
+ * Deletable: true
+ */
+ REPORT_TO_MOD_TIMEOUT_USER = 59,
+ /**
+ * A message sent when a user resolves a moderation report by kicking the offending user
+ *
+ * Value: 60
+ * Name: REPORT_TO_MOD_KICK_USER
+ * Rendered Content: "{author} kicked {mentions [0] }"
+ * Deletable: true
+ */
+ REPORT_TO_MOD_KICK_USER = 60,
+ /**
+ * A message sent when a user resolves a moderation report by banning the offending user
+ *
+ * Value: 61
+ * Name: REPORT_TO_MOD_BAN_USER
+ * Rendered Content: "{author} banned {mentions [0] }"
+ * Deletable: true
+ */
+ REPORT_TO_MOD_BAN_USER = 61,
+ /**
+ * A message sent when a user resolves a moderation report
+ *
+ * Value: 62
+ * Name: REPORT_TO_MOD_CLOSED_REPORT
+ * Rendered Content: "{author} resolved this flag"
+ * Deletable: true
+ */
+ REPORT_TO_MOD_CLOSED_REPORT = 62,
+ /**
+ * A message sent when a user adds a new emoji to a guild
+ *
+ * Value: 63
+ * Name: EMOJI_ADDED
+ * Rendered Content: "{author} added a new emoji, {content} :{emoji.name}: "
+ * Deletable: true
+ */
+ EMOJI_ADDED = 63,
+}
+
+export const enum MessageFlags {
+ /**
+ * Message has been published to subscribed channels (via Channel Following)
+ *
+ * Value: 1 << 0
+ */
+ CROSSPOSTED = 1 << 0,
+ /**
+ * Message originated from a message in another channel (via Channel Following)
+ */
+ IS_CROSSPOST = 1 << 1,
+ /**
+ * Embeds will not be included when serializing this message
+ */
+ SUPPRESS_EMBEDS = 1 << 2,
+ /**
+ * Source message for this crosspost has been deleted (via Channel Following)
+ */
+ SOURCE_MESSAGE_DELETED = 1 << 3,
+ /**
+ * Message came from the urgent message system
+ */
+ URGENT = 1 << 4,
+ /**
+ * Message has an associated thread, with the same ID as the message
+ */
+ HAS_THREAD = 1 << 5,
+ /**
+ * Message is only visible to the user who invoked the interaction
+ */
+ EPHEMERAL = 1 << 6,
+ /**
+ * Message is an interaction response and the bot is "thinking"
+ */
+ LOADING = 1 << 7,
+ /**
+ * Some roles were not mentioned and added to the thread
+ */
+ FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8,
+ /**
+ * Message is hidden from the guild's feed
+ */
+ GUILD_FEED_HIDDEN = 1 << 9,
+ /**
+ * Message contains a link that impersonates Discord
+ */
+ SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10,
+ /**
+ * Message will not trigger push and desktop notifications
+ */
+ SUPPRESS_NOTIFICATIONS = 1 << 12,
+ /**
+ * Message's audio attachment is rendered as a voice message
+ */
+ IS_VOICE_MESSAGE = 1 << 13,
+ /**
+ * Message has a forwarded message snapshot attached
+ */
+ HAS_SNAPSHOT = 1 << 14,
+ /**
+ * Message contains components from version 2 of the UI kit
+ */
+ IS_COMPONENTS_V2 = 1 << 15,
+ /**
+ * Message was triggered by the social layer integration
+ */
+ SENT_BY_SOCIAL_LAYER_INTEGRATION = 1 << 16,
+}
diff --git a/packages/discord-types/src/common/messages/Message.d.ts b/packages/discord-types/src/common/messages/Message.d.ts
index e3586255..c2af2496 100644
--- a/packages/discord-types/src/common/messages/Message.d.ts
+++ b/packages/discord-types/src/common/messages/Message.d.ts
@@ -2,9 +2,9 @@ import { CommandOption } from './Commands';
import { User, UserJSON } from '../User';
import { Embed, EmbedJSON } from './Embed';
import { DiscordRecord } from "../Record";
-import { StickerFormatType } from "../../../enums";
+import { MessageFlags, MessageType, StickerFormatType } from "../../../enums";
-/**
+/*
* TODO: looks like discord has moved over to Date instead of Moment;
*/
export class Message extends DiscordRecord {
@@ -35,7 +35,7 @@ export class Message extends DiscordRecord {
customRenderedContent: unknown;
editedTimestamp: Date;
embeds: Embed[];
- flags: number;
+ flags: MessageFlags;
giftCodes: string[];
id: string;
interaction: {
@@ -100,7 +100,7 @@ export class Message extends DiscordRecord {
stickers: unknown[];
timestamp: moment.Moment;
tts: boolean;
- type: number;
+ type: MessageType;
webhookId: string | undefined;
/**
@@ -121,10 +121,13 @@ export class Message extends DiscordRecord {
removeReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message;
getChannelId(): string;
- hasFlag(flag: number): boolean;
+ hasFlag(flag: MessageFlags): boolean;
isCommandType(): boolean;
isEdited(): boolean;
isSystemDM(): boolean;
+
+ /** Vencord added */
+ deleted?: boolean;
}
/** A smaller Message object found in FluxDispatcher and elsewhere. */
@@ -193,3 +196,9 @@ export interface MessageReaction {
emoji: ReactionEmoji;
me: boolean;
}
+
+// Object.keys(findByProps("REPLYABLE")).map(JSON.stringify).join("|")
+export type MessageTypeSets = Record<
+ "UNDELETABLE" | "GUILD_DISCOVERY_STATUS" | "USER_MESSAGE" | "NOTIFIABLE_SYSTEM_MESSAGE" | "REPLYABLE" | "FORWARDABLE" | "REFERENCED_MESSAGE_AVAILABLE" | "AVAILABLE_IN_GUILD_FEED" | "DEADCHAT_PROMPTS" | "NON_COLLAPSIBLE" | "NON_PARSED" | "AUTOMOD_INCIDENT_ACTIONS" | "SELF_MENTIONABLE_SYSTEM" | "SCHEDULABLE",
+ Set
+>;
diff --git a/src/plugins/messageClickActions/index.ts b/src/plugins/messageClickActions/index.ts
index 723ece12..148998f8 100644
--- a/src/plugins/messageClickActions/index.ts
+++ b/src/plugins/messageClickActions/index.ts
@@ -19,8 +19,9 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
+import { MessageFlags } from "@vencord/discord-types/enums";
import { findByPropsLazy } from "@webpack";
-import { FluxDispatcher, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common";
+import { FluxDispatcher, MessageTypeSets, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common";
import NoReplyMentionPlugin from "plugins/noReplyMention";
const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage");
@@ -73,7 +74,7 @@ export default definePlugin({
WindowStore.removeChangeListener(focusChanged);
},
- onMessageClick(msg: any, channel, event) {
+ onMessageClick(msg, channel, event) {
const isMe = msg.author.id === UserStore.getCurrentUser().id;
if (!isDeletePressed) {
if (event.detail < 2) return;
@@ -89,8 +90,7 @@ export default definePlugin({
} else {
if (!settings.store.enableDoubleClickToReply) return;
- const EPHEMERAL = 64;
- if (msg.hasFlag(EPHEMERAL)) return;
+ if (!MessageTypeSets.REPLYABLE.has(msg.type) || msg.hasFlag(MessageFlags.EPHEMERAL)) return;
const isShiftPress = event.shiftKey && !settings.store.requireModifier;
const shouldMention = Vencord.Plugins.isPluginEnabled(NoReplyMentionPlugin.name)
diff --git a/src/plugins/quickReply/index.ts b/src/plugins/quickReply/index.ts
index 215268c6..06cd7688 100644
--- a/src/plugins/quickReply/index.ts
+++ b/src/plugins/quickReply/index.ts
@@ -20,7 +20,8 @@ import { definePluginSettings } from "@api/Settings";
import { Devs, IS_MAC } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Message } from "@vencord/discord-types";
-import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageActions, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common";
+import { MessageFlags } from "@vencord/discord-types/enums";
+import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageActions, MessageStore, MessageTypeSets, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common";
import NoBlockedMessagesPlugin from "plugins/noBlockedMessages";
import NoReplyMentionPlugin from "plugins/noReplyMention";
@@ -134,6 +135,7 @@ function getNextMessage(isUp: boolean, isReply: boolean) {
if (m.deleted) return false;
if (!isReply && m.author.id !== meId) return false; // editing only own messages
if (hasNoBlockedMessages && NoBlockedMessagesPlugin.shouldIgnoreMessage(m)) return false;
+ if (!MessageTypeSets.REPLYABLE.has(m.type) || m.hasFlag(MessageFlags.EPHEMERAL)) return false;
return true;
});
diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts
index 633eceea..cae1ecd5 100644
--- a/src/webpack/common/utils.ts
+++ b/src/webpack/common/utils.ts
@@ -207,3 +207,5 @@ export const DateUtils: t.DateUtils = mapMangledModuleLazy("millisecondsInUnit:"
isSameDay: filters.byCode(/Math\.abs\(\+?\i-\+?\i\)/),
diffAsUnits: filters.byCode("days:0", "millisecondsInUnit")
});
+
+export const MessageTypeSets: t.MessageTypeSets = findByPropsLazy("REPLYABLE", "FORWARDABLE");
From 8943c90cb0b8be7c6c268ec4fb6931d30ad1dc59 Mon Sep 17 00:00:00 2001
From: sadan4 <117494111+sadan4@users.noreply.github.com>
Date: Sun, 28 Sep 2025 17:34:23 -0400
Subject: [PATCH 114/116] arRPC: increase connection timeout to 5s (#3680)
Co-authored-by: V
---
src/plugins/arRPC.web/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx
index 426b9a9b..538bfe31 100644
--- a/src/plugins/arRPC.web/index.tsx
+++ b/src/plugins/arRPC.web/index.tsx
@@ -79,7 +79,7 @@ export default definePlugin({
ws.onmessage = this.handleEvent;
- const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 1000)); // check if open after 1s
+ const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 5000)); // check if open after 5s
if (!connectionSuccessful) {
showNotice("Failed to connect to arRPC, is it running?", "Retry", () => { // show notice about failure to connect, with retry/ignore
popNotice();
From 2a33d719eecb067c2d08cb6fd17168e5c1fa7654 Mon Sep 17 00:00:00 2001
From: Eve
Date: Mon, 29 Sep 2025 16:53:07 -0400
Subject: [PATCH 115/116] Bump Vencord version.
---
src/main/csp/index.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/main/csp/index.ts b/src/main/csp/index.ts
index c4192f4b..a46da7ee 100644
--- a/src/main/csp/index.ts
+++ b/src/main/csp/index.ts
@@ -63,6 +63,9 @@ export const CspPolicies: PolicyMap = {
"dearrow-thumb.ajay.app": ImageSrc, // Dearrow Thumbnail CDN
"usrbg.is-hardly.online": ImageSrc, // USRBG API
"icons.duckduckgo.com": ImageSrc, // DuckDuckGo Favicon API (Reverse Image Search)
+
+ // forked addition
+ "*.dorkbutt.lol": ImageAndCssSrc,
};
const findHeader = (headers: PolicyMap, headerName: Lowercase) => {
From b79b102833ca7c41ec6a37ae2b1d7b7e119d4d71 Mon Sep 17 00:00:00 2001
From: Eve
Date: Mon, 29 Sep 2025 20:44:49 -0400
Subject: [PATCH 116/116] add the plugin
---
src/plugins/usrbg-fork/index.ts | 120 ++++++++++++++++++++++++++++++++
1 file changed, 120 insertions(+)
create mode 100644 src/plugins/usrbg-fork/index.ts
diff --git a/src/plugins/usrbg-fork/index.ts b/src/plugins/usrbg-fork/index.ts
new file mode 100644
index 00000000..d77e8780
--- /dev/null
+++ b/src/plugins/usrbg-fork/index.ts
@@ -0,0 +1,120 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2023 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+import { definePluginSettings } from "@api/Settings";
+import { Link } from "@components/Link";
+import { Devs } from "@utils/constants";
+import definePlugin, { OptionType } from "@utils/types";
+
+const API_URL = "https://usrbg.dorkbutt.lol/users/users.json";
+
+interface UsrbgApiReturn {
+ endpoint: string;
+ bucket: string;
+ prefix: string;
+ users: Record;
+}
+
+const settings = definePluginSettings({
+ nitroFirst: {
+ description: "Banner to use if both Nitro and USRBG banners are present",
+ type: OptionType.SELECT,
+ options: [
+ { label: "Nitro banner", value: true, default: true },
+ { label: "USRBG banner", value: false },
+ ]
+ },
+ voiceBackground: {
+ description: "Use USRBG banners as voice chat backgrounds",
+ type: OptionType.BOOLEAN,
+ default: true,
+ restartNeeded: true
+ }
+});
+
+export default definePlugin({
+ name: "FORKED - USRBG",
+ description: "Displays user banners from dorkbutt's USRBG server, allowing anyone to get a banner without Nitro",
+ authors: [Devs.dorkbutt, Devs.AutumnVN, Devs.katlyn, Devs.pylix, Devs.TheKodeToad],
+ settings,
+ patches: [
+ {
+ find: '.banner)==null?"COMPLETE"',
+ replacement: {
+ match: /(?<=void 0:)\i.getPreviewBanner\(\i,\i,\i\)/,
+ replace: "$self.patchBannerUrl(arguments[0])||$&"
+
+ }
+ },
+ {
+ find: "\"data-selenium-video-tile\":",
+ predicate: () => settings.store.voiceBackground,
+ replacement: [
+ {
+ match: /(?<=function\((\i),\i\)\{)(?=let.{20,40},style:)/,
+ replace: "$1.style=$self.getVoiceBackgroundStyles($1);"
+ }
+ ]
+ }
+ ],
+
+ data: null as UsrbgApiReturn | null,
+
+ settingsAboutComponent: () => {
+ return (
+ "Message @dorkbutt about getting your personal banner added!"
+ );
+ },
+
+ getVoiceBackgroundStyles({ className, participantUserId }: any) {
+ if (className.includes("tile_")) {
+ if (this.userHasBackground(participantUserId)) {
+ return {
+ backgroundImage: `url(${this.getImageUrl(participantUserId)})`,
+ backgroundSize: "cover",
+ backgroundPosition: "center",
+ backgroundRepeat: "no-repeat"
+ };
+ }
+ }
+ },
+
+ patchBannerUrl({ displayProfile }: any) {
+ if (displayProfile?.banner && settings.store.nitroFirst) return;
+ if (this.userHasBackground(displayProfile?.userId)) return this.getImageUrl(displayProfile?.userId);
+ },
+
+ userHasBackground(userId: string) {
+ return !!this.data?.users[userId];
+ },
+
+ getImageUrl(userId: string): string | null {
+ if (!this.userHasBackground(userId)) return null;
+
+ // We can assert that data exists because userHasBackground returned true
+ const { endpoint, bucket, prefix, users: { [userId]: etag } } = this.data!;
+ return `${endpoint}/${bucket}/${prefix}${userId}?${etag}`;
+ },
+
+ async start() {
+ const res = await fetch(API_URL);
+ if (res.ok) {
+ this.data = await res.json();
+ }
+ }
+});