diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c133225c..0146065e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,6 +31,7 @@ Before starting your plugin: - No FakeDeafen or FakeMute - No StereoMic - No plugins that simply hide or redesign ui elements. This can be done with CSS +- No plugins that interact with specific Discord bots (official Discord apps like Youtube WatchTogether are okay) - No selfbots or API spam (animated status, message pruner, auto reply, nitro snipers, etc) - No untrusted third party APIs. Popular services like Google or GitHub are fine, but absolutely no self hosted ones - No plugins that require the user to enter their own API key diff --git a/package.json b/package.json index d5b23e57c..967c0ccc6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.10.7", + "version": "1.10.8", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/src/api/Commands/commandHelpers.ts b/src/api/Commands/commandHelpers.ts index 4ae022c59..ac1dafc98 100644 --- a/src/api/Commands/commandHelpers.ts +++ b/src/api/Commands/commandHelpers.ts @@ -54,5 +54,5 @@ export function sendBotMessage(channelId: string, message: PartialDeep) export function findOption(args: Argument[], name: string): T & {} | undefined; export function findOption(args: Argument[], name: string, fallbackValue: T): T & {}; export function findOption(args: Argument[], name: string, fallbackValue?: any) { - return (args.find(a => a.name === name)?.value || fallbackValue) as any; + return (args.find(a => a.name === name)?.value ?? fallbackValue) as any; } diff --git a/src/api/Commands/index.ts b/src/api/Commands/index.ts index e5803ba02..2b7a4de6c 100644 --- a/src/api/Commands/index.ts +++ b/src/api/Commands/index.ts @@ -110,6 +110,7 @@ function registerSubCommands(cmd: Command, plugin: string) { const subCmd = { ...cmd, ...o, + options: o.options !== undefined ? o.options : undefined, type: ApplicationCommandType.CHAT_INPUT, name: `${cmd.name} ${o.name}`, id: `${o.name}-${cmd.id}`, diff --git a/src/plugins/_api/badges/fixDiscordBadgePadding.css b/src/plugins/_api/badges/fixDiscordBadgePadding.css new file mode 100644 index 000000000..eb1d60d5c --- /dev/null +++ b/src/plugins/_api/badges/fixDiscordBadgePadding.css @@ -0,0 +1,5 @@ +/* the profile popout badge container(s) */ +[class*="biteSize_"] [class*="tags_"] [class*="container_"] { + /* Discord has padding set to 2px instead of 1px, which causes the 12th badge to wrap to a new line. */ + padding: 0 1px; +} diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index c44d98b90..d02b5e1d8 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import "./fixDiscordBadgePadding.css"; + import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges"; import DonateButton from "@components/DonateButton"; import ErrorBoundary from "@components/ErrorBoundary"; diff --git a/src/plugins/_api/messagePopover.ts b/src/plugins/_api/messagePopover.ts index 21e5accde..0133d0c64 100644 --- a/src/plugins/_api/messagePopover.ts +++ b/src/plugins/_api/messagePopover.ts @@ -23,11 +23,14 @@ export default definePlugin({ name: "MessagePopoverAPI", description: "API to add buttons to message popovers.", authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz], - patches: [{ - find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}", - replacement: { - match: /\.jsx\)\((\i\.\i),\{label:\i\.\i\.string\(\i\.\i#{intl::MESSAGE_ACTION_REPLY}.{0,200}?"reply-self".{0,50}?\}\):null(?=,.+?message:(\i))/, - replace: "$&,Vencord.Api.MessagePopover._buildPopoverElements($1,$2)" + patches: [ + { + find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}", + replacement: { + match: /(?<=:null),(.{0,40}togglePopout:.+?}\))\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/, + replace: (_, ReactButton, ButtonComponent, showReactButton, message) => "" + + `]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,` + } } - }], + ] }); diff --git a/src/plugins/alwaysTrust/index.ts b/src/plugins/alwaysTrust/index.ts index 7484a619c..0c89ae116 100644 --- a/src/plugins/alwaysTrust/index.ts +++ b/src/plugins/alwaysTrust/index.ts @@ -51,7 +51,7 @@ export default definePlugin({ { find: "bitbucket.org", replacement: { - match: /function \i\(\i\){(?=.{0,60}\.parse\(\i\))/, + match: /function \i\(\i\){(?=.{0,30}pathname:\i)/, replace: "$&return null;" }, predicate: () => settings.store.file diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 5d4d52c42..68746d361 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -123,7 +123,7 @@ export default definePlugin({ }, // If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children { - match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/, + match: /lastTargetNode:\i\[\i\.length-1\].+?}\)\](?=}\))/, 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 @@ -275,24 +275,30 @@ export default definePlugin({ }, makeGuildsBarGuildListFilter(isBetterFolders: boolean) { - return child => { - if (isBetterFolders) { - try { - return child?.props?.["aria-label"] === getIntlMessage("SERVERS"); - } catch (e) { - console.error(e); - } - } - return true; - }; + return child => { + if (!isBetterFolders) return true; + + try { + return child?.props?.["aria-label"] === getIntlMessage("SERVERS"); + } catch (e) { + console.error(e); + } + + return true; + }; }, makeGuildsBarTreeFilter(isBetterFolders: boolean) { return child => { - if (isBetterFolders) { - return child?.props?.onScroll != null; + 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); + return true; } - return true; + + return false; }; }, diff --git a/src/plugins/imageZoom/components/Magnifier.tsx b/src/plugins/imageZoom/components/Magnifier.tsx index 585026d60..31fa7a117 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, React, useRef, useState } from "@webpack/common"; +import { FluxDispatcher, useLayoutEffect, useRef, useState } from "@webpack/common"; import { ELEMENT_ID } from "../constants"; import { settings } from "../index"; @@ -55,7 +55,7 @@ export const Magnifier = ErrorBoundary.wrap(({ instance, size: i const imageRef = useRef(null); // since we accessing document im gonna use useLayoutEffect - React.useLayoutEffect(() => { + useLayoutEffect(() => { const onKeyDown = (e: KeyboardEvent) => { if (e.key === "Shift") { isShiftDown.current = true; @@ -135,12 +135,14 @@ export const Magnifier = ErrorBoundary.wrap(({ instance, size: i setReady(true); }); + document.addEventListener("keydown", onKeyDown); document.addEventListener("keyup", onKeyUp); document.addEventListener("mousemove", updateMousePosition); document.addEventListener("mousedown", onMouseDown); document.addEventListener("mouseup", onMouseUp); document.addEventListener("wheel", onWheel); + return () => { document.removeEventListener("keydown", onKeyDown); document.removeEventListener("keyup", onKeyUp); @@ -148,14 +150,16 @@ export const Magnifier = ErrorBoundary.wrap(({ instance, size: i document.removeEventListener("mousedown", onMouseDown); document.removeEventListener("mouseup", onMouseUp); document.removeEventListener("wheel", onWheel); - - if (settings.store.saveZoomValues) { - settings.store.zoom = zoom.current; - settings.store.size = size.current; - } }; }, []); + useLayoutEffect(() => () => { + if (settings.store.saveZoomValues) { + settings.store.zoom = zoom.current; + settings.store.size = size.current; + } + }); + if (!ready) return null; const box = element.current?.getBoundingClientRect(); diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index a4f690af1..080a79891 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -19,6 +19,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; +import { Logger } from "@utils/Logger"; import definePlugin from "@utils/types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common"; @@ -87,7 +88,7 @@ export default definePlugin({ replacement: [ { match: /\i\.useEffect.{0,100}(\i)\[0\]\.section/, - replace: "$self.pushSection($1, arguments[0].user);$&" + replace: "$self.pushSection($1,arguments[0].user);$&" }, { match: /\(0,\i\.jsx\)\(\i,\{items:\i,section:(\i)/, @@ -97,26 +98,46 @@ export default definePlugin({ }, { find: 'section:"MUTUAL_FRIENDS"', - replacement: { - match: /\.openUserProfileModal.+?\)}\)}\)(?<=(\(0,\i\.jsxs?\)\(\i\.\i,{className:(\i)\.divider}\)).+?)/, - replace: "$&,$self.renderDMPageList({user: arguments[0].user, Divider: $1, listStyle: $2.list})" - } + replacement: [ + { + match: /\i\|\|\i(?=\?\(0,\i\.jsxs?\)\(\i\.\i\.Overlay,)/, + replace: "$&||$self.getMutualGroupDms(arguments[0].user.id).length>0" + }, + { + match: /\.openUserProfileModal.+?\)}\)}\)(?<=,(\i)&&(\i)&&(\(0,\i\.jsxs?\)\(\i\.\i,{className:(\i)\.divider}\)).+?)/, + replace: (m, hasMutualGuilds, hasMutualFriends, Divider, classes) => "" + + `${m},$self.renderDMPageList({user:arguments[0].user,hasDivider:${hasMutualGuilds}||${hasMutualFriends},Divider:${Divider},listStyle:${classes}.list})` + } + ] } ], - pushSection(sections: any[], user: User) { - if (isBotOrSelf(user) || sections[IS_PATCHED]) return; + getMutualGroupDms(userId: string) { + try { + return getMutualGroupDms(userId); + } catch (e) { + new Logger("MutualGroupDMs").error("Failed to get mutual group dms:", e); + } - sections[IS_PATCHED] = true; - sections.push({ - section: "MUTUAL_GDMS", - text: getMutualGDMCountText(user) - }); + return []; + }, + + pushSection(sections: any[], user: User) { + try { + if (isBotOrSelf(user) || sections[IS_PATCHED]) return; + + sections[IS_PATCHED] = true; + sections.push({ + section: "MUTUAL_GDMS", + text: getMutualGDMCountText(user) + }); + } catch (e) { + new Logger("MutualGroupDMs").error("Failed to push mutual group dms section:", e); + } }, renderMutualGDMs: ErrorBoundary.wrap(({ user, onClose }: { user: User, onClose: () => void; }) => { const mutualGDms = useMemo(() => getMutualGroupDms(user.id), [user.id]); - const entries = renderClickableGDMs(mutualGDms, onClose); return ( @@ -138,14 +159,13 @@ export default definePlugin({ ); }), - renderDMPageList: ErrorBoundary.wrap(({ user, Divider, listStyle }: { user: User, Divider: JSX.Element, listStyle: string; }) => { + renderDMPageList: ErrorBoundary.wrap(({ user, hasDivider, Divider, listStyle }: { user: User, hasDivider: boolean, Divider: JSX.Element, listStyle: string; }) => { const mutualGDms = getMutualGroupDms(user.id); if (mutualGDms.length === 0) return null; - return ( <> - {Divider} + {hasDivider && Divider} void; -let forceUpdateGuildCount: () => void; +const UserGuildJoinRequestStore = findStoreLazy("UserGuildJoinRequestStore"); function FriendsIndicator() { - forceUpdateFriendCount = useForceUpdater(); + const onlineFriendsCount = useStateFromStores([RelationshipStore, PresenceStore], () => { + let count = 0; + + const friendIds = RelationshipStore.getFriendIDs(); + for (const id of friendIds) { + const status = PresenceStore.getStatus(id) ?? "offline"; + if (status === "offline") { + continue; + } + + count++; + } + + return count; + }); return ( - {onlineFriends} online + {onlineFriendsCount} online ); } function ServersIndicator() { - forceUpdateGuildCount = useForceUpdater(); + const guildCount = useStateFromStores([GuildStore, UserGuildJoinRequestStore], () => { + const guildJoinRequests: string[] = UserGuildJoinRequestStore.computeGuildIds(); + const guilds = GuildStore.getGuilds(); + + // Filter only pending guild join requests + return GuildStore.getGuildCount() + guildJoinRequests.filter(id => guilds[id] == null).length; + }); return ( ; }, - flux: { - PRESENCE_UPDATES: handlePresenceUpdate, - GUILD_CREATE: handleGuildUpdate, - GUILD_DELETE: handleGuildUpdate, - }, - - start() { addServerListElement(ServerListRenderPosition.Above, this.renderIndicator); - - handlePresenceUpdate(); - handleGuildUpdate(); }, stop() { diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index d3934bc26..a11ef2431 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -216,9 +216,12 @@ export default definePlugin({ replace: "true" }, { - match: /\i\.\i\.copy/, + match: /\i\.\i\.copy(?=\(\i)/, replace: "Vencord.Webpack.Common.Clipboard.copy" - }] + } + ], + all: true, + noWarn: true }, // Automod add filter words { diff --git a/src/webpack/common/react.ts b/src/webpack/common/react.ts index 17196132d..99f3f9dd4 100644 --- a/src/webpack/common/react.ts +++ b/src/webpack/common/react.ts @@ -22,6 +22,7 @@ import { findByPropsLazy, waitFor } from "../webpack"; export let React: typeof import("react"); export let useState: typeof React.useState; export let useEffect: typeof React.useEffect; +export let useLayoutEffect: typeof React.useLayoutEffect; export let useMemo: typeof React.useMemo; export let useRef: typeof React.useRef; export let useReducer: typeof React.useReducer; @@ -31,5 +32,5 @@ export const ReactDOM: typeof import("react-dom") & typeof import("react-dom/cli waitFor("useState", m => { React = m; - ({ useEffect, useState, useMemo, useRef, useReducer, useCallback } = React); + ({ useEffect, useState, useLayoutEffect, useMemo, useRef, useReducer, useCallback } = React); });