From 6ad17ff7e7f7bedcea3099e62a7e6601137239b3 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 8 May 2024 17:12:13 -0300 Subject: [PATCH 01/37] BetterFolders: Fix component erroring --- src/plugins/betterFolders/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 0b06d035..d252682f 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -279,7 +279,7 @@ export default definePlugin({ makeGuildsBarTreeFilter(isBetterFolders: boolean) { return child => { if (isBetterFolders) { - return "onScroll" in child.props; + return child?.props?.onScroll != null; } return true; }; From b1cc67a860fe2b6546900797dfa49a936b97f7dd Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 8 May 2024 23:42:04 +0200 Subject: [PATCH 02/37] fix(BetterSettings): do not catch errors of other ui --- src/plugins/betterSettings/index.tsx | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx index 7d81c6f5..5836734e 100644 --- a/src/plugins/betterSettings/index.tsx +++ b/src/plugins/betterSettings/index.tsx @@ -6,8 +6,8 @@ import { definePluginSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; +import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common"; @@ -124,12 +124,23 @@ export default definePlugin({ } ], + // This is the very outer layer of the entire ui, so we can't wrap this in an ErrorBoundary + // without possibly also catching unrelated errors of children. + // + // Thus, we sanity check webpack modules & do this really hacky try catch to hopefully prevent hard crashes if something goes wrong. + // try catch will only catch errors in the Layer function (hence why it's called as a plain function rather than a component), but + // not in children Layer(props: LayerProps) { - return ( - props.children as any}> - - - ); + try { + if (!FocusLock || !ComponentDispatch) + throw new Error("Failed to fetch some webpack modules"); + + return Layer(props); + } catch (e) { + new Logger("BetterSettings").error("Failed to render Layer", e); + } + + return props.children; }, wrapMenu(list: SettingsEntry[]) { From 025193533d1baf0e37df236cbda97f2f539e8b20 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 8 May 2024 23:49:47 +0200 Subject: [PATCH 03/37] VoiceDownload: fix doing nothing on discord desktop app --- src/plugins/voiceDownload/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/voiceDownload/index.tsx b/src/plugins/voiceDownload/index.tsx index 8586b9f9..571c3d0e 100644 --- a/src/plugins/voiceDownload/index.tsx +++ b/src/plugins/voiceDownload/index.tsx @@ -28,9 +28,12 @@ export default definePlugin({ e.stopPropagation()} aria-label="Download voice message" + {...IS_DISCORD_DESKTOP + ? { target: "_blank" } // open externally + : { download: "voice-message.ogg" } // download directly (not supported on discord desktop) + } > From 6bd0898efe489901178e45012a586c991947a068 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 8 May 2024 23:52:28 +0200 Subject: [PATCH 04/37] fix(SupportHelper): dont flag vencord web as externally updated --- src/plugins/_core/supportHelper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 63f32cbd..c7377833 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -139,7 +139,7 @@ ${makeCodeblock(enabledPlugins.join(", "))} const roles = GuildMemberStore.getSelfMember(VENCORD_GUILD_ID)?.roles; if (!roles || TrustedRolesIds.some(id => roles.includes(id))) return; - if (IS_UPDATER_DISABLED) { + if (!IS_WEB && IS_UPDATER_DISABLED) { return Alerts.show({ title: "Hold on!", body:
From 840a8f1fdd6e9f16c8ac44a2f21b09fb873a141f Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 8 May 2024 18:37:27 -0300 Subject: [PATCH 05/37] CrashHandler: Increment timeout for trying to recover --- src/plugins/crashHandler/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/crashHandler/index.ts b/src/plugins/crashHandler/index.ts index f8c76d7f..10053021 100644 --- a/src/plugins/crashHandler/index.ts +++ b/src/plugins/crashHandler/index.ts @@ -104,7 +104,7 @@ export default definePlugin({ shouldAttemptRecover = false; // This is enough to avoid a crash loop - setTimeout(() => shouldAttemptRecover = true, 500); + setTimeout(() => shouldAttemptRecover = true, 1000); } catch { } try { From a2acce55c34ddf3e0eafdfe7ecf747dcee859fb5 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 9 May 2024 03:04:03 +0200 Subject: [PATCH 06/37] BetterSettings: fix error handling crashing in some niche cases --- src/plugins/betterSettings/index.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx index 5836734e..c9bf070f 100644 --- a/src/plugins/betterSettings/index.tsx +++ b/src/plugins/betterSettings/index.tsx @@ -131,16 +131,11 @@ export default definePlugin({ // try catch will only catch errors in the Layer function (hence why it's called as a plain function rather than a component), but // not in children Layer(props: LayerProps) { - try { - if (!FocusLock || !ComponentDispatch) - throw new Error("Failed to fetch some webpack modules"); - - return Layer(props); - } catch (e) { - new Logger("BetterSettings").error("Failed to render Layer", e); + if (!FocusLock || !ComponentDispatch) { + new Logger("BetterSettings").error("Failed to find some components"); } - return props.children; + return ; }, wrapMenu(list: SettingsEntry[]) { From 251ee32e01ab70a6ec1c6a6a003c89682edca21b Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 9 May 2024 03:10:07 +0200 Subject: [PATCH 07/37] new plugin ShowTimeoutDuration ~ shows how much longer a user's timeout will last --- src/plugins/showTimeoutDuration/README.md | 8 ++ src/plugins/showTimeoutDuration/index.tsx | 106 +++++++++++++++++++++ src/plugins/showTimeoutDuration/styles.css | 4 + 3 files changed, 118 insertions(+) create mode 100644 src/plugins/showTimeoutDuration/README.md create mode 100644 src/plugins/showTimeoutDuration/index.tsx create mode 100644 src/plugins/showTimeoutDuration/styles.css diff --git a/src/plugins/showTimeoutDuration/README.md b/src/plugins/showTimeoutDuration/README.md new file mode 100644 index 00000000..13780247 --- /dev/null +++ b/src/plugins/showTimeoutDuration/README.md @@ -0,0 +1,8 @@ +# ShowTimeoutDuration + +Displays how much longer a user's timeout will last. +Either in the timeout icon tooltip, or next to it, configurable via settings! + +![indicator in tooltip](https://github.com/Vendicated/Vencord/assets/45497981/606588a3-2646-40d9-8800-b6307f650136) + +![indicator next to timeout icon](https://github.com/Vendicated/Vencord/assets/45497981/ab9d2101-0fdc-4143-9310-9488f056eeee) diff --git a/src/plugins/showTimeoutDuration/index.tsx b/src/plugins/showTimeoutDuration/index.tsx new file mode 100644 index 00000000..f57ee0fc --- /dev/null +++ b/src/plugins/showTimeoutDuration/index.tsx @@ -0,0 +1,106 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +import { definePluginSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import { Margins } from "@utils/margins"; +import definePlugin, { OptionType } from "@utils/types"; +import { findComponentLazy } from "@webpack"; +import { ChannelStore, Forms, GuildMemberStore, i18n, Text, Tooltip } from "@webpack/common"; +import { Message } from "discord-types/general"; + +const CountDown = findComponentLazy(m => m.prototype?.render?.toString().includes(".MAX_AGE_NEVER")); + +const enum DisplayStyle { + Tooltip = "tooltip", + Inline = "ssalggnikool" +} + +const settings = definePluginSettings({ + displayStyle: { + description: "How to display the timeout duration", + type: OptionType.SELECT, + restartNeeded: true, + options: [ + { label: "In the Tooltip", value: DisplayStyle.Tooltip }, + { label: "Next to the timeout icon", value: DisplayStyle.Inline, default: true }, + ], + } +}); + +function renderTimeout(message: Message, inline: boolean) { + const guildId = ChannelStore.getChannel(message.channel_id)?.guild_id; + if (!guildId) return null; + + const member = GuildMemberStore.getMember(guildId, message.author.id); + if (!member?.communicationDisabledUntil) return null; + + const countdown = () => ( + + ); + + return inline + ? countdown() + : i18n.Messages.GUILD_ENABLE_COMMUNICATION_TIME_REMAINING.format({ + username: message.author.username, + countdown + }); +} + +export default definePlugin({ + name: "ShowTimeoutDuration", + description: "Shows how much longer a user's timeout will last, either in the timeout icon tooltip or next to it", + authors: [Devs.Ven], + + settings, + + patches: [ + { + find: ".GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY", + replacement: [ + { + match: /(\i)\.Tooltip,{(text:.{0,30}\.Messages\.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY)/, + get replace() { + if (settings.store.displayStyle === DisplayStyle.Inline) + return "$self.TooltipWrapper,{vcProps:arguments[0],$2"; + + return "$1.Tooltip,{text:$self.renderTimeoutDuration(arguments[0])"; + } + } + ] + } + ], + + renderTimeoutDuration: ErrorBoundary.wrap(({ message }: { message: Message; }) => { + return ( + <> + {i18n.Messages.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY} + + {renderTimeout(message, false)} + + + ); + }, { noop: true }), + + TooltipWrapper: ErrorBoundary.wrap(({ vcProps: { message }, ...tooltipProps }: { vcProps: { message: Message; }; }) => { + return ( +
+ + + + {renderTimeout(message, true)} timeout remaining + +
+ ); + }, { noop: true }) +}); diff --git a/src/plugins/showTimeoutDuration/styles.css b/src/plugins/showTimeoutDuration/styles.css new file mode 100644 index 00000000..70a826e1 --- /dev/null +++ b/src/plugins/showTimeoutDuration/styles.css @@ -0,0 +1,4 @@ +.vc-std-wrapper { + display: flex; + align-items: center; +} From 14e68d9a246006122d2b1a4e2a17a53d955cbbca Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 9 May 2024 03:12:11 +0200 Subject: [PATCH 08/37] im the dumbest dumdum --- src/plugins/betterSettings/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx index c9bf070f..5064bd53 100644 --- a/src/plugins/betterSettings/index.tsx +++ b/src/plugins/betterSettings/index.tsx @@ -9,14 +9,15 @@ import { classNameFactory } from "@api/Styles"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { waitFor } from "@webpack"; import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common"; import type { HTMLAttributes, ReactElement } from "react"; type SettingsEntry = { section: string, label: string; }; const cl = classNameFactory(""); -const Classes = findByPropsLazy("animating", "baseLayer", "bg", "layer", "layers"); +let Classes: Record; +waitFor(["animating", "baseLayer", "bg", "layer", "layers"], m => Classes = m); const settings = definePluginSettings({ disableFade: { @@ -131,8 +132,9 @@ export default definePlugin({ // try catch will only catch errors in the Layer function (hence why it's called as a plain function rather than a component), but // not in children Layer(props: LayerProps) { - if (!FocusLock || !ComponentDispatch) { + if (!FocusLock || !ComponentDispatch || !Classes) { new Logger("BetterSettings").error("Failed to find some components"); + return props.children; } return ; From 1a3a378fb15adae9d0240773dd301ff88fe434da Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 9 May 2024 03:14:20 -0300 Subject: [PATCH 09/37] ErrorBoundary some more components --- src/plugins/betterNotes/index.tsx | 5 ++++- .../imageZoom/components/Magnifier.tsx | 5 +++-- src/plugins/mutualGroupDMs/index.tsx | 5 +++-- src/plugins/pauseInvitesForever/index.tsx | 20 ++++++++++--------- .../readAllNotificationsButton/index.tsx | 3 ++- src/plugins/showMeYourName/index.tsx | 11 +++++----- 6 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/plugins/betterNotes/index.tsx b/src/plugins/betterNotes/index.tsx index 2183d98e..5ebca1f8 100644 --- a/src/plugins/betterNotes/index.tsx +++ b/src/plugins/betterNotes/index.tsx @@ -17,6 +17,7 @@ */ import { Settings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { canonicalizeMatch } from "@utils/patches"; import definePlugin, { OptionType } from "@utils/types"; @@ -83,7 +84,9 @@ export default definePlugin({ patchPadding(lastSection: any) { if (!lastSection) return; return ( -
+ +
+
); } }); diff --git a/src/plugins/imageZoom/components/Magnifier.tsx b/src/plugins/imageZoom/components/Magnifier.tsx index 81671735..aadd0903 100644 --- a/src/plugins/imageZoom/components/Magnifier.tsx +++ b/src/plugins/imageZoom/components/Magnifier.tsx @@ -17,6 +17,7 @@ */ import { classNameFactory } from "@api/Styles"; +import ErrorBoundary from "@components/ErrorBoundary"; import { FluxDispatcher, React, useRef, useState } from "@webpack/common"; import { ELEMENT_ID } from "../constants"; @@ -36,7 +37,7 @@ export interface MagnifierProps { const cl = classNameFactory("vc-imgzoom-"); -export const Magnifier: React.FC = ({ instance, size: initialSize, zoom: initalZoom }) => { +export const Magnifier = ErrorBoundary.wrap(({ instance, size: initialSize, zoom: initalZoom }) => { const [ready, setReady] = useState(false); const [lensPosition, setLensPosition] = useState({ x: 0, y: 0 }); @@ -199,4 +200,4 @@ export const Magnifier: React.FC = ({ instance, size: initialSiz )}
); -}; +}, { noop: true }); diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 1753fefb..e787fefb 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; import definePlugin from "@utils/types"; @@ -60,7 +61,7 @@ export default definePlugin({ } ], - renderMutualGDMs(user: User, onClose: () => void) { + renderMutualGDMs: ErrorBoundary.wrap((user: User, onClose: () => void) => { const entries = ChannelStore.getSortedPrivateChannels().filter(c => c.isGroupDM() && c.recipients.includes(user.id)).map(c => ( ); - } + }) }); diff --git a/src/plugins/pauseInvitesForever/index.tsx b/src/plugins/pauseInvitesForever/index.tsx index 81f18fd6..1e71a4ed 100644 --- a/src/plugins/pauseInvitesForever/index.tsx +++ b/src/plugins/pauseInvitesForever/index.tsx @@ -16,12 +16,12 @@ * along with this program. If not, see . */ +import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { GuildStore, RestAPI } from "@webpack/common"; +import { GuildStore, i18n, RestAPI } from "@webpack/common"; -const Messages = findByPropsLazy("GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION"); const { InvitesDisabledExperiment } = findByPropsLazy("InvitesDisabledExperiment"); export default definePlugin({ @@ -62,13 +62,15 @@ export default definePlugin({ renderInvitesLabel(guildId: string, setChecked: Function) { return ( -
- {Messages.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION} - {this.showDisableInvites(guildId) && { - setChecked(true); - this.disableInvites(guildId); - }}> Pause Indefinitely.} -
+ +
+ {i18n.Messages.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION} + {this.showDisableInvites(guildId) && { + setChecked(true); + this.disableInvites(guildId); + }}> Pause Indefinitely.} +
+
); } }); diff --git a/src/plugins/readAllNotificationsButton/index.tsx b/src/plugins/readAllNotificationsButton/index.tsx index ae66e11a..3bf53f99 100644 --- a/src/plugins/readAllNotificationsButton/index.tsx +++ b/src/plugins/readAllNotificationsButton/index.tsx @@ -19,6 +19,7 @@ import "./style.css"; import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList"; +import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { Button, FluxDispatcher, GuildChannelStore, GuildStore, React, ReadStateStore } from "@webpack/common"; @@ -64,7 +65,7 @@ export default definePlugin({ authors: [Devs.kemo], dependencies: ["ServerListAPI"], - renderReadAllButton: () => , + renderReadAllButton: ErrorBoundary.wrap(ReadAllButton, { noop: true }), start() { addServerListElement(ServerListRenderPosition.Above, this.renderReadAllButton); diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index a9db1af9..7ba245da 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -7,6 +7,7 @@ import "./styles.css"; import { definePluginSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { Message, User } from "discord-types/general"; @@ -56,7 +57,7 @@ export default definePlugin({ ], settings, - renderUsername: ({ author, message, isRepliedMessage, withMentionPrefix, userOverride }: UsernameProps) => { + renderUsername: ErrorBoundary.wrap(({ author, message, isRepliedMessage, withMentionPrefix, userOverride }: UsernameProps) => { try { const user = userOverride ?? message.author; let { username } = user; @@ -66,14 +67,14 @@ export default definePlugin({ const { nick } = author; const prefix = withMentionPrefix ? "@" : ""; if (username === nick || isRepliedMessage && !settings.store.inReplies) - return prefix + nick; + return <>{prefix}{nick}; if (settings.store.mode === "user-nick") return <>{prefix}{username} {nick}; if (settings.store.mode === "nick-user") return <>{prefix}{nick} {username}; - return prefix + username; + return <>{prefix}{username}; } catch { - return author?.nick; + return <>{author?.nick}; } - }, + }, { noop: true }), }); From 395b0007bf15255f6c3e29586d4e56ffc16d3624 Mon Sep 17 00:00:00 2001 From: DShadow <62884000+CatGirlDShadow@users.noreply.github.com> Date: Sat, 11 May 2024 18:43:34 +0300 Subject: [PATCH 10/37] permissionsViewer: add role & user context menus to copy id (#2436) Co-authored-by: V --- .../components/RolesAndUsersPermissions.tsx | 69 ++++++++++++++----- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx index c2e50ced..963750fa 100644 --- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx +++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx @@ -21,7 +21,7 @@ import { Flex } from "@components/Flex"; import { InfoIcon, OwnerCrownIcon } from "@components/Icons"; import { getUniqueUsername } from "@utils/discord"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common"; +import { Clipboard, ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, i18n, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common"; import type { Guild } from "discord-types/general"; import { settings } from ".."; @@ -112,7 +112,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
{ - if ((settings.store as any).unsafeViewAsRole && permission.type === PermissionType.Role) + if (permission.type === PermissionType.Role) ContextMenuApi.openContextMenu(e, () => ( )); + else if (permission.type === PermissionType.User) { + ContextMenuApi.openContextMenu(e, () => ( + + )); + } }} > {(permission.type === PermissionType.Role || permission.type === PermissionType.Owner) && ( @@ -200,24 +208,53 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str aria-label="Role Options" > { - const role = GuildStore.getRole(guild.id, roleId); - if (!role) return; + Clipboard.copy(roleId); + }} + /> - onClose(); + {(settings.store as any).unsafeViewAsRole && ( + { + const role = GuildStore.getRole(guild.id, roleId); + if (!role) return; - FluxDispatcher.dispatch({ - type: "IMPERSONATE_UPDATE", - guildId: guild.id, - data: { - type: "ROLES", - roles: { - [roleId]: role + onClose(); + + FluxDispatcher.dispatch({ + type: "IMPERSONATE_UPDATE", + guildId: guild.id, + data: { + type: "ROLES", + roles: { + [roleId]: role + } } - } - }); + }); + } + } + /> + )} + + ); +} + +function UserContextMenu({ userId, onClose }: { userId: string; onClose: () => void; }) { + return ( + + { + Clipboard.copy(userId); }} /> From fbaa4ad5bcd32788a1d9f891be56dd71f93c9448 Mon Sep 17 00:00:00 2001 From: nyan <24845294+nyakowint@users.noreply.github.com> Date: Sat, 11 May 2024 12:05:22 -0400 Subject: [PATCH 11/37] XSOverlay: add settings for different notification types (#2055) Co-authored-by: vee --- src/plugins/xsOverlay.desktop/index.ts | 69 ++++++++++++++++++-------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/src/plugins/xsOverlay.desktop/index.ts b/src/plugins/xsOverlay.desktop/index.ts index 763f6a78..b666d116 100644 --- a/src/plugins/xsOverlay.desktop/index.ts +++ b/src/plugins/xsOverlay.desktop/index.ts @@ -1,6 +1,6 @@ /* * Vencord, a Discord client mod - * Copyright (c) 2023 Vendicated and contributors + * Copyright (c) 2024 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -13,10 +13,7 @@ import { findByPropsLazy } from "@webpack"; import { ChannelStore, GuildStore, UserStore } from "@webpack/common"; import type { Channel, Embed, GuildMember, MessageAttachment, User } from "discord-types/general"; -const enum ChannelTypes { - DM = 1, - GROUP_DM = 3 -} +const { ChannelTypes } = findByPropsLazy("ChannelTypes"); interface Message { guild_id: string, @@ -72,14 +69,35 @@ interface Call { } const MuteStore = findByPropsLazy("isSuppressEveryoneEnabled"); +const Notifs = findByPropsLazy("makeTextChatNotification"); const XSLog = new Logger("XSOverlay"); const settings = definePluginSettings({ - ignoreBots: { + botNotifications: { type: OptionType.BOOLEAN, - description: "Ignore messages from bots", + description: "Allow bot notifications", default: false }, + serverNotifications: { + type: OptionType.BOOLEAN, + description: "Allow server notifications", + default: true + }, + dmNotifications: { + type: OptionType.BOOLEAN, + description: "Allow Direct Message notifications", + default: true + }, + groupDmNotifications: { + type: OptionType.BOOLEAN, + description: "Allow Group DM notifications", + default: true + }, + callNotifications: { + type: OptionType.BOOLEAN, + description: "Allow call notifications", + default: true + }, pingColor: { type: OptionType.STRING, description: "User mention color", @@ -100,6 +118,11 @@ const settings = definePluginSettings({ description: "Notif duration (secs)", default: 1.0, }, + timeoutPerCharacter: { + type: OptionType.NUMBER, + description: "Duration multiplier per character", + default: 0.5 + }, opacity: { type: OptionType.SLIDER, description: "Notif opacity", @@ -124,7 +147,7 @@ export default definePlugin({ settings, flux: { CALL_UPDATE({ call }: { call: Call; }) { - if (call?.ringing?.includes(UserStore.getCurrentUser().id)) { + if (call?.ringing?.includes(UserStore.getCurrentUser().id) && settings.store.callNotifications) { const channel = ChannelStore.getChannel(call.channel_id); sendOtherNotif("Incoming call", `${channel.name} is calling you...`); } @@ -134,7 +157,7 @@ export default definePlugin({ try { if (optimistic) return; const channel = ChannelStore.getChannel(message.channel_id); - if (!shouldNotify(message, channel)) return; + if (!shouldNotify(message, message.channel_id)) return; const pingColor = settings.store.pingColor.replaceAll("#", "").trim(); const channelPingColor = settings.store.channelPingColor.replaceAll("#", "").trim(); @@ -194,6 +217,7 @@ export default definePlugin({ finalMsg = finalMsg.replace(/<@!?(\d{17,20})>/g, (_, id) => `@${UserStore.getUser(id)?.username || "unknown-user"}`); } + // color role mentions (unity styling btw lol) if (message.mention_roles.length > 0) { for (const roleId of message.mention_roles) { const role = GuildStore.getRole(channel.guild_id, roleId); @@ -213,6 +237,7 @@ export default definePlugin({ } } + // color channel mentions if (channelMatches) { for (const cMatch of channelMatches) { let channelId = cMatch.split("<#")[1]; @@ -221,6 +246,7 @@ export default definePlugin({ } } + if (shouldIgnoreForChannelType(channel)) return; sendMsgNotif(titleString, finalMsg, message); } catch (err) { XSLog.error(`Failed to catch MESSAGE_CREATE: ${err}`); @@ -229,13 +255,20 @@ export default definePlugin({ } }); +function shouldIgnoreForChannelType(channel: Channel) { + if (channel.type === ChannelTypes.DM && settings.store.dmNotifications) return false; + if (channel.type === ChannelTypes.GROUP_DM && settings.store.groupDmNotifications) return false; + else return !settings.store.serverNotifications; +} + function sendMsgNotif(titleString: string, content: string, message: Message) { + const timeout = Math.max(settings.store.timeout, content.length * settings.store.timeoutPerCharacter); fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => { const msgData = { messageType: 1, index: 0, - timeout: settings.store.timeout, - height: calculateHeight(cleanMessage(content)), + timeout, + height: calculateHeight(content), opacity: settings.store.opacity, volume: settings.store.volume, audioPath: settings.store.soundPath, @@ -254,7 +287,7 @@ function sendOtherNotif(content: string, titleString: string) { messageType: 1, index: 0, timeout: settings.store.timeout, - height: calculateHeight(cleanMessage(content)), + height: calculateHeight(content), opacity: settings.store.opacity, volume: settings.store.volume, audioPath: settings.store.soundPath, @@ -267,13 +300,11 @@ function sendOtherNotif(content: string, titleString: string) { Native.sendToOverlay(msgData); } -function shouldNotify(message: Message, channel: Channel) { +function shouldNotify(message: Message, channel: string) { const currentUser = UserStore.getCurrentUser(); if (message.author.id === currentUser.id) return false; - if (message.author.bot && settings.store.ignoreBots) return false; - if (MuteStore.allowAllMessages(channel) || message.mention_everyone && !MuteStore.isSuppressEveryoneEnabled(message.guild_id)) return true; - - return message.mentions.some(m => m.id === currentUser.id); + if (message.author.bot && !settings.store.botNotifications) return false; + return Notifs.shouldNotify(message, channel); } function calculateHeight(content: string) { @@ -282,7 +313,3 @@ function calculateHeight(content: string) { if (content.length <= 300) return 200; return 250; } - -function cleanMessage(content: string) { - return content.replace(new RegExp("<[^>]*>", "g"), ""); -} From 6b88eaccbb82dee8ff4bedbc29adad7cd1782be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Han=20Seung=20Min=20-=20=ED=95=9C=EC=8A=B9=EB=AF=BC?= Date: Sun, 12 May 2024 01:09:48 +0900 Subject: [PATCH 12/37] messageLatency: fix grammar & add aliucord/kotlin client tooltip (#2426) --- src/plugins/messageLatency/index.tsx | 35 ++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx index 0b6d7503..48b57863 100644 --- a/src/plugins/messageLatency/index.tsx +++ b/src/plugins/messageLatency/index.tsx @@ -13,7 +13,7 @@ import { findExportedComponentLazy } from "@webpack"; import { SnowflakeUtils, Tooltip } from "@webpack/common"; import { Message } from "discord-types/general"; -type FillValue = ("status-danger" | "status-warning" | "text-muted"); +type FillValue = ("status-danger" | "status-warning" | "status-positive" | "text-muted"); type Fill = [FillValue, FillValue, FillValue]; type DiffKey = keyof Diff; @@ -54,10 +54,24 @@ export default definePlugin({ seconds: Math.round(delta % 60), }; - const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${k}` : null; + const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${diff[k] > 1 ? k : k.substring(0, k.length - 1)}` : null; const keys = Object.keys(diff) as DiffKey[]; - return keys.map(str).filter(isNonNullish).join(" ") || "0 seconds"; + const ts = keys.reduce((prev, k) => { + const s = str(k); + + return prev + ( + isNonNullish(s) + ? (prev !== "" + ? k === "seconds" + ? " and " + : " " + : "") + s + : "" + ); + }, ""); + + return [ts || "0 seconds", diff.days === 17 && diff.hours === 1] as const; }, latencyTooltipData(message: Message) { const { id, nonce } = message; @@ -73,16 +87,23 @@ export default definePlugin({ const abs = Math.abs(delta); const ahead = abs !== delta; - const stringDelta = this.stringDelta(abs); + const [stringDelta, isSuspectedKotlinDiscord] = this.stringDelta(abs); + const isKotlinDiscord = ahead && isSuspectedKotlinDiscord; // Also thanks dziurwa // 2 minutes const TROLL_LIMIT = 2 * 60; const { latency } = this.settings.store; - const fill: Fill = delta >= TROLL_LIMIT || ahead ? ["text-muted", "text-muted", "text-muted"] : delta >= (latency * 2) ? ["status-danger", "text-muted", "text-muted"] : ["status-warning", "status-warning", "text-muted"]; + const fill: Fill = isKotlinDiscord + ? ["status-positive", "status-positive", "text-muted"] + : delta >= TROLL_LIMIT || ahead + ? ["text-muted", "text-muted", "text-muted"] + : delta >= (latency * 2) + ? ["status-danger", "text-muted", "text-muted"] + : ["status-warning", "status-warning", "text-muted"]; - return abs >= latency ? { delta: stringDelta, ahead: abs !== delta, fill } : null; + return abs >= latency ? { delta: stringDelta, ahead, fill, isKotlinDiscord } : null; }, Tooltip() { return ErrorBoundary.wrap(({ message }: { message: Message; }) => { @@ -92,7 +113,7 @@ export default definePlugin({ if (!isNonNullish(d)) return null; return { From 2eb8ba18410188715ecdeea80b4b5b046204e153 Mon Sep 17 00:00:00 2001 From: Im_Banana <67657872+ImBonana@users.noreply.github.com> Date: Sat, 11 May 2024 23:49:00 +0300 Subject: [PATCH 13/37] SilentTyping: add chat input context menu option to toggle (#2386) Co-authored-by: vee --- src/plugins/silentTyping/index.tsx | 36 +++++++++++++++++++++++++++--- src/utils/constants.ts | 4 ++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/plugins/silentTyping/index.tsx b/src/plugins/silentTyping/index.tsx index 8b59c6ac..2a6a6428 100644 --- a/src/plugins/silentTyping/index.tsx +++ b/src/plugins/silentTyping/index.tsx @@ -18,10 +18,11 @@ import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands"; +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { FluxDispatcher, React } from "@webpack/common"; +import { FluxDispatcher, Menu, React } from "@webpack/common"; const settings = definePluginSettings({ showIcon: { @@ -30,6 +31,11 @@ const settings = definePluginSettings({ description: "Show an icon for toggling the plugin", restartNeeded: true, }, + contextMenu: { + type: OptionType.BOOLEAN, + description: "Add option to toggle the functionality in the chat input context menu", + default: true + }, isEnabled: { type: OptionType.BOOLEAN, description: "Toggle functionality", @@ -56,13 +62,37 @@ const SilentTypingToggle: ChatBarButton = ({ isMainChat }) => { ); }; + +const ChatBarContextCheckbox: NavContextMenuPatchCallback = children => { + const { isEnabled, contextMenu } = settings.use(["isEnabled", "contextMenu"]); + if (!contextMenu) return; + + const group = findGroupChildrenByChildId("submit-button", children); + + if (!group) return; + + const idx = group.findIndex(c => c?.props?.id === "submit-button"); + + group.splice(idx + 1, 0, + settings.store.isEnabled = !settings.store.isEnabled} + /> + ); +}; + + export default definePlugin({ name: "SilentTyping", - authors: [Devs.Ven, Devs.Rini], + authors: [Devs.Ven, Devs.Rini, Devs.ImBanana], description: "Hide that you are typing", dependencies: ["CommandsAPI", "ChatInputButtonAPI"], settings, - + contextMenus: { + "textarea-context": ChatBarContextCheckbox + }, patches: [ { find: '.dispatch({type:"TYPING_START_LOCAL"', diff --git a/src/utils/constants.ts b/src/utils/constants.ts index a0c4752d..6d9950a9 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -461,6 +461,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ GabiRP: { name: "GabiRP", id: 507955112027750401n + }, + ImBanana: { + name: "Im_Banana", + id: 635250116688871425n } } satisfies Record); From 9b328da4ce2363fa0e9e2fc24795d09597df1ab7 Mon Sep 17 00:00:00 2001 From: Overcast Warmth System <110815694+OvercastWarmth@users.noreply.github.com> Date: Sun, 12 May 2024 06:57:48 +1000 Subject: [PATCH 14/37] ThemeAttributes: add data-author-username to messages (#2422) --- src/plugins/themeAttributes/README.md | 1 + src/plugins/themeAttributes/index.ts | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/plugins/themeAttributes/README.md b/src/plugins/themeAttributes/README.md index 110eca57..87cb803c 100644 --- a/src/plugins/themeAttributes/README.md +++ b/src/plugins/themeAttributes/README.md @@ -15,6 +15,7 @@ This allows themes to more easily theme those elements or even do things that ot ### Chat Messages - `data-author-id` contains the id of the author +- `data-author-username` contains the username of the author - `data-is-self` is a boolean indicating whether this is the current user's message ![image](https://github.com/Vendicated/Vencord/assets/45497981/34bd5053-3381-402f-82b2-9c812cc7e122) diff --git a/src/plugins/themeAttributes/index.ts b/src/plugins/themeAttributes/index.ts index 8afc2121..b8ceac62 100644 --- a/src/plugins/themeAttributes/index.ts +++ b/src/plugins/themeAttributes/index.ts @@ -36,10 +36,12 @@ export default definePlugin({ ], getMessageProps(props: { message: Message; }) { - const authorId = props.message?.author?.id; + const author = props.message?.author; + const authorId = author?.id; return { "data-author-id": authorId, - "data-is-self": authorId && authorId === UserStore.getCurrentUser()?.id + "data-author-username": author?.username, + "data-is-self": authorId && authorId === UserStore.getCurrentUser()?.id, }; } }); From 207fe846367afc7ed75104968e52d47550a1cc08 Mon Sep 17 00:00:00 2001 From: nin0dev Date: Sat, 11 May 2024 17:29:31 -0400 Subject: [PATCH 15/37] CustomRPC: show warning when game activity is disabled (#2245) Co-authored-by: V --- src/plugins/customRPC/index.tsx | 30 +++++++++++++++++++++++++++--- src/utils/constants.ts | 4 ++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index 334372e3..f1b2fbf5 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -17,13 +17,16 @@ */ import { definePluginSettings, Settings } from "@api/Settings"; +import { ErrorCard } from "@components/ErrorCard"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; import { isTruthy } from "@utils/guards"; +import { Margins } from "@utils/margins"; +import { classes } from "@utils/misc"; import { useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack"; -import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common"; +import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, StatusSettingsStores, UserStore } from "@webpack/common"; const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color"); const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile"); @@ -386,17 +389,36 @@ async function setRpc(disable?: boolean) { export default definePlugin({ name: "CustomRPC", description: "Allows you to set a custom rich presence.", - authors: [Devs.captain, Devs.AutumnVN], + authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev], start: setRpc, stop: () => setRpc(true), settings, settingsAboutComponent: () => { const activity = useAwaiter(createActivity); + const gameActivityEnabled = StatusSettingsStores.ShowCurrentGame.useSetting(); const { profileThemeStyle } = useProfileThemeStyle({}); return ( <> + {!gameActivityEnabled && ( + + Notice + Game activity isn't enabled, people won't be able to see your custom rich presence! + + + + )} + Go to Discord Developer Portal to create an application and get the application ID. @@ -407,7 +429,9 @@ export default definePlugin({ If you want to use image link, download your image and reupload the image to Imgur and get the image link by right-clicking the image and select "Copy image address". - + + +
{activity[0] && Date: Sat, 11 May 2024 18:34:47 -0300 Subject: [PATCH 16/37] FakeNitro: Update description --- src/plugins/fakeNitro/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 03feda0a..ea171c83 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -190,7 +190,7 @@ const hasAttachmentPerms = (channelId: string) => hasPermission(channelId, Permi export default definePlugin({ name: "FakeNitro", authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN], - description: "Allows you to stream in nitro quality, send fake emojis/stickers and use client themes.", + description: "Allows you to stream in nitro quality, send fake emojis/stickers, use client themes and custom Discord notifications.", dependencies: ["MessageEventsAPI"], settings, From cc5e39c9a96aca4dfbd29f83e7377f942888dc1b Mon Sep 17 00:00:00 2001 From: Fafa <87046111+Faf4a@users.noreply.github.com> Date: Sat, 11 May 2024 23:50:29 +0200 Subject: [PATCH 17/37] Dearrow: allow configuring which elements get dearrowd (#2414) Co-authored-by: Vendicated --- src/plugins/dearrow/index.tsx | 38 +++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx index b02c80d3..888e2bb4 100644 --- a/src/plugins/dearrow/index.tsx +++ b/src/plugins/dearrow/index.tsx @@ -6,10 +6,11 @@ import "./styles.css"; +import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; import { Tooltip } from "@webpack/common"; import type { Component } from "react"; @@ -34,11 +35,19 @@ interface Props { }; } +const enum ReplaceElements { + ReplaceAllElements, + ReplaceTitlesOnly, + ReplaceThumbnailsOnly +} + const embedUrlRe = /https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/; async function embedDidMount(this: Component) { try { const { embed } = this.props; + const { replaceElements } = settings.store; + if (!embed || embed.dearrow || embed.provider?.name !== "YouTube" || !embed.video?.url) return; const videoId = embedUrlRe.exec(embed.video.url)?.[1]; @@ -58,12 +67,12 @@ async function embedDidMount(this: Component) { enabled: true }; - if (hasTitle) { + if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) { embed.dearrow.oldTitle = embed.rawTitle; embed.rawTitle = titles[0].title.replace(/ >(\S)/g, " $1"); } - if (hasThumb) { + if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) { embed.dearrow.oldThumb = embed.thumbnail.proxyURL; embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`; } @@ -128,10 +137,30 @@ function DearrowButton({ component }: { component: Component; }) { ); } +const settings = definePluginSettings({ + hideButton: { + description: "Hides the Dearrow button from YouTube embeds", + type: OptionType.BOOLEAN, + default: false, + restartNeeded: true + }, + replaceElements: { + description: "Choose which elements of the embed will be replaced", + type: OptionType.SELECT, + restartNeeded: true, + options: [ + { label: "Everything (Titles & Thumbnails)", value: ReplaceElements.ReplaceAllElements, default: true }, + { label: "Titles", value: ReplaceElements.ReplaceTitlesOnly }, + { label: "Thumbnails", value: ReplaceElements.ReplaceThumbnailsOnly }, + ], + } +}); + export default definePlugin({ name: "Dearrow", description: "Makes YouTube embed titles and thumbnails less sensationalist, powered by Dearrow", authors: [Devs.Ven], + settings, embedDidMount, renderButton(component: Component) { @@ -154,7 +183,8 @@ export default definePlugin({ // add dearrow button { match: /children:\[(?=null!=\i\?\i\.renderSuppressButton)/, - replace: "children:[$self.renderButton(this)," + replace: "children:[$self.renderButton(this),", + predicate: () => !settings.store.hideButton } ] }], From b22bfc80fdafcca52b5b547b4fa74212fba82283 Mon Sep 17 00:00:00 2001 From: Elvyra <88881326+EdVraz@users.noreply.github.com> Date: Sun, 12 May 2024 01:23:51 +0200 Subject: [PATCH 18/37] pronounDB: Update to API v2 (#2355) Co-authored-by: vee --- src/plugins/pronoundb/index.ts | 2 +- src/plugins/pronoundb/pronoundbUtils.ts | 44 ++++++++++++++----------- src/plugins/pronoundb/types.ts | 34 +++++++++---------- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/plugins/pronoundb/index.ts b/src/plugins/pronoundb/index.ts index b14b2657..a5891d2e 100644 --- a/src/plugins/pronoundb/index.ts +++ b/src/plugins/pronoundb/index.ts @@ -33,7 +33,7 @@ const PRONOUN_TOOLTIP_PATCH = { export default definePlugin({ name: "PronounDB", - authors: [Devs.Tyman, Devs.TheKodeToad, Devs.Ven], + authors: [Devs.Tyman, Devs.TheKodeToad, Devs.Ven, Devs.Elvyra], description: "Adds pronouns to user messages using pronoundb", patches: [ { diff --git a/src/plugins/pronoundb/pronoundbUtils.ts b/src/plugins/pronoundb/pronoundbUtils.ts index 6373c56a..d4fdb09d 100644 --- a/src/plugins/pronoundb/pronoundbUtils.ts +++ b/src/plugins/pronoundb/pronoundbUtils.ts @@ -24,7 +24,7 @@ import { useAwaiter } from "@utils/react"; import { UserProfileStore, UserStore } from "@webpack/common"; import { settings } from "./settings"; -import { PronounCode, PronounMapping, PronounsResponse } from "./types"; +import { CachePronouns, PronounCode, PronounMapping, PronounsResponse } from "./types"; type PronounsWithSource = [string | null, string]; const EmptyPronouns: PronounsWithSource = [null, ""]; @@ -40,9 +40,9 @@ export const enum PronounSource { } // A map of cached pronouns so the same request isn't sent twice -const cache: Record = {}; +const cache: Record = {}; // A map of ids and callbacks that should be triggered on fetch -const requestQueue: Record void)[]> = {}; +const requestQueue: Record void)[]> = {}; // Executes all queued requests and calls their callbacks const bulkFetch = debounce(async () => { @@ -50,7 +50,7 @@ const bulkFetch = debounce(async () => { const pronouns = await bulkFetchPronouns(ids); for (const id of ids) { // Call all callbacks for the id - requestQueue[id]?.forEach(c => c(pronouns[id])); + requestQueue[id]?.forEach(c => c(pronouns[id] ? extractPronouns(pronouns[id].sets) : "")); delete requestQueue[id]; } }); @@ -78,8 +78,8 @@ export function useFormattedPronouns(id: string, useGlobalProfile: boolean = fal if (settings.store.pronounSource === PronounSource.PreferDiscord && discordPronouns) return [discordPronouns, "Discord"]; - if (result && result !== "unspecified") - return [formatPronouns(result), "PronounDB"]; + if (result && result !== PronounMapping.unspecified) + return [result, "PronounDB"]; return [discordPronouns, "Discord"]; } @@ -98,8 +98,9 @@ const NewLineRe = /\n+/g; // Gets the cached pronouns, if you're too impatient for a promise! export function getCachedPronouns(id: string): string | null { - const cached = cache[id]; - if (cached && cached !== "unspecified") return cached; + const cached = cache[id] ? extractPronouns(cache[id].sets) : undefined; + + if (cached && cached !== PronounMapping.unspecified) return cached; return cached || null; } @@ -125,7 +126,7 @@ async function bulkFetchPronouns(ids: string[]): Promise { params.append("ids", ids.join(",")); try { - const req = await fetch("https://pronoundb.org/api/v1/lookup-bulk?" + params.toString(), { + const req = await fetch("https://pronoundb.org/api/v2/lookup?" + params.toString(), { method: "GET", headers: { "Accept": "application/json", @@ -140,21 +141,24 @@ async function bulkFetchPronouns(ids: string[]): Promise { } catch (e) { // If the request errors, treat it as if no pronouns were found for all ids, and log it console.error("PronounDB fetching failed: ", e); - const dummyPronouns = Object.fromEntries(ids.map(id => [id, "unspecified"] as const)); + const dummyPronouns = Object.fromEntries(ids.map(id => [id, { sets: {} }] as const)); Object.assign(cache, dummyPronouns); return dummyPronouns; } } -export function formatPronouns(pronouns: string): string { +export function extractPronouns(pronounSet?: { [locale: string]: PronounCode[] }): string { + if (!pronounSet || !pronounSet.en) return PronounMapping.unspecified; + // PronounDB returns an empty set instead of {sets: {en: ["unspecified"]}}. + const pronouns = pronounSet.en; const { pronounsFormat } = Settings.plugins.PronounDB as { pronounsFormat: PronounsFormat, enabled: boolean; }; - // For capitalized pronouns, just return the mapping (it is by default capitalized) - if (pronounsFormat === PronounsFormat.Capitalized) return PronounMapping[pronouns]; - // If it is set to lowercase and a special code (any, ask, avoid), then just return the capitalized text - else if ( - pronounsFormat === PronounsFormat.Lowercase - && ["any", "ask", "avoid", "other"].includes(pronouns) - ) return PronounMapping[pronouns]; - // Otherwise (lowercase and not a special code), then convert the mapping to lowercase - else return PronounMapping[pronouns].toLowerCase(); + + if (pronouns.length === 1) { + // For capitalized pronouns or special codes (any, ask, avoid), we always return the normal (capitalized) string + if (pronounsFormat === PronounsFormat.Capitalized || ["any", "ask", "avoid", "other", "unspecified"].includes(pronouns[0])) + return PronounMapping[pronouns[0]]; + else return PronounMapping[pronouns[0]].toLowerCase(); + } + const pronounString = pronouns.map(p => p[0].toUpperCase() + p.slice(1)).join("/"); + return pronounsFormat === PronounsFormat.Capitalized ? pronounString : pronounString.toLowerCase(); } diff --git a/src/plugins/pronoundb/types.ts b/src/plugins/pronoundb/types.ts index 9cfd77c8..d099a7de 100644 --- a/src/plugins/pronoundb/types.ts +++ b/src/plugins/pronoundb/types.ts @@ -26,31 +26,29 @@ export interface UserProfilePronounsProps { } export interface PronounsResponse { - [id: string]: PronounCode; + [id: string]: { + sets?: { + [locale: string]: PronounCode[]; + } + } +} + +export interface CachePronouns { + sets?: { + [locale: string]: PronounCode[]; + } } export type PronounCode = keyof typeof PronounMapping; export const PronounMapping = { - hh: "He/Him", - hi: "He/It", - hs: "He/She", - ht: "He/They", - ih: "It/Him", - ii: "It/Its", - is: "It/She", - it: "It/They", - shh: "She/He", - sh: "She/Her", - si: "She/It", - st: "She/They", - th: "They/He", - ti: "They/It", - ts: "They/She", - tt: "They/Them", + he: "He/Him", + it: "It/Its", + she: "She/Her", + they: "They/Them", any: "Any pronouns", other: "Other pronouns", ask: "Ask me my pronouns", avoid: "Avoid pronouns, use my name", - unspecified: "Unspecified" + unspecified: "No pronouns specified.", } as const; From 0f9acba59ed4096eb6fc6a73da3c4c252c494c21 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sun, 12 May 2024 02:00:29 +0200 Subject: [PATCH 19/37] settingsSync: include date in filename for better sorting Co-authored-by: cd CreepArghhh_ <65649991+cd-CreepArghhh@users.noreply.github.com> --- src/plugins/fakeNitro/index.tsx | 4 ++-- src/utils/settingsSync.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index ea171c83..087928e9 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -111,7 +111,7 @@ const hyperLinkRegex = /\[.+?\]\((https?:\/\/.+?)\)/; const settings = definePluginSettings({ enableEmojiBypass: { - description: "Allow sending fake emojis", + description: "Allows sending fake emojis (also bypasses missing permission to use custom emojis)", type: OptionType.BOOLEAN, default: true, restartNeeded: true @@ -129,7 +129,7 @@ const settings = definePluginSettings({ restartNeeded: true }, enableStickerBypass: { - description: "Allow sending fake stickers", + description: "Allows sending fake stickers (also bypasses missing permission to use stickers)", type: OptionType.BOOLEAN, default: true, restartNeeded: true diff --git a/src/utils/settingsSync.ts b/src/utils/settingsSync.ts index 843922f2..f19928ac 100644 --- a/src/utils/settingsSync.ts +++ b/src/utils/settingsSync.ts @@ -18,7 +18,7 @@ import { showNotification } from "@api/Notifications"; import { PlainSettings, Settings } from "@api/Settings"; -import { Toasts } from "@webpack/common"; +import { moment, Toasts } from "@webpack/common"; import { deflateSync, inflateSync } from "fflate"; import { getCloudAuth, getCloudUrl } from "./cloud"; @@ -49,7 +49,7 @@ export async function exportSettings({ minify }: { minify?: boolean; } = {}) { } export async function downloadSettingsBackup() { - const filename = "vencord-settings-backup.json"; + const filename = `vencord-settings-backup-${moment().format("YYYY-MM-DD")}.json`; const backup = await exportSettings(); const data = new TextEncoder().encode(backup); From f21db5cb011202a50dc6b7fd5695e15e5dea564c Mon Sep 17 00:00:00 2001 From: Claire Date: Sat, 11 May 2024 17:08:17 -0700 Subject: [PATCH 20/37] add Native settings implementation (#2346) Co-authored-by: vee --- src/api/Commands/commandHelpers.ts | 2 +- src/api/Settings.ts | 2 +- src/main/settings.ts | 18 +++++++++++++++++- src/utils/mergeDefaults.ts | 24 ++++++++++++++++++++++++ src/utils/misc.tsx | 19 ------------------- 5 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 src/utils/mergeDefaults.ts diff --git a/src/api/Commands/commandHelpers.ts b/src/api/Commands/commandHelpers.ts index dc5ecfd6..2f703913 100644 --- a/src/api/Commands/commandHelpers.ts +++ b/src/api/Commands/commandHelpers.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { mergeDefaults } from "@utils/misc"; +import { mergeDefaults } from "@utils/mergeDefaults"; import { findByPropsLazy } from "@webpack"; import { MessageActions, SnowflakeUtils } from "@webpack/common"; import { Message } from "discord-types/general"; diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 696c12c2..490e6ef7 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -20,7 +20,7 @@ import { debounce } from "@shared/debounce"; import { SettingsStore as SettingsStoreClass } from "@shared/SettingsStore"; import { localStorage } from "@utils/localStorage"; import { Logger } from "@utils/Logger"; -import { mergeDefaults } from "@utils/misc"; +import { mergeDefaults } from "@utils/mergeDefaults"; import { putCloudSettings } from "@utils/settingsSync"; import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types"; import { React } from "@webpack/common"; diff --git a/src/main/settings.ts b/src/main/settings.ts index 96efdd67..3d367a94 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -7,6 +7,7 @@ import type { Settings } from "@api/Settings"; import { IpcEvents } from "@shared/IpcEvents"; import { SettingsStore } from "@shared/SettingsStore"; +import { mergeDefaults } from "@utils/mergeDefaults"; import { ipcMain } from "electron"; import { mkdirSync, readFileSync, writeFileSync } from "fs"; @@ -42,7 +43,22 @@ ipcMain.handle(IpcEvents.SET_SETTINGS, (_, data: Settings, pathToNotify?: string RendererSettings.setData(data, pathToNotify); }); -export const NativeSettings = new SettingsStore(readSettings("native", NATIVE_SETTINGS_FILE)); +export interface NativeSettings { + plugins: { + [plugin: string]: { + [setting: string]: any; + }; + }; +} + +const DefaultNativeSettings: NativeSettings = { + plugins: {} +}; + +const nativeSettings = readSettings("native", NATIVE_SETTINGS_FILE); +mergeDefaults(nativeSettings, DefaultNativeSettings); + +export const NativeSettings = new SettingsStore(nativeSettings); NativeSettings.addGlobalChangeListener(() => { try { diff --git a/src/utils/mergeDefaults.ts b/src/utils/mergeDefaults.ts new file mode 100644 index 00000000..58ba136d --- /dev/null +++ b/src/utils/mergeDefaults.ts @@ -0,0 +1,24 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** + * Recursively merges defaults into an object and returns the same object + * @param obj Object + * @param defaults Defaults + * @returns obj + */ +export function mergeDefaults(obj: T, defaults: T): T { + for (const key in defaults) { + const v = defaults[key]; + if (typeof v === "object" && !Array.isArray(v)) { + obj[key] ??= {} as any; + mergeDefaults(obj[key], v); + } else { + obj[key] ??= v; + } + } + return obj; +} diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx index 32010e59..fb08c93f 100644 --- a/src/utils/misc.tsx +++ b/src/utils/misc.tsx @@ -20,25 +20,6 @@ import { Clipboard, Toasts } from "@webpack/common"; import { DevsById } from "./constants"; -/** - * Recursively merges defaults into an object and returns the same object - * @param obj Object - * @param defaults Defaults - * @returns obj - */ -export function mergeDefaults(obj: T, defaults: T): T { - for (const key in defaults) { - const v = defaults[key]; - if (typeof v === "object" && !Array.isArray(v)) { - obj[key] ??= {} as any; - mergeDefaults(obj[key], v); - } else { - obj[key] ??= v; - } - } - return obj; -} - /** * Calls .join(" ") on the arguments * classes("one", "two") => "one two" From d6507947f57b84e7ed51c73d7596ab7df667d5de Mon Sep 17 00:00:00 2001 From: HAHALOSAH <67280050+HAHALOSAH@users.noreply.github.com> Date: Sat, 11 May 2024 17:32:44 -0700 Subject: [PATCH 21/37] Plugin Settings: fix text overflow for long plugin names (#2383) Co-authored-by: V --- src/components/VencordSettings/AddonCard.tsx | 20 ++++++++++-- src/components/VencordSettings/addonCard.css | 33 ++++++++++++++++++++ src/utils/constants.ts | 4 +++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/components/VencordSettings/AddonCard.tsx b/src/components/VencordSettings/AddonCard.tsx index c4c3aaca..1161a641 100644 --- a/src/components/VencordSettings/AddonCard.tsx +++ b/src/components/VencordSettings/AddonCard.tsx @@ -21,7 +21,7 @@ import "./addonCard.css"; import { classNameFactory } from "@api/Styles"; import { Badge } from "@components/Badge"; import { Switch } from "@components/Switch"; -import { Text } from "@webpack/common"; +import { Text, useRef } from "@webpack/common"; import type { MouseEventHandler, ReactNode } from "react"; const cl = classNameFactory("vc-addon-"); @@ -42,6 +42,8 @@ interface Props { } export function AddonCard({ disabled, isNew, name, infoButton, footer, author, enabled, setEnabled, description, onMouseEnter, onMouseLeave }: Props) { + const titleRef = useRef(null); + const titleContainerRef = useRef(null); return (
- {name}{isNew && } +
+
{ + const title = titleRef.current!; + const titleContainer = titleContainerRef.current!; + + title.style.setProperty("--offset", `${titleContainer.clientWidth - title.scrollWidth}px`); + title.style.setProperty("--duration", `${Math.max(0.5, (title.scrollWidth - titleContainer.clientWidth) / 7)}s`); + }} + > + {name} +
+
{isNew && }
{!!author && ( diff --git a/src/components/VencordSettings/addonCard.css b/src/components/VencordSettings/addonCard.css index f2dee11d..e46e4c29 100644 --- a/src/components/VencordSettings/addonCard.css +++ b/src/components/VencordSettings/addonCard.css @@ -62,3 +62,36 @@ .vc-addon-author::before { content: "by "; } + +.vc-addon-title-container { + width: 100%; + overflow: hidden; + height: 1.25em; + position: relative; +} + +.vc-addon-title { + position: absolute; + inset: 0; + overflow: hidden; + text-overflow: ellipsis; +} + +@keyframes vc-addon-title { + 0% { + transform: translateX(0); + } + + 50% { + transform: translateX(var(--offset)); + } + + 100% { + transform: translateX(0); + } +} + +.vc-addon-title:hover { + overflow: visible; + animation: vc-addon-title var(--duration) linear infinite; +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 09c27d15..c1e2cea2 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -462,6 +462,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Oleh Polisan", id: 242305263313485825n }, + HAHALOSAH: { + name: "HAHALOSAH", + id: 903418691268513883n + }, GabiRP: { name: "GabiRP", id: 507955112027750401n From a99354503f81cd41b6f3f8f33ee76c653f358560 Mon Sep 17 00:00:00 2001 From: Cats <42129397+Cats1337@users.noreply.github.com> Date: Sat, 11 May 2024 20:44:06 -0400 Subject: [PATCH 22/37] feat(Translate): add toggle for chat bar icon (#2418) --- src/plugins/translate/TranslateIcon.tsx | 4 ++-- src/plugins/translate/settings.ts | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/plugins/translate/TranslateIcon.tsx b/src/plugins/translate/TranslateIcon.tsx index cc0ed5e9..b22c488e 100644 --- a/src/plugins/translate/TranslateIcon.tsx +++ b/src/plugins/translate/TranslateIcon.tsx @@ -40,9 +40,9 @@ export function TranslateIcon({ height = 24, width = 24, className }: { height?: } export const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => { - const { autoTranslate } = settings.use(["autoTranslate"]); + const { autoTranslate, showChatBarButton } = settings.use(["autoTranslate", "showChatBarButton"]); - if (!isMainChat) return null; + if (!isMainChat || !showChatBarButton) return null; const toggle = () => { const newState = !autoTranslate; diff --git a/src/plugins/translate/settings.ts b/src/plugins/translate/settings.ts index cef003a8..65d84535 100644 --- a/src/plugins/translate/settings.ts +++ b/src/plugins/translate/settings.ts @@ -48,6 +48,11 @@ export const settings = definePluginSettings({ type: OptionType.BOOLEAN, description: "Automatically translate your messages before sending. You can also shift/right click the translate button to toggle this", default: false + }, + showChatBarButton: { + type: OptionType.BOOLEAN, + description: "Show translate button in chat bar", + default: true } }).withPrivateSettings<{ showAutoTranslateAlert: boolean; From bbec51fd19a23d5ab41cfa637901ece5d83f683c Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sun, 12 May 2024 03:23:00 +0200 Subject: [PATCH 23/37] but here's the bumper (v1.8.3) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9fd84f9b..0e584598 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.8.2", + "version": "1.8.3", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 5c7fa5578ce97b8f5c59e38a963d0e240eec833e Mon Sep 17 00:00:00 2001 From: nyan <24845294+nyakowint@users.noreply.github.com> Date: Sun, 12 May 2024 18:54:08 -0400 Subject: [PATCH 24/37] XSOverlay: Adjust message length timeout (#2445) --- src/plugins/xsOverlay.desktop/index.ts | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/plugins/xsOverlay.desktop/index.ts b/src/plugins/xsOverlay.desktop/index.ts index b666d116..5251959f 100644 --- a/src/plugins/xsOverlay.desktop/index.ts +++ b/src/plugins/xsOverlay.desktop/index.ts @@ -68,7 +68,6 @@ interface Call { ringing: string[]; } -const MuteStore = findByPropsLazy("isSuppressEveryoneEnabled"); const Notifs = findByPropsLazy("makeTextChatNotification"); const XSLog = new Logger("XSOverlay"); @@ -115,13 +114,13 @@ const settings = definePluginSettings({ }, timeout: { type: OptionType.NUMBER, - description: "Notif duration (secs)", - default: 1.0, + description: "Notification duration (secs)", + default: 3, }, - timeoutPerCharacter: { - type: OptionType.NUMBER, - description: "Duration multiplier per character", - default: 0.5 + lengthBasedTimeout: { + type: OptionType.BOOLEAN, + description: "Extend duration with message length", + default: true }, opacity: { type: OptionType.SLIDER, @@ -262,12 +261,11 @@ function shouldIgnoreForChannelType(channel: Channel) { } function sendMsgNotif(titleString: string, content: string, message: Message) { - const timeout = Math.max(settings.store.timeout, content.length * settings.store.timeoutPerCharacter); fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => { const msgData = { messageType: 1, index: 0, - timeout, + timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout, height: calculateHeight(content), opacity: settings.store.opacity, volume: settings.store.volume, @@ -286,7 +284,7 @@ function sendOtherNotif(content: string, titleString: string) { const msgData = { messageType: 1, index: 0, - timeout: settings.store.timeout, + timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout, height: calculateHeight(content), opacity: settings.store.opacity, volume: settings.store.volume, @@ -313,3 +311,10 @@ function calculateHeight(content: string) { if (content.length <= 300) return 200; return 250; } + +function calculateTimeout(content: string) { + if (content.length <= 100) return 3; + if (content.length <= 200) return 4; + if (content.length <= 300) return 5; + return 6; +} From fd7dafb15327efa95eedc313998e6900715f67f4 Mon Sep 17 00:00:00 2001 From: dolfies Date: Sun, 12 May 2024 19:07:12 -0400 Subject: [PATCH 25/37] fix(MessageLatency): Adjust for Discord kotlin clients (#2443) --- src/plugins/messageLatency/index.tsx | 47 +++++++++++++++++++++------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx index 48b57863..301e605f 100644 --- a/src/plugins/messageLatency/index.tsx +++ b/src/plugins/messageLatency/index.tsx @@ -24,19 +24,27 @@ interface Diff { seconds: number; } +const DISCORD_KT_DELAY = 1471228.928; const HiddenVisually = findExportedComponentLazy("HiddenVisually"); export default definePlugin({ name: "MessageLatency", description: "Displays an indicator for messages that took ≥n seconds to send", authors: [Devs.arHSM], + settings: definePluginSettings({ latency: { type: OptionType.NUMBER, description: "Threshold in seconds for latency indicator", default: 2 + }, + detectDiscordKotlin: { + type: OptionType.BOOLEAN, + description: "Detect old Discord Android clients", + default: true } }), + patches: [ { find: "showCommunicationDisabledStyles", @@ -46,6 +54,7 @@ export default definePlugin({ } } ], + stringDelta(delta: number) { const diff: Diff = { days: Math.round(delta / (60 * 60 * 24)), @@ -71,15 +80,25 @@ export default definePlugin({ ); }, ""); - return [ts || "0 seconds", diff.days === 17 && diff.hours === 1] as const; + return ts || "0 seconds"; }, + latencyTooltipData(message: Message) { + const { latency, detectDiscordKotlin } = this.settings.store; const { id, nonce } = message; // Message wasn't received through gateway if (!isNonNullish(nonce)) return null; - const delta = Math.round((SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce)) / 1000); + let isDiscordKotlin = false; + let delta = Math.round((SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce)) / 1000); + + // Old Discord Android clients have a delay of around 17 days + // This is a workaround for that + if (-delta >= DISCORD_KT_DELAY - 86400) { // One day of padding for good measure + isDiscordKotlin = detectDiscordKotlin; + delta += DISCORD_KT_DELAY; + } // Thanks dziurwa (I hate you) // This is when the user's clock is ahead @@ -87,15 +106,13 @@ export default definePlugin({ const abs = Math.abs(delta); const ahead = abs !== delta; - const [stringDelta, isSuspectedKotlinDiscord] = this.stringDelta(abs); - const isKotlinDiscord = ahead && isSuspectedKotlinDiscord; + const stringDelta = abs >= latency ? this.stringDelta(abs) : null; // Also thanks dziurwa // 2 minutes const TROLL_LIMIT = 2 * 60; - const { latency } = this.settings.store; - const fill: Fill = isKotlinDiscord + const fill: Fill = isDiscordKotlin ? ["status-positive", "status-positive", "text-muted"] : delta >= TROLL_LIMIT || ahead ? ["text-muted", "text-muted", "text-muted"] @@ -103,17 +120,24 @@ export default definePlugin({ ? ["status-danger", "text-muted", "text-muted"] : ["status-warning", "status-warning", "text-muted"]; - return abs >= latency ? { delta: stringDelta, ahead, fill, isKotlinDiscord } : null; + return (abs >= latency || isDiscordKotlin) ? { delta: stringDelta, ahead, fill, isDiscordKotlin } : null; }, + Tooltip() { return ErrorBoundary.wrap(({ message }: { message: Message; }) => { - const d = this.latencyTooltipData(message); if (!isNonNullish(d)) return null; + let text: string; + if (!d.delta) { + text = "User is suspected to be on an old Discord Android client"; + } else { + text = (d.ahead ? `This user's clock is ${d.delta} ahead.` : `This message was sent with a delay of ${d.delta}.`) + (d.isDiscordKotlin ? " User is suspected to be on an old Discord Android client." : ""); + } + return { @@ -126,8 +150,9 @@ export default definePlugin({ ; }); }, + Icon({ delta, fill, props }: { - delta: string; + delta: string | null; fill: Fill, props: { onClick(): void; @@ -147,7 +172,7 @@ export default definePlugin({ role="img" fill="none" style={{ marginRight: "8px", verticalAlign: -1 }} - aria-label={delta} + aria-label={delta ?? "Old Discord Android client"} aria-hidden="false" {...props} > From 902b6bcdf20c6e9b3170ace70c124834e6e4f8cd Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sun, 12 May 2024 20:58:26 -0300 Subject: [PATCH 26/37] PinDMs: ErrorBoundary renderChannel --- src/plugins/pinDms/index.tsx | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/plugins/pinDms/index.tsx b/src/plugins/pinDms/index.tsx index 010b5506..79503b9a 100644 --- a/src/plugins/pinDms/index.tsx +++ b/src/plugins/pinDms/index.tsx @@ -320,25 +320,26 @@ export default definePlugin({ ); - }), + }, { noop: true }), renderChannel(sectionIndex: number, index: number, ChannelComponent: React.ComponentType) { - const { channel, category } = this.getChannel(sectionIndex, index, this.instance.props.channels); + return ErrorBoundary.wrap(() => { + const { channel, category } = this.getChannel(sectionIndex, index, this.instance.props.channels); - if (!channel || !category) return null; - if (this.isChannelHidden(sectionIndex, index)) return null; + if (!channel || !category) return null; + if (this.isChannelHidden(sectionIndex, index)) return null; - return ( - - {channel.id} - - ); + return ( + + {channel.id} + + ); + }, { noop: true }); }, - getChannel(sectionIndex: number, index: number, channels: Record) { const category = categories[sectionIndex - 1]; if (!category) return { channel: null, category: null }; From 1f1c80c5f3bf897cec82d08302a2d3f396436ed0 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 13 May 2024 03:30:10 +0200 Subject: [PATCH 27/37] ValidUser: fix crashing when viewing a valid-userd staff's profile --- src/plugins/validUser/index.tsx | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/plugins/validUser/index.tsx b/src/plugins/validUser/index.tsx index 7a21ac86..fd88ca9c 100644 --- a/src/plugins/validUser/index.tsx +++ b/src/plugins/validUser/index.tsx @@ -18,6 +18,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; +import { isNonNullish } from "@utils/guards"; import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; import definePlugin from "@utils/types"; @@ -27,19 +28,20 @@ import type { ComponentType, ReactNode } from "react"; // LYING to the type checker here const UserFlags = Constants.UserFlags as Record; const badges: Record = { - "active_developer": { id: "active_developer", description: "Active Developer", icon: "6bdc42827a38498929a4920da12695d9", link: "https://support-dev.discord.com/hc/en-us/articles/10113997751447" }, - "bug_hunter_level_1": { id: "bug_hunter_level_1", description: "Discord Bug Hunter", icon: "2717692c7dca7289b35297368a940dd0", link: "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs" }, - "bug_hunter_level_2": { id: "bug_hunter_level_2", description: "Discord Bug Hunter", icon: "848f79194d4be5ff5f81505cbd0ce1e6", link: "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs" }, - "certified_moderator": { id: "certified_moderator", description: "Moderator Programs Alumni", icon: "fee1624003e2fee35cb398e125dc479b", link: "https://discord.com/safety" }, - "discord_employee": { id: "staff", description: "Discord Staff", icon: "5e74e9b61934fc1f67c65515d1f7e60d", link: "https://discord.com/company" }, - "hypesquad": { id: "hypesquad", description: "HypeSquad Events", icon: "bf01d1073931f921909045f3a39fd264", link: "https://discord.com/hypesquad" }, - "hypesquad_online_house_1": { id: "hypesquad_house_1", description: "HypeSquad Bravery", icon: "8a88d63823d8a71cd5e390baa45efa02", link: "https://discord.com/settings/hypesquad-online" }, - "hypesquad_online_house_2": { id: "hypesquad_house_2", description: "HypeSquad Brilliance", icon: "011940fd013da3f7fb926e4a1cd2e618", link: "https://discord.com/settings/hypesquad-online" }, - "hypesquad_online_house_3": { id: "hypesquad_house_3", description: "HypeSquad Balance", icon: "3aa41de486fa12454c3761e8e223442e", link: "https://discord.com/settings/hypesquad-online" }, - "partner": { id: "partner", description: "Partnered Server Owner", icon: "3f9748e53446a137a052f3454e2de41e", link: "https://discord.com/partners" }, - "premium": { id: "premium", description: "Subscriber", icon: "2ba85e8026a8614b640c2837bcdfe21b", link: "https://discord.com/settings/premium" }, - "premium_early_supporter": { id: "early_supporter", description: "Early Supporter", icon: "7060786766c9c840eb3019e725d2b358", link: "https://discord.com/settings/premium" }, - "verified_developer": { id: "verified_developer", description: "Early Verified Bot Developer", icon: "6df5892e0f35b051f8b61eace34f4967" }, + active_developer: { id: "active_developer", description: "Active Developer", icon: "6bdc42827a38498929a4920da12695d9", link: "https://support-dev.discord.com/hc/en-us/articles/10113997751447" }, + bug_hunter_level_1: { id: "bug_hunter_level_1", description: "Discord Bug Hunter", icon: "2717692c7dca7289b35297368a940dd0", link: "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs" }, + bug_hunter_level_2: { id: "bug_hunter_level_2", description: "Discord Bug Hunter", icon: "848f79194d4be5ff5f81505cbd0ce1e6", link: "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs" }, + certified_moderator: { id: "certified_moderator", description: "Moderator Programs Alumni", icon: "fee1624003e2fee35cb398e125dc479b", link: "https://discord.com/safety" }, + discord_employee: { id: "staff", description: "Discord Staff", icon: "5e74e9b61934fc1f67c65515d1f7e60d", link: "https://discord.com/company" }, + get staff() { return this.discord_employee; }, + hypesquad: { id: "hypesquad", description: "HypeSquad Events", icon: "bf01d1073931f921909045f3a39fd264", link: "https://discord.com/hypesquad" }, + hypesquad_online_house_1: { id: "hypesquad_house_1", description: "HypeSquad Bravery", icon: "8a88d63823d8a71cd5e390baa45efa02", link: "https://discord.com/settings/hypesquad-online" }, + hypesquad_online_house_2: { id: "hypesquad_house_2", description: "HypeSquad Brilliance", icon: "011940fd013da3f7fb926e4a1cd2e618", link: "https://discord.com/settings/hypesquad-online" }, + hypesquad_online_house_3: { id: "hypesquad_house_3", description: "HypeSquad Balance", icon: "3aa41de486fa12454c3761e8e223442e", link: "https://discord.com/settings/hypesquad-online" }, + partner: { id: "partner", description: "Partnered Server Owner", icon: "3f9748e53446a137a052f3454e2de41e", link: "https://discord.com/partners" }, + premium: { id: "premium", description: "Subscriber", icon: "2ba85e8026a8614b640c2837bcdfe21b", link: "https://discord.com/settings/premium" }, + premium_early_supporter: { id: "early_supporter", description: "Early Supporter", icon: "7060786766c9c840eb3019e725d2b358", link: "https://discord.com/settings/premium" }, + verified_developer: { id: "verified_developer", description: "Early Verified Bot Developer", icon: "6df5892e0f35b051f8b61eace34f4967" }, }; const fetching = new Set(); @@ -93,7 +95,8 @@ async function getUser(id: string) { userObj = UserStore.getUser(id); const fakeBadges: ProfileBadge[] = Object.entries(UserFlags) .filter(([_, flag]) => !isNaN(flag) && userObj.hasFlag(flag)) - .map(([key]) => badges[key.toLowerCase()]); + .map(([key]) => badges[key.toLowerCase()]) + .filter(isNonNullish); if (user.premium_type || !user.bot && (user.banner || user.avatar?.startsWith?.("a_"))) fakeBadges.push(badges.premium); From f6765818d2248292b4dacf74c3ae3e2eae5856d4 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 13 May 2024 03:34:01 +0200 Subject: [PATCH 28/37] ValidUser: fix rendering old mentions when message is edited Fixes https://github.com/Vendicated/Vencord/issues/2451 --- src/plugins/pinDms/index.tsx | 2 +- src/plugins/validUser/index.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/pinDms/index.tsx b/src/plugins/pinDms/index.tsx index 79503b9a..60484561 100644 --- a/src/plugins/pinDms/index.tsx +++ b/src/plugins/pinDms/index.tsx @@ -83,7 +83,7 @@ export default definePlugin({ // Rendering { match: /"renderRow",(\i)=>{(?<="renderDM",.+?(\i\.default),\{channel:.+?)/, - replace: "$&if($self.isChannelIndex($1.section, $1.row))return $self.renderChannel($1.section,$1.row,$2);" + replace: "$&if($self.isChannelIndex($1.section, $1.row))return $self.renderChannel($1.section,$1.row,$2)();" }, { match: /"renderSection",(\i)=>{/, diff --git a/src/plugins/validUser/index.tsx b/src/plugins/validUser/index.tsx index fd88ca9c..64b734f5 100644 --- a/src/plugins/validUser/index.tsx +++ b/src/plugins/validUser/index.tsx @@ -23,7 +23,7 @@ import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; import definePlugin from "@utils/types"; import { Constants, FluxDispatcher, RestAPI, UserProfileStore, UserStore, useState } from "@webpack/common"; -import type { ComponentType, ReactNode } from "react"; +import { type ComponentType, type ReactNode } from "react"; // LYING to the type checker here const UserFlags = Constants.UserFlags as Record; @@ -205,6 +205,7 @@ export default definePlugin({ return ( Date: Mon, 13 May 2024 01:09:19 -0400 Subject: [PATCH 29/37] feat(ShowHiddenThings): Remove Discovery banned/NSFW filters (#2453) --- src/plugins/showHiddenThings/README.md | 4 +++- src/plugins/showHiddenThings/index.ts | 24 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/plugins/showHiddenThings/README.md b/src/plugins/showHiddenThings/README.md index e969391e..753e5c14 100644 --- a/src/plugins/showHiddenThings/README.md +++ b/src/plugins/showHiddenThings/README.md @@ -1,6 +1,6 @@ # ShowHiddenThings -Displays various moderator-only elements regardless of permissions. +Displays various hidden & moderator-only things regardless of permissions. ## Features @@ -15,3 +15,5 @@ Displays various moderator-only elements regardless of permissions. ![](https://github.com/Vendicated/Vencord/assets/47677887/3dac95dd-841c-4c15-ad87-2db7bd1e4dab) - Disable filters in Server Discovery search that hide servers that don't meet discovery criteria + +- Disable filters in Server Discovery search that hide NSFW & disallowed servers diff --git a/src/plugins/showHiddenThings/index.ts b/src/plugins/showHiddenThings/index.ts index 1858582a..8de70aca 100644 --- a/src/plugins/showHiddenThings/index.ts +++ b/src/plugins/showHiddenThings/index.ts @@ -41,13 +41,18 @@ const settings = definePluginSettings({ description: "Disable filters in Server Discovery search that hide servers that don't meet discovery criteria.", default: true, }, + disableDisallowedDiscoveryFilters: { + type: OptionType.BOOLEAN, + description: "Disable filters in Server Discovery search that hide NSFW & disallowed servers.", + default: true, + }, }); migratePluginSettings("ShowHiddenThings", "ShowTimeouts"); export default definePlugin({ name: "ShowHiddenThings", tags: ["ShowTimeouts", "ShowInvitesPaused", "ShowModView", "DisableDiscoveryFilters"], - description: "Displays various moderator-only elements regardless of permissions.", + description: "Displays various hidden & moderator-only things regardless of permissions.", authors: [Devs.Dolfies], patches: [ { @@ -81,6 +86,23 @@ export default definePlugin({ match: /filters:\i\.join\(" AND "\),facets:\[/, replace: "facets:[" } + }, + { + find: "DiscoveryBannedSearchWords.includes", + predicate: () => settings.store.disableDisallowedDiscoveryFilters, + replacement: { + match: /(?<=function\(\){)(?=.{0,130}DiscoveryBannedSearchWords\.includes)/, + replace: "return false;" + } + }, + { + find: "Endpoints.GUILD_DISCOVERY_VALID_TERM", + predicate: () => settings.store.disableDisallowedDiscoveryFilters, + all: true, + replacement: { + match: /\i\.HTTP\.get\(\{url:\i\.Endpoints\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0\}\);/g, + replace: "Promise.resolve({ body: { valid: true } });" + } } ], settings, From 9621dc7bb39087a752910eab170701fb6b4be585 Mon Sep 17 00:00:00 2001 From: axiand Date: Tue, 14 May 2024 03:16:49 +0200 Subject: [PATCH 30/37] EmoteCloner: allow cloning from reactions (#2458) --- src/plugins/emoteCloner/index.tsx | 5 +++-- src/utils/constants.ts | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/plugins/emoteCloner/index.tsx b/src/plugins/emoteCloner/index.tsx index cd9890a8..8c3ded6a 100644 --- a/src/plugins/emoteCloner/index.tsx +++ b/src/plugins/emoteCloner/index.tsx @@ -322,8 +322,9 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) = switch (favoriteableType) { case "emoji": const match = props.message.content.match(RegExp(`|https://cdn\\.discordapp\\.com/emojis/${favoriteableId}\\.`)); - if (!match) return; - const name = match[1] ?? "FakeNitroEmoji"; + const reaction = props.message.reactions.find(reaction => reaction.emoji.id === favoriteableId); + if (!match && !reaction) return; + const name = (match && match[1]) ?? reaction?.emoji.name ?? "FakeNitroEmoji"; return buildMenuItem("Emoji", () => ({ id: favoriteableId, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index c1e2cea2..e446a27b 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -473,6 +473,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ ImBanana: { name: "Im_Banana", id: 635250116688871425n + }, + xocherry: { + name: "xocherry", + id: 221288171013406720n } } satisfies Record); From bd6f9e6f32e5ad95f7e16f323746a5770b1208de Mon Sep 17 00:00:00 2001 From: Amia <9750071+aamiaa@users.noreply.github.com> Date: Tue, 14 May 2024 03:22:49 +0200 Subject: [PATCH 31/37] fix(MutualGroupDMs): properly pass props (#2457) Co-authored-by: vee --- src/plugins/mutualGroupDMs/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index e787fefb..94998677 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -56,12 +56,12 @@ export default definePlugin({ find: ".UserProfileSections.USER_INFO_CONNECTIONS:", replacement: { match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/, - replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs($1,$2);" + replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});" } } ], - renderMutualGDMs: ErrorBoundary.wrap((user: User, onClose: () => void) => { + renderMutualGDMs: ErrorBoundary.wrap(({ user, onClose }: { user: User, onClose: () => void; }) => { const entries = ChannelStore.getSortedPrivateChannels().filter(c => c.isGroupDM() && c.recipients.includes(user.id)).map(c => ( Date: Tue, 14 May 2024 04:39:05 +0300 Subject: [PATCH 32/37] FakeNitro: allow using subscription-locked emojis (#2456) Co-authored-by: Vendicated --- src/plugins/fakeNitro/index.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 087928e9..29318366 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -39,6 +39,7 @@ const StickerStore = findStoreLazy("StickersStore") as { const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore"); const ProtoUtils = findByPropsLazy("BINARY_READ_OPTIONS"); +const RoleSubscriptionEmojiUtils = findByPropsLazy("isUnusableRoleSubscriptionEmoji"); function searchProtoClassField(localName: string, protoClass: any) { const field = protoClass?.fields?.find((field: any) => field.localName === localName); @@ -408,6 +409,15 @@ export default definePlugin({ match: /canUseCustomNotificationSounds:function\(\i\){/, replace: "$&return true;" } + }, + // Allows the usage of subscription-locked emojis + { + find: "isUnusableRoleSubscriptionEmoji:function", + replacement: { + match: /isUnusableRoleSubscriptionEmoji:function/, + // replace the original export with a func that always returns false and alias the original + replace: "isUnusableRoleSubscriptionEmoji:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function" + } } ], @@ -804,6 +814,9 @@ export default definePlugin({ if (e.require_colons === false) return true; if (e.available === false) return false; + const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji; + if (isUnusableRoleSubEmoji(e, this.guildId)) return false; + if (this.canUseEmotes) return e.guildId === this.guildId || hasExternalEmojiPerms(channelId); else From 892167420aa2db6fc6563c7e6ace5e37a4c946ea Mon Sep 17 00:00:00 2001 From: Anubis <102488279+AnubisNekhet@users.noreply.github.com> Date: Tue, 14 May 2024 07:27:20 +0530 Subject: [PATCH 33/37] MessageLogger: use discord variables instead of hardcoded colors (#2428) --- src/plugins/messageLogger/deleteStyleOverlay.css | 2 +- src/plugins/messageLogger/deleteStyleText.css | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/messageLogger/deleteStyleOverlay.css b/src/plugins/messageLogger/deleteStyleOverlay.css index 3778e80b..59e4ac66 100644 --- a/src/plugins/messageLogger/deleteStyleOverlay.css +++ b/src/plugins/messageLogger/deleteStyleOverlay.css @@ -1,3 +1,3 @@ .messagelogger-deleted { - background-color: rgba(240 71 71 / 15%) !important; + background-color: hsla(var(--red-430-hsl, 0 85% 61%) / 15%) !important; } diff --git a/src/plugins/messageLogger/deleteStyleText.css b/src/plugins/messageLogger/deleteStyleText.css index 8fb8bf12..3477ef22 100644 --- a/src/plugins/messageLogger/deleteStyleText.css +++ b/src/plugins/messageLogger/deleteStyleText.css @@ -1,19 +1,19 @@ /* Message content highlighting */ .messagelogger-deleted [class*="contents"] > :is(div, h1, h2, h3, p) { - color: #f04747 !important; + color: var(--status-danger, #f04747) !important; } /* Bot "thinking" text highlighting */ .messagelogger-deleted [class*="colorStandard"] { - color: #f04747 !important; + color: var(--status-danger, #f04747) !important; } /* Embed highlighting */ .messagelogger-deleted article :is(div, span, h1, h2, h3, p) { - color: #f04747 !important; + color: var(--status-danger, #f04747) !important; } .messagelogger-deleted a { - color: #be3535 !important; + color: var(--red-460, #be3535) !important; text-decoration: underline; } From 9dc8e4e24493b0dd5bd4758eb419e5d1c4052029 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 13 May 2024 22:45:57 -0300 Subject: [PATCH 34/37] Properly ErrorBoundary recent changes --- src/plugins/betterNotes/index.tsx | 12 ++-- src/plugins/pauseInvitesForever/index.tsx | 71 ++++++++++++----------- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/src/plugins/betterNotes/index.tsx b/src/plugins/betterNotes/index.tsx index 5ebca1f8..cacdba5f 100644 --- a/src/plugins/betterNotes/index.tsx +++ b/src/plugins/betterNotes/index.tsx @@ -61,7 +61,7 @@ export default definePlugin({ find: ".popularApplicationCommandIds,", replacement: { match: /lastSection:(!?\i)}\),/, - replace: "$&$self.patchPadding($1)," + replace: "$&$self.patchPadding({lastSection:$1})," } } ], @@ -81,12 +81,10 @@ export default definePlugin({ } }, - patchPadding(lastSection: any) { - if (!lastSection) return; + patchPadding: ErrorBoundary.wrap(({ lastSection }) => { + if (!lastSection) return null; return ( - -
-
+
); - } + }) }); diff --git a/src/plugins/pauseInvitesForever/index.tsx b/src/plugins/pauseInvitesForever/index.tsx index 1e71a4ed..e7abaa17 100644 --- a/src/plugins/pauseInvitesForever/index.tsx +++ b/src/plugins/pauseInvitesForever/index.tsx @@ -24,6 +24,22 @@ import { GuildStore, i18n, RestAPI } from "@webpack/common"; const { InvitesDisabledExperiment } = findByPropsLazy("InvitesDisabledExperiment"); +function showDisableInvites(guildId: string) { + // Once the experiment is removed, this should keep working + const { enableInvitesDisabled } = InvitesDisabledExperiment?.getCurrentConfig?.({ guildId }) ?? { enableInvitesDisabled: true }; + // @ts-ignore + return enableInvitesDisabled && !GuildStore.getGuild(guildId).hasFeature("INVITES_DISABLED"); +} + +function disableInvites(guildId: string) { + const guild = GuildStore.getGuild(guildId); + const features = [...guild.features, "INVITES_DISABLED"]; + RestAPI.patch({ + url: `/guilds/${guild.id}`, + body: { features }, + }); +} + export default definePlugin({ name: "PauseInvitesForever", tags: ["DisableInvitesForever"], @@ -33,44 +49,29 @@ export default definePlugin({ patches: [ { find: "Messages.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION", - replacement: [{ - match: /children:\i\.\i\.\i\.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION/, - replace: "children: $self.renderInvitesLabel(arguments[0].guildId, setChecked)", - }, - { - match: /(\i\.hasDMsDisabled\)\(\i\),\[\i,(\i)\]=\i\.useState\(\i\))/, - replace: "$1,setChecked=$2" - }] + group: true, + replacement: [ + { + match: /children:\i\.\i\.\i\.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION/, + replace: "children: $self.renderInvitesLabel({guildId:arguments[0].guildId,setChecked})", + }, + { + match: /(\i\.hasDMsDisabled\)\(\i\),\[\i,(\i)\]=\i\.useState\(\i\))/, + replace: "$1,setChecked=$2" + } + ] } ], - showDisableInvites(guildId: string) { - // Once the experiment is removed, this should keep working - const { enableInvitesDisabled } = InvitesDisabledExperiment?.getCurrentConfig?.({ guildId }) ?? { enableInvitesDisabled: true }; - // @ts-ignore - return enableInvitesDisabled && !GuildStore.getGuild(guildId).hasFeature("INVITES_DISABLED"); - }, - - disableInvites(guildId: string) { - const guild = GuildStore.getGuild(guildId); - const features = [...guild.features, "INVITES_DISABLED"]; - RestAPI.patch({ - url: `/guilds/${guild.id}`, - body: { features }, - }); - }, - - renderInvitesLabel(guildId: string, setChecked: Function) { + renderInvitesLabel: ErrorBoundary.wrap(({ guildId, setChecked }) => { return ( - -
- {i18n.Messages.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION} - {this.showDisableInvites(guildId) && { - setChecked(true); - this.disableInvites(guildId); - }}> Pause Indefinitely.} -
-
+
+ {i18n.Messages.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION} + {showDisableInvites(guildId) && { + setChecked(true); + disableInvites(guildId); + }}> Pause Indefinitely.} +
); - } + }) }); From d4ebfc233fde9e54b76ca17cb2cac82fbb4d6aa9 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 13 May 2024 23:00:02 -0300 Subject: [PATCH 35/37] Make all RestAPI calls use Endpoints object --- src/plugins/betterSessions/index.tsx | 4 ++-- src/plugins/emoteCloner/index.tsx | 6 +++--- src/plugins/friendInvites/index.ts | 4 ++-- src/plugins/invisibleChat.desktop/index.tsx | 4 ++-- src/plugins/messageLinkEmbeds/index.tsx | 3 ++- src/plugins/pauseInvitesForever/index.tsx | 4 ++-- src/plugins/unsuppressEmbeds/index.tsx | 4 ++-- src/plugins/validUser/index.tsx | 2 +- src/plugins/voiceMessages/index.tsx | 4 ++-- src/plugins/whoReacted/index.tsx | 4 ++-- src/utils/discord.tsx | 4 ++-- 11 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/plugins/betterSessions/index.tsx b/src/plugins/betterSessions/index.tsx index 539508f8..9c93289c 100644 --- a/src/plugins/betterSessions/index.tsx +++ b/src/plugins/betterSessions/index.tsx @@ -22,7 +22,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy, findExportedComponentLazy, findStoreLazy } from "@webpack"; -import { React, RestAPI, Tooltip } from "@webpack/common"; +import { Constants, React, RestAPI, Tooltip } from "@webpack/common"; import { RenameButton } from "./components/RenameButton"; import { Session, SessionInfo } from "./types"; @@ -168,7 +168,7 @@ export default definePlugin({ async checkNewSessions() { const data = await RestAPI.get({ - url: "/auth/sessions" + url: Constants.Endpoints.AUTH_SESSIONS }); for (const session of data.body.user_sessions) { diff --git a/src/plugins/emoteCloner/index.tsx b/src/plugins/emoteCloner/index.tsx index 8c3ded6a..b456c351 100644 --- a/src/plugins/emoteCloner/index.tsx +++ b/src/plugins/emoteCloner/index.tsx @@ -24,7 +24,7 @@ import { Margins } from "@utils/margins"; import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal"; import definePlugin from "@utils/types"; import { findByPropsLazy, findStoreLazy } from "@webpack"; -import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; +import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; import { Promisable } from "type-fest"; const StickersStore = findStoreLazy("StickersStore"); @@ -64,7 +64,7 @@ async function fetchSticker(id: string) { if (cached) return cached; const { body } = await RestAPI.get({ - url: `/stickers/${id}` + url: Constants.Endpoints.STICKER(id) }); FluxDispatcher.dispatch({ @@ -83,7 +83,7 @@ async function cloneSticker(guildId: string, sticker: Sticker) { data.append("file", await fetchBlob(getUrl(sticker))); const { body } = await RestAPI.post({ - url: `/guilds/${guildId}/stickers`, + url: Constants.Endpoints.GUILD_STICKER_PACKS(guildId), body: data, }); diff --git a/src/plugins/friendInvites/index.ts b/src/plugins/friendInvites/index.ts index e5ff447e..47e312c3 100644 --- a/src/plugins/friendInvites/index.ts +++ b/src/plugins/friendInvites/index.ts @@ -20,7 +20,7 @@ import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { RestAPI, UserStore } from "@webpack/common"; +import { Constants, RestAPI, UserStore } from "@webpack/common"; const FriendInvites = findByPropsLazy("createFriendInvite"); const { uuid4 } = findByPropsLazy("uuid4"); @@ -58,7 +58,7 @@ export default definePlugin({ if (uses === 1) { const random = uuid4(); const { body: { invite_suggestions } } = await RestAPI.post({ - url: "/friend-finder/find-friends", + url: Constants.Endpoints.FRIEND_FINDER, body: { modified_contacts: { [random]: [1, "", ""] diff --git a/src/plugins/invisibleChat.desktop/index.tsx b/src/plugins/invisibleChat.desktop/index.tsx index fcb0af71..3dfe51e7 100644 --- a/src/plugins/invisibleChat.desktop/index.tsx +++ b/src/plugins/invisibleChat.desktop/index.tsx @@ -23,7 +23,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getStegCloak } from "@utils/dependencies"; import definePlugin, { OptionType } from "@utils/types"; -import { ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common"; +import { ChannelStore, Constants, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common"; import { Message } from "discord-types/general"; import { buildDecModal } from "./components/DecryptionModal"; @@ -153,7 +153,7 @@ export default definePlugin({ // Gets the Embed of a Link async getEmbed(url: URL): Promise { const { body } = await RestAPI.post({ - url: "/unfurler/embed-urls", + url: Constants.Endpoints.UNFURL_EMBED_URLS, body: { urls: [url] } diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index 2a5f8828..5c306362 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -27,6 +27,7 @@ import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Button, ChannelStore, + Constants, FluxDispatcher, GuildStore, IconUtils, @@ -132,7 +133,7 @@ async function fetchMessage(channelID: string, messageID: string) { messageCache.set(messageID, { fetched: false }); const res = await RestAPI.get({ - url: `/channels/${channelID}/messages`, + url: Constants.Endpoints.MESSAGES(channelID), query: { limit: 1, around: messageID diff --git a/src/plugins/pauseInvitesForever/index.tsx b/src/plugins/pauseInvitesForever/index.tsx index e7abaa17..6a70be1a 100644 --- a/src/plugins/pauseInvitesForever/index.tsx +++ b/src/plugins/pauseInvitesForever/index.tsx @@ -20,7 +20,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { GuildStore, i18n, RestAPI } from "@webpack/common"; +import { Constants, GuildStore, i18n, RestAPI } from "@webpack/common"; const { InvitesDisabledExperiment } = findByPropsLazy("InvitesDisabledExperiment"); @@ -35,7 +35,7 @@ function disableInvites(guildId: string) { const guild = GuildStore.getGuild(guildId); const features = [...guild.features, "INVITES_DISABLED"]; RestAPI.patch({ - url: `/guilds/${guild.id}`, + url: Constants.Endpoints.GUILD(guildId), body: { features }, }); } diff --git a/src/plugins/unsuppressEmbeds/index.tsx b/src/plugins/unsuppressEmbeds/index.tsx index 0e87201c..16debf71 100644 --- a/src/plugins/unsuppressEmbeds/index.tsx +++ b/src/plugins/unsuppressEmbeds/index.tsx @@ -20,7 +20,7 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co import { ImageInvisible, ImageVisible } from "@components/Icons"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common"; +import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common"; const EMBED_SUPPRESSED = 1 << 2; @@ -44,7 +44,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channe icon={isEmbedSuppressed ? ImageVisible : ImageInvisible} action={() => RestAPI.patch({ - url: `/channels/${channel.id}/messages/${messageId}`, + url: Constants.Endpoints.MESSAGE(channel.id, messageId), body: { flags: isEmbedSuppressed ? flags & ~EMBED_SUPPRESSED : flags | EMBED_SUPPRESSED } }) } diff --git a/src/plugins/validUser/index.tsx b/src/plugins/validUser/index.tsx index 64b734f5..4825cdaa 100644 --- a/src/plugins/validUser/index.tsx +++ b/src/plugins/validUser/index.tsx @@ -75,7 +75,7 @@ async function getUser(id: string) { if (userObj) return userObj; - const user: any = await RestAPI.get({ url: `/users/${id}` }).then(response => { + const user: any = await RestAPI.get({ url: Constants.Endpoints.USER(id) }).then(response => { FluxDispatcher.dispatch({ type: "USER_UPDATE", user: response.body, diff --git a/src/plugins/voiceMessages/index.tsx b/src/plugins/voiceMessages/index.tsx index 2f232f34..40e877df 100644 --- a/src/plugins/voiceMessages/index.tsx +++ b/src/plugins/voiceMessages/index.tsx @@ -28,7 +28,7 @@ import { useAwaiter } from "@utils/react"; import definePlugin from "@utils/types"; import { chooseFile } from "@utils/web"; import { findByPropsLazy, findStoreLazy } from "@webpack"; -import { Button, Card, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common"; +import { Button, Card, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common"; import { ComponentType } from "react"; import { VoiceRecorderDesktop } from "./DesktopRecorder"; @@ -98,7 +98,7 @@ function sendAudio(blob: Blob, meta: AudioMetadata) { upload.on("complete", () => { RestAPI.post({ - url: `/channels/${channelId}/messages`, + url: Constants.Endpoints.MESSAGES(channelId), body: { flags: 1 << 13, channel_id: channelId, diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index b3728c21..5721dc91 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -23,7 +23,7 @@ import { Queue } from "@utils/Queue"; import { useForceUpdater } from "@utils/react"; import definePlugin from "@utils/types"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; -import { ChannelStore, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common"; +import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common"; import { CustomEmoji } from "@webpack/types"; import { Message, ReactionEmoji, User } from "discord-types/general"; @@ -36,7 +36,7 @@ let reactions: Record; function fetchReactions(msg: Message, emoji: ReactionEmoji, type: number) { const key = emoji.name + (emoji.id ? `:${emoji.id}` : ""); return RestAPI.get({ - url: `/channels/${msg.channel_id}/messages/${msg.id}/reactions/${key}`, + url: Constants.Endpoints.REACTIONS(msg.channel_id, msg.id, key), query: { limit: 100, type diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index 74e1aefe..57202ba3 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -17,7 +17,7 @@ */ import { MessageObject } from "@api/MessageEvents"; -import { ChannelStore, ComponentDispatch, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; +import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { Guild, Message, User } from "discord-types/general"; import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal"; @@ -162,7 +162,7 @@ export async function fetchUserProfile(id: string, options?: FetchUserProfileOpt FluxDispatcher.dispatch({ type: "USER_PROFILE_FETCH_START", userId: id }); const { body } = await RestAPI.get({ - url: `/users/${id}/profile`, + url: Constants.Endpoints.USER_PROFILE(id), query: { with_mutual_guilds: false, with_mutual_friends_count: false, From 12376c622ee1568d7c4633272362534ca7aea2df Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 14 May 2024 18:52:35 +0200 Subject: [PATCH 36/37] fix settings ui on canary --- src/plugins/_core/settings.tsx | 112 ++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 37 deletions(-) diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index 772ee9b6..0a6aea8b 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -26,7 +26,7 @@ import UpdaterTab from "@components/VencordSettings/UpdaterTab"; import VencordTab from "@components/VencordSettings/VencordTab"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { React } from "@webpack/common"; +import { i18n, React } from "@webpack/common"; import gitHash from "~git-hash"; @@ -36,41 +36,55 @@ export default definePlugin({ authors: [Devs.Ven, Devs.Megu], required: true, - patches: [{ - find: ".versionHash", - replacement: [ - { - match: /\[\(0,.{1,3}\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/, - replace: (m, component, props) => { - props = props.replace(/children:\[.+\]/, ""); - return `${m},$self.makeInfoElements(${component}, ${props})`; + patches: [ + { + find: ".versionHash", + replacement: [ + { + match: /\[\(0,.{1,3}\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/, + replace: (m, component, props) => { + props = props.replace(/children:\[.+\]/, ""); + return `${m},$self.makeInfoElements(${component}, ${props})`; + } } + ] + }, + // Discord Stable + // FIXME: remove once change merged to stable + { + find: "Messages.ACTIVITY_SETTINGS", + replacement: { + get match() { + switch (Settings.plugins.Settings.settingsLocation) { + case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS/; + case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS/; + case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS/; + case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/; + case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/; + case "aboveActivity": + default: + return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS/; + } + }, + replace: "...$self.makeSettingsCategories($1),$&" + } + }, + // Discord Canary + { + find: "Messages.ACTIVITY_SETTINGS", + replacement: { + match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/, + replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}` + } + }, + { + find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL", + replacement: { + match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.UserSettingsSections\).*?(\i)\.default\.open\()/, + replace: "$2.default.open($1);return;" } - ] - }, { - find: "Messages.ACTIVITY_SETTINGS", - replacement: { - get match() { - switch (Settings.plugins.Settings.settingsLocation) { - case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS/; - case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS/; - case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS/; - case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/; - case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/; - case "aboveActivity": - default: - return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS/; - } - }, - replace: "...$self.makeSettingsCategories($1),$&" } - }, { - find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL", - replacement: { - match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.UserSettingsSections\).*?(\i)\.default\.open\()/, - replace: "$2.default.open($1);return;" - } - }], + ], customSections: [] as ((SectionTypes: Record) => any)[], @@ -130,19 +144,43 @@ export default definePlugin({ ].filter(Boolean); }, + isRightSpot({ header, settings }: { header?: string; settings?: string[]; }) { + const firstChild = settings?.[0]; + // lowest two elements... sanity backup + if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true; + + const { settingsLocation } = Settings.plugins.Settings; + + if (settingsLocation === "bottom") return firstChild === "LOGOUT"; + if (settingsLocation === "belowActivity") return firstChild === "CHANGELOG"; + + const names = { + top: i18n.Messages.USER_SETTINGS, + aboveNitro: i18n.Messages.BILLING_SETTINGS, + belowNitro: i18n.Messages.APP_SETTINGS, + aboveActivity: i18n.Messages.ACTIVITY_SETTINGS + }; + return header === names[settingsLocation]; + }, + + addSettings(elements: any[], element: { header?: string; settings: string[]; }, sectionTypes: Record) { + if (!this.isRightSpot(element)) return; + + elements.push(...this.makeSettingsCategories(sectionTypes)); + }, + options: { settingsLocation: { type: OptionType.SELECT, description: "Where to put the Vencord settings section", options: [ { label: "At the very top", value: "top" }, - { label: "Above the Nitro section", value: "aboveNitro" }, + { label: "Above the Nitro section", value: "aboveNitro", default: true }, { label: "Below the Nitro section", value: "belowNitro" }, - { label: "Above Activity Settings", value: "aboveActivity", default: true }, + { label: "Above Activity Settings", value: "aboveActivity" }, { label: "Below Activity Settings", value: "belowActivity" }, { label: "At the very bottom", value: "bottom" }, - ], - restartNeeded: true + ] }, }, From a54b55edad3e33c44ac1419d7d20c1724a528f7e Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 14 May 2024 18:54:00 +0200 Subject: [PATCH 37/37] bump to v1.8.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0e584598..95f98a8b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.8.3", + "version": "1.8.4", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": {