From 40ae4d2a39aa7a8e1b5f3364c20befd2963f9fdf Mon Sep 17 00:00:00 2001 From: Connor Date: Wed, 30 Sep 2026 16:02:18 +0200 Subject: [PATCH 1/8] CustomRPC: add toggle & Toolbox action for disabling presence (#4636) Co-authored-by: Vendicated --- src/plugins/customRPC/index.tsx | 101 +++++++++++++++++++++----------- src/utils/constants.ts | 4 ++ 2 files changed, 70 insertions(+), 35 deletions(-) diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index 8c28ef45d..998cd9c96 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -18,20 +18,21 @@ import { definePluginSettings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; +import { BaseText } from "@components/BaseText"; +import { Button } from "@components/Button"; +import { Card } from "@components/Card"; import { Divider } from "@components/Divider"; -import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; import { Link } from "@components/Link"; +import { Paragraph } from "@components/Paragraph"; import { Devs } from "@utils/constants"; import { isTruthy } from "@utils/guards"; -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"; +import { ApplicationAssetUtils, FluxDispatcher, Menu, React, UserStore } from "@webpack/common"; import { RPCSettings } from "./RpcSettings"; @@ -52,6 +53,12 @@ export const enum TimestampMode { } export const settings = definePluginSettings({ + enablePresence: { + type: OptionType.BOOLEAN, + description: "Whether to show the presence", + default: true, + onChange: v => setRpc(!v) + }, config: { type: OptionType.COMPONENT, component: RPCSettings @@ -201,11 +208,12 @@ async function createActivity(): Promise { } export async function setRpc(disable?: boolean) { + const shouldDisable = disable ?? !settings.store.enablePresence; const activity: Activity | undefined = await createActivity(); FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", - activity: !disable ? activity : null, + activity: !shouldDisable ? activity : null, socketId: "CustomRPC", }); } @@ -214,12 +222,28 @@ export default definePlugin({ name: "CustomRPC", description: "Add a fully customisable Rich Presence (Game status) to your Discord profile", tags: ["Activity", "Customisation"], - authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev], + authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev, Devs.c0nnorgg], dependencies: ["UserSettingsAPI"], // This plugin's patch is not important for functionality, so don't require a restart requiresRestart: false, settings, + toolboxActions() { + const { enablePresence } = settings.use(["enablePresence"]); + + return ( + { + settings.store.enablePresence = !enablePresence; + setRpc(); + }} + /> + ); + }, + start: setRpc, stop: () => setRpc(true), @@ -235,59 +259,66 @@ export default definePlugin({ ], settingsAboutComponent: () => { + const { enablePresence } = settings.use(["enablePresence"]); const [activity] = useAwaiter(createActivity, { fallbackValue: undefined, deps: Object.values(settings.store) }); const gameActivityEnabled = ShowCurrentGame.useSetting(); const { profileThemeStyle } = useProfileThemeStyle({}); return ( - <> + + {!enablePresence && ( + + + Custom RPC disabled + The "Enable Presence" setting is disabled, so your presence won't show. If this was unintentional, enable it below. + + + )} {!gameActivityEnabled && ( - - Notice - Activity Sharing isn't enabled, people won't be able to see your custom rich presence! + + + Activity Sharing disabled + Activity Sharing isn't enabled, so people won't be able to see your custom rich presence! - - + + + )} - - + + Go to the Discord Developer Portal to create an application and get the application ID. - - + + Upload images in the Rich Presence tab to get the image keys. - - + + If you want to use an image link, download your image and reupload the image to Imgur and get the image link by right-clicking the image and selecting "Copy image address". - - + + You can't see your own buttons on your profile, but everyone else can see it fine. - - + + Some weird unicode text ("fonts" 𝖑𝖎𝖐𝖊 𝖙𝖍𝖎𝖘) may cause the rich presence to not show up, try using normal letters instead. - + - + -
+
{activity && }
- + ); } }); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 28a0efd8b..8395a2b3c 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -677,6 +677,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ Kaede: { name: "Kaede", id: 1492642701320126504n + }, + c0nnorgg: { + name: "c0nnorgg", + id: 1088882977985003620n, } } satisfies Record); From d3edab7ba898ad20a5c695faf5f64c2328fb0796 Mon Sep 17 00:00:00 2001 From: Ambushfall <95249049+Ambushfall@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:54:44 +0200 Subject: [PATCH 2/8] ImplicitRelationship: fix bug in user fetching (#4217) --- src/plugins/implicitRelationships/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/implicitRelationships/index.ts b/src/plugins/implicitRelationships/index.ts index 905b341a6..8c02d5a1d 100644 --- a/src/plugins/implicitRelationships/index.ts +++ b/src/plugins/implicitRelationships/index.ts @@ -163,7 +163,7 @@ export default definePlugin({ FluxDispatcher.dispatch({ type: "GUILD_MEMBERS_REQUEST", guildIds: allGuildIds, - userIds: toRequest.slice(i, i + 100), + userIds: toRequest.slice(i, i + 100).map(e => e.otherUserId), presences: true, nonce: sentNonce, }); From 09210898a08689e35f3b83d0ae8086d04c306737 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 30 Sep 2026 17:13:02 +0200 Subject: [PATCH 3/8] Fix native titlebar setting --- src/Vencord.ts | 11 +++++------ src/api/Settings.ts | 4 ++-- src/components/settings/tabs/vencord/index.tsx | 14 +++++--------- src/main/patcher.ts | 6 ++---- src/nativeTitleBar.css | 7 +++++++ 5 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 src/nativeTitleBar.css diff --git a/src/Vencord.ts b/src/Vencord.ts index 17790136d..4a7e544bc 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -20,6 +20,8 @@ import "~plugins"; import "./fixWeirdAppRegionBug.css"; +import nativeTitleBarStyles from "./nativeTitleBar.css?managed"; + export * as Api from "./api"; export * as Plugins from "./api/PluginManager"; export * as Components from "./components"; @@ -29,11 +31,9 @@ export * as Webpack from "./webpack"; export * as WebpackPatcher from "./webpack/patchWebpack"; export { PlainSettings, Settings }; -import { coreStyleRootNode, initStyles } from "@api/Styles"; +import { enableStyle, initStyles } from "@api/Styles"; import { openSettingsTabModal, UpdaterTab } from "@components/settings"; import { debounce } from "@shared/debounce"; -import { IS_WINDOWS } from "@utils/constants"; -import { createAndAppendStyle } from "@utils/css"; import { StartAt } from "@utils/types"; import { SettingsRouter } from "@webpack/common"; @@ -181,8 +181,7 @@ init(); document.addEventListener("DOMContentLoaded", () => { startAllPlugins(StartAt.DOMContentLoaded); - // FIXME - if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && IS_WINDOWS) { - createAndAppendStyle("vencord-native-titlebar-style", coreStyleRootNode).textContent = "[class*=titleBar]{display: none!important}"; + if (IS_DISCORD_DESKTOP && Settings.nativeTitleBar) { + enableStyle(nativeTitleBarStyles); } }, { once: true }); diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 2bcc785f9..f5ddfa294 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -64,7 +64,7 @@ export interface Settings { | undefined; windowsMaterial: "none" | "mica" | "tabbed" | "acrylic"; disableMinSize: boolean; - winNativeTitleBar: boolean; + nativeTitleBar: boolean; plugins: { [plugin: string]: { enabled: boolean; @@ -107,7 +107,7 @@ const DefaultSettings: Settings = { macosVibrancyStyle: undefined, windowsMaterial: "none", disableMinSize: false, - winNativeTitleBar: false, + nativeTitleBar: false, plugins: {}, uiElements: { diff --git a/src/components/settings/tabs/vencord/index.tsx b/src/components/settings/tabs/vencord/index.tsx index ca4ddede6..5bd1718a3 100644 --- a/src/components/settings/tabs/vencord/index.tsx +++ b/src/components/settings/tabs/vencord/index.tsx @@ -51,7 +51,7 @@ type KeysOfType = { }[keyof Object]; function Switches() { - const settings = useSettings(["useQuickCss", "enableReactDevtools", "frameless", "winNativeTitleBar", "transparent", "winCtrlQ", "disableMinSize"]); + const settings = useSettings(["useQuickCss", "enableReactDevtools", "frameless", "nativeTitleBar", "transparent", "winCtrlQ", "disableMinSize"]); const Switches = [ { @@ -59,15 +59,11 @@ function Switches() { title: "Enable Custom CSS", description: "Apply your configured QuickCSS" }, - !IS_WEB && (!IS_DISCORD_DESKTOP || !IS_WINDOWS ? { - key: "frameless", - title: "Disable the window frame", + IS_DISCORD_DESKTOP && { + key: "nativeTitleBar", + title: "Enable the system's title bar in addition to Discord's custom one", restartRequired: true - } : { - key: "winNativeTitleBar", - title: "Use Windows' native title bar instead of Discord's custom one", - restartRequired: true - }), + }, !IS_WEB && { key: "transparent", title: "Enable window transparency", diff --git a/src/main/patcher.ts b/src/main/patcher.ts index 42083f655..a3e5d75e3 100644 --- a/src/main/patcher.ts +++ b/src/main/patcher.ts @@ -74,15 +74,13 @@ if (!IS_VANILLA) { return; } - const { frameless, winNativeTitleBar, disableMinSize, transparent, macosVibrancyStyle, windowsMaterial } = settings; + const { nativeTitleBar, disableMinSize, transparent, macosVibrancyStyle, windowsMaterial } = settings; const original = options.webPreferences.preload; options.webPreferences.preload = join(__dirname, "preload.js"); options.webPreferences.sandbox = false; - if (frameless) { - options.frame = false; - } else if (process.platform === "win32" && winNativeTitleBar) { + if (nativeTitleBar) { delete options.frame; } diff --git a/src/nativeTitleBar.css b/src/nativeTitleBar.css new file mode 100644 index 000000000..f9e5ba465 --- /dev/null +++ b/src/nativeTitleBar.css @@ -0,0 +1,7 @@ +[class*="bar_"] { + margin-inline-end: 12px !important; +} + +[class*="winButtons_"] { + display: none !important; +} \ No newline at end of file From d9bc6f71fdd552efcc66f9050f54b24c0a0b3609 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 3 Oct 2026 00:05:16 +0200 Subject: [PATCH 4/8] IgnoreActivities: fix breaking all activities --- src/plugins/ignoreActivities/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index f512cf984..f14ca21f3 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -295,8 +295,8 @@ export default definePlugin({ find: '"LocalActivityStore"', replacement: [ { - match: /\.LISTENING.+?(?=!?\i\(\)\(\i,\i\))(?<=(\i)\.push.+?)/, - replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);` + match: /\.LISTENING.+?([,:])(?=!?\i\(\)\(\i,\i\))(?<=(\i)\.push.+?)/, + replace: (m, commaOrSemiColon, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored)${commaOrSemiColon}` } ] }, From 0c3c9625a7d615ec1218fa5bfcd3d7c64e61db96 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 3 Oct 2026 00:50:35 +0200 Subject: [PATCH 5/8] fix Toasts --- packages/discord-types/enums/misc.ts | 5 ++ packages/discord-types/src/index.d.ts | 1 + packages/discord-types/src/toasts.d.ts | 35 ++++++++++++ src/api/SettingsSync/offline.ts | 13 ++--- .../settings/tabs/plugins/PluginCard.tsx | 7 ++- .../settings/tabs/updater/Components.tsx | 12 ++--- src/plugins/_api/badges/index.tsx | 8 +-- src/plugins/_core/supportHelper.tsx | 12 ++--- src/plugins/addAttachments/index.tsx | 10 ++-- src/plugins/arRPC.web/index.tsx | 12 +---- .../decor/lib/stores/AuthorizationStore.tsx | 4 +- src/plugins/expressionCloner/index.tsx | 14 ++--- src/plugins/ignoreActivities/index.tsx | 6 +-- src/plugins/openInApp/index.ts | 4 +- .../pinDms/components/CreateCategoryModal.tsx | 4 +- src/plugins/reviewDB/auth.tsx | 8 +-- src/plugins/reviewDB/reviewDbApi.ts | 18 +++---- src/plugins/reviewDB/utils.tsx | 15 +++--- src/plugins/translate/utils.ts | 8 +-- src/plugins/userVoiceShow/components.tsx | 4 +- src/plugins/voiceMessages/DesktopRecorder.tsx | 6 +-- src/plugins/voiceMessages/index.tsx | 6 +-- src/utils/discord.tsx | 8 +-- src/webpack/common/utils.ts | 53 +++---------------- 24 files changed, 117 insertions(+), 156 deletions(-) create mode 100644 packages/discord-types/src/toasts.d.ts diff --git a/packages/discord-types/enums/misc.ts b/packages/discord-types/enums/misc.ts index 81a6e41ca..aaecd1a70 100644 --- a/packages/discord-types/enums/misc.ts +++ b/packages/discord-types/enums/misc.ts @@ -53,3 +53,8 @@ export const enum PremiumType { TIER_2 = 2, TIER_0 = 3, } + +export const enum ToastPosition { + TOP = 0, + BOTTOM = 1, +} diff --git a/packages/discord-types/src/index.d.ts b/packages/discord-types/src/index.d.ts index 83bbba26e..024f3418c 100644 --- a/packages/discord-types/src/index.d.ts +++ b/packages/discord-types/src/index.d.ts @@ -7,5 +7,6 @@ export * from "./fluxEvents"; export * from "./menu"; export * from "./modules"; export * from "./stores"; +export * from "./toasts"; export * from "./utils"; export * as Webpack from "../webpack"; diff --git a/packages/discord-types/src/toasts.d.ts b/packages/discord-types/src/toasts.d.ts new file mode 100644 index 000000000..c058a920b --- /dev/null +++ b/packages/discord-types/src/toasts.d.ts @@ -0,0 +1,35 @@ +import type { ComponentType } from "react"; +import { ToastPosition } from "../enums"; + +export type ToastType = "message" | "success" | "failure" | "custom" | "clip" | "link" | "forward" | "bookmark" | "clock"; + +export type NewToastVariant = "default" | "success" | "critical"; +export type NewToastPosition = "top" | "bottom"; + +export interface ToastData { + message: string; + type?: ToastType; + options?: { + position?: ToastPosition; + duration?: number; + }; +} + +export interface NewToastData { + text: string; + variant: NewToastVariant; + position?: NewToastPosition; + duration?: number; + + icon?: ComponentType; + iconColor?: any; +} + +export type showToast = (data: NewToastData) => void; +export type popToast = (context?: string) => void; +export type createToast = (data: ToastData) => NewToastData; + +export interface Toasts { + show: showToast; + pop: popToast; +} diff --git a/src/api/SettingsSync/offline.ts b/src/api/SettingsSync/offline.ts index 5ff509caf..cc4be37fc 100644 --- a/src/api/SettingsSync/offline.ts +++ b/src/api/SettingsSync/offline.ts @@ -7,20 +7,13 @@ import { PlainSettings } from "@api/Settings"; import { Logger } from "@utils/Logger"; import { chooseFile, saveFile } from "@utils/web"; -import { moment, Toasts } from "@webpack/common"; - -const toast = (type: string, message: string) => - Toasts.show({ - type, - message, - id: Toasts.genId() - }); +import { moment, showToast } from "@webpack/common"; const toastSuccess = () => - toast(Toasts.Type.SUCCESS, "Settings successfully imported. Restart to apply changes!"); + showToast("Settings successfully imported. Restart to apply changes!", "success"); const toastFailure = (err: any) => - toast(Toasts.Type.FAILURE, `Failed to import settings: ${String(err)}`); + showToast(`Failed to import settings: ${String(err)}`, "failure"); const logger = new Logger("SettingsSync:Offline", "#39b7e0"); diff --git a/src/components/settings/tabs/plugins/PluginCard.tsx b/src/components/settings/tabs/plugins/PluginCard.tsx index ac8b9b839..6c84f39dd 100644 --- a/src/components/settings/tabs/plugins/PluginCard.tsx +++ b/src/components/settings/tabs/plugins/PluginCard.tsx @@ -10,7 +10,8 @@ import { Settings } from "@api/Settings"; import { CogWheel, InfoIcon } from "@components/Icons"; import { AddonCard } from "@components/settings/AddonCard"; import { Plugin } from "@utils/types"; -import { React, showToast, Toasts } from "@webpack/common"; +import { ToastPosition } from "@vencord/discord-types/enums"; +import { React, showToast } from "@webpack/common"; import { cl, logger } from "."; import { openPluginModal } from "./PluginModal"; @@ -67,9 +68,7 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on settings.enabled = false; const msg = `Error while ${wasEnabled ? "stopping" : "starting"} plugin ${plugin.name}`; - showToast(msg, Toasts.Type.FAILURE, { - position: Toasts.Position.BOTTOM, - }); + showToast(msg, "failure", { position: ToastPosition.BOTTOM }); return; } diff --git a/src/components/settings/tabs/updater/Components.tsx b/src/components/settings/tabs/updater/Components.tsx index 67c3b443d..d1b13d4e0 100644 --- a/src/components/settings/tabs/updater/Components.tsx +++ b/src/components/settings/tabs/updater/Components.tsx @@ -12,7 +12,8 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { relaunch } from "@utils/native"; import { changes, checkForUpdates, update, updateError } from "@utils/updater"; -import { Button, ConfirmModal,Forms, openModal, React, Toasts, useState } from "@webpack/common"; +import { ToastPosition } from "@vencord/discord-types/enums"; +import { Button, ConfirmModal, Forms, openModal, React, showToast, useState } from "@webpack/common"; import { runWithDispatch } from "./runWithDispatch"; @@ -132,13 +133,8 @@ export function Updatable(props: CommonProps) { } else { setUpdates([]); - Toasts.show({ - message: "No updates found!", - id: Toasts.genId(), - type: Toasts.Type.MESSAGE, - options: { - position: Toasts.Position.BOTTOM - } + showToast("No updates found!", "message", { + position: ToastPosition.BOTTOM }); } })} diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index d91b4eae7..88e41d6d2 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -31,7 +31,7 @@ import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { shouldShowContributorBadge } from "@utils/misc"; import definePlugin from "@utils/types"; -import { ContextMenuApi, Forms, Menu, Modal, openModal, Toasts, UserStore } from "@webpack/common"; +import { ContextMenuApi, Forms, Menu, Modal, openModal, showToast, UserStore } from "@webpack/common"; const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64"; @@ -131,11 +131,7 @@ export default definePlugin({ toolboxActions: { async "Refetch Badges"() { await loadBadges(true); - Toasts.show({ - id: Toasts.genId(), - message: "Successfully refetched badges!", - type: Toasts.Type.SUCCESS - }); + showToast("Successfully refetched badges!", "success"); } }, diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 3312c51e9..159ef3865 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -35,7 +35,7 @@ import { makeCodeblock } from "@utils/text"; import definePlugin from "@utils/types"; import { checkForUpdates, isOutdated, update } from "@utils/updater"; import { Channel, RenderModalProps } from "@vencord/discord-types"; -import { Button, ChannelStore, ConfirmModal, Forms, GuildMemberStore, openModal, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; +import { Button, ChannelStore, ConfirmModal, Forms, GuildMemberStore, openModal, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, UserStore } from "@webpack/common"; import { JSX } from "react"; import gitHash from "~git-hash"; @@ -284,12 +284,12 @@ export default definePlugin({ onClick={async () => { try { if (await forceUpdate()) - showToast("Success! Restarting...", Toasts.Type.SUCCESS); + showToast("Success! Restarting...", "success"); else - showToast("Already up to date!", Toasts.Type.MESSAGE); + showToast("Already up to date!"); } catch (e) { new Logger(this.name).error("Error while updating:", e); - showToast("Failed to update :(", Toasts.Type.FAILURE); + showToast("Failed to update :(", "failure"); } }} > @@ -328,10 +328,10 @@ export default definePlugin({ onClick={async () => { try { await AsyncFunction(match[1])(); - showToast("Success!", Toasts.Type.SUCCESS); + showToast("Success!", "success"); } catch (e) { new Logger(this.name).error("Error while running snippet:", e); - showToast("Failed to run snippet :(", Toasts.Type.FAILURE); + showToast("Failed to run snippet :(", "failure"); } }} > diff --git a/src/plugins/addAttachments/index.tsx b/src/plugins/addAttachments/index.tsx index 8f09666fd..81fce021e 100644 --- a/src/plugins/addAttachments/index.tsx +++ b/src/plugins/addAttachments/index.tsx @@ -12,7 +12,7 @@ import definePlugin from "@utils/types"; import { Message } from "@vencord/discord-types"; import { MessageFlags, MessageType } from "@vencord/discord-types/enums"; import { findByPropsLazy } from "@webpack"; -import { AuthenticationStore, ChannelStore, EditMessageStore, MessageStore, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, Toasts } from "@webpack/common"; +import { AuthenticationStore, ChannelStore, EditMessageStore, MessageStore, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast } from "@webpack/common"; const { uniqueId } = findByPropsLazy("uniqueId"); @@ -39,15 +39,15 @@ async function addAttachments(channelId: string, messageId: string, existingAtta if (files.length + existingAttachmentCount > 10) { const remaining = 10 - existingAttachmentCount; if (remaining <= 0) { - showToast("You cannot add more attachments to this message.", Toasts.Type.FAILURE); + showToast("You cannot add more attachments to this message.", "failure"); return; } - showToast(`You can only add ${pluralise(remaining, "more attachment")} to this message.`, Toasts.Type.FAILURE); + showToast(`You can only add ${pluralise(remaining, "more attachment")} to this message.`, "failure"); return; } - showToast("Uploading, this can take a while...", Toasts.Type.CLOCK); + showToast("Uploading, this can take a while...", "clock"); const { body: { attachments } } = await RestAPI.post({ url: `/channels/${channelId}/attachments`, @@ -87,7 +87,7 @@ async function addAttachments(channelId: string, messageId: string, existingAtta } }); - showToast(`${files.length === 1 ? "Attachment" : "Attachments"} added successfully!`, Toasts.Type.SUCCESS); + showToast(`${files.length === 1 ? "Attachment" : "Attachments"} added successfully!`, "success"); } function canAddAttachments(msg: Message) { diff --git a/src/plugins/arRPC.web/index.tsx b/src/plugins/arRPC.web/index.tsx index 7d54ddc36..c631a471e 100644 --- a/src/plugins/arRPC.web/index.tsx +++ b/src/plugins/arRPC.web/index.tsx @@ -21,7 +21,7 @@ import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; import definePlugin, { ReporterTestable } from "@utils/types"; import { findByCodeLazy } from "@webpack"; -import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common"; +import { ApplicationAssetUtils, FluxDispatcher, Forms, showToast } from "@webpack/common"; const fetchApplicationsRPC = findByCodeLazy('"Invalid Origin"', ".application"); @@ -89,15 +89,7 @@ export default definePlugin({ return; } - Toasts.show({ // show toast on success - message: "Connected to arRPC", - type: Toasts.Type.SUCCESS, - id: Toasts.genId(), - options: { - duration: 1000, - position: Toasts.Position.BOTTOM - } - }); + showToast("Connected to arRPC", "success"); }, stop() { diff --git a/src/plugins/decor/lib/stores/AuthorizationStore.tsx b/src/plugins/decor/lib/stores/AuthorizationStore.tsx index 06fc47400..7aa223d43 100644 --- a/src/plugins/decor/lib/stores/AuthorizationStore.tsx +++ b/src/plugins/decor/lib/stores/AuthorizationStore.tsx @@ -8,7 +8,7 @@ import * as DataStore from "@api/DataStore"; import { AUTHORIZE_URL, CLIENT_ID } from "@plugins/decor/lib/constants"; import { proxyLazy } from "@utils/lazy"; import { Logger } from "@utils/Logger"; -import { OAuth2AuthorizeModal, openModal, showToast, Toasts, UserStore, zustandCreate, zustandPersist } from "@webpack/common"; +import { OAuth2AuthorizeModal, openModal, showToast, UserStore, zustandCreate, zustandPersist } from "@webpack/common"; interface AuthorizationState { token: string | null; @@ -74,7 +74,7 @@ export const useAuthorizationStore = proxyLazy(() => zustandCreate( resolve(void 0); } catch (e) { if (e instanceof Error) { - showToast(`Failed to authorize: ${e.message}`, Toasts.Type.FAILURE); + showToast(`Failed to authorize: ${e.message}`, "failure"); new Logger("Decor").error("Failed to authorize", e); reject(e); } diff --git a/src/plugins/expressionCloner/index.tsx b/src/plugins/expressionCloner/index.tsx index 60f148651..d66ddd572 100644 --- a/src/plugins/expressionCloner/index.tsx +++ b/src/plugins/expressionCloner/index.tsx @@ -29,7 +29,7 @@ import definePlugin from "@utils/types"; import { Guild, GuildSticker, Message } from "@vencord/discord-types"; import { StickerFormatType } from "@vencord/discord-types/enums"; import { findByCodeLazy } from "@webpack"; -import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, IconUtils, Menu, Modal, openModalLazy, PermissionsBits, PermissionStore, React, RestAPI, StickersStore, Toasts, Tooltip, UserStore } from "@webpack/common"; +import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, IconUtils, Menu, Modal, openModalLazy, PermissionsBits, PermissionStore, React, RestAPI, showToast, StickersStore, Tooltip, UserStore } from "@webpack/common"; import { Promisable } from "type-fest"; const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START"); @@ -196,11 +196,7 @@ async function doClone(guildId: string, data: Sticker | Emoji) { else await cloneEmoji(guildId, data); - Toasts.show({ - message: `Successfully cloned ${data.name} to ${GuildStore.getGuild(guildId)?.name ?? "your server"}!`, - type: Toasts.Type.SUCCESS, - id: Toasts.genId() - }); + showToast(`Successfully cloned ${data.name} to ${GuildStore.getGuild(guildId)?.name ?? "your server"}!`, "success"); } catch (e: any) { let message = "Something went wrong (check console!)"; try { @@ -208,11 +204,7 @@ async function doClone(guildId: string, data: Sticker | Emoji) { } catch { } new Logger("ExpressionCloner").error("Failed to clone", data.name, "to", guildId, e); - Toasts.show({ - message: "Failed to clone: " + message, - type: Toasts.Type.FAILURE, - id: Toasts.genId() - }); + showToast("Failed to clone: " + message, "failure"); } } diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index f14ca21f3..d0caf7c3e 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -16,7 +16,7 @@ import CustomRpcPlugin from "@plugins/customRPC"; import { Devs } from "@utils/constants"; import { Margins } from "@utils/margins"; import definePlugin, { OptionType } from "@utils/types"; -import { FluxDispatcher, Menu, RunningGameStore, showToast, TextArea, Toasts, Tooltip, useEffect, useState } from "@webpack/common"; +import { FluxDispatcher, Menu, RunningGameStore, showToast, TextArea, Tooltip, useEffect, useState } from "@webpack/common"; const enum ActivitiesTypes { Game, @@ -91,12 +91,12 @@ function ImportCustomRPCComponent() { onClick={() => { const id = CustomRpcPlugin.settings.store.appID; if (!id) { - return showToast("CustomRPC application ID is not set.", Toasts.Type.FAILURE); + return showToast("CustomRPC application ID is not set.", "failure"); } const isAlreadyAdded = idsListPushID?.(id); if (isAlreadyAdded) { - showToast("CustomRPC application ID is already added.", Toasts.Type.FAILURE); + showToast("CustomRPC application ID is already added.", "failure"); } }} > diff --git a/src/plugins/openInApp/index.ts b/src/plugins/openInApp/index.ts index 08184eed1..443a61b85 100644 --- a/src/plugins/openInApp/index.ts +++ b/src/plugins/openInApp/index.ts @@ -19,7 +19,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType, PluginNative, SettingsDefinition } from "@utils/types"; -import { showToast, Toasts } from "@webpack/common"; +import { showToast } from "@webpack/common"; import type { MouseEvent } from "react"; interface URLReplacementRule { @@ -135,7 +135,7 @@ export default definePlugin({ } if (rule.match.test(url)) { - showToast("Opened link in native app", Toasts.Type.SUCCESS); + showToast("Opened link in native app", "success"); const newUrl = url.replace(rule.match, rule.replace); VencordNative.native.openExternal(newUrl); diff --git a/src/plugins/pinDms/components/CreateCategoryModal.tsx b/src/plugins/pinDms/components/CreateCategoryModal.tsx index 6b4f075ce..930c4f4ee 100644 --- a/src/plugins/pinDms/components/CreateCategoryModal.tsx +++ b/src/plugins/pinDms/components/CreateCategoryModal.tsx @@ -9,7 +9,7 @@ import { categoryLen, createCategory, getCategory } from "@plugins/pinDms/data"; import { classNameFactory } from "@utils/css"; import { RenderModalProps } from "@vencord/discord-types"; import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack"; -import { ColorPicker, Forms, Modal,openModalLazy, TextInput, Toasts, useMemo, useState } from "@webpack/common"; +import { ColorPicker, Forms, Modal, openModalLazy, TextInput, useMemo, useState } from "@webpack/common"; interface ColorPickerWithSwatchesProps { className?: string; @@ -40,7 +40,7 @@ function useCategory(categoryId: string | null, initalChannelId: string | null) return getCategory(categoryId); } else if (initalChannelId) { return { - id: Toasts.genId(), + id: crypto.randomUUID(), name: `Pin Category ${categoryLen() + 1}`, color: DEFAULT_COLOR, collapsed: false, diff --git a/src/plugins/reviewDB/auth.tsx b/src/plugins/reviewDB/auth.tsx index cd8728980..8215bff62 100644 --- a/src/plugins/reviewDB/auth.tsx +++ b/src/plugins/reviewDB/auth.tsx @@ -6,7 +6,7 @@ import * as DataStore from "@api/DataStore"; import { Logger } from "@utils/Logger"; -import { OAuth2AuthorizeModal, openModal, showToast, Toasts, UserStore } from "@webpack/common"; +import { OAuth2AuthorizeModal, openModal, showToast, UserStore } from "@webpack/common"; import { ReviewDBAuth } from "./entities"; @@ -50,7 +50,7 @@ export function authorize(callback?: () => void) { permissions={0n} clientId="915703782174752809" cancelCompletesFlow={false} - callback={async (response: { location: string }) => { + callback={async (response: { location: string; }) => { try { const url = new URL(response.location); url.searchParams.append("clientMod", "vencord"); @@ -60,13 +60,13 @@ export function authorize(callback?: () => void) { if (!res.ok) { const { message } = await res.json(); - showToast(message ?? "An error occured while authorizing", Toasts.Type.FAILURE); + showToast(message ?? "An error occured while authorizing", "failure"); return; } const { token } = await res.json(); updateAuth({ token }); - showToast("Successfully logged in!", Toasts.Type.SUCCESS); + showToast("Successfully logged in!", "success"); callback?.(); } catch (e) { new Logger("ReviewDB").error("Failed to authorize", e); diff --git a/src/plugins/reviewDB/reviewDbApi.ts b/src/plugins/reviewDB/reviewDbApi.ts index 48de46e73..0f57853ae 100644 --- a/src/plugins/reviewDB/reviewDbApi.ts +++ b/src/plugins/reviewDB/reviewDbApi.ts @@ -4,8 +4,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { Toasts } from "@webpack/common"; - import { Auth, authorize, getToken, updateAuth } from "./auth"; import { Review, ReviewDBCurrentUser, ReviewDBUser, ReviewType } from "./entities"; import { settings } from "./settings"; @@ -51,7 +49,7 @@ async function rdbRequest(path: string, options: RequestInit = {}): ...options, headers, }).catch(err => { - showToast("Network error: Failed to connect to ReviewDB.", Toasts.Type.FAILURE); + showToast("Network error: Failed to connect to ReviewDB.", "failure"); return null; }); @@ -61,14 +59,14 @@ async function rdbRequest(path: string, options: RequestInit = {}): if (!res.ok) { const message = data?.message ?? `ReviewDB: Request failed with status ${res.status}`; - showToast(message, Toasts.Type.FAILURE); + showToast(message, "failure"); return null; } return data as T; } -export async function getReviews(id: string, { limit, offset = 0, fetchVotes = false }: { limit?: number; offset?: number; fetchVotes?: boolean } = {}): Promise { +export async function getReviews(id: string, { limit, offset = 0, fetchVotes = false }: { limit?: number; offset?: number; fetchVotes?: boolean; } = {}): Promise { let flags = 0; if (!settings.store.showWarning) flags |= WarningFlag; @@ -92,7 +90,7 @@ export async function getReviews(id: string, { limit, offset = 0, fetchVotes = f }; if (!req.ok) { - showToast(res.message, Toasts.Type.FAILURE); + showToast(res.message, "failure"); return { ...res, reviews: [ @@ -140,7 +138,7 @@ export async function getReviewVotes(id: string): Promise { return res?.votes ?? []; } -export async function addReview(review: { userid: string; comment: string }): Promise { +export async function addReview(review: { userid: string; comment: string; }): Promise { const token = await getToken(); if (!token) { @@ -186,7 +184,7 @@ export async function voteReview(id: number, isUpvote: boolean) { return false; } - const data = await rdbRequest<{ message?: string }>(`/reviews/${id}/vote`, { + const data = await rdbRequest<{ message?: string; }>(`/reviews/${id}/vote`, { method: "POST", body: JSON.stringify({ isUpvote }) }); @@ -204,7 +202,7 @@ export async function deleteReviewVote(id: number) { return false; } - const data = await rdbRequest<{ message?: string }>(`/reviews/${id}/vote`, { + const data = await rdbRequest<{ message?: string; }>(`/reviews/${id}/vote`, { method: "DELETE", }); @@ -224,7 +222,7 @@ async function patchBlock(action: "block" | "unblock", userId: string) { if (!data) return; - showToast(`Successfully ${action}ed user`, Toasts.Type.SUCCESS); + showToast(`Successfully ${action}ed user`, "success"); if (Auth?.user?.blockedUsers) { const newBlockedUsers = action === "block" diff --git a/src/plugins/reviewDB/utils.tsx b/src/plugins/reviewDB/utils.tsx index ec7609c50..e9ea0a68e 100644 --- a/src/plugins/reviewDB/utils.tsx +++ b/src/plugins/reviewDB/utils.tsx @@ -17,7 +17,9 @@ */ import { classNameFactory } from "@utils/css"; -import { Toasts, UserStore } from "@webpack/common"; +import { ToastType } from "@vencord/discord-types"; +import { ToastPosition } from "@vencord/discord-types/enums"; +import { showToast as originalShowToast, UserStore } from "@webpack/common"; import { Auth } from "./auth"; import { Review, UserType } from "./entities"; @@ -42,13 +44,8 @@ export function canReportReview(review: Review) { return review.sender.discordID !== UserStore.getCurrentUser().id; } -export function showToast(message: string, type = Toasts.Type.MESSAGE) { - Toasts.show({ - id: Toasts.genId(), - message, - type, - options: { - position: Toasts.Position.BOTTOM, // NOBODY LIKES TOASTS AT THE TOP - }, +export function showToast(message: string, type: ToastType = "message") { + originalShowToast(message, type, { + position: ToastPosition.BOTTOM }); } diff --git a/src/plugins/translate/utils.ts b/src/plugins/translate/utils.ts index 2c77db511..934def25d 100644 --- a/src/plugins/translate/utils.ts +++ b/src/plugins/translate/utils.ts @@ -19,7 +19,7 @@ import { classNameFactory } from "@utils/css"; import { onlyOnce } from "@utils/onlyOnce"; import { PluginNative } from "@utils/types"; -import { showToast, Toasts } from "@webpack/common"; +import { showToast } from "@webpack/common"; import { DeeplLanguages, deeplLanguageToGoogleLanguage, GoogleLanguages, KagiLanguages } from "./languages"; import { resetLanguageDefaults, settings } from "./settings"; @@ -89,7 +89,7 @@ export async function translate(kind: "received" | "sent", text: string): Promis ? e : "Something went wrong. If this issue persists, please check the console or ask for help in the support server."; - showToast(userMessage, Toasts.Type.FAILURE); + showToast(userMessage, "failure"); throw e instanceof Error ? e @@ -131,12 +131,12 @@ function fallbackToGoogle(text: string, sourceLang: string, targetLang: string): } const showDeeplApiQuotaToast = onlyOnce( - () => showToast("Deepl API quota exceeded. Falling back to Google Translate", Toasts.Type.FAILURE) + () => showToast("Deepl API quota exceeded. Falling back to Google Translate", "failure") ); async function deeplTranslate(text: string, sourceLang: string, targetLang: string): Promise { if (!settings.store.deeplApiKey) { - showToast("DeepL API key is not set. Resetting to Google", Toasts.Type.FAILURE); + showToast("DeepL API key is not set. Resetting to Google", "failure"); settings.store.service = "google"; resetLanguageDefaults(); diff --git a/src/plugins/userVoiceShow/components.tsx b/src/plugins/userVoiceShow/components.tsx index 3da84a7af..ccb5faf47 100644 --- a/src/plugins/userVoiceShow/components.tsx +++ b/src/plugins/userVoiceShow/components.tsx @@ -11,7 +11,7 @@ import { classNameFactory } from "@utils/css"; import { classes } from "@utils/misc"; import { Channel } from "@vencord/discord-types"; import { findByPropsLazy, findCssClassesLazy } from "@webpack"; -import { ChannelRouter, ChannelStore, Parser, PermissionsBits, PermissionStore, React, showToast, Text, Toasts, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common"; +import { ChannelRouter, ChannelStore, Parser, PermissionsBits, PermissionStore, React, showToast, Text, Tooltip, useMemo, UserStore, UserSummaryItem, useStateFromStores, VoiceStateStore } from "@webpack/common"; import { PropsWithChildren } from "react"; const cl = classNameFactory("vc-uvs-"); @@ -153,7 +153,7 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, is if (e.detail > 1) { if (!isDM && !PermissionStore.can(PermissionsBits.CONNECT, channel)) { - showToast("You cannot join the user's Voice Channel", Toasts.Type.FAILURE); + showToast("You cannot join the user's Voice Channel", "failure"); return; } diff --git a/src/plugins/voiceMessages/DesktopRecorder.tsx b/src/plugins/voiceMessages/DesktopRecorder.tsx index 8b6bc6d06..72fd8c12f 100644 --- a/src/plugins/voiceMessages/DesktopRecorder.tsx +++ b/src/plugins/voiceMessages/DesktopRecorder.tsx @@ -17,7 +17,7 @@ */ import { PluginNative } from "@utils/types"; -import { Button, MediaEngineStore, showToast, Toasts, useState } from "@webpack/common"; +import { Button, MediaEngineStore, showToast, useState } from "@webpack/common"; import type { VoiceRecorder } from "."; import { settings } from "./settings"; @@ -47,7 +47,7 @@ export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingC if (success) changeRecording(true); else - showToast("Failed to start recording", Toasts.Type.FAILURE); + showToast("Failed to start recording", "failure"); } ); } else { @@ -57,7 +57,7 @@ export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingC if (buf) setAudioBlob(new Blob([buf], { type: "audio/ogg; codecs=opus" })); else - showToast("Failed to finish recording", Toasts.Type.FAILURE); + showToast("Failed to finish recording", "failure"); } changeRecording(false); }); diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx index d2c6c3f3d..e619e44c9 100644 --- a/src/plugins/voiceMessages/index.tsx +++ b/src/plugins/voiceMessages/index.tsx @@ -32,7 +32,7 @@ import { chooseFile } from "@utils/web"; import { CloudUpload as TCloudUpload, RenderModalProps } from "@vencord/discord-types"; import { CloudUploadPlatform } from "@vencord/discord-types/enums"; import { findLazy } from "@webpack"; -import { Button, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, Modal,openModal, PendingReplyStore, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common"; +import { Button, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, Modal, openModal, PendingReplyStore, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, useEffect, useState } from "@webpack/common"; import { ComponentType } from "react"; import { VoiceRecorderDesktop } from "./DesktopRecorder"; @@ -140,7 +140,7 @@ function sendAudio(blob: Blob, meta: AudioMetadata) { } }); }); - upload.on("error", () => showToast("Failed to upload voice message", Toasts.Type.FAILURE)); + upload.on("error", () => showToast("Failed to upload voice message", "failure")); upload.upload(); } @@ -216,7 +216,7 @@ function VoiceMessageModal({ modalProps }: { modalProps: RenderModalProps; }) { onClick: () => { sendAudio(blob!, meta ?? EMPTY_META); modalProps.onClose(); - showToast("Now sending voice message... Please be patient", Toasts.Type.MESSAGE); + showToast("Now sending voice message... Please be patient", "clock"); }, disabled: !blob }]} diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index 89759d07b..47aea0cc4 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -18,7 +18,7 @@ import type { MessageObject } from "@api/MessageEvents"; import type { Channel, CloudUpload, Guild, GuildFeatures, MediaModalItem, MediaModalProps, Message, User } from "@vencord/discord-types"; -import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, InviteActions, MessageActions, openMediaModal, RestAPI, SelectedChannelStore, SelectedGuildStore, Toasts, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; +import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, InviteActions, MessageActions, openMediaModal, RestAPI, SelectedChannelStore, SelectedGuildStore, showToast, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { Except } from "type-fest"; import { copyToClipboard } from "./clipboard"; @@ -116,11 +116,7 @@ export function insertTextIntoChatInputBox(text: string) { export async function copyWithToast(text: string, toastMessage = "Copied to clipboard!") { await copyToClipboard(text); - Toasts.show({ - message: toastMessage, - id: Toasts.genId(), - type: Toasts.Type.SUCCESS - }); + showToast(toastMessage, "success"); } interface MessageOptions { diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 32eb73a71..47d23bf8a 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import type * as t from "@vencord/discord-types"; +import * as t from "@vencord/discord-types"; import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "@webpack"; import type * as TSPattern from "ts-pattern"; @@ -66,23 +66,6 @@ waitFor("parseTopic", m => Parser = m); export let Alerts: t.Alerts; waitFor(["show", "close"], m => Alerts = m); -const ToastType = { - MESSAGE: "message", - SUCCESS: "success", - FAILURE: "failure", - CUSTOM: "custom", - CLIP: "clip", - LINK: "link", - FORWARD: "forward", - BOOKMARK: "bookmark", - CLOCK: "clock" -}; - -const ToastPosition = { - TOP: 0, - BOTTOM: 1 -}; - export interface ToastData { message: string, id: string, @@ -102,40 +85,18 @@ export interface ToastOptions { duration?: number; } -interface ToastsExports { - showToast: (data: ToastData) => void; - popToast(): void; -} - -const ToastsExports = mapMangledModuleLazy(".currentToastMap.has(", { - showToast: filters.byCode(".currentToastMap.has("), - popToast: filters.byCode(".delete(") +export const Toasts: t.Toasts = mapMangledModuleLazy(".currentToastMap.has(", { + show: filters.byCode(".currentToastMap.has("), + pop: filters.byCode(".delete(") }); -export function createToast(message: string, type: string, options?: ToastOptions): ToastData { - return { - message, - id: Toasts.genId(), - type, - options - }; -} - -export const Toasts = { - Type: ToastType, - Position: ToastPosition, - genId: () => (Math.random() || Math.random()).toString(36).slice(2), - - show: ToastsExports.showToast, - pop: ToastsExports.popToast, - create: createToast, -}; +export const createToast: t.createToast = findByCodeLazy('variant:"default",icon:', ".duration"); /** * Show a simple toast. If you need more options, use Toasts.show manually */ -export function showToast(message: string, type = ToastType.MESSAGE, options?: ToastOptions) { - Toasts.show(Toasts.create(message, type, options)); +export function showToast(message: string, type: t.ToastType = "message", options?: ToastOptions) { + Toasts.show(createToast({ message, type, options })); } export const UserUtils = { From b52ed365cf2ea64a5019b419291a2d9d4d76852d Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 3 Oct 2026 00:56:19 +0200 Subject: [PATCH 6/8] Remove 'Run Snippet' functionality This was really nice to have support, but having functionality like this is inherently unsafe. For security reasons, it's better to remove it. --- src/plugins/_core/supportHelper.tsx | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 159ef3865..bfedb942f 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -43,8 +43,6 @@ import plugins, { PluginMeta } from "~plugins"; import SettingsPlugin from "./settings"; -const CodeBlockRe = /```js\n(.+?)```/s; - const AdditionalAllowedChannelIds = [ "1024286218801926184", // Vencord > #bot-commands ]; @@ -55,8 +53,6 @@ const TrustedRolesIds = [ DONOR_ROLE_ID, // donor ]; -const AsyncFunction = async function () { }.constructor; - const ShowCurrentGame = getUserSettingLazy("status", "showCurrentGame")!; const isSupportAllowedChannel = (channel: Channel) => channel.parent_id === SUPPORT_CATEGORY_ID || AdditionalAllowedChannelIds.includes(channel.id); @@ -319,28 +315,6 @@ export default definePlugin({ } } - if (props.channel.parent_id === KNOWN_ISSUES_CHANNEL_ID || (props.channel.parent_id === SUPPORT_CATEGORY_ID && props.message.author.id === VENBOT_USER_ID)) { - const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || ""); - if (match) { - buttons.push( - - ); - } - } - return buttons.length ? {buttons} : null; From 445edc7586d4241dfe9cfc9e7251f9586f5d1f77 Mon Sep 17 00:00:00 2001 From: sadan <117494111+sadan4@users.noreply.github.com> Date: Sat, 3 Oct 2026 09:41:29 -0400 Subject: [PATCH 7/8] fix RoleColorEverywhere, StickerPaste, BetterRoleContext (#4641) Co-authored-by: V --- packages/discord-types/src/utils.d.ts | 43 +++++++++++++++++++++++ src/plugins/betterRoleContext/index.tsx | 12 +++++-- src/plugins/roleColorEverywhere/index.tsx | 32 +++++++---------- src/plugins/stickerPaste/index.ts | 6 ++-- src/utils/discord.tsx | 12 +++---- src/webpack/common/utils.ts | 7 ++-- 6 files changed, 78 insertions(+), 34 deletions(-) diff --git a/packages/discord-types/src/utils.d.ts b/packages/discord-types/src/utils.d.ts index b0a6ee343..b6eecc342 100644 --- a/packages/discord-types/src/utils.d.ts +++ b/packages/discord-types/src/utils.d.ts @@ -415,3 +415,46 @@ export interface EmojiUtils { triggerFullscreenAnimation(emoji: any, node: HTMLElement): void; applyPlatformToThemedEmojiColorPalette(colors: any): any; } + +export interface OpenUserSettingsOptions { + /** + * a *sub*section of settings to navigate to + * + * to navigate to a top-level section, use the navigationTarget parameter of {@link SettingsRouter.openUserSettings} instead + * + * discord has an enum with all the possible values (go dig in the {@link SettingsRouter.openUserSettings} source) + */ + path?: string; + /** + * modal stacking behavior, defaults to "replaceAll" + */ + stackingBehavior?: "replaceAll" | "stack" | "replace"; + // ... misc analytics options +} + +export interface SettingsRouter { + openUserSettings(navigationTarget?: string, opts?: OpenUserSettingsOptions, onOpen?: () => void): Promise; +} + +export interface OpenUserProfileModalOptions { + userId: string; + guildId?: string | null; + originGuildId?: string | null; + channelId?: string | null; + messageId?: string | null; + roleId?: string | null; + sessionId?: unknown | null; + joinRequestId?: unknown; + tabSection?: unknown; + scrollTarget?: unknown; + hideRestrictedProfile?: boolean; + sourceAnalyticsLocations?: string[]; + appContext?: unknown; + /** + * defaults to null + */ + customStatusPrompt?: unknown; + onModalOpen?: unknown; +} + +export type OpenUserProfileModal = (opts: OpenUserProfileModalOptions) => Promise; diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx index 9b5653aef..561b8a332 100644 --- a/src/plugins/betterRoleContext/index.tsx +++ b/src/plugins/betterRoleContext/index.tsx @@ -206,14 +206,20 @@ export default definePlugin({ settings, openRoleContextMenu, patches: [ - // Conflicts with RoleColorEverywhere which changes the code at the end of our match. (and also uses same find & similar match) - // However, BetterRoleContext applies first (alphabetic order), so it's not an issue { find: 'tutorialId:"whos-online', replacement: { - match: /(?<=#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.{0,200}?"aria-hidden":!0,)children:.{0,200}?(?:—|\\u2014) ",\i\]\}\)\]/, + match: /(?<=#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.{0,400}?)children:(?=.{0,20}?(?:—|\\u2014) ",\i\])/, replace: "onContextMenu:e=>$self.openRoleContextMenu(e,arguments[0]),$&" } + }, + // member list role headers + { + find: "?null:new Intl.NumberFormat", + replacement: { + match: /(?<=#{intl::CHANNEL_MEMBERS_A11Y_LABEL},\{title:\i,count:\i\}\)\}\),\(0,\i\.jsxs\)\("div",\{)/, + replace: "onContextMenu:e=>$self.openRoleContextMenu(e,arguments[0])," + } } ], diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index d50596398..7ff6cf1fb 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -17,7 +17,6 @@ */ import { definePluginSettings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { makeRange, OptionType } from "@utils/types"; @@ -102,23 +101,24 @@ export default definePlugin({ ], predicate: () => settings.store.chatMentions }, - // Member List Role Headers + // Member List Role Headers (in threads) { find: 'tutorialId:"whos-online', replacement: [ { - match: /(#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.+}\):null,).{0,100}?(?:—|\\u2014) ",\i\]\}\)\]/, - replace: "$1$self.RoleGroupColor(arguments[0])]" + match: /(?<=#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.{0,400}?)children:(?=.{0,20}?(?:—|\\u2014) ",\i\])/, + replace: "style:{color:$self.getRoleColor(arguments[0])},$&" }, ], predicate: () => settings.store.memberList }, + // Member List Role Headers { - find: "#{intl::THREAD_BROWSER_PRIVATE}", + find: "?null:new Intl.NumberFormat", replacement: [ { - match: /children:\[\i," (?:—|\\u2014) ",\i\]/, - replace: "children:[$self.RoleGroupColor(arguments[0])]" + match: /(?<=#{intl::CHANNEL_MEMBERS_A11Y_LABEL},\{title:\i,count:\i\}\)\}\),\(0,\i\.jsxs\)\("div",\{)/, + replace: "style:{color:$self.getRoleColor(arguments[0])}," }, ], predicate: () => settings.store.memberList @@ -213,17 +213,9 @@ export default definePlugin({ return null; }, - RoleGroupColor: ErrorBoundary.wrap(({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) => { - const role = GuildRoleStore.getRole(guildId, id); - - return ( - - {title ?? label} — {count} - - ); - }, { noop: true }) + getRoleColor(props: any) { + try { + return GuildRoleStore.getRole(props?.guildId, props?.id)?.colorString; + } catch (e) { } + } }); diff --git a/src/plugins/stickerPaste/index.ts b/src/plugins/stickerPaste/index.ts index 0dd0bd2b3..4d216d6ba 100644 --- a/src/plugins/stickerPaste/index.ts +++ b/src/plugins/stickerPaste/index.ts @@ -15,9 +15,11 @@ export default definePlugin({ patches: [ { - find: ".stickers,previewSticker:", + // This function is a util func from another module + // but it only has once use here so it's inlined + find: '.STICKER_PICKER:return""', replacement: { - match: /if\(\i\.\i\.getUploadCount/, + match: /(?<=\){)if\(\i\.\i\.getUploadCount/, replace: "return true;$&", } } diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index 47aea0cc4..6821839fe 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -18,7 +18,7 @@ import type { MessageObject } from "@api/MessageEvents"; import type { Channel, CloudUpload, Guild, GuildFeatures, MediaModalItem, MediaModalProps, Message, User } from "@vencord/discord-types"; -import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, InviteActions, MessageActions, openMediaModal, RestAPI, SelectedChannelStore, SelectedGuildStore, showToast, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; +import { ChannelActionCreators, ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, InviteActions, MessageActions, openMediaModal, openUserProfileModal, RestAPI, SelectedChannelStore, SelectedGuildStore, showToast, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { Except } from "type-fest"; import { copyToClipboard } from "./clipboard"; @@ -178,14 +178,14 @@ export async function openUserProfile(id: string) { if (!user) throw new Error("No such user: " + id); const guildId = SelectedGuildStore.getGuildId(); - UserProfileActions.openUserProfileModal({ + openUserProfileModal({ userId: id, guildId, channelId: SelectedChannelStore.getChannelId(), - analyticsLocation: { - page: guildId ? "Guild Channel" : "DM Channel", - section: "Profile Popout" - } + sourceAnalyticsLocations: [ + "username", + "user profile popout", + ] }); } diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 47d23bf8a..b6749b521 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -126,8 +126,9 @@ export const ChannelRouter: t.ChannelRouter = mapMangledModuleLazy('"Thread must transitionToThread: filters.byCode('"Thread must have a parent ID."') }); -export let SettingsRouter: any; -waitFor(["openUserSettings", "USER_SETTINGS_MODAL_KEY"], m => SettingsRouter = m); +export const SettingsRouter: t.SettingsRouter = mapMangledModuleLazy('type:"USER_SETTINGS_MODAL_OPEN"', { + openUserSettings: filters.byCode('type:"USER_SETTINGS_MODAL_OPEN"') +}); export const PermissionsBits: t.PermissionsBits = findLazy(m => typeof m.ADMINISTRATOR === "bigint"); @@ -141,7 +142,7 @@ export const { zustandPersist } = mapMangledModuleLazy(".onRehydrateStorage)?", export const MessageActions = findByPropsLazy("editMessage", "sendMessage"); export const MessageCache = findByPropsLazy("clearCache", "_channelMessages"); -export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal"); +export const openUserProfileModal: t.OpenUserProfileModal = findByCodeLazy('type:"USER_PROFILE_MODAL_OPEN"'); export const InviteActions = findByPropsLazy("resolveInvite"); export const ChannelActionCreators = findByPropsLazy("openPrivateChannel"); From 14723674a201c1e90b5b7115b0af86410395a3f1 Mon Sep 17 00:00:00 2001 From: uku Date: Sat, 3 Oct 2026 16:57:26 +0200 Subject: [PATCH 8/8] FakeNitro: fix transforming stickers with spaces in name (#4640) Co-authored-by: Vendicated --- src/plugins/fakeNitro/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 7927f405d..f480745b3 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -74,6 +74,7 @@ const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/; const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./; const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/; const hyperLinkRegex = /\[.+?\]\((https?:\/\/.+?)\)/; +const hyperLinkOnlyRegex = new RegExp(`^${hyperLinkRegex.source}$`); const mediaSizes = [16, 32, 48, 56, 64, 96, 128, 160, 256, 512, 1024]; const DEFAULT_EMOJI_SIZE = 48; @@ -573,6 +574,7 @@ export default definePlugin({ const contentItems = message.content.split(/\s/); if (settings.store.transformCompoundSentence) itemsToMaybePush.push(...contentItems); else if (contentItems.length === 1) itemsToMaybePush.push(contentItems[0]); + else if (hyperLinkOnlyRegex.test(message.content)) itemsToMaybePush.push(message.content); itemsToMaybePush.push(...message.attachments.filter(attachment => attachment.content_type === "image/gif").map(attachment => attachment.url)); @@ -617,7 +619,7 @@ export default definePlugin({ shouldIgnoreEmbed(embed: Message["embeds"][number], message: Message) { try { const contentItems = message.content.split(/\s/); - if (contentItems.length > 1 && !settings.store.transformCompoundSentence) return false; + if (contentItems.length > 1 && !settings.store.transformCompoundSentence && !hyperLinkOnlyRegex.test(message.content)) return false; switch (embed.type) { case "image": { @@ -626,6 +628,7 @@ export default definePlugin({ if ( !settings.store.transformCompoundSentence && !contentItems.some(item => item === url || item.match(hyperLinkRegex)?.[1] === url) + && !hyperLinkOnlyRegex.test(message.content) ) return false; if (settings.store.transformEmojis) {