From a8c01a2a05bcacbd326143bd9839cbd9e1ff1d71 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 14 Apr 2025 14:43:13 +0200 Subject: [PATCH 01/34] ConsoleShortcuts: fix module preloader --- src/plugins/consoleShortcuts/index.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index 5afdbdd9..5ddf4639 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -20,6 +20,7 @@ import { Devs } from "@utils/constants"; import { getCurrentChannel, getCurrentGuild } from "@utils/discord"; import { runtimeHashMessageKey } from "@utils/intlHash"; import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy"; +import { sleep } from "@utils/misc"; import { ModalAPI } from "@utils/modal"; import { relaunch } from "@utils/native"; import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches"; @@ -206,10 +207,13 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) { return value; } +const webpackModulesProbablyLoaded = Webpack.onceReady.then(() => sleep(1000)); + export default definePlugin({ name: "ConsoleShortcuts", description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.", authors: [Devs.Ven], + startAt: StartAt.Init, patches: [ { @@ -221,7 +225,7 @@ export default definePlugin({ } ], - startAt: StartAt.Init, + start() { const shortcuts = makeShortcuts(); window.shortcutList = {}; @@ -242,18 +246,16 @@ export default definePlugin({ } // unproxy loaded modules - Webpack.onceReady.then(() => { - setTimeout(() => this.eagerLoad(false), 1000); + this.eagerLoad(false); - if (!IS_WEB) { - const Native = VencordNative.pluginHelpers.ConsoleShortcuts as PluginNative; - Native.initDevtoolsOpenEagerLoad(); - } - }); + if (!IS_WEB) { + const Native = VencordNative.pluginHelpers.ConsoleShortcuts as PluginNative; + Native.initDevtoolsOpenEagerLoad(); + } }, async eagerLoad(forceLoad: boolean) { - await Webpack.onceReady; + await webpackModulesProbablyLoaded; const shortcuts = makeShortcuts(); From 0f4d3dfd3accc2d9f38a47fea56bebb962b27a05 Mon Sep 17 00:00:00 2001 From: Vending Machine Date: Mon, 14 Apr 2025 15:21:30 +0200 Subject: [PATCH 02/34] SpotifyControls: fix SeekBar not updating (#3381) Also slightly reworks LazyComponents for more useful typing --- src/components/ErrorBoundary.tsx | 6 ++--- src/debug/runReporter.ts | 2 +- src/plugins/betterSettings/index.tsx | 3 +-- src/plugins/consoleShortcuts/index.ts | 4 +-- .../spotifyControls/PlayerComponent.tsx | 24 ++++++++++-------- src/plugins/spotifyControls/SeekBar.ts | 25 +++++++++++++++++++ src/utils/lazyReact.tsx | 10 +++++--- src/webpack/common/internal.tsx | 6 ++--- src/webpack/common/types/components.d.ts | 4 +-- 9 files changed, 57 insertions(+), 27 deletions(-) create mode 100644 src/plugins/spotifyControls/SeekBar.ts diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index bb2df342..e609d564 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -18,7 +18,7 @@ import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; -import { LazyComponent } from "@utils/react"; +import { LazyComponent, LazyComponentWrapper } from "@utils/react"; import { React } from "@webpack/common"; import { ErrorCard } from "./ErrorCard"; @@ -107,9 +107,9 @@ const ErrorBoundary = LazyComponent(() => { } }; }) as - React.ComponentType> & { + LazyComponentWrapper> & { wrap(Component: React.ComponentType, errorBoundaryProps?: Omit, "wrappedProps">): React.FunctionComponent; - }; + }>; ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => ( diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts index 2ca83b7f..21802b6a 100644 --- a/src/debug/runReporter.ts +++ b/src/debug/runReporter.ts @@ -87,7 +87,7 @@ async function runReporter() { result = Webpack[method](...args); } - if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail"); + if (result == null || (result.$$vencordGetWrappedComponent != null && result.$$vencordGetWrappedComponent() == null)) throw new Error("Webpack Find Fail"); } catch (e) { let logMessage = searchType; if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") { diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx index 84e338ef..cbf94c2b 100644 --- a/src/plugins/betterSettings/index.tsx +++ b/src/plugins/betterSettings/index.tsx @@ -142,8 +142,7 @@ export default definePlugin({ // Thus, we sanity check webpack modules Layer(props: LayerProps) { try { - // @ts-ignore - [FocusLock.$$vencordInternal(), ComponentDispatch, Classes].forEach(e => e.test); + [FocusLock.$$vencordGetWrappedComponent(), ComponentDispatch, Classes].forEach(e => e.test); } catch { new Logger("BetterSettings").error("Failed to find some components"); return props.children; diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index 5ddf4639..49e67158 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -174,8 +174,8 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) { function unwrapProxy(value: any) { if (value[SYM_LAZY_GET]) { forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED]; - } else if (value.$$vencordInternal) { - return forceLoad ? value.$$vencordInternal() : value; + } else if (value.$$vencordGetWrappedComponent) { + return forceLoad ? value.$$vencordGetWrappedComponent() : value; } return value; diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx index 4184931f..78a69a14 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/plugins/spotifyControls/PlayerComponent.tsx @@ -28,6 +28,7 @@ import { openImageModal } from "@utils/discord"; import { classes, copyWithToast } from "@utils/misc"; import { ContextMenuApi, FluxDispatcher, Forms, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common"; +import { SeekBar } from "./SeekBar"; import { SpotifyStore, Track } from "./SpotifyStore"; const cl = classNameFactory("vc-spotify-"); @@ -160,7 +161,7 @@ const seek = debounce((v: number) => { SpotifyStore.seek(v); }); -function SeekBar() { +function SpotifySeekBar() { const { duration } = SpotifyStore.track!; const [storePosition, isSettingPosition, isPlaying] = useStateFromStores( @@ -181,6 +182,12 @@ function SeekBar() { } }, [storePosition, isSettingPosition, isPlaying]); + const onChange = (v: number) => { + if (isSettingPosition) return; + setPosition(v); + seek(v); + }; + return (
{msToHuman(position)} - { - if (isSettingPosition) return; - setPosition(v); - seek(v); - }} - renderValue={msToHuman} + onValueChange={onChange} + asValueChanges={onChange} + onValueRender={msToHuman} /> - +
); diff --git a/src/plugins/spotifyControls/SeekBar.ts b/src/plugins/spotifyControls/SeekBar.ts new file mode 100644 index 00000000..8d6c8a30 --- /dev/null +++ b/src/plugins/spotifyControls/SeekBar.ts @@ -0,0 +1,25 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { LazyComponent } from "@utils/lazyReact"; +import { Slider } from "@webpack/common"; + +export const SeekBar = LazyComponent(() => { + const SliderClass = Slider.$$vencordGetWrappedComponent(); + + // Discord's Slider does not update `state.value` when `props.initialValue` changes if state.value is not nullish. + // We extend their class and override their `getDerivedStateFromProps` to update the value + return class SeekBar extends SliderClass { + static getDerivedStateFromProps(props: any, state: any) { + const newState = super.getDerivedStateFromProps!(props, state); + if (newState) { + newState.value = props.initialValue; + } + + return newState; + } + }; +}); diff --git a/src/utils/lazyReact.tsx b/src/utils/lazyReact.tsx index 4896a058..0a15bf92 100644 --- a/src/utils/lazyReact.tsx +++ b/src/utils/lazyReact.tsx @@ -4,26 +4,28 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { ComponentType } from "react"; +import type { ComponentType } from "react"; import { makeLazy } from "./lazy"; const NoopComponent = () => null; +export type LazyComponentWrapper = ComponentType & { $$vencordGetWrappedComponent(): ComponentType; }; + /** * A lazy component. The factory method is called on first render. * @param factory Function returning a Component * @param attempts How many times to try to get the component before giving up * @returns Result of factory function */ -export function LazyComponent(factory: () => React.ComponentType, attempts = 5) { +export function LazyComponent(factory: () => ComponentType, attempts = 5): LazyComponentWrapper> { const get = makeLazy(factory, attempts); const LazyComponent = (props: T) => { const Component = get() ?? NoopComponent; return ; }; - LazyComponent.$$vencordInternal = get; + LazyComponent.$$vencordGetWrappedComponent = get; - return LazyComponent as ComponentType; + return LazyComponent; } diff --git a/src/webpack/common/internal.tsx b/src/webpack/common/internal.tsx index 8957c254..090d9898 100644 --- a/src/webpack/common/internal.tsx +++ b/src/webpack/common/internal.tsx @@ -16,19 +16,19 @@ * along with this program. If not, see . */ -import { LazyComponent } from "@utils/react"; +import { LazyComponent, LazyComponentWrapper } from "@utils/react"; // eslint-disable-next-line path-alias/no-relative import { FilterFn, filters, lazyWebpackSearchHistory, waitFor } from "../webpack"; -export function waitForComponent = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]): T { +export function waitForComponent = React.ComponentType & Record>(name: string, filter: FilterFn | string | string[]) { if (IS_REPORTER) lazyWebpackSearchHistory.push(["waitForComponent", Array.isArray(filter) ? filter : [filter]]); let myValue: T = function () { throw new Error(`Vencord could not find the ${name} Component`); } as any; - const lazyComponent = LazyComponent(() => myValue) as T; + const lazyComponent = LazyComponent(() => myValue) as LazyComponentWrapper; waitFor(filter, (v: any) => { myValue = v; Object.assign(lazyComponent, v); diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index 2b8ee92a..b5f4ff5c 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import type { ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref } from "react"; +import type { ComponentClass, ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref } from "react"; export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; @@ -356,7 +356,7 @@ export type SearchableSelect = ComponentType>; -export type Slider = ComponentType Date: Tue, 15 Apr 2025 01:43:50 +0200 Subject: [PATCH 03/34] Fix: RoleColorEverywhere, AccountPanelServerProfile, BANger (#3378) Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Co-authored-by: Vending Machine --- src/plugins/accountPanelServerProfile/index.tsx | 2 +- src/plugins/banger/index.ts | 2 +- src/plugins/roleColorEverywhere/index.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index 2b212d34..8b561581 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -77,7 +77,7 @@ export default definePlugin({ replace: "$self.useAccountPanelRef();$&" }, { - match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/, + match: /(\.AVATAR,children:.+?renderPopout:(\(\i,\i\))=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/, replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})` }, { diff --git a/src/plugins/banger/index.ts b/src/plugins/banger/index.ts index f13fd351..eed0e1b4 100644 --- a/src/plugins/banger/index.ts +++ b/src/plugins/banger/index.ts @@ -36,7 +36,7 @@ export default definePlugin({ settings, patches: [ { - find: "#{intl::BAN_CONFIRM_TITLE}", + find: "#{intl::jeKpoq::raw}", // BAN_CONFIRM_TITLE replacement: { match: /src:\i\("?\d+"?\)/g, replace: "src:$self.source" diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index b81a0cce..71f87b13 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -84,8 +84,8 @@ export default definePlugin({ find: ".USER_MENTION)", replacement: [ { - match: /(?<=onContextMenu:\i,color:)\i(?=,onClick)(?<=user:(\i),channel:(\i).+?)/, - replace: "$self.getColorInt($1?.id,$2?.id)", + match: /(?<=onContextMenu:\i,color:)\i(?<=\.getNickname\((\i),\i,(\i).+?)/, + replace: "$self.getColorInt($2?.id,$1)", } ], predicate: () => settings.store.chatMentions From 524202e49dfd2efba822370a9f7cbe716eea9561 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:34:16 -0400 Subject: [PATCH 04/34] Fix clipboard api, IrcColors, and Memberlist Decorators (#3383) Co-authored-by: Vending Machine --- .../VencordSettings/PatchHelperTab.tsx | 7 +++-- .../_api/memberListDecorators/index.tsx | 7 +++-- src/plugins/betterRoleContext/index.tsx | 5 ++-- src/plugins/betterRoleDot/index.ts | 13 ++------- src/plugins/copyUserURLs/index.tsx | 5 ++-- .../ui/components/DecorationContextMenu.tsx | 5 ++-- src/plugins/ircColors/index.ts | 4 +-- .../components/RolesAndUsersPermissions.tsx | 7 +++-- .../components/ButtonRow.tsx | 29 +++++++------------ .../hooks/useCopyCooldown.ts | 5 ++-- .../previewExample.tsx | 2 +- src/plugins/webContextMenus.web/index.ts | 9 +++--- src/utils/clipboard.ts | 9 ++++++ src/utils/index.ts | 1 + src/utils/misc.ts | 11 +++---- src/webpack/common/utils.ts | 5 ---- 16 files changed, 58 insertions(+), 66 deletions(-) create mode 100644 src/utils/clipboard.ts diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx index f930a40d..55822069 100644 --- a/src/components/VencordSettings/PatchHelperTab.tsx +++ b/src/components/VencordSettings/PatchHelperTab.tsx @@ -18,12 +18,13 @@ import { CodeBlock } from "@components/CodeBlock"; import { debounce } from "@shared/debounce"; +import { copyToClipboard } from "@utils/clipboard"; import { Margins } from "@utils/margins"; import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches"; import { makeCodeblock } from "@utils/text"; import { Patch, ReplaceFn } from "@utils/types"; import { search } from "@webpack"; -import { Button, Clipboard, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common"; +import { Button, Forms, Parser, React, Switch, TextArea, TextInput } from "@webpack/common"; import { SettingsTab, wrapTab } from "./shared"; @@ -381,8 +382,8 @@ function PatchHelper() { <> Code - - + + )} diff --git a/src/plugins/_api/memberListDecorators/index.tsx b/src/plugins/_api/memberListDecorators/index.tsx index 39c82a1e..365c21f3 100644 --- a/src/plugins/_api/memberListDecorators/index.tsx +++ b/src/plugins/_api/memberListDecorators/index.tsx @@ -35,9 +35,10 @@ export default definePlugin({ { match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/, replace: "$&vencordProps=$1," - }, { - match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/, - replace: "$&(typeof vencordProps=='undefined'?null:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps))," + }, + { + match: /children:\[(?=.{0,300},lostPermissionTooltipText:)/, + replace: "children:[(typeof vencordProps!=='undefined'&&Vencord.Api.MemberListDecorators.__getDecorators(vencordProps))," } ] }, diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx index afef6390..a9bab1a7 100644 --- a/src/plugins/betterRoleContext/index.tsx +++ b/src/plugins/betterRoleContext/index.tsx @@ -7,11 +7,12 @@ import { definePluginSettings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; import { ImageIcon } from "@components/Icons"; +import { copyToClipboard } from "@utils/clipboard"; import { Devs } from "@utils/constants"; import { getCurrentGuild, openImageModal } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { Clipboard, GuildStore, Menu, PermissionStore } from "@webpack/common"; +import { GuildStore, Menu, PermissionStore } from "@webpack/common"; const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild"); @@ -87,7 +88,7 @@ export default definePlugin({ Clipboard.copy(role.colorString!)} + action={() => copyToClipboard(role.colorString!)} icon={AppearanceIcon} /> ); diff --git a/src/plugins/betterRoleDot/index.ts b/src/plugins/betterRoleDot/index.ts index 3a8a1456..bdafe02c 100644 --- a/src/plugins/betterRoleDot/index.ts +++ b/src/plugins/betterRoleDot/index.ts @@ -18,8 +18,8 @@ import { Settings } from "@api/Settings"; import { Devs } from "@utils/constants"; +import { copyWithToast } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; -import { Clipboard, Toasts } from "@webpack/common"; export default definePlugin({ name: "BetterRoleDot", @@ -84,15 +84,6 @@ export default definePlugin({ }, copyToClipBoard(color: string) { - Clipboard.copy(color); - Toasts.show({ - message: "Copied to Clipboard!", - type: Toasts.Type.SUCCESS, - id: Toasts.genId(), - options: { - duration: 1000, - position: Toasts.Position.BOTTOM - } - }); + copyWithToast(color); }, }); diff --git a/src/plugins/copyUserURLs/index.tsx b/src/plugins/copyUserURLs/index.tsx index 7af8502d..9e15cc82 100644 --- a/src/plugins/copyUserURLs/index.tsx +++ b/src/plugins/copyUserURLs/index.tsx @@ -18,9 +18,10 @@ import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { LinkIcon } from "@components/Icons"; +import { copyToClipboard } from "@utils/clipboard"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { Clipboard, Menu } from "@webpack/common"; +import { Menu } from "@webpack/common"; import type { Channel, User } from "discord-types/general"; interface UserContextProps { @@ -36,7 +37,7 @@ const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: U Clipboard.copy(``)} + action={() => copyToClipboard(``)} icon={LinkIcon} /> ); diff --git a/src/plugins/decor/ui/components/DecorationContextMenu.tsx b/src/plugins/decor/ui/components/DecorationContextMenu.tsx index 7c1542f6..db3f0090 100644 --- a/src/plugins/decor/ui/components/DecorationContextMenu.tsx +++ b/src/plugins/decor/ui/components/DecorationContextMenu.tsx @@ -5,7 +5,8 @@ */ import { CopyIcon, DeleteIcon } from "@components/Icons"; -import { Alerts, Clipboard, ContextMenuApi, Menu, UserStore } from "@webpack/common"; +import { copyToClipboard } from "@utils/clipboard"; +import { Alerts, ContextMenuApi, Menu, UserStore } from "@webpack/common"; import { Decoration } from "../../lib/api"; import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore"; @@ -23,7 +24,7 @@ export default function DecorationContextMenu({ decoration }: { decoration: Deco id={cl("decoration-context-menu-copy-hash")} label="Copy Decoration Hash" icon={CopyIcon} - action={() => Clipboard.copy(decoration.hash)} + action={() => copyToClipboard(decoration.hash)} /> {decoration.authorId === UserStore.getCurrentUser().id && `${rest1}ircColor=$self.calculateNameColorForListContext(arguments[0]),${rest2}color:ircColor` + match: /(?<=roleName:\i,)color:/, + replace: "color:$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 02662fe9..ed620d7f 100644 --- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx +++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx @@ -19,10 +19,11 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { InfoIcon, OwnerCrownIcon } from "@components/Icons"; +import { copyToClipboard } from "@utils/clipboard"; import { getIntlMessage, getUniqueUsername } from "@utils/discord"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { findByCodeLazy } from "@webpack"; -import { Clipboard, ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, i18n, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, useMemo, UserStore, useState, useStateFromStores } from "@webpack/common"; +import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, i18n, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, useMemo, UserStore, useState, useStateFromStores } from "@webpack/common"; import { UnicodeEmoji } from "@webpack/types"; import type { Guild, Role, User } from "discord-types/general"; @@ -228,7 +229,7 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str id={cl("copy-role-id")} label={getIntlMessage("COPY_ID_ROLE")} action={() => { - Clipboard.copy(roleId); + copyToClipboard(roleId); }} /> @@ -269,7 +270,7 @@ function UserContextMenu({ userId }: { userId: string; }) { id={cl("copy-user-id")} label={getIntlMessage("COPY_ID_USER")} action={() => { - Clipboard.copy(userId); + copyToClipboard(userId); }} /> diff --git a/src/plugins/shikiCodeblocks.desktop/components/ButtonRow.tsx b/src/plugins/shikiCodeblocks.desktop/components/ButtonRow.tsx index 6f0690d9..408de154 100644 --- a/src/plugins/shikiCodeblocks.desktop/components/ButtonRow.tsx +++ b/src/plugins/shikiCodeblocks.desktop/components/ButtonRow.tsx @@ -16,9 +16,6 @@ * along with this program. If not, see . */ -import { Clipboard } from "@webpack/common"; -import { JSX } from "react"; - import { cl } from "../utils/misc"; import { CopyButton } from "./CopyButton"; @@ -28,20 +25,14 @@ export interface ButtonRowProps { } export function ButtonRow({ content, theme }: ButtonRowProps) { - const buttons: JSX.Element[] = []; - - if (Clipboard.SUPPORTS_COPY) { - buttons.push( - - ); - } - - return
{buttons}
; + return
+ +
; } diff --git a/src/plugins/shikiCodeblocks.desktop/hooks/useCopyCooldown.ts b/src/plugins/shikiCodeblocks.desktop/hooks/useCopyCooldown.ts index 414500bd..d3f35fb2 100644 --- a/src/plugins/shikiCodeblocks.desktop/hooks/useCopyCooldown.ts +++ b/src/plugins/shikiCodeblocks.desktop/hooks/useCopyCooldown.ts @@ -16,13 +16,14 @@ * along with this program. If not, see . */ -import { Clipboard, React } from "@webpack/common"; +import { copyToClipboard } from "@utils/clipboard"; +import { React } from "@webpack/common"; export function useCopyCooldown(cooldown: number) { const [copyCooldown, setCopyCooldown] = React.useState(false); function copy(text: string) { - Clipboard.copy(text); + copyToClipboard(text); setCopyCooldown(true); setTimeout(() => { diff --git a/src/plugins/shikiCodeblocks.desktop/previewExample.tsx b/src/plugins/shikiCodeblocks.desktop/previewExample.tsx index 508153b4..db7edcf0 100644 --- a/src/plugins/shikiCodeblocks.desktop/previewExample.tsx +++ b/src/plugins/shikiCodeblocks.desktop/previewExample.tsx @@ -2,7 +2,7 @@ import React from "react"; const handleClick = async () => - console.log((await import("@webpack/common")).Clipboard.copy("\u200b")); + console.log((await import("@utils/clipboard")).copyToClipboard("\u200b")); export const Example: React.FC<{ real: boolean, diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index 07eb4a3e..a0f84dd6 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -17,11 +17,12 @@ */ import { definePluginSettings } from "@api/Settings"; +import { copyToClipboard } from "@utils/clipboard"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { saveFile } from "@utils/web"; import { filters, mapMangledModuleLazy } from "@webpack"; -import { Clipboard, ComponentDispatch } from "@webpack/common"; +import { ComponentDispatch } from "@webpack/common"; const ctxMenuCallbacks = mapMangledModuleLazy('.tagName)==="TEXTAREA"||', { contextMenuCallbackWeb: filters.byCode('.tagName)==="INPUT"||'), @@ -114,7 +115,7 @@ export default definePlugin({ // Fix silly Discord calling the non web support copy { match: /\i\.\i\.copy/, - replace: "Vencord.Webpack.Common.Clipboard.copy" + replace: "Vencord.Util.copyToClipboard" } ] }, @@ -223,7 +224,7 @@ export default definePlugin({ }, { match: /\i\.\i\.copy(?=\(\i)/, - replace: "Vencord.Webpack.Common.Clipboard.copy" + replace: "Vencord.Util.copyToClipboard" } ], all: true, @@ -288,7 +289,7 @@ export default definePlugin({ const selection = document.getSelection(); if (!selection) return; - Clipboard.copy(selection.toString()); + copyToClipboard(selection.toString()); }, cut() { diff --git a/src/utils/clipboard.ts b/src/utils/clipboard.ts new file mode 100644 index 00000000..c098a549 --- /dev/null +++ b/src/utils/clipboard.ts @@ -0,0 +1,9 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export function copyToClipboard(text: string): Promise { + return IS_DISCORD_DESKTOP ? DiscordNative.clipboard.copy(text) : navigator.clipboard.writeText(text); +} diff --git a/src/utils/index.ts b/src/utils/index.ts index ed347fdc..70ac9d42 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -19,6 +19,7 @@ export * from "../shared/debounce"; export * from "../shared/onceDefined"; export * from "./ChangeList"; +export * from "./clipboard"; export * from "./constants"; export * from "./discord"; export * from "./guards"; diff --git a/src/utils/misc.ts b/src/utils/misc.ts index adca15d3..7f9f6e59 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -16,8 +16,9 @@ * along with this program. If not, see . */ -import { Clipboard, Toasts } from "@webpack/common"; +import { Toasts } from "@webpack/common"; +import { copyToClipboard } from "./clipboard"; import { DevsById } from "./constants"; /** @@ -35,12 +36,8 @@ export function sleep(ms: number): Promise { return new Promise(r => setTimeout(r, ms)); } -export function copyWithToast(text: string, toastMessage = "Copied to clipboard!") { - if (Clipboard.SUPPORTS_COPY) { - Clipboard.copy(text); - } else { - toastMessage = "Your browser does not support copying to clipboard"; - } +export async function copyWithToast(text: string, toastMessage = "Copied to clipboard!") { + await copyToClipboard(text); Toasts.show({ message: toastMessage, id: Toasts.genId(), diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index f5535b19..0396f0f3 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -149,11 +149,6 @@ export const ApplicationAssetUtils = mapMangledModuleLazy("getAssetImage: size m getAssets: filters.byCode(".assets") }); -export const Clipboard: t.Clipboard = mapMangledModuleLazy('queryCommandEnabled("copy")', { - copy: filters.byCode(".copy("), - SUPPORTS_COPY: e => typeof e === "boolean" -}); - export const NavigationRouter: t.NavigationRouter = mapMangledModuleLazy("Transitioning to ", { transitionTo: filters.byCode("transitionTo -"), transitionToGuild: filters.byCode("transitionToGuild -"), From e99e89e964da257adefc795e03a7bce0ca794654 Mon Sep 17 00:00:00 2001 From: khcrysalis Date: Wed, 16 Apr 2025 11:57:49 -0700 Subject: [PATCH 05/34] MessageLogger: correctly ignore venbot in all vc support channels (#3384) Co-authored-by: Vending Machine --- src/plugins/messageLogger/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 4bce7f5e..ffe5286e 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -23,7 +23,7 @@ import { updateMessage } from "@api/MessageUpdater"; import { Settings } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; -import { Devs } from "@utils/constants"; +import { Devs, SUPPORT_CATEGORY_ID, VENBOT_USER_ID } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import { Logger } from "@utils/Logger"; import { classes } from "@utils/misc"; @@ -295,8 +295,8 @@ export default definePlugin({ ignoreChannels.includes(ChannelStore.getChannel(message.channel_id)?.parent_id) || (isEdit ? !logEdits : !logDeletes) || ignoreGuilds.includes(ChannelStore.getChannel(message.channel_id)?.guild_id) || - // Ignore Venbot in the support channel - (message.channel_id === "1026515880080842772" && message.author?.id === "1017176847865352332"); + // Ignore Venbot in the support channels + (message.author?.id === VENBOT_USER_ID && ChannelStore.getChannel(message.channel_id)?.parent_id === SUPPORT_CATEGORY_ID); }, EditMarker({ message, className, children, ...props }: any) { From cf78ddcfe27e48d3c49e6a12a4bf1834ae6ea29c Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:36:43 -0400 Subject: [PATCH 06/34] fix plugins for latest discord update (#3387) Co-authored-by: Vendicated --- src/api/MemberListDecorators.tsx | 31 ++++++------------- src/plugins/_api/badges/index.tsx | 2 +- .../_api/memberListDecorators/index.tsx | 10 ++---- src/plugins/_api/serverList.ts | 4 +-- .../accountPanelServerProfile/index.tsx | 19 +++++++----- src/plugins/forceOwnerCrown/index.ts | 4 +-- src/plugins/friendsSince/index.tsx | 2 +- src/plugins/ircColors/index.ts | 4 +-- src/plugins/mutualGroupDMs/index.tsx | 8 +++++ src/plugins/mutualGroupDMs/style.css | 4 +++ src/plugins/noUnblockToJump/index.ts | 2 +- 11 files changed, 44 insertions(+), 46 deletions(-) create mode 100644 src/plugins/mutualGroupDMs/style.css diff --git a/src/api/MemberListDecorators.tsx b/src/api/MemberListDecorators.tsx index ab5a618b..ada60776 100644 --- a/src/api/MemberListDecorators.tsx +++ b/src/api/MemberListDecorators.tsx @@ -21,25 +21,14 @@ import { Channel, User } from "discord-types/general/index.js"; import { JSX } from "react"; interface DecoratorProps { - activities: any[]; - channel: Channel; - /** - * Only for DM members - */ - channelName?: string; - /** - * Only for server members - */ - currentUser?: User; - guildId?: string; - isMobile: boolean; - isOwner?: boolean; - isTyping: boolean; - selected: boolean; - status: string; + type: "guild" | "dm"; user: User; - [key: string]: any; + /** only present when this is a DM list item */ + channel: Channel; + /** only present when this is a guild list item */ + isOwner: boolean; } + export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null; type OnlyIn = "guilds" | "dms"; @@ -53,18 +42,16 @@ export function removeMemberListDecorator(identifier: string) { decoratorsFactories.delete(identifier); } -export function __getDecorators(props: DecoratorProps): JSX.Element { - const isInGuild = !!(props.guildId); - +export function __getDecorators(props: DecoratorProps, type: "guild" | "dm"): JSX.Element { const decorators = Array.from( decoratorsFactories.entries(), ([key, { render: Decorator, onlyIn }]) => { - if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild)) + if ((onlyIn === "guilds" && type !== "guild") || (onlyIn === "dms" && type !== "dm")) return null; return ( - + ); } diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index e63073ce..52bede44 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -63,7 +63,7 @@ export default definePlugin({ required: true, patches: [ { - find: ".FULL_SIZE]:26", + find: ".MODAL]:26", replacement: { match: /(?=;return 0===(\i)\.length\?)(?<=(\i)\.useMemo.+?)/, replace: ";$1=$2.useMemo(()=>[...$self.getBadges(arguments[0].displayProfile),...$1],[$1])" diff --git a/src/plugins/_api/memberListDecorators/index.tsx b/src/plugins/_api/memberListDecorators/index.tsx index 365c21f3..90f09d8f 100644 --- a/src/plugins/_api/memberListDecorators/index.tsx +++ b/src/plugins/_api/memberListDecorators/index.tsx @@ -32,21 +32,17 @@ export default definePlugin({ { find: ".lostPermission)", replacement: [ - { - match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/, - replace: "$&vencordProps=$1," - }, { match: /children:\[(?=.{0,300},lostPermissionTooltipText:)/, - replace: "children:[(typeof vencordProps!=='undefined'&&Vencord.Api.MemberListDecorators.__getDecorators(vencordProps))," + replace: "children:[Vencord.Api.MemberListDecorators.__getDecorators(arguments[0],'guild')," } ] }, { find: "PrivateChannel.renderAvatar", replacement: { - match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/, - replace: "decorators:[Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]),$1?$2:null]" + match: /decorators:(\i\.isSystemDM\(\)\?.+?:null)/, + replace: "decorators:[Vencord.Api.MemberListDecorators.__getDecorators(arguments[0],'dm'),$1]" } } ] diff --git a/src/plugins/_api/serverList.ts b/src/plugins/_api/serverList.ts index dfd40de7..89c40796 100644 --- a/src/plugins/_api/serverList.ts +++ b/src/plugins/_api/serverList.ts @@ -32,9 +32,9 @@ export default definePlugin({ } }, { - find: "#{intl::SERVERS}),children", + find: ".setGuildsTree(", replacement: { - match: /(?<=#{intl::SERVERS}\),children:)\i\.map\(\i\)/, + match: /(?<=#{intl::SERVERS}\),gap:"xs",children:)\i\.map\(.{0,50}\.length\)/, replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)" } } diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index 8b561581..ad63ba27 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -9,7 +9,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; +import { findComponentByCodeLazy } from "@webpack"; import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common"; import { User } from "discord-types/general"; @@ -19,8 +19,7 @@ interface UserProfileProps { originalRenderPopout: () => React.ReactNode; } -const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cannot be undefined"); -const styles = findByPropsLazy("accountProfilePopoutWrapper"); +const UserProfile = findComponentByCodeLazy(".POPOUT,user"); let openAlternatePopout = false; let accountPanelRef: React.RefObject | null> = { current: null }; @@ -77,7 +76,7 @@ export default definePlugin({ replace: "$self.useAccountPanelRef();$&" }, { - match: /(\.AVATAR,children:.+?renderPopout:(\(\i,\i\))=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/, + match: /(\.AVATAR,children:.+?renderPopout:\((\i),\i\)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/, replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})` }, { @@ -121,14 +120,18 @@ export default definePlugin({ } const currentChannel = getCurrentChannel(); - if (currentChannel?.getGuildId() == null) { + if (currentChannel?.getGuildId() == null || !UserProfile.$$vencordGetWrappedComponent()) { return originalRenderPopout(); } return ( -
- -
+ ); }, { noop: true }) }); diff --git a/src/plugins/forceOwnerCrown/index.ts b/src/plugins/forceOwnerCrown/index.ts index 907d9dc0..bf115c64 100644 --- a/src/plugins/forceOwnerCrown/index.ts +++ b/src/plugins/forceOwnerCrown/index.ts @@ -29,8 +29,8 @@ export default definePlugin({ { find: "#{intl::GUILD_OWNER}),children:", replacement: { - match: /,isOwner:(\i),/, - replace: ",_isOwner:$1=$self.isGuildOwner(e)," + match: /(?<=decorators:.{0,200}?isOwner:)\i/, + replace: "$self.isGuildOwner(arguments[0])" } } ], diff --git a/src/plugins/friendsSince/index.tsx b/src/plugins/friendsSince/index.tsx index 0f4016ad..07723bcc 100644 --- a/src/plugins/friendsSince/index.tsx +++ b/src/plugins/friendsSince/index.tsx @@ -24,7 +24,7 @@ export default definePlugin({ patches: [ // DM User Sidebar { - find: ".PANEL}),nicknameIcons", + find: ".SIDEBAR}),nicknameIcons", replacement: { match: /#{intl::USER_PROFILE_MEMBER_SINCE}\),.{0,100}userId:(\i\.id)}\)}\)/, replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:true})" diff --git a/src/plugins/ircColors/index.ts b/src/plugins/ircColors/index.ts index 3e0515f5..d4f453a7 100644 --- a/src/plugins/ircColors/index.ts +++ b/src/plugins/ircColors/index.ts @@ -66,8 +66,8 @@ export default definePlugin({ { find: '="SYSTEM_TAG"', replacement: { - match: /\i.gradientClassName]\),style:/, - replace: "$&{color:$self.calculateNameColorForMessageContext(arguments[0])},_style:" + match: /(?<=\.username.{0,50}?)style:/, + replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},_style:" } }, { diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 1c9ad40e..d71fd933 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import "./style.css"; + import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; @@ -95,6 +97,12 @@ export default definePlugin({ { match: /\(0,\i\.jsx\)\(\i,\{items:\i,section:(\i)/, replace: "$1==='MUTUAL_GDMS'?$self.renderMutualGDMs(arguments[0]):$&" + }, + // Discord adds spacing between each item which pushes our tab off screen. + // set the gap to zero to ensure ours stays on screen + { + match: /className:\i\.tabBar/, + replace: "$& + ' vc-mutual-gdms-tab-bar'" } ] }, diff --git a/src/plugins/mutualGroupDMs/style.css b/src/plugins/mutualGroupDMs/style.css new file mode 100644 index 00000000..14ea83c9 --- /dev/null +++ b/src/plugins/mutualGroupDMs/style.css @@ -0,0 +1,4 @@ +.vc-mutual-gdms-tab-bar { + gap: 0; + justify-content: space-between; +} diff --git a/src/plugins/noUnblockToJump/index.ts b/src/plugins/noUnblockToJump/index.ts index 04ddf2ed..4dbbe462 100644 --- a/src/plugins/noUnblockToJump/index.ts +++ b/src/plugins/noUnblockToJump/index.ts @@ -30,7 +30,7 @@ export default definePlugin({ find: '.id,"Search Results"', replacement: [ { - match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/, + match: /if\(.{1,40}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/, replace: "if(false)$1" }, { From 700b971e7dced02e4b5995e1cb0179897607af35 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 25 Apr 2025 00:45:17 +0200 Subject: [PATCH 07/34] MutualGroupDMs: fix display in dm profile sidebar --- src/plugins/mutualGroupDMs/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index d71fd933..3c8e30fe 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -32,7 +32,7 @@ const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); const UserUtils = findByPropsLazy("getGlobalName"); const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds"); -const ExpandableList = findComponentByCodeLazy('"PRESS_SECTION"'); +const ExpandableList = findComponentByCodeLazy('"PRESS_SECTION"', ".header"); const GuildLabelClasses = findByPropsLazy("guildNick", "guildAvatarWithoutIcon"); function getGroupDMName(channel: Channel) { From dad69e0d0fea7163385e72868ca16670b50659fd Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 30 Apr 2025 03:11:56 +0200 Subject: [PATCH 08/34] Fix Vencord Notifications & ImageZoom causing crashes --- src/plugins/imageZoom/index.tsx | 15 ++++++++++----- src/webpack/common/react.ts | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index 25a0ab7d..d7e8d487 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -21,6 +21,7 @@ import { definePluginSettings } from "@api/Settings"; import { makeRange } from "@components/PluginSettings/components"; import { debounce } from "@shared/debounce"; import { Devs } from "@utils/constants"; +import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { Menu, ReactDOM } from "@webpack/common"; import { JSX } from "react"; @@ -237,12 +238,16 @@ export default definePlugin({ }, renderMagnifier(instance) { - if (instance.props.id === ELEMENT_ID) { - if (!this.currentMagnifierElement) { - this.currentMagnifierElement = ; - this.root = ReactDOM.createRoot(this.element!); - this.root.render(this.currentMagnifierElement); + try { + if (instance.props.id === ELEMENT_ID) { + if (!this.currentMagnifierElement) { + this.currentMagnifierElement = ; + this.root = ReactDOM.createRoot(this.element!); + this.root.render(this.currentMagnifierElement); + } } + } catch (error) { + new Logger("ImageZoom").error("Failed to render magnifier:", error); } }, diff --git a/src/webpack/common/react.ts b/src/webpack/common/react.ts index 99f3f9dd..aa279d65 100644 --- a/src/webpack/common/react.ts +++ b/src/webpack/common/react.ts @@ -28,7 +28,7 @@ export let useRef: typeof React.useRef; export let useReducer: typeof React.useReducer; export let useCallback: typeof React.useCallback; -export const ReactDOM: typeof import("react-dom") & typeof import("react-dom/client") = findByPropsLazy("createPortal", "render"); +export const ReactDOM: typeof import("react-dom") & typeof import("react-dom/client") = findByPropsLazy("createPortal"); waitFor("useState", m => { React = m; From 9fa91c193d78fa8a0c32131c0ffa8aec70931c5e Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 30 Apr 2025 03:58:46 +0200 Subject: [PATCH 09/34] Fix ServerInfo, ShowMeYourName & WebContextMenus --- src/plugins/serverInfo/GuildInfoModal.tsx | 2 +- src/plugins/serverInfo/styles.css | 1 - src/plugins/showMeYourName/index.tsx | 5 +++-- src/plugins/webContextMenus.web/index.ts | 9 ++++++++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/plugins/serverInfo/GuildInfoModal.tsx b/src/plugins/serverInfo/GuildInfoModal.tsx index 9f2d3008..0f08af59 100644 --- a/src/plugins/serverInfo/GuildInfoModal.tsx +++ b/src/plugins/serverInfo/GuildInfoModal.tsx @@ -16,7 +16,7 @@ import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, GuildStore, import { Guild, User } from "discord-types/general"; const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper"); -const FriendRow = findComponentByCodeLazy(".listName,discriminatorClass"); +const FriendRow = findComponentByCodeLazy("discriminatorClass:", ".isMobileOnline", "getAvatarURL"); const cl = classNameFactory("vc-gp-"); diff --git a/src/plugins/serverInfo/styles.css b/src/plugins/serverInfo/styles.css index 274b7d13..42a7899c 100644 --- a/src/plugins/serverInfo/styles.css +++ b/src/plugins/serverInfo/styles.css @@ -50,7 +50,6 @@ border-bottom: 2px solid transparent; color: var(--interactive-normal); cursor: pointer; - height: 39px; line-height: 14px; } diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 1f04f1f3..ac727f69 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -48,9 +48,10 @@ export default definePlugin({ authors: [Devs.Rini, Devs.TheKodeToad], patches: [ { - find: '?"@":""', + find: '"BaseUsername"', replacement: { - match: /(?<=onContextMenu:\i,children:)\i\+\i/, + /* TODO: remove \i+\i once change makes it to stable */ + match: /(?<=onContextMenu:\i,children:)(?:\i\+\i|\i)/, replace: "$self.renderUsername(arguments[0])" } }, diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index a0f84dd6..45e6fa00 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -120,6 +120,13 @@ export default definePlugin({ ] }, + { + find: "Copy image not supported", + replacement: { + match: /(?<=(?:canSaveImage|canCopyImage)\(\i?\)\{.{0,50})!\i\.isPlatformEmbedded/g, + replace: "false" + } + }, // Add back Copy & Save Image { find: 'id:"copy-image"', @@ -130,7 +137,7 @@ export default definePlugin({ replace: "false" }, { - match: /return\s*?\[\i\.\i\.canCopyImage\(\)/, + match: /return\s*?\[.{0,50}?(?=\?.{0,100}?id:"copy-image")/, replace: "return [true" }, { From 096f8483852a0edf1e4e472f250f396237c662b5 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 30 Apr 2025 04:13:31 +0200 Subject: [PATCH 10/34] MutualGroupDMs: fix weird spacing --- src/plugins/mutualGroupDMs/style.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/mutualGroupDMs/style.css b/src/plugins/mutualGroupDMs/style.css index 14ea83c9..3d06568f 100644 --- a/src/plugins/mutualGroupDMs/style.css +++ b/src/plugins/mutualGroupDMs/style.css @@ -1,4 +1,3 @@ .vc-mutual-gdms-tab-bar { gap: 0; - justify-content: space-between; } From b954bf3c9d9329e55c9dc2bd93ac81944a8bdd21 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 30 Apr 2025 03:30:37 +0200 Subject: [PATCH 11/34] MutualGroupDMs: fix DM sidebar (again) --- src/plugins/mutualGroupDMs/index.tsx | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 3c8e30fe..42f4f2e3 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -32,8 +32,8 @@ const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); const UserUtils = findByPropsLazy("getGlobalName"); const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds"); +const MutualsListClasses = findByPropsLazy("row", "icon", "name", "nick"); const ExpandableList = findComponentByCodeLazy('"PRESS_SECTION"', ".header"); -const GuildLabelClasses = findByPropsLazy("guildNick", "guildAvatarWithoutIcon"); function getGroupDMName(channel: Channel) { return channel.name || @@ -59,21 +59,22 @@ function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) { return mutualDms.map(c => ( { onClose(); SelectedChannelActionCreators.selectPrivateChannel(c.id); }} > - - -
-
{getGroupDMName(c)}
-
{c.recipients.length + 1} Members
+
+ + +
+
{getGroupDMName(c)}
+
{c.recipients.length + 1} Members
+
)); From 838a90831f0b0405150cc69e9ee0ca11d96f5c59 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Fri, 2 May 2025 20:40:07 -0400 Subject: [PATCH 12/34] Fix BetterFolders, ReviewDB and minor ShowHiddenChannels patch (#3396) Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> --- src/plugins/betterFolders/index.tsx | 101 ++++++++++++++--------- src/plugins/reviewDB/index.tsx | 12 +-- src/plugins/showHiddenChannels/index.tsx | 17 ++-- 3 files changed, 80 insertions(+), 50 deletions(-) diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 3bcf0335..2b1defd8 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -50,6 +50,35 @@ function closeFolders() { FolderUtils.toggleGuildFolderExpand(id); } +// Nuckyz: Unsure if this should be a general utility or not +function filterTreeWithTargetNode(children: any, predicate: (node: any) => boolean) { + if (children == null) { + return false; + } + + if (!Array.isArray(children)) { + if (predicate(children)) { + return true; + } + + return filterTreeWithTargetNode(children.props.children, predicate); + } + + + let childIsTargetChild = false; + for (let i = 0; i < children.length; i++) { + const shouldKeep = filterTreeWithTargetNode(children[i], predicate); + if (shouldKeep) { + childIsTargetChild = true; + continue; + } + + children.splice(i--, 1); + } + + return childIsTargetChild; +} + export const settings = definePluginSettings({ sidebar: { type: OptionType.BOOLEAN, @@ -114,29 +143,35 @@ export default definePlugin({ predicate: () => settings.store.sidebar, replacement: [ // Create the isBetterFolders variable in the GuildsBar component + // Needed because we access this from a non-arrow closure so we can't use arguments[0] { match: /let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?(?=}=\i,)/, replace: "$&,isBetterFolders" }, + // Export the isBetterFolders and betterFoldersExpandedIds variable to the Guild List component + { + match: /0,\i\.jsxs?[^0}]{0,100}guildDiscoveryButton:\i,/g, + replace: "$&isBetterFolders:arguments[0]?.isBetterFolders,betterFoldersExpandedIds:arguments[0]?.betterFoldersExpandedIds," + }, + // Export the isBetterFolders variable to the folders component + { + match: /switch\(\i\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,/, + replace: '$&isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,' + }, // If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders { match: /\[(\i)\]=(\(0,\i\.\i\).{0,40}getGuildsTree\(\).+?}\))(?=,)/, replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0]?.isBetterFolders,betterFoldersOriginalTree,arguments[0]?.betterFoldersExpandedIds)` }, - // If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children + // If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the Guild List children { match: /lastTargetNode:\i\[\i\.length-1\].+?}\)(?::null)?\](?=}\))/, replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0]?.isBetterFolders))" }, - // If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children + // If we are rendering the Better Folders sidebar, we filter out everything but the Guild List from the Sidebar children { - match: /unreadMentionsIndicatorBottom,.+?}\)\]/, - replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0]?.isBetterFolders))" - }, - // Export the isBetterFolders variable to the folders component - { - match: /switch\(\i\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,/, - replace: '$&isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,' + match: /unreadMentionsFixedFooter\].+?\]/, + replace: "$&.filter($self.makeGuildsBarSidebarFilter(!!arguments[0]?.isBetterFolders))" } ] }, @@ -161,7 +196,7 @@ export default definePlugin({ ] }, { - find: ".expandedFolderBackground,", + find: ".FOLDER_ITEM_ANIMATION_DURATION),", predicate: () => settings.store.sidebar, replacement: [ // We use arguments[0] to access the isBetterFolders variable in this nested folder component (the parent exports all the props so we don't have to patch it) @@ -181,27 +216,20 @@ export default definePlugin({ // If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded { predicate: () => !settings.store.keepIcons, - match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/, + match: /folderGroupBackground.+?,(?=\i\(\(\i,\i,\i\)=>{let{key:.{0,70}"ul")(?<=selected:\i,expanded:(\i),.+?)/, replace: (m, isExpanded) => `${m}$self.shouldRenderContents(arguments[0],${isExpanded})?null:` }, + // Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar { - // Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always, - match: /\.isExpanded\),.{0,30}children:\[/, + match: /\.isExpanded\].{0,110}children:\[/, replace: "$&$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&" }, + // Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar { - // Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always, - match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/, + match: /(?<=\.folderGroupBackground.*?}\),)(?=\i,)/, replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)?null:" - }, - { - // Discord adds a slight bottom margin of 4px when it's expanded - // Which looks off when there's nothing open in the folder - predicate: () => !settings.store.keepIcons, - match: /(?=className:.{0,50}folderIcon)/, - replace: "style:arguments[0]?.isBetterFolders?{}:{marginBottom:0}," } ] }, @@ -278,6 +306,9 @@ export default definePlugin({ } }, + FolderSideBar, + closeFolders, + gridStyle: "vc-betterFolders-sidebar-grid", getGuildTree(isBetterFolders: boolean, originalTree: any, expandedFolderIds?: Set) { @@ -299,34 +330,33 @@ export default definePlugin({ makeGuildsBarGuildListFilter(isBetterFolders: boolean) { return child => { - if (!isBetterFolders) return true; + if (!isBetterFolders) { + return true; + } try { return child?.props?.["aria-label"] === getIntlMessage("SERVERS"); } catch (e) { console.error(e); + return true; } - - return true; }; }, - makeGuildsBarTreeFilter(isBetterFolders: boolean) { + makeGuildsBarSidebarFilter(isBetterFolders: boolean) { return child => { - if (!isBetterFolders) return true; - - if (child?.props?.className?.includes("itemsContainer") && child.props.children != null) { - // Filter out everything but the scroller for the guild list - child.props.children = child.props.children.filter(child => child?.props?.onScroll != null); + if (!isBetterFolders) { return true; } - return false; + return filterTreeWithTargetNode(child, child => child?.props?.renderTreeNode != null); }; }, shouldShowFolderIconAndBackground(isBetterFolders: boolean, expandedFolderIds?: Set) { - if (!isBetterFolders) return true; + if (!isBetterFolders) { + return true; + } switch (settings.store.showFolderIcon) { case FolderIconDisplay.Never: @@ -352,8 +382,5 @@ export default definePlugin({ if (props?.folderNode?.id === 1) return false; return !props?.isBetterFolders && isExpanded; - }, - - FolderSideBar, - closeFolders, + } }); diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index 822ebde6..32546b9b 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -77,23 +77,23 @@ export default definePlugin({ patches: [ { - find: ".BITE_SIZE,user:", + find: ".POPOUT,user:", replacement: { - match: /{profileType:\i\.\i\.BITE_SIZE,children:\[/, + match: /children:\[(?=[^[]+?shouldShowTooltip:)/, replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," } }, { - find: ".FULL_SIZE,user:", + find: ".MODAL,user:", replacement: { - match: /{profileType:\i\.\i\.FULL_SIZE,children:\[/, + match: /children:\[(?=[^[]+?shouldShowTooltip:)/, replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," } }, { - find: 'location:"UserProfilePanel"', + find: ".SIDEBAR,shouldShowTooltip:", replacement: { - match: /{profileType:\i\.\i\.PANEL,children:\[/, + match: /children:\[(?=[^[]+?shouldShowTooltip:)/, replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," } } diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 7a38bb12..7a3dd9fb 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -325,7 +325,7 @@ export default definePlugin({ ] }, { - find: '})},"overflow"))', + find: '="interactive-normal",overflowCountClassName:', replacement: [ { // Create a variable for the channel prop @@ -334,18 +334,21 @@ export default definePlugin({ }, { // Make Discord always render the plus button if the component is used inside the HiddenChannelLockScreen - match: /\i>0(?=&&.{0,60}renderPopout)/, + match: /\i>0(?=&&.{0,30}Math.min)/, 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: /(?<=\.value\(\),(\i)=.+?length-)1(?=\]=.{0,60}renderPopout)/, + // 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 - match: /(?<="\+",)(\i)\+1/, - replace: (m, amount) => `$self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)&&${amount}<=0?"":${m}` + // Show only the plus text without overflowed children amount + // if the overflow amount is <= 0 and the component is used inside the HiddenChannelLockScreen + match: /(?<="\+"\.concat\()\i/, + replace: overflowTextAmount => "" + + `$self.isHiddenChannel(typeof shcChannel!=="undefined"?shcChannel:void 0,true)&&(${overflowTextAmount}-1)<=0?"":${overflowTextAmount}` } ] }, From bebf3dd0681a5a8c8a81f7e501f06a0738b763af Mon Sep 17 00:00:00 2001 From: hexa <67880600+hexa0@users.noreply.github.com> Date: Fri, 2 May 2025 21:28:53 -0400 Subject: [PATCH 13/34] ImageZoom: Fix for animated WebP images (#3407) --- src/plugins/imageZoom/components/Magnifier.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/imageZoom/components/Magnifier.tsx b/src/plugins/imageZoom/components/Magnifier.tsx index 009165ff..2acd0259 100644 --- a/src/plugins/imageZoom/components/Magnifier.tsx +++ b/src/plugins/imageZoom/components/Magnifier.tsx @@ -203,7 +203,7 @@ export const Magnifier = ErrorBoundary.wrap(({ instance, size: i }} width={`${box.width * zoom.current}px`} height={`${box.height * zoom.current}px`} - src={instance.props.src} + src={instance.props.src + "?animated=true"} alt="" /> )} From 59974a162eca499f1562b7f6ba43c7644b689159 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Fri, 2 May 2025 21:30:10 -0400 Subject: [PATCH 14/34] PlainFolderIcon: Fix plugin not working (#3409) --- src/plugins/betterFolders/index.tsx | 11 ++++++--- src/plugins/mutualGroupDMs/index.tsx | 21 ++++++++-------- src/plugins/plainFolderIcon/index.ts | 35 +++++++++++++-------------- src/plugins/plainFolderIcon/style.css | 10 ++++++++ 4 files changed, 45 insertions(+), 32 deletions(-) create mode 100644 src/plugins/plainFolderIcon/style.css diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 2b1defd8..b5049883 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -61,7 +61,7 @@ function filterTreeWithTargetNode(children: any, predicate: (node: any) => boole return true; } - return filterTreeWithTargetNode(children.props.children, predicate); + return filterTreeWithTargetNode(children.props?.children, predicate); } @@ -150,7 +150,7 @@ export default definePlugin({ }, // Export the isBetterFolders and betterFoldersExpandedIds variable to the Guild List component { - match: /0,\i\.jsxs?[^0}]{0,100}guildDiscoveryButton:\i,/g, + match: /,{guildDiscoveryButton:\i,/g, replace: "$&isBetterFolders:arguments[0]?.isBetterFolders,betterFoldersExpandedIds:arguments[0]?.betterFoldersExpandedIds," }, // Export the isBetterFolders variable to the folders component @@ -349,7 +349,12 @@ export default definePlugin({ return true; } - return filterTreeWithTargetNode(child, child => child?.props?.renderTreeNode != null); + try { + return filterTreeWithTargetNode(child, child => child?.props?.renderTreeNode != null); + } catch (e) { + console.error(e); + return true; + } }; }, diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 42f4f2e3..f06cac0c 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -59,22 +59,21 @@ function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) { return mutualDms.map(c => ( { onClose(); SelectedChannelActionCreators.selectPrivateChannel(c.id); }} > -
- - -
-
{getGroupDMName(c)}
-
{c.recipients.length + 1} Members
-
+ + +
+
{getGroupDMName(c)}
+
{c.recipients.length + 1} Members
)); diff --git a/src/plugins/plainFolderIcon/index.ts b/src/plugins/plainFolderIcon/index.ts index bb6876b5..8eb87896 100644 --- a/src/plugins/plainFolderIcon/index.ts +++ b/src/plugins/plainFolderIcon/index.ts @@ -16,28 +16,27 @@ * along with this program. If not, see . */ +import "./style.css"; + import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; export default definePlugin({ name: "PlainFolderIcon", - description: "Doesn't show the small guild icons in folders", + description: "Dont show the small guild icons in folders", authors: [Devs.botato], - patches: [{ - find: ".expandedFolderIconWrapper", - replacement: [ - // there are two elements, the first one is the plain folder icon - // the second is the four guild preview icons - // always show this one (the plain icons) - { - match: /\(\i\|\|\i\)&&(\(.{0,40}\(\i\.animated)/, - replace: "$1", - }, - // and never show this one (the guild preview icons) - { - match: /\(\i\|\|!\i\)&&(\(.{0,40}\(\i\.animated)/, - replace: "false&&$1", - } - ] - }] + + patches: [ + { + find: ".folderPreviewGuildIconError", + replacement: [ + { + // Discord always renders both plain and guild icons folders and uses a css transtion to switch between them + match: /(?<=.folderButtonContent]:(!\i))/, + replace: (_, hasFolderButtonContentClass) => `,"vc-plainFolderIcon-plain":${hasFolderButtonContentClass}` + } + + ] + } + ] }); diff --git a/src/plugins/plainFolderIcon/style.css b/src/plugins/plainFolderIcon/style.css new file mode 100644 index 00000000..70a49078 --- /dev/null +++ b/src/plugins/plainFolderIcon/style.css @@ -0,0 +1,10 @@ +.vc-plainFolderIcon-plain { + /* Without this, they are a bit laggier */ + transition: none !important; + + /* Don't show the mini guild icons */ + transform: translateZ(0); + + /* The new icons are fully transparent. Add a sane default to match the old behavior */ + background-color: color-mix(in oklab, var(--custom-folder-color, var(--bg-brand)) 30%, var(--background-surface-higher) 70%); +} From 3b53ad0c919b0b95e415640ddb83a4657aaedb0a Mon Sep 17 00:00:00 2001 From: Mufaro <81554673+mufaroxyz@users.noreply.github.com> Date: Sat, 3 May 2025 03:46:34 +0200 Subject: [PATCH 15/34] Experiments: Support new experiment link embeds & fix toolbar patch (#3372) --- src/plugins/betterFolders/index.tsx | 1 - src/plugins/experiments/index.tsx | 28 +++++++++++++------ .../imageZoom/components/Magnifier.tsx | 14 ++++++++-- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index b5049883..fc5b4436 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -64,7 +64,6 @@ function filterTreeWithTargetNode(children: any, predicate: (node: any) => boole return filterTreeWithTargetNode(children.props?.children, predicate); } - let childIsTargetChild = false; for (let i = 0; i < children.length; i++) { const shouldKeep = filterTreeWithTargetNode(children[i], predicate); diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index 82e20f73..49ae1ab7 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -31,6 +31,10 @@ import hideBugReport from "./hideBugReport.css?managed"; const KbdStyles = findByPropsLazy("key", "combo"); const BugReporterExperiment = findLazy(m => m?.definition?.id === "2024-09_bug_reporter"); +const isMacOS = navigator.platform.includes("Mac"); +const modKey = isMacOS ? "cmd" : "ctrl"; +const altKey = isMacOS ? "opt" : "alt"; + const settings = definePluginSettings({ toolbarDevMenu: { type: OptionType.BOOLEAN, @@ -48,7 +52,7 @@ export default definePlugin({ Devs.Ven, Devs.Nickyux, Devs.BanTheNons, - Devs.Nuckyz + Devs.Nuckyz, ], settings, @@ -75,9 +79,9 @@ export default definePlugin({ replace: "$&$self.WarningCard()," } }, - // change top right chat toolbar button from the help one to the dev one + // Change top right chat toolbar button from the help one to the dev one { - find: "toolbar:function", + find: ".CONTEXTLESS,isActivityPanelMode:", replacement: { match: /hasBugReporterAccess:(\i)/, replace: "_hasBugReporterAccess:$1=true" @@ -85,7 +89,7 @@ export default definePlugin({ predicate: () => settings.store.toolbarDevMenu }, - // makes the Favourites Server experiment allow favouriting DMs and threads + // Make the Favourites Server experiment allow favouriting DMs and threads { find: "useCanFavoriteChannel", replacement: { @@ -93,23 +97,29 @@ export default definePlugin({ replace: "false", } }, - // enable option to always record clips even if you are not streaming + // Enable option to always record clips even if you are not streaming { find: "isDecoupledGameClippingEnabled(){", replacement: { match: /\i\.isStaff\(\)/, replace: "true" } - } + }, + + // Enable experiment embed on sent experiment links + { + find: "dev://experiment/", + replacement: { + match: /\i\.isStaff\(\)/, + replace: "true" + } + }, ], start: () => !BugReporterExperiment.getCurrentConfig().hasBugReporterAccess && enableStyle(hideBugReport), stop: () => disableStyle(hideBugReport), settingsAboutComponent: () => { - const isMacOS = navigator.platform.includes("Mac"); - const modKey = isMacOS ? "cmd" : "ctrl"; - const altKey = isMacOS ? "opt" : "alt"; return ( More Information diff --git a/src/plugins/imageZoom/components/Magnifier.tsx b/src/plugins/imageZoom/components/Magnifier.tsx index 2acd0259..d68f916e 100644 --- a/src/plugins/imageZoom/components/Magnifier.tsx +++ b/src/plugins/imageZoom/components/Magnifier.tsx @@ -18,7 +18,7 @@ import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; -import { FluxDispatcher, useLayoutEffect, useRef, useState } from "@webpack/common"; +import { FluxDispatcher, useLayoutEffect, useMemo, useRef, useState } from "@webpack/common"; import { ELEMENT_ID } from "../constants"; import { settings } from "../index"; @@ -160,6 +160,16 @@ export const Magnifier = ErrorBoundary.wrap(({ instance, size: i } }); + const imageSrc = useMemo(() => { + try { + const imageUrl = new URL(instance.props.src); + imageUrl.searchParams.set("animated", "true"); + return imageUrl.toString(); + } catch { + return instance.props.src; + } + }, [instance.props.src]); + if (!ready) return null; const box = element.current?.getBoundingClientRect(); @@ -203,7 +213,7 @@ export const Magnifier = ErrorBoundary.wrap(({ instance, size: i }} width={`${box.width * zoom.current}px`} height={`${box.height * zoom.current}px`} - src={instance.props.src + "?animated=true"} + src={imageSrc} alt="" /> )} From 8aa92873b9a401cfa83c47ae7e367330e9d623b2 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 2 May 2025 22:52:20 -0300 Subject: [PATCH 16/34] Fix MutualGroupDMs duplicate find --- src/plugins/mutualGroupDMs/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index f06cac0c..858a366c 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -88,7 +88,7 @@ export default definePlugin({ patches: [ { - find: ".MUTUAL_FRIENDS?(", + find: ".BOT_DATA_ACCESS?(", replacement: [ { match: /\i\.useEffect.{0,100}(\i)\[0\]\.section/, From 34269e2339ecae74625432062052ef27b6fbc3f2 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 2 May 2025 22:54:37 -0300 Subject: [PATCH 17/34] Bump to 1.12.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c75345d7..60fa7703 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.11.9", + "version": "1.12.0", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 15fa0ff7a7351c7217793f30ad3a5d32846ff873 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 3 May 2025 08:20:32 -0300 Subject: [PATCH 18/34] PlainFolderIcon: Fix folder colors changing with other themes --- src/plugins/plainFolderIcon/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/plainFolderIcon/style.css b/src/plugins/plainFolderIcon/style.css index 70a49078..3e2992fc 100644 --- a/src/plugins/plainFolderIcon/style.css +++ b/src/plugins/plainFolderIcon/style.css @@ -6,5 +6,5 @@ transform: translateZ(0); /* The new icons are fully transparent. Add a sane default to match the old behavior */ - background-color: color-mix(in oklab, var(--custom-folder-color, var(--bg-brand)) 30%, var(--background-surface-higher) 70%); + background-color: color-mix(in oklab, var(--custom-folder-color, var(--bg-brand)) 40%, transparent); } From 578aa5f107c03d7cd96f18fa6b3546fe836fd04e Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 3 May 2025 08:53:58 -0300 Subject: [PATCH 19/34] Experiments: Fix client crash with new experiment embed --- src/plugins/experiments/index.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx index 49ae1ab7..2482d051 100644 --- a/src/plugins/experiments/index.tsx +++ b/src/plugins/experiments/index.tsx @@ -109,10 +109,17 @@ export default definePlugin({ // Enable experiment embed on sent experiment links { find: "dev://experiment/", - replacement: { - match: /\i\.isStaff\(\)/, - replace: "true" - } + replacement: [ + { + match: /\i\.isStaff\(\)/, + replace: "true" + }, + // Fix some tricky experiments name causing a client crash + { + match: /.getRegisteredExperiments\(\)(?<=(\i)=.+?).+?if\(null==(\i)(?=\)return null;)/, + replace: "$&||!Object.hasOwn($1,$2)" + } + ] }, ], From 235bdee061d64de4ceb7a3ab76deea1f5cb817bf Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Sun, 4 May 2025 11:42:13 -0400 Subject: [PATCH 20/34] BetterFolders: Fix hang (#3412) Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com> --- src/plugins/betterFolders/FolderSideBar.tsx | 5 +- src/plugins/betterFolders/index.tsx | 64 ++++++++++++--------- src/plugins/betterFolders/sidebarFix.css | 3 +- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx index d2ffe6bb..cfd15517 100644 --- a/src/plugins/betterFolders/FolderSideBar.tsx +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -42,7 +42,8 @@ export default ErrorBoundary.wrap(guildsBarProps => { 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. - // Also display flex otherwise to fix scrolling + // Also display flex otherwise to fix scrolling. + // gridArea is needed to align properly with the base app grid. const barStyle = { display: isFullscreen ? "none" : "flex", gridArea: "betterFoldersSidebar" @@ -62,7 +63,7 @@ export default ErrorBoundary.wrap(guildsBarProps => { leave={{ width: 0 }} config={{ duration: 200 }} > - {(animationStyle, show) => + {(animationStyle: any, show: any) => show && ( {Sidebar} diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index fc5b4436..ca46f843 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -23,7 +23,8 @@ import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; -import { FluxDispatcher, useMemo } from "@webpack/common"; +import { FluxDispatcher } from "@webpack/common"; +import { ReactNode } from "react"; import FolderSideBar from "./FolderSideBar"; @@ -129,6 +130,10 @@ export const settings = definePluginSettings({ } }); +const IS_BETTER_FOLDERS_VAR = "typeof isBetterFolders!=='undefined'?isBetterFolders:arguments[0]?.isBetterFolders"; +const BETTER_FOLDERS_EXPANDED_IDS_VAR = "typeof betterFoldersExpandedIds!=='undefined'?betterFoldersExpandedIds:arguments[0]?.betterFoldersExpandedIds"; +const GRID_STYLE_NAME = "vc-betterFolders-sidebar-grid"; + export default definePlugin({ name: "BetterFolders", description: "Shows server folders on dedicated sidebar and adds folder related improvements", @@ -141,28 +146,33 @@ export default definePlugin({ find: '("guildsnav")', predicate: () => settings.store.sidebar, replacement: [ - // Create the isBetterFolders variable in the GuildsBar component + // Create the isBetterFolders and betterFoldersExpandedIds variables in the GuildsBar component // Needed because we access this from a non-arrow closure so we can't use arguments[0] { match: /let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?(?=}=\i,)/, - replace: "$&,isBetterFolders" + replace: "$&,isBetterFolders,betterFoldersExpandedIds" }, // Export the isBetterFolders and betterFoldersExpandedIds variable to the Guild List component { match: /,{guildDiscoveryButton:\i,/g, replace: "$&isBetterFolders:arguments[0]?.isBetterFolders,betterFoldersExpandedIds:arguments[0]?.betterFoldersExpandedIds," }, - // Export the isBetterFolders variable to the folders component + // Wrap the guild node (guild or folder) component in a div with display: none if it's not an expanded folder or a guild in an expanded folder + { + match: /switch\((\i)\.type\){.+?default:return null}/, + replace: `return $self.wrapGuildNodeComponent($1,()=>{$&},${IS_BETTER_FOLDERS_VAR},${BETTER_FOLDERS_EXPANDED_IDS_VAR});` + }, + // Export the isBetterFolders variable to the folder component { match: /switch\(\i\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,/, - replace: '$&isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,' + replace: `$&isBetterFolders:${IS_BETTER_FOLDERS_VAR},` }, - // If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders + // Make the callback for returning the guild node component depend on isBetterFolders and betterFoldersExpandedIds { - match: /\[(\i)\]=(\(0,\i\.\i\).{0,40}getGuildsTree\(\).+?}\))(?=,)/, - replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0]?.isBetterFolders,betterFoldersOriginalTree,arguments[0]?.betterFoldersExpandedIds)` + match: /switch\(\i\.type\).+?,\i,\i\.setNodeRef/, + replace: "$&,arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds" }, - // If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the Guild List children + // If we are rendering the Better Folders sidebar, we filter out everything but the guilds and folders from the Guild List children { match: /lastTargetNode:\i\[\i\.length-1\].+?}\)(?::null)?\](?=}\))/, replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0]?.isBetterFolders))" @@ -248,8 +258,8 @@ export default definePlugin({ }, { // Add grid styles to fix aligment with other visual refresh elements - match: /(?<=className:)(\i\.base)(?=,)/, - replace: "`${$self.gridStyle} ${$1}`" + match: /(?<=className:)\i\.base(?=,)/, + replace: `"${GRID_STYLE_NAME} "+$&` } ] }, @@ -308,27 +318,25 @@ export default definePlugin({ FolderSideBar, closeFolders, - gridStyle: "vc-betterFolders-sidebar-grid", - getGuildTree(isBetterFolders: boolean, originalTree: any, expandedFolderIds?: Set) { - return useMemo(() => { - if (!isBetterFolders || expandedFolderIds == null) return originalTree; + wrapGuildNodeComponent(node: any, originalComponent: () => ReactNode, isBetterFolders: boolean, expandedFolderIds?: Set) { + if ( + !isBetterFolders || + node.type === "folder" && expandedFolderIds?.has(node.id) || + node.type === "guild" && expandedFolderIds?.has(node.parentId) + ) { + return originalComponent(); + } - const newTree = new GuildsTree(); - // Children is every folder and guild which is not in a folder, this filters out only the expanded folders - newTree.root.children = originalTree.root.children.filter(guildOrFolder => expandedFolderIds.has(guildOrFolder.id)); - // Nodes is every folder and guild, even if it's in a folder, this filters out only the expanded folders and guilds inside them - newTree.nodes = Object.fromEntries( - Object.entries(originalTree.nodes) - .filter(([_, guildOrFolder]: any[]) => expandedFolderIds.has(guildOrFolder.id) || expandedFolderIds.has(guildOrFolder.parentId)) - ); - - return newTree; - }, [isBetterFolders, originalTree, expandedFolderIds]); + return ( +
+ {originalComponent()} +
+ ); }, makeGuildsBarGuildListFilter(isBetterFolders: boolean) { - return child => { + return (child: any) => { if (!isBetterFolders) { return true; } @@ -343,7 +351,7 @@ export default definePlugin({ }, makeGuildsBarSidebarFilter(isBetterFolders: boolean) { - return child => { + return (child: any) => { if (!isBetterFolders) { return true; } diff --git a/src/plugins/betterFolders/sidebarFix.css b/src/plugins/betterFolders/sidebarFix.css index 7a048eb7..b7c0e66e 100644 --- a/src/plugins/betterFolders/sidebarFix.css +++ b/src/plugins/betterFolders/sidebarFix.css @@ -1,7 +1,8 @@ /* These area names need to be hardcoded. Only betterFoldersSidebar is added by the plugin. */ .visual-refresh .vc-betterFolders-sidebar-grid { - grid-template-columns: [start] min-content [guildsEnd] min-content [sidebarEnd] min-content [channelsEnd] 1fr [end]; /* stylelint-disable-line value-keyword-case */ + /* 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" From b0b616d92a7150fafdc9bc97ba904807a0524035 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sun, 4 May 2025 13:55:52 -0300 Subject: [PATCH 21/34] ClientTheme: Fix startup freeze & clean-up plugin (#3413) Co-authored-by: Vendicated --- src/plugins/clientTheme/README.md | 2 +- src/plugins/clientTheme/clientTheme.css | 14 +- .../clientTheme/components/Settings.tsx | 104 +++++++ src/plugins/clientTheme/index.tsx | 276 +----------------- src/plugins/clientTheme/utils/colorUtils.ts | 65 +++++ src/plugins/clientTheme/utils/styleUtils.ts | 90 ++++++ 6 files changed, 270 insertions(+), 281 deletions(-) create mode 100644 src/plugins/clientTheme/components/Settings.tsx create mode 100644 src/plugins/clientTheme/utils/colorUtils.ts create mode 100644 src/plugins/clientTheme/utils/styleUtils.ts diff --git a/src/plugins/clientTheme/README.md b/src/plugins/clientTheme/README.md index 4b40148c..e2723f8b 100644 --- a/src/plugins/clientTheme/README.md +++ b/src/plugins/clientTheme/README.md @@ -1,4 +1,4 @@ -# Classic Client Theme +# Client Theme Revival of the old client theme experiment (The one that came before the sucky one that we actually got) diff --git a/src/plugins/clientTheme/clientTheme.css b/src/plugins/clientTheme/clientTheme.css index 795b5457..49cc3e15 100644 --- a/src/plugins/clientTheme/clientTheme.css +++ b/src/plugins/clientTheme/clientTheme.css @@ -19,16 +19,8 @@ border: thin solid var(--background-modifier-accent) !important; } -.vc-clientTheme-warning-text { - color: var(--text-danger); -} - -.vc-clientTheme-contrast-warning { - background-color: var(--background-primary); - padding: 0.5rem; - border-radius: .5rem; +.vc-clientTheme-buttons-container { + margin-top: 16px; display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; + gap: 4px; } diff --git a/src/plugins/clientTheme/components/Settings.tsx b/src/plugins/clientTheme/components/Settings.tsx new file mode 100644 index 00000000..f38380fa --- /dev/null +++ b/src/plugins/clientTheme/components/Settings.tsx @@ -0,0 +1,104 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { classNameFactory } from "@api/Styles"; +import { ErrorCard } from "@components/ErrorCard"; +import { Margins } from "@utils/margins"; +import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; +import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common"; + +import { settings } from ".."; +import { relativeLuminance } from "../utils/colorUtils"; +import { createOrUpdateThemeColorVars } from "../utils/styleUtils"; + +const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)"); +const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE', '"system"==='); +const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore"); + +const cl = classNameFactory("vc-clientTheme-"); + +const colorPresets = [ + "#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D", + "#3A483D", "#344242", "#313D4B", "#2D2F47", "#322B42", + "#3C2E42", "#422938", "#b6908f", "#bfa088", "#d3c77d", + "#86ac86", "#88aab3", "#8693b5", "#8a89ba", "#ad94bb", +]; + +function onPickColor(color: number) { + const hexColor = color.toString(16).padStart(6, "0"); + + settings.store.color = hexColor; + createOrUpdateThemeColorVars(hexColor); +} + +function setDiscordTheme(theme: string) { + saveClientTheme({ theme }); +} + +export function ThemeSettingsComponent() { + const currentTheme = useStateFromStores([ThemeStore], () => ThemeStore.theme); + const isLightTheme = currentTheme === "light"; + const oppositeTheme = isLightTheme ? "Dark" : "Light"; + + const nitroThemeEnabled = useStateFromStores([NitroThemeStore], () => NitroThemeStore.gradientPreset != null); + + const selectedLuminance = relativeLuminance(settings.store.color); + + let contrastWarning = false; + let fixableContrast = true; + + if ((isLightTheme && selectedLuminance < 0.26) || !isLightTheme && selectedLuminance > 0.12) { + contrastWarning = true; + } + + if (selectedLuminance < 0.26 && selectedLuminance > 0.12) { + fixableContrast = false; + } + + // Light mode with values greater than 65 leads to background colors getting crushed together and poor text contrast for muted channels + if (isLightTheme && selectedLuminance > 0.65) { + contrastWarning = true; + fixableContrast = false; + } + + return ( +
+
+
+ Theme Color + Add a color to your Discord client theme +
+ +
+ {(contrastWarning || nitroThemeEnabled) && (<> + + Your theme won't look good! + + {contrastWarning && {">"} Selected color won't contrast well with text} + {nitroThemeEnabled && {">"} Nitro themes aren't supported} + +
+ {(contrastWarning && fixableContrast) && } + {(nitroThemeEnabled) && } +
+
+ )} +
+ ); +} + +export function ResetThemeColorComponent() { + return ( + + ); +} diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 2b77d00d..984318e2 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -7,104 +7,21 @@ import "./clientTheme.css"; import { definePluginSettings } from "@api/Settings"; -import { classNameFactory } from "@api/Styles"; import { Devs } from "@utils/constants"; -import { Margins } from "@utils/margins"; -import { classes } from "@utils/misc"; import definePlugin, { OptionType, StartAt } from "@utils/types"; -import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; -import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common"; -const cl = classNameFactory("vc-clientTheme-"); +import { ResetThemeColorComponent, ThemeSettingsComponent } from "./components/Settings"; +import { disableClientTheme, startClientTheme } from "./utils/styleUtils"; -const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)"); - -const colorPresets = [ - "#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D", - "#3A483D", "#344242", "#313D4B", "#2D2F47", "#322B42", - "#3C2E42", "#422938", "#b6908f", "#bfa088", "#d3c77d", - "#86ac86", "#88aab3", "#8693b5", "#8a89ba", "#ad94bb", -]; - -function onPickColor(color: number) { - const hexColor = color.toString(16).padStart(6, "0"); - - settings.store.color = hexColor; - updateColorVars(hexColor); -} - -const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE', '"system"==='); - -function setTheme(theme: string) { - saveClientTheme({ theme }); -} - -const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore"); - -function ThemeSettings() { - const theme = useStateFromStores([ThemeStore], () => ThemeStore.theme); - const isLightTheme = theme === "light"; - const oppositeTheme = isLightTheme ? "dark" : "light"; - - const nitroTheme = useStateFromStores([NitroThemeStore], () => NitroThemeStore.gradientPreset); - const nitroThemeEnabled = nitroTheme !== undefined; - - const selectedLuminance = relativeLuminance(settings.store.color); - - let contrastWarning = false, fixableContrast = true; - if ((isLightTheme && selectedLuminance < 0.26) || !isLightTheme && selectedLuminance > 0.12) - contrastWarning = true; - if (selectedLuminance < 0.26 && selectedLuminance > 0.12) - fixableContrast = false; - // light mode with values greater than 65 leads to background colors getting crushed together and poor text contrast for muted channels - if (isLightTheme && selectedLuminance > 0.65) { - contrastWarning = true; - fixableContrast = false; - } - - return ( -
-
-
- Theme Color - Add a color to your Discord client theme -
- -
- {(contrastWarning || nitroThemeEnabled) && (<> - -
-
- Warning, your theme won't look good: - {contrastWarning && Selected color won't contrast well with text} - {nitroThemeEnabled && Nitro themes aren't supported} -
- {(contrastWarning && fixableContrast) && } - {(nitroThemeEnabled) && } -
- )} -
- ); -} - -const settings = definePluginSettings({ +export const settings = definePluginSettings({ color: { type: OptionType.COMPONENT, default: "313338", - component: ThemeSettings + component: ThemeSettingsComponent }, resetColor: { type: OptionType.COMPONENT, - component: () => ( - - ) + component: ResetThemeColorComponent } }); @@ -115,185 +32,6 @@ export default definePlugin({ settings, startAt: StartAt.DOMContentLoaded, - async start() { - updateColorVars(settings.store.color); - - const styles = await getStyles(); - generateColorOffsets(styles); - generateLightModeFixes(styles); - }, - - stop() { - document.getElementById("clientThemeVars")?.remove(); - document.getElementById("clientThemeOffsets")?.remove(); - document.getElementById("clientThemeLightModeFixes")?.remove(); - } + start: () => startClientTheme(settings.store.color), + stop: disableClientTheme }); - -const visualRefreshVariableRegex = /(--neutral-\d{1,3}-hsl):.*?(\S*)%;/g; -const oldVariableRegex = /(--primary-\d{3}-hsl):.*?(\S*)%;/g; -const lightVariableRegex = /^--primary-[1-5]\d{2}-hsl/g; -const darkVariableRegex = /^--primary-[5-9]\d{2}-hsl/g; - -// generates variables per theme by: -// - matching regex (so we can limit what variables are included in light/dark theme, otherwise text becomes unreadable) -// - offset from specified center (light/dark theme get different offsets because light uses 100 for background-primary, while dark uses 600) -function genThemeSpecificOffsets(variableLightness: Record, regex: RegExp | null, centerVariable: string): string { - return Object.entries(variableLightness).filter(([key]) => regex == null || key.search(regex) > -1) - .map(([key, lightness]) => { - const lightnessOffset = lightness - variableLightness[centerVariable]; - const plusOrMinus = lightnessOffset >= 0 ? "+" : "-"; - return `${key}: var(--theme-h) var(--theme-s) calc(var(--theme-l) ${plusOrMinus} ${Math.abs(lightnessOffset).toFixed(2)}%);`; - }) - .join("\n"); -} - -function generateColorOffsets(styles) { - const oldVariableLightness = {} as Record; - const visualRefreshVariableLightness = {} as Record; - - // Get lightness values of --primary variables - for (const [, variable, lightness] of styles.matchAll(oldVariableRegex)) { - oldVariableLightness[variable] = parseFloat(lightness); - } - - for (const [, variable, lightness] of styles.matchAll(visualRefreshVariableRegex)) { - visualRefreshVariableLightness[variable] = parseFloat(lightness); - } - - createStyleSheet("clientThemeOffsets", [ - `.theme-light {\n ${genThemeSpecificOffsets(oldVariableLightness, lightVariableRegex, "--primary-345-hsl")} \n}`, - `.theme-dark {\n ${genThemeSpecificOffsets(oldVariableLightness, darkVariableRegex, "--primary-600-hsl")} \n}`, - `.visual-refresh.theme-light {\n ${genThemeSpecificOffsets(visualRefreshVariableLightness, null, "--neutral-2-hsl")} \n}`, - `.visual-refresh.theme-dark {\n ${genThemeSpecificOffsets(visualRefreshVariableLightness, null, "--neutral-69-hsl")} \n}`, - ].join("\n\n")); -} - -function generateLightModeFixes(styles: string) { - const groupLightUsesW500Regex = /\.theme-light[^{]*\{[^}]*var\(--white-500\)[^}]*}/gm; - // get light capturing groups that mention --white-500 - const relevantStyles = [...styles.matchAll(groupLightUsesW500Regex)].flat(); - - const groupBackgroundRegex = /^([^{]*)\{background:var\(--white-500\)/m; - const groupBackgroundColorRegex = /^([^{]*)\{background-color:var\(--white-500\)/m; - // find all capturing groups that assign background or background-color directly to w500 - const backgroundGroups = mapReject(relevantStyles, entry => captureOne(entry, groupBackgroundRegex)).join(",\n"); - const backgroundColorGroups = mapReject(relevantStyles, entry => captureOne(entry, groupBackgroundColorRegex)).join(",\n"); - // create css to reassign them to --primary-100 - const reassignBackgrounds = `${backgroundGroups} {\n background: var(--primary-100) \n}`; - const reassignBackgroundColors = `${backgroundColorGroups} {\n background-color: var(--primary-100) \n}`; - - const groupBgVarRegex = /\.theme-light\{([^}]*--[^:}]*(?:background|bg)[^:}]*:var\(--white-500\)[^}]*)\}/m; - const bgVarRegex = /^(--[^:]*(?:background|bg)[^:]*):var\(--white-500\)/m; - // get all global variables used for backgrounds - const lightVars = mapReject(relevantStyles, style => captureOne(style, groupBgVarRegex)) // get the insides of capture groups that have at least one background var with w500 - .map(str => str.split(";")).flat(); // captureGroupInsides[] -> cssRule[] - const lightBgVars = mapReject(lightVars, variable => captureOne(variable, bgVarRegex)); // remove vars that aren't for backgrounds or w500 - // create css to reassign every var - const reassignVariables = `.theme-light {\n ${lightBgVars.map(variable => `${variable}: var(--primary-100);`).join("\n")} \n}`; - - createStyleSheet("clientThemeLightModeFixes", [ - reassignBackgrounds, - reassignBackgroundColors, - reassignVariables, - ].join("\n\n")); -} - -function captureOne(str, regex) { - const result = str.match(regex); - return (result === null) ? null : result[1]; -} - -function mapReject(arr, mapFunc) { - return arr.map(mapFunc).filter(Boolean); -} - -function updateColorVars(color: string) { - const { hue, saturation, lightness } = hexToHSL(color); - - let style = document.getElementById("clientThemeVars"); - if (!style) - style = createStyleSheet("clientThemeVars"); - - style.textContent = `:root { - --theme-h: ${hue}; - --theme-s: ${saturation}%; - --theme-l: ${lightness}%; - }`; -} - -function createStyleSheet(id, content = "") { - const style = document.createElement("style"); - style.setAttribute("id", id); - style.textContent = content.split("\n").map(line => line.trim()).join("\n"); - document.body.appendChild(style); - return style; -} - -// returns all of discord's native styles in a single string -async function getStyles(): Promise { - let out = ""; - const styleLinkNodes = document.querySelectorAll('link[rel="stylesheet"]'); - for (const styleLinkNode of styleLinkNodes) { - const cssLink = styleLinkNode.getAttribute("href"); - if (!cssLink) continue; - - const res = await fetch(cssLink); - out += await res.text(); - } - return out; -} - -// https://css-tricks.com/converting-color-spaces-in-javascript/ -function hexToHSL(hexCode: string) { - // Hex => RGB normalized to 0-1 - const r = parseInt(hexCode.substring(0, 2), 16) / 255; - const g = parseInt(hexCode.substring(2, 4), 16) / 255; - const b = parseInt(hexCode.substring(4, 6), 16) / 255; - - // RGB => HSL - const cMax = Math.max(r, g, b); - const cMin = Math.min(r, g, b); - const delta = cMax - cMin; - - let hue: number, saturation: number, lightness: number; - - lightness = (cMax + cMin) / 2; - - if (delta === 0) { - // If r=g=b then the only thing that matters is lightness - hue = 0; - saturation = 0; - } else { - // Magic - saturation = delta / (1 - Math.abs(2 * lightness - 1)); - - if (cMax === r) - hue = ((g - b) / delta) % 6; - else if (cMax === g) - hue = (b - r) / delta + 2; - else - hue = (r - g) / delta + 4; - hue *= 60; - if (hue < 0) - hue += 360; - } - - // Move saturation and lightness from 0-1 to 0-100 - saturation *= 100; - lightness *= 100; - - return { hue, saturation, lightness }; -} - -// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance -function relativeLuminance(hexCode: string) { - const normalize = (x: number) => - x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4; - - const r = normalize(parseInt(hexCode.substring(0, 2), 16) / 255); - const g = normalize(parseInt(hexCode.substring(2, 4), 16) / 255); - const b = normalize(parseInt(hexCode.substring(4, 6), 16) / 255); - - return r * 0.2126 + g * 0.7152 + b * 0.0722; -} diff --git a/src/plugins/clientTheme/utils/colorUtils.ts b/src/plugins/clientTheme/utils/colorUtils.ts new file mode 100644 index 00000000..88d94714 --- /dev/null +++ b/src/plugins/clientTheme/utils/colorUtils.ts @@ -0,0 +1,65 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +// https://css-tricks.com/converting-color-spaces-in-javascript/ +export function hexToHSL(hexCode: string) { + // Hex => RGB normalized to 0-1 + const r = parseInt(hexCode.substring(0, 2), 16) / 255; + const g = parseInt(hexCode.substring(2, 4), 16) / 255; + const b = parseInt(hexCode.substring(4, 6), 16) / 255; + + // RGB => HSL + const cMax = Math.max(r, g, b); + const cMin = Math.min(r, g, b); + const delta = cMax - cMin; + + let hue: number; + let saturation: number; + let lightness: number; + + lightness = (cMax + cMin) / 2; + + if (delta === 0) { + // If r=g=b then the only thing that matters is lightness + hue = 0; + saturation = 0; + } else { + // Magic + saturation = delta / (1 - Math.abs(2 * lightness - 1)); + + if (cMax === r) { + hue = ((g - b) / delta) % 6; + } else if (cMax === g) { + hue = (b - r) / delta + 2; + } else { + hue = (r - g) / delta + 4; + } + + hue *= 60; + if (hue < 0) { + hue += 360; + } + } + + // Move saturation and lightness from 0-1 to 0-100 + saturation *= 100; + lightness *= 100; + + return { hue, saturation, lightness }; +} + +// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance +export function relativeLuminance(hexCode: string) { + const normalize = (x: number) => ( + x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4 + ); + + const r = normalize(parseInt(hexCode.substring(0, 2), 16) / 255); + const g = normalize(parseInt(hexCode.substring(2, 4), 16) / 255); + const b = normalize(parseInt(hexCode.substring(4, 6), 16) / 255); + + return r * 0.2126 + g * 0.7152 + b * 0.0722; +} diff --git a/src/plugins/clientTheme/utils/styleUtils.ts b/src/plugins/clientTheme/utils/styleUtils.ts new file mode 100644 index 00000000..bc6169d4 --- /dev/null +++ b/src/plugins/clientTheme/utils/styleUtils.ts @@ -0,0 +1,90 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { hexToHSL } from "./colorUtils"; + +const VARS_STYLE_ID = "vc-clientTheme-vars"; +const OVERRIDES_STYLE_ID = "vc-clientTheme-overrides"; + +export function createOrUpdateThemeColorVars(color: string) { + const { hue, saturation, lightness } = hexToHSL(color); + + createOrUpdateStyle(VARS_STYLE_ID, `:root { + --theme-h: ${hue}; + --theme-s: ${saturation}%; + --theme-l: ${lightness}%; + }`); +} + +export async function startClientTheme(color: string) { + createOrUpdateThemeColorVars(color); + createColorsOverrides(await getDiscordStyles()); +} + +export function disableClientTheme() { + document.getElementById(VARS_STYLE_ID)?.remove(); + document.getElementById(OVERRIDES_STYLE_ID)?.remove(); +} + +function getOrCreateStyle(styleId: string) { + const existingStyle = document.getElementById(styleId); + if (existingStyle) { + return existingStyle as HTMLStyleElement; + } + + const newStyle = document.createElement("style"); + newStyle.id = styleId; + + return document.head.appendChild(newStyle); +} + +function createOrUpdateStyle(styleId: string, css: string) { + const style = getOrCreateStyle(styleId); + style.textContent = css; +} + +/** + * @returns A string containing all the CSS styles from the Discord client. + */ +async function getDiscordStyles(): Promise { + const styleLinkNodes = document.querySelectorAll('link[rel="stylesheet"]'); + + const cssTexts = await Promise.all(Array.from(styleLinkNodes, async node => { + if (!node.href) + return null; + + return fetch(node.href).then(res => res.text()); + })); + + return cssTexts.filter(Boolean).join("\n"); +} + +const VISUAL_REFRESH_COLORS_VARIABLES_REGEX = /(--neutral-\d{1,3}?-hsl):.+?([\d.]+?)%;/g; + +function createColorsOverrides(styles: string) { + const visualRefreshColorsLightness = {} as Record; + + for (const [, colorVariableName, lightness] of styles.matchAll(VISUAL_REFRESH_COLORS_VARIABLES_REGEX)) { + visualRefreshColorsLightness[colorVariableName] = parseFloat(lightness); + } + + const lightThemeBaseLightness = visualRefreshColorsLightness["--neutral-2-hsl"]; + 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}`, + ].join("\n\n")); +} + +function generateNewColorVars(colorsLightess: Record, baseLightness: number) { + return Object.entries(colorsLightess).map(([colorVariableName, lightness]) => { + const lightnessOffset = lightness - baseLightness; + const plusOrMinus = lightnessOffset >= 0 ? "+" : "-"; + + return `${colorVariableName}: var(--theme-h) var(--theme-s) calc(var(--theme-l) ${plusOrMinus} ${Math.abs(lightnessOffset).toFixed(2)}%);`; + }).join("\n"); +} From 0a2d4a2ab2da38d2a1d91c838b1db31c392a870c Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sun, 4 May 2025 16:25:13 -0300 Subject: [PATCH 22/34] Fix plugins for new V2 User Profile Modal --- src/plugins/friendsSince/index.tsx | 10 +++++++++- src/plugins/mutualGroupDMs/index.tsx | 29 ++++++++++++++++++++++++++-- src/plugins/mutualGroupDMs/style.css | 6 +++++- src/plugins/userVoiceShow/index.tsx | 2 +- src/plugins/viewIcons/index.tsx | 2 +- src/utils/modal.tsx | 2 +- 6 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/plugins/friendsSince/index.tsx b/src/plugins/friendsSince/index.tsx index 07723bcc..b59c8a62 100644 --- a/src/plugins/friendsSince/index.tsx +++ b/src/plugins/friendsSince/index.tsx @@ -32,7 +32,15 @@ export default definePlugin({ }, // User Profile Modal { - find: "action:\"PRESS_APP_CONNECTION\"", + find: ".connections,userId:", + replacement: { + match: /#{intl::USER_PROFILE_MEMBER_SINCE}\),.{0,100}userId:(\i\.id),.{0,100}}\)}\),/, + replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:false})," + } + }, + // User Profile Modal v2 + { + find: ".MODAL_V2,onClose:", replacement: { match: /#{intl::USER_PROFILE_MEMBER_SINCE}\),.{0,100}userId:(\i\.id),.{0,100}}\)}\),/, replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:false})," diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 858a366c..94811ea7 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -87,6 +87,7 @@ export default definePlugin({ authors: [Devs.amia], patches: [ + // User Profile Modal { find: ".BOT_DATA_ACCESS?(", replacement: [ @@ -102,7 +103,31 @@ export default definePlugin({ // set the gap to zero to ensure ours stays on screen { match: /className:\i\.tabBar/, - replace: "$& + ' vc-mutual-gdms-tab-bar'" + replace: '$& + " vc-mutual-gdms-modal-tab-bar"' + } + ] + }, + // User Profile Modal v2 + { + find: ".tabBarPanel,children:", + replacement: [ + { + match: /items:(\i),.+?(?=return\(0,\i\.jsxs?\)\("div)/, + replace: "$&$self.pushSection($1,arguments[0].user);" + }, + { + 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. + { + match: /className:\i\.tabBar/, + replace: '$& + " vc-mutual-gdms-modal-v2-tab-bar"' + }, + // Make the tab bar item text smaller so our tab can fit. + { + match: /(\.tabBarItem.+?variant:)"heading-lg\/medium"/, + replace: '$1"heading-md/medium"' } ] }, @@ -138,8 +163,8 @@ export default definePlugin({ sections[IS_PATCHED] = true; sections.push({ + text: getMutualGDMCountText(user), section: "MUTUAL_GDMS", - text: getMutualGDMCountText(user) }); } catch (e) { new Logger("MutualGroupDMs").error("Failed to push mutual group dms section:", e); diff --git a/src/plugins/mutualGroupDMs/style.css b/src/plugins/mutualGroupDMs/style.css index 3d06568f..698e21de 100644 --- a/src/plugins/mutualGroupDMs/style.css +++ b/src/plugins/mutualGroupDMs/style.css @@ -1,3 +1,7 @@ -.vc-mutual-gdms-tab-bar { +.vc-mutual-gdms-modal-tab-bar { gap: 0; } + +.vc-mutual-gdms-modal-v2-tab-bar { + gap: 6px; +} diff --git a/src/plugins/userVoiceShow/index.tsx b/src/plugins/userVoiceShow/index.tsx index 3d119c43..0ee41414 100644 --- a/src/plugins/userVoiceShow/index.tsx +++ b/src/plugins/userVoiceShow/index.tsx @@ -55,7 +55,7 @@ export default definePlugin({ settings, patches: [ - // User Popout, Full Size Profile, Direct Messages Side Profile + // User Popout, User Profile Modal, Direct Messages Side Profile { find: "#{intl::USER_PROFILE_LOAD_ERROR}", replacement: { diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index afd9d48c..07630a00 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -190,7 +190,7 @@ export default definePlugin({ }, patches: [ - // Avatar component used in User DMs "User Profile" popup in the right and Profiles Modal pfp + // Avatar component used in User DMs "User Profile" popup in the right and User Profile Modal pfp { find: ".overlay:void 0,status:", replacement: [ diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index eebdb95e..17bf3987 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -140,7 +140,7 @@ export type MediaModalProps = { shouldHideMediaOptions?: boolean; }; -// modal key: "Media Viewer Modal" +// Modal key: "Media Viewer Modal" export const openMediaModal: (props: MediaModalProps) => void = findByCodeLazy("hasMediaOptions", "shouldHideMediaOptions"); interface ModalAPI { From 133e92442549308cc4b9fff728aa57a022216e1d Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sun, 4 May 2025 17:05:48 -0300 Subject: [PATCH 23/34] BetterFolders: Add className to Sidebar component --- src/plugins/betterFolders/FolderSideBar.tsx | 10 ++++------ src/plugins/betterFolders/index.tsx | 2 +- .../betterFolders/{sidebarFix.css => style.css} | 5 ++++- 3 files changed, 9 insertions(+), 8 deletions(-) rename src/plugins/betterFolders/{sidebarFix.css => style.css} (88%) diff --git a/src/plugins/betterFolders/FolderSideBar.tsx b/src/plugins/betterFolders/FolderSideBar.tsx index cfd15517..40329122 100644 --- a/src/plugins/betterFolders/FolderSideBar.tsx +++ b/src/plugins/betterFolders/FolderSideBar.tsx @@ -43,15 +43,13 @@ export default ErrorBoundary.wrap(guildsBarProps => { // 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. // Also display flex otherwise to fix scrolling. - // gridArea is needed to align properly with the base app grid. - const barStyle = { - display: isFullscreen ? "none" : "flex", - gridArea: "betterFoldersSidebar" + const sidebarStyle = { + display: isFullscreen ? "none" : "flex" } satisfies CSSProperties; if (!guilds || !settings.store.sidebarAnim) { return visible - ?
{Sidebar}
+ ?
{Sidebar}
: null; } @@ -65,7 +63,7 @@ export default ErrorBoundary.wrap(guildsBarProps => { > {(animationStyle: any, show: any) => show && ( - + {Sidebar} ) diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index ca46f843..3dc812a4 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import "./sidebarFix.css"; +import "./style.css"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; diff --git a/src/plugins/betterFolders/sidebarFix.css b/src/plugins/betterFolders/style.css similarity index 88% rename from src/plugins/betterFolders/sidebarFix.css rename to src/plugins/betterFolders/style.css index b7c0e66e..a3c82dcb 100644 --- a/src/plugins/betterFolders/sidebarFix.css +++ b/src/plugins/betterFolders/style.css @@ -1,5 +1,8 @@ -/* These area names need to be hardcoded. Only betterFoldersSidebar is added by the plugin. */ +.vc-betterFolders-sidebar { + grid-area: betterFoldersSidebar +} +/* These area names need to be hardcoded. Only betterFoldersSidebar is added by the plugin. */ .visual-refresh .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]; From 73f68fe0b776e1e5738930478ac60bc1e01a1ae6 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sun, 4 May 2025 22:03:42 -0300 Subject: [PATCH 24/34] MutualGroupDMs: Fix overlap in V2 User Profile Modal --- src/plugins/mutualGroupDMs/index.tsx | 2 +- src/plugins/mutualGroupDMs/style.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 94811ea7..d88e1ee0 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -127,7 +127,7 @@ export default definePlugin({ // Make the tab bar item text smaller so our tab can fit. { match: /(\.tabBarItem.+?variant:)"heading-lg\/medium"/, - replace: '$1"heading-md/medium"' + replace: '$1"heading-sm/medium"' } ] }, diff --git a/src/plugins/mutualGroupDMs/style.css b/src/plugins/mutualGroupDMs/style.css index 698e21de..f0ad3c60 100644 --- a/src/plugins/mutualGroupDMs/style.css +++ b/src/plugins/mutualGroupDMs/style.css @@ -3,5 +3,5 @@ } .vc-mutual-gdms-modal-v2-tab-bar { - gap: 6px; + gap: 12px; } From 9a3c66abfda58cf868f5741f9954782dd74d98d5 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 5 May 2025 03:42:38 +0200 Subject: [PATCH 25/34] Remove NSFWGateBypass This plugin was always meant as a tool for adults who don't want to give Discord their birthday/ID. But obviously there's nothing stopping minors from using this which is troublesome legally and possibly could lead to me being legally liable for distributing it. Bypassing it is also becoming progressively harder with the changes Discord is making to abide with UK and Australia law. As such, I no longer feel comfortable providing this plugin. If you rely on this plugin (as an adult), find a suitable third party replacement from elsewhere. --- src/plugins/nsfwGateBypass/index.ts | 41 ----------------------------- 1 file changed, 41 deletions(-) delete mode 100644 src/plugins/nsfwGateBypass/index.ts diff --git a/src/plugins/nsfwGateBypass/index.ts b/src/plugins/nsfwGateBypass/index.ts deleted file mode 100644 index 6d0cb702..00000000 --- a/src/plugins/nsfwGateBypass/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -export default definePlugin({ - name: "NSFWGateBypass", - description: "Allows you to access NSFW channels without setting/verifying your age", - authors: [Devs.Commandtechno], - patches: [ - { - find: ".nsfwAllowed=null", - replacement: [ - { - match: /(?<=\.nsfwAllowed=)null!=.+?(?=[,;])/, - replace: "true", - }, - { - match: /(?<=\.ageVerificationStatus=)null!=.+?(?=[,;])/, - replace: "3", // VERIFIED_ADULT - } - ], - } - ], -}); From 89ef26e719ce3d257737b58e4f5e50052474ed74 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 5 May 2025 19:39:34 -0300 Subject: [PATCH 26/34] Fix ImageZoom, Vencord Notifications & ReactErrorDecoder --- src/api/Notifications/Notifications.tsx | 4 ++-- src/plugins/consoleShortcuts/index.ts | 2 +- src/plugins/imageZoom/index.tsx | 4 ++-- src/plugins/reactErrorDecoder/index.ts | 15 +++++++-------- src/webpack/common/react.ts | 6 ++++-- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/api/Notifications/Notifications.tsx b/src/api/Notifications/Notifications.tsx index 1350e5bd..96a3425b 100644 --- a/src/api/Notifications/Notifications.tsx +++ b/src/api/Notifications/Notifications.tsx @@ -18,7 +18,7 @@ import { Settings } from "@api/Settings"; import { Queue } from "@utils/Queue"; -import { ReactDOM } from "@webpack/common"; +import { createRoot } from "@webpack/common"; import type { ReactNode } from "react"; import type { Root } from "react-dom/client"; @@ -35,7 +35,7 @@ function getRoot() { const container = document.createElement("div"); container.id = "vc-notification-container"; document.body.append(container); - reactRoot = ReactDOM.createRoot(container); + reactRoot = createRoot(container); } return reactRoot; } diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index 49e67158..e8150f8f 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -136,7 +136,7 @@ function makeShortcuts() { }); } - const root = Common.ReactDOM.createRoot(doc.body.appendChild(document.createElement("div"))); + const root = Common.createRoot(doc.body.appendChild(document.createElement("div"))); root.render(Common.React.createElement(component, props)); doc.addEventListener("close", () => root.unmount(), { once: true }); diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx index d7e8d487..56e1d4a2 100644 --- a/src/plugins/imageZoom/index.tsx +++ b/src/plugins/imageZoom/index.tsx @@ -23,7 +23,7 @@ import { debounce } from "@shared/debounce"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; -import { Menu, ReactDOM } from "@webpack/common"; +import { createRoot, Menu } from "@webpack/common"; import { JSX } from "react"; import type { Root } from "react-dom/client"; @@ -242,7 +242,7 @@ export default definePlugin({ if (instance.props.id === ELEMENT_ID) { if (!this.currentMagnifierElement) { this.currentMagnifierElement = ; - this.root = ReactDOM.createRoot(this.element!); + this.root = createRoot(this.element!); this.root.render(this.currentMagnifierElement); } } diff --git a/src/plugins/reactErrorDecoder/index.ts b/src/plugins/reactErrorDecoder/index.ts index 9e2e5dc5..45ab297a 100644 --- a/src/plugins/reactErrorDecoder/index.ts +++ b/src/plugins/reactErrorDecoder/index.ts @@ -20,7 +20,7 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { React } from "@webpack/common"; -let ERROR_CODES: any; +let ERROR_CODES: Record | undefined; export default definePlugin({ name: "ReactErrorDecoder", @@ -28,13 +28,12 @@ export default definePlugin({ authors: [Devs.Cyn, Devs.maisymoe], patches: [ { - find: '"https://reactjs.org/docs/error-decoder.html?invariant="', + find: "React has blocked a javascript: URL as a security precaution.", replacement: { - match: /(function .\(.\)){(for\(var .="https:\/\/reactjs\.org\/docs\/error-decoder\.html\?invariant="\+.,.=1;. - `${func}{var decoded=$self.decodeError.apply(null, arguments);if(decoded)return decoded;${original}}`, - }, - }, + match: /"https:\/\/react.dev\/errors\/"\+\i;/, + replace: "$&const vcDecodedError=$self.decodeError(...arguments);if(vcDecodedError)return vcDecodedError;" + } + } ], async start() { @@ -56,5 +55,5 @@ export default definePlugin({ index++; return arg; }); - }, + } }); diff --git a/src/webpack/common/react.ts b/src/webpack/common/react.ts index aa279d65..89b19506 100644 --- a/src/webpack/common/react.ts +++ b/src/webpack/common/react.ts @@ -17,7 +17,7 @@ */ // eslint-disable-next-line path-alias/no-relative -import { findByPropsLazy, waitFor } from "../webpack"; +import { findByCodeLazy, findByPropsLazy, waitFor } from "../webpack"; export let React: typeof import("react"); export let useState: typeof React.useState; @@ -28,7 +28,9 @@ export let useRef: typeof React.useRef; export let useReducer: typeof React.useReducer; export let useCallback: typeof React.useCallback; -export const ReactDOM: typeof import("react-dom") & typeof import("react-dom/client") = findByPropsLazy("createPortal"); +export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPortal"); +// 299 is an error code used in createRoot and createPortal +export const createRoot: typeof import("react-dom/client").createRoot = findByCodeLazy("(299));", ".onRecoverableError"); waitFor("useState", m => { React = m; From 97af9f8d98aa0ad9c210b9314214d931efd44ff1 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 6 May 2025 18:16:15 -0300 Subject: [PATCH 27/34] NoUnblockToJump: Fix for latest changes --- src/plugins/noUnblockToJump/index.ts | 46 +++------------------------- 1 file changed, 5 insertions(+), 41 deletions(-) diff --git a/src/plugins/noUnblockToJump/index.ts b/src/plugins/noUnblockToJump/index.ts index 4dbbe462..cb379bf8 100644 --- a/src/plugins/noUnblockToJump/index.ts +++ b/src/plugins/noUnblockToJump/index.ts @@ -19,53 +19,17 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; - export default definePlugin({ name: "NoUnblockToJump", description: "Allows you to jump to messages of blocked users without unblocking them", authors: [Devs.dzshn], patches: [ { - // Clicking on search results to jump - find: '.id,"Search Results"', - replacement: [ - { - match: /if\(.{1,40}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/, - replace: "if(false)$1" - }, - { - match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNIGNORE_TO_JUMP_TITLE})/, - replace: "if(false)$1" - }, - ] - }, - { - // Jump buttton in top right corner of messages - find: "renderJumpButton()", - replacement: [ - { - match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/, - replace: "if(false)$1" - }, - { - match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNIGNORE_TO_JUMP_TITLE})/, - replace: "if(false)$1" - }, - ] - }, - { - // Clicking on replied messages to jump - find: '("interactionUsernameProfile', - replacement: [ - { - match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/, - replace: "false?$1" - }, - { - match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNIGNORE_TO_JUMP_TITLE})/, - replace: "false?$1" - }, - ] + find: "#{intl::UNIGNORE_TO_JUMP_BODY}", + replacement: { + match: /return \i\.\i\.isBlockedForMessage\(/, + replace: "return true;$&" + } } ] }); From a8b2b0ca64d22d48f76538d709af5226c41a324a Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 7 May 2025 09:41:45 -0300 Subject: [PATCH 28/34] AlwaysAnimate: Fix animating status emojis --- src/plugins/alwaysAnimate/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/alwaysAnimate/index.ts b/src/plugins/alwaysAnimate/index.ts index 8edae08b..4064de9f 100644 --- a/src/plugins/alwaysAnimate/index.ts +++ b/src/plugins/alwaysAnimate/index.ts @@ -43,7 +43,7 @@ export default definePlugin({ // Status emojis find: "#{intl::GUILD_OWNER}),children:", replacement: { - match: /(\.CUSTOM_STATUS.+?animate:)\i/, + match: /(\.CUSTOM_STATUS.+?animateEmoji:)\i/, replace: "$1!0" } }, From 690574376bba7c5848b8ebf076be31d7ebf16037 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 9 May 2025 02:19:02 +0200 Subject: [PATCH 29/34] update to latest React Devtools --- src/main/utils/extensions.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/utils/extensions.ts b/src/main/utils/extensions.ts index 8a211baf..1323bd37 100644 --- a/src/main/utils/extensions.ts +++ b/src/main/utils/extensions.ts @@ -67,13 +67,7 @@ export async function installExt(id: string) { try { await access(extDir, fsConstants.F_OK); } catch (err) { - const url = id === "fmkadmapgofadopljbjfkapdkoienihi" - // React Devtools v4.25 - // v4.27 is broken in Electron, see https://github.com/facebook/react/issues/25843 - // Unfortunately, Google does not serve old versions, so this is the only way - // This zip file is pinned to long commit hash so it cannot be changed remotely - ? "https://raw.githubusercontent.com/Vendicated/random-files/f6f550e4c58ac5f2012095a130406c2ab25b984d/fmkadmapgofadopljbjfkapdkoienihi.zip" - : `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=${process.versions.chrome}`; + const url = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=${process.versions.chrome}`; const buf = await get(url, { headers: { From 92ff9c1ca4dfb787178cdfdd1d4178ac113b9188 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 12 May 2025 20:38:58 -0300 Subject: [PATCH 30/34] Fix IrcColors for gradient roles and other patches --- src/plugins/consoleJanitor/index.tsx | 2 +- src/plugins/ircColors/index.ts | 14 ++++++++++++-- src/plugins/mutualGroupDMs/index.tsx | 4 ++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/plugins/consoleJanitor/index.tsx b/src/plugins/consoleJanitor/index.tsx index d32f525e..7ef7ec9c 100644 --- a/src/plugins/consoleJanitor/index.tsx +++ b/src/plugins/consoleJanitor/index.tsx @@ -197,7 +197,7 @@ export default definePlugin({ }, // Patches Discord generic logger function { - find: "Σ:", + find: '"file-only"!==', predicate: () => settings.store.disableLoggers, replacement: { match: /(?<=&&)(?=console)/, diff --git a/src/plugins/ircColors/index.ts b/src/plugins/ircColors/index.ts index d4f453a7..d543c995 100644 --- a/src/plugins/ircColors/index.ts +++ b/src/plugins/ircColors/index.ts @@ -66,8 +66,10 @@ export default definePlugin({ { find: '="SYSTEM_TAG"', replacement: { - match: /(?<=\.username.{0,50}?)style:/, - replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},_style:" + // Override colorString with our custom color and disable gradients if applying the custom color. + match: /&&null!=\i\.secondaryColor,(?<=colorString:(\i).+?(\i)=.+?)/, + replace: (m, colorString, hasGradientColors) => `${m}` + + `vcIrcColorsDummy=[${colorString},${hasGradientColors}]=$self.getMessageColorsVariables(arguments[0],${hasGradientColors}),` } }, { @@ -80,6 +82,13 @@ export default definePlugin({ } ], + getMessageColorsVariables(context: any, hasGradientColors: boolean) { + const colorString = this.calculateNameColorForMessageContext(context); + const originalColorString = context?.author?.colorString; + + return [colorString, hasGradientColors && colorString === originalColorString]; + }, + calculateNameColorForMessageContext(context: any) { const userId: string | undefined = context?.message?.author?.id; const colorString = context?.author?.colorString; @@ -97,6 +106,7 @@ export default definePlugin({ ? color : colorString; }, + calculateNameColorForListContext(context: any) { const id = context?.user?.id; const colorString = context?.colorString; diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index d88e1ee0..e46824b4 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -126,8 +126,8 @@ export default definePlugin({ }, // Make the tab bar item text smaller so our tab can fit. { - match: /(\.tabBarItem.+?variant:)"heading-lg\/medium"/, - replace: '$1"heading-sm/medium"' + match: /(\.tabBarItem.+?variant:)"heading-md\/normal"/, + replace: '$1"heading-sm/normal"' } ] }, From e69575f2735363e884fb73b8a682f2b5de24241e Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 13 May 2025 21:15:03 +0200 Subject: [PATCH 31/34] fix canary --- src/webpack/patchWebpack.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 9b66a5b4..a542cb9b 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -412,9 +412,12 @@ function runFactoryWithWrap(patchedFactory: PatchedModuleFactory, thisArg: unkno exports = module.exports; if (typeof require === "function" && require.c) { - if (_blacklistBadModules(require.c, exports, module.id)) { - return factoryReturn; - } + // module might not have been fully initialised yet + try { + if (_blacklistBadModules(require.c, exports, module.id)) { + return factoryReturn; + } + } catch { } } if (exports == null) { From d5420959938865aa9cf81ab7abc7a7d49a31ecd5 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 13 May 2025 21:34:11 +0200 Subject: [PATCH 32/34] fix ContextMenus on canary --- src/webpack/common/menu.ts | 11 +++++++---- src/webpack/patchWebpack.ts | 14 ++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts index 9896b3a2..e77b5456 100644 --- a/src/webpack/common/menu.ts +++ b/src/webpack/common/menu.ts @@ -27,10 +27,13 @@ waitFor(m => m.name === "MenuCheckboxItem", (_, id) => { // we have to do this manual require by ID because m is in this case the MenuCheckBoxItem instead of the entire module const module = wreq(id); - for (const e of Object.values(module)) { - if (typeof e === "function" && e.name.startsWith("Menu")) { - Menu[e.name] = e; - } + for (const key in module) { + try { + const e = module[key]; + if (typeof e === "function" && e.name.startsWith("Menu")) { + Menu[e.name] = e; + } + } catch { } } }); diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index a542cb9b..3186b503 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -445,13 +445,15 @@ function runFactoryWithWrap(patchedFactory: PatchedModuleFactory, thisArg: unkno } for (const exportKey in exports) { - const exportValue = exports[exportKey]; + try { + const exportValue = exports[exportKey]; - if (exportValue != null && filter(exportValue)) { - waitForSubscriptions.delete(filter); - callback(exportValue, module.id); - break; - } + if (exportValue != null && filter(exportValue)) { + waitForSubscriptions.delete(filter); + callback(exportValue, module.id); + break; + } + } catch { } } } catch (err) { logger.error("Error while firing callback for Webpack waitFor subscription:\n", err, filter, callback); From 8c7225d106674d934acdd897eda362ef9d28baf1 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 13 May 2025 17:09:57 -0300 Subject: [PATCH 33/34] Clean up circular imports fixes --- src/webpack/common/menu.ts | 20 ++++++++++++-------- src/webpack/patchWebpack.ts | 26 +++++++++++++------------- src/webpack/webpack.ts | 9 ++++++++- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts index e77b5456..5b1056dd 100644 --- a/src/webpack/common/menu.ts +++ b/src/webpack/common/menu.ts @@ -24,16 +24,20 @@ export const Menu = {} as t.Menu; // Relies on .name properties added by the MenuItemDemanglerAPI waitFor(m => m.name === "MenuCheckboxItem", (_, id) => { - // we have to do this manual require by ID because m is in this case the MenuCheckBoxItem instead of the entire module - const module = wreq(id); + // We have to do this manual require by ID because m in this case is the MenuCheckBoxItem instead of the entire module + const exports = wreq(id); - for (const key in module) { + for (const exportKey in exports) { + // Some exports might have not been initialized yet due to circular imports, so try catch it. try { - const e = module[key]; - if (typeof e === "function" && e.name.startsWith("Menu")) { - Menu[e.name] = e; - } - } catch { } + var exportValue = exports[exportKey]; + } catch { + continue; + } + + if (typeof exportValue === "function" && exportValue.name.startsWith("Menu")) { + Menu[exportValue.name] = exportValue; + } } }); diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 3186b503..4f5899bc 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -412,12 +412,9 @@ function runFactoryWithWrap(patchedFactory: PatchedModuleFactory, thisArg: unkno exports = module.exports; if (typeof require === "function" && require.c) { - // module might not have been fully initialised yet - try { - if (_blacklistBadModules(require.c, exports, module.id)) { - return factoryReturn; - } - } catch { } + if (_blacklistBadModules(require.c, exports, module.id)) { + return factoryReturn; + } } if (exports == null) { @@ -445,15 +442,18 @@ function runFactoryWithWrap(patchedFactory: PatchedModuleFactory, thisArg: unkno } for (const exportKey in exports) { + // Some exports might have not been initialized yet due to circular imports, so try catch it. try { - const exportValue = exports[exportKey]; + var exportValue = exports[exportKey]; + } catch { + continue; + } - if (exportValue != null && filter(exportValue)) { - waitForSubscriptions.delete(filter); - callback(exportValue, module.id); - break; - } - } catch { } + if (exportValue != null && filter(exportValue)) { + waitForSubscriptions.delete(filter); + callback(exportValue, module.id); + break; + } } } catch (err) { logger.error("Error while firing callback for Webpack waitFor subscription:\n", err, filter, callback); diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 0e3d641b..c1847474 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -156,7 +156,14 @@ export function _blacklistBadModules(requireCache: NonNullable Date: Tue, 13 May 2025 22:39:56 +0200 Subject: [PATCH 34/34] updater: periodically check for updates while open --- src/Vencord.ts | 71 +++++++++++++++++++------------ src/plugins/_api/badges/index.tsx | 16 ++++++- src/utils/updater.ts | 11 +++-- 3 files changed, 65 insertions(+), 33 deletions(-) diff --git a/src/Vencord.ts b/src/Vencord.ts index 63508eb0..48ecce97 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -33,7 +33,7 @@ import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; import { StartAt } from "@utils/types"; import { get as dsGet } from "./api/DataStore"; -import { showNotification } from "./api/Notifications"; +import { NotificationData, showNotification } from "./api/Notifications"; import { PlainSettings, Settings } from "./api/Settings"; import { patches, PMLogger, startAllPlugins } from "./plugins"; import { localStorage } from "./utils/localStorage"; @@ -86,6 +86,46 @@ async function syncSettings() { } } +let notifiedForUpdatesThisSession = false; + +async function runUpdateCheck() { + const notify = (data: NotificationData) => { + if (notifiedForUpdatesThisSession) return; + notifiedForUpdatesThisSession = true; + + setTimeout(() => showNotification({ + permanent: true, + noPersist: true, + ...data + }), 10_000); + }; + + try { + const isOutdated = await checkForUpdates(); + if (!isOutdated) return; + + if (Settings.autoUpdate) { + await update(); + if (Settings.autoUpdateNotification) { + notify({ + title: "Vencord has been updated!", + body: "Click here to restart", + onClick: relaunch + }); + } + return; + } + + notify({ + title: "A Vencord update is available!", + body: "Click here to view the update", + onClick: openUpdaterModal! + }); + } catch (err) { + UpdateLogger.error("Failed to check for updates", err); + } +} + async function init() { await onceReady; startAllPlugins(StartAt.WebpackReady); @@ -93,33 +133,8 @@ async function init() { syncSettings(); if (!IS_WEB && !IS_UPDATER_DISABLED) { - try { - const isOutdated = await checkForUpdates(); - if (!isOutdated) return; - - if (Settings.autoUpdate) { - await update(); - if (Settings.autoUpdateNotification) - setTimeout(() => showNotification({ - title: "Vencord has been updated!", - body: "Click here to restart", - permanent: true, - noPersist: true, - onClick: relaunch - }), 10_000); - return; - } - - setTimeout(() => showNotification({ - title: "A Vencord update is available!", - body: "Click here to view the update", - permanent: true, - noPersist: true, - onClick: openUpdaterModal! - }), 10_000); - } catch (err) { - UpdateLogger.error("Failed to check for updates", err); - } + runUpdateCheck(); + setInterval(runUpdateCheck, 1000 * 60 * 30); // 30 minutes } if (IS_DEV) { diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index 52bede44..a30f41ce 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -46,8 +46,6 @@ const ContributorBadge: ProfileBadge = { let DonorBadges = {} as Record>>; async function loadBadges(noCache = false) { - DonorBadges = {}; - const init = {} as RequestInit; if (noCache) init.cache = "no-cache"; @@ -56,6 +54,8 @@ async function loadBadges(noCache = false) { .then(r => r.json()); } +let intervalId: any; + export default definePlugin({ name: "BadgeAPI", description: "API to add badges to users.", @@ -89,6 +89,11 @@ export default definePlugin({ } ], + // for access from the console or other plugins + get DonorBadges() { + return DonorBadges; + }, + toolboxActions: { async "Refetch Badges"() { await loadBadges(true); @@ -104,6 +109,13 @@ export default definePlugin({ async start() { await loadBadges(); + + clearInterval(intervalId); + intervalId = setInterval(loadBadges, 1000 * 60 * 30); // 30 minutes + }, + + async stop() { + clearInterval(intervalId); }, getBadges(props: { userId: string; user?: User; guildId: string; }) { diff --git a/src/utils/updater.ts b/src/utils/updater.ts index f99c6ca1..25fe6ee8 100644 --- a/src/utils/updater.ts +++ b/src/utils/updater.ts @@ -39,10 +39,15 @@ async function Unwrap(p: Promise>) { export async function checkForUpdates() { changes = await Unwrap(VencordNative.updater.getUpdates()); - if (changes.some(c => c.hash === gitHash)) { - isNewer = true; - return (isOutdated = false); + + // we only want to check this for the git updater, not the http updater + if (!IS_STANDALONE) { + if (changes.some(c => c.hash === gitHash)) { + isNewer = true; + return (isOutdated = false); + } } + return (isOutdated = changes.length > 0); }