From ad3d936dfd75a22e17213e66d2288b46abe6cc90 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 29 Aug 2024 21:41:51 -0300 Subject: [PATCH 01/30] Fix settings wrapping fallback --- src/plugins/_core/settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx index 3cc020836..be220db1a 100644 --- a/src/plugins/_core/settings.tsx +++ b/src/plugins/_core/settings.tsx @@ -64,7 +64,7 @@ export default definePlugin({ replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}` }, { - match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,30}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/, + match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,60}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/, replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})` } ] From e473a57c3d5ee624669910f7f9120038fe12a050 Mon Sep 17 00:00:00 2001 From: Cookie <52550063+Covkie@users.noreply.github.com> Date: Thu, 29 Aug 2024 21:08:48 -0400 Subject: [PATCH 02/30] IgnoreActivities: Add option for blacklist filter (#2712) --- src/plugins/ignoreActivities/index.tsx | 88 ++++++++++++++++++-------- src/utils/constants.ts | 4 ++ 2 files changed, 66 insertions(+), 26 deletions(-) diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index bab69d89b..9e6c21bdd 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -26,6 +26,11 @@ interface IgnoredActivity { type: ActivitiesTypes; } +const enum FilterMode { + Whitelist, + Blacklist +} + const RunningGameStore = findStoreLazy("RunningGameStore"); const ShowCurrentGame = getUserSettingLazy("status", "showCurrentGame")!; @@ -70,14 +75,17 @@ function handleActivityToggle(e: React.MouseEvent if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity); else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex); - // Trigger activities recalculation + recalculateActivities(); +} + +function recalculateActivities() { ShowCurrentGame.updateSetting(old => old); } function ImportCustomRPCComponent() { return ( - Import the application id of the CustomRPC plugin to the allowed list + Import the application id of the CustomRPC plugin to the filter list
+ )} From 273981deb74039c0aa8252f5c981d21f7352a412 Mon Sep 17 00:00:00 2001 From: ImBanana Date: Sun, 1 Sep 2024 05:33:07 +0300 Subject: [PATCH 08/30] new plugin StickerPaste ~ Insert stickers instead of sending (#2781) --- src/plugins/stickerPaste/README.md | 3 +++ src/plugins/stickerPaste/index.ts | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/plugins/stickerPaste/README.md create mode 100644 src/plugins/stickerPaste/index.ts diff --git a/src/plugins/stickerPaste/README.md b/src/plugins/stickerPaste/README.md new file mode 100644 index 000000000..a29170272 --- /dev/null +++ b/src/plugins/stickerPaste/README.md @@ -0,0 +1,3 @@ +# StickerPaste + +Makes picking a sticker in the sticker picker insert it into the chatbox instead of instantly sending. diff --git a/src/plugins/stickerPaste/index.ts b/src/plugins/stickerPaste/index.ts new file mode 100644 index 000000000..8a0082073 --- /dev/null +++ b/src/plugins/stickerPaste/index.ts @@ -0,0 +1,24 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "StickerPaste", + description: "Makes picking a sticker in the sticker picker insert it into the chatbox instead of instantly sending", + authors: [Devs.ImBanana], + + patches: [ + { + find: ".stickers,previewSticker:", + replacement: { + match: /if\(\i\.\i\.getUploadCount/, + replace: "return true;$&", + } + } + ] +}); From e07a4e19e65b556b0e398a39499b85b88699756b Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Sat, 31 Aug 2024 23:08:33 -0400 Subject: [PATCH 09/30] VolumeBooster: Support browser and Vesktop (#2730) --- src/plugins/volumeBooster/README.md | 9 +++ .../index.ts | 61 +++++++++++++++++-- src/utils/constants.ts | 4 ++ 3 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 src/plugins/volumeBooster/README.md rename src/plugins/{volumeBooster.discordDesktop => volumeBooster}/index.ts (64%) diff --git a/src/plugins/volumeBooster/README.md b/src/plugins/volumeBooster/README.md new file mode 100644 index 000000000..62bd5ab52 --- /dev/null +++ b/src/plugins/volumeBooster/README.md @@ -0,0 +1,9 @@ +# Volume Booster + +Allows you to boost the volume over 200% on desktop and over 100% on other clients. + +Works on users, bots, and streams! + +![the volume being moved up to 270% on vesktop](https://github.com/user-attachments/assets/793e012e-c069-4fa4-a3d5-61c2f55edd3e) + +![the volume being moved up to 297% on a stream](https://github.com/user-attachments/assets/77463eb9-2537-4821-a3ab-82f60633ccbc) diff --git a/src/plugins/volumeBooster.discordDesktop/index.ts b/src/plugins/volumeBooster/index.ts similarity index 64% rename from src/plugins/volumeBooster.discordDesktop/index.ts rename to src/plugins/volumeBooster/index.ts index b455c97ec..3ab47b197 100644 --- a/src/plugins/volumeBooster.discordDesktop/index.ts +++ b/src/plugins/volumeBooster/index.ts @@ -31,10 +31,27 @@ const settings = definePluginSettings({ } }); +interface StreamData { + audioContext: AudioContext, + audioElement: HTMLAudioElement, + emitter: any, + // added by this plugin + gainNode?: GainNode, + id: string, + levelNode: AudioWorkletNode, + sinkId: string, + stream: MediaStream, + streamSourceNode?: MediaStreamAudioSourceNode, + videoStreamId: string, + _mute: boolean, + _speakingFlags: number, + _volume: number; +} + export default definePlugin({ name: "VolumeBooster", - authors: [Devs.Nuckyz], - description: "Allows you to set the user and stream volume above the default maximum.", + authors: [Devs.Nuckyz, Devs.sadan], + description: "Allows you to set the user and stream volume above the default maximum", settings, patches: [ @@ -45,12 +62,28 @@ export default definePlugin({ ].map(find => ({ find, replacement: { - match: /(?<=maxValue:\i\.\i)\?(\d+?):(\d+?)(?=,)/, - replace: (_, higherMaxVolume, minorMaxVolume) => "" - + `?${higherMaxVolume}*$self.settings.store.multiplier` - + `:${minorMaxVolume}*$self.settings.store.multiplier` + match: /(?<=maxValue:)\i\.\i\?(\d+?):(\d+?)(?=,)/, + replace: (_, higherMaxVolume, minorMaxVolume) => `${higherMaxVolume}*$self.settings.store.multiplier` } })), + // Patches needed for web/vesktop + { + find: "streamSourceNode", + predicate: () => IS_WEB, + group: true, + replacement: [ + // Remove rounding algorithm + { + match: /Math\.max.{0,30}\)\)/, + replace: "arguments[0]" + }, + // Patch the volume + { + match: /\.volume=this\._volume\/100;/, + replace: ".volume=0.00;$self.patchVolume(this);" + } + ] + }, // Prevent Audio Context Settings sync from trying to sync with values above 200, changing them to 200 before we send to Discord { find: "AudioContextSettingsMigrated", @@ -83,4 +116,20 @@ export default definePlugin({ ] } ], + + patchVolume(data: StreamData) { + if (data.stream.getAudioTracks().length === 0) return; + + data.streamSourceNode ??= data.audioContext.createMediaStreamSource(data.stream); + + if (!data.gainNode) { + const gain = data.gainNode = data.audioContext.createGain(); + data.streamSourceNode.connect(gain); + gain.connect(data.audioContext.destination); + } + + data.gainNode.gain.value = data._mute + ? 0 + : data._volume / 100; + } }); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 60c203252..7fb2b6bf0 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -534,6 +534,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Joona", id: 297410829589020673n }, + sadan: { + name: "sadan", + id: 521819891141967883n, + }, Kylie: { name: "Cookie", id: 721853658941227088n From b595a3e33c4a39e67a36dc9041ed2aa90c30aaea Mon Sep 17 00:00:00 2001 From: vxray <41696677+vxray@users.noreply.github.com> Date: Sun, 1 Sep 2024 05:20:22 +0200 Subject: [PATCH 10/30] OpenInApp: Add support for localization in Spotify URL regex (#2776) --- src/plugins/openInApp/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/openInApp/index.ts b/src/plugins/openInApp/index.ts index 1a68e8f5d..2e332465e 100644 --- a/src/plugins/openInApp/index.ts +++ b/src/plugins/openInApp/index.ts @@ -33,7 +33,7 @@ interface URLReplacementRule { // Do not forget to add protocols to the ALLOWED_PROTOCOLS constant const UrlReplacementRules: Record = { spotify: { - match: /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/, + match: /^https:\/\/open\.spotify\.com\/(?:intl-[a-z]{2}\/)?(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/, replace: (_, type, id) => `spotify://${type}/${id}`, description: "Open Spotify links in the Spotify app", shortlinkMatch: /^https:\/\/spotify\.link\/.+$/, From 968e688c106ea7b45c1bb522c73104239d3663a6 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sun, 1 Sep 2024 00:53:46 -0300 Subject: [PATCH 11/30] Bump to 1.9.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0fdda2659..65a2f4512 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.9.8", + "version": "1.9.9", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 74fd85bd3d7a86c495534f08c6c508f379514b23 Mon Sep 17 00:00:00 2001 From: sadan4 <117494111+sadan4@users.noreply.github.com> Date: Sun, 1 Sep 2024 19:39:19 -0400 Subject: [PATCH 12/30] VolumeBooster: Fix on Vesktop (#2828) Also fixed an IgnoreActivities patch and added a README to it --- .../README.md | 0 .../index.tsx | 0 .../style.css | 0 src/plugins/ignoreActivities/README.md | 13 +++++++++++++ src/plugins/ignoreActivities/index.tsx | 10 +++++++++- src/plugins/volumeBooster/index.ts | 2 +- 6 files changed, 23 insertions(+), 2 deletions(-) rename src/plugins/{CopyFileContents => copyFileContents}/README.md (100%) rename src/plugins/{CopyFileContents => copyFileContents}/index.tsx (100%) rename src/plugins/{CopyFileContents => copyFileContents}/style.css (100%) create mode 100644 src/plugins/ignoreActivities/README.md diff --git a/src/plugins/CopyFileContents/README.md b/src/plugins/copyFileContents/README.md similarity index 100% rename from src/plugins/CopyFileContents/README.md rename to src/plugins/copyFileContents/README.md diff --git a/src/plugins/CopyFileContents/index.tsx b/src/plugins/copyFileContents/index.tsx similarity index 100% rename from src/plugins/CopyFileContents/index.tsx rename to src/plugins/copyFileContents/index.tsx diff --git a/src/plugins/CopyFileContents/style.css b/src/plugins/copyFileContents/style.css similarity index 100% rename from src/plugins/CopyFileContents/style.css rename to src/plugins/copyFileContents/style.css diff --git a/src/plugins/ignoreActivities/README.md b/src/plugins/ignoreActivities/README.md new file mode 100644 index 000000000..abc75720f --- /dev/null +++ b/src/plugins/ignoreActivities/README.md @@ -0,0 +1,13 @@ +# IgnoreActivities + +Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings. + +![](https://github.com/user-attachments/assets/f0c19060-0ecf-4f1c-8165-a5aa40143c82) + +![](https://github.com/user-attachments/assets/73c3fa7a-5b90-41ee-a4d6-91fa76458b74) + +![](https://github.com/user-attachments/assets/1ab3fe73-3911-48d1-8a08-e976af614b41) + +The activity stays showing as a detected game even if ignored, differently from the stock Toggle Detection button from Discord: + +![](https://github.com/user-attachments/assets/08ea60c3-3a31-42de-ae4c-7535fbf1b45a) diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index 9e6c21bdd..02261b5ba 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -237,7 +237,7 @@ function isActivityTypeIgnored(type: number, id?: string) { export default definePlugin({ name: "IgnoreActivities", authors: [Devs.Nuckyz, Devs.Kylie], - description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.", + description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below", dependencies: ["UserSettingsAPI"], settings, @@ -266,6 +266,7 @@ export default definePlugin({ replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),` } }, + // Discord has 3 different components for activities. Currently, the last is the one being used { find: ".activityTitleText,variant", replacement: { @@ -279,6 +280,13 @@ export default definePlugin({ match: /\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),/, replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),` } + }, + { + find: ".promotedLabelWrapperNonBanner,children", + replacement: { + match: /\.appDetailsHeaderContainer.+?children:\i.*?}\),(?<=application:(\i).+?)/, + replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),` + } } ], diff --git a/src/plugins/volumeBooster/index.ts b/src/plugins/volumeBooster/index.ts index 3ab47b197..02a955a80 100644 --- a/src/plugins/volumeBooster/index.ts +++ b/src/plugins/volumeBooster/index.ts @@ -69,7 +69,7 @@ export default definePlugin({ // Patches needed for web/vesktop { find: "streamSourceNode", - predicate: () => IS_WEB, + predicate: () => !IS_DISCORD_DESKTOP, group: true, replacement: [ // Remove rounding algorithm From 0c71d6c3fae57a3e5f9a86a69b309897171aa31d Mon Sep 17 00:00:00 2001 From: vishnyanetchereshnya <151846235+vishnyanetchereshnya@users.noreply.github.com> Date: Mon, 2 Sep 2024 06:42:52 +0300 Subject: [PATCH 13/30] PermissionsViewer: Show RoleIcons & which role grants permission (#2824) --- .../components/RolesAndUsersPermissions.tsx | 106 ++++++++------- .../components/UserPermissions.tsx | 92 +++++++++---- src/plugins/permissionsViewer/index.tsx | 91 +++++++------ src/plugins/permissionsViewer/styles.css | 123 ++++++++---------- src/webpack/common/types/components.d.ts | 2 +- 5 files changed, 227 insertions(+), 187 deletions(-) diff --git a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx index 963750fa3..82925d610 100644 --- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx +++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx @@ -21,8 +21,10 @@ 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 { 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 { findByCodeLazy } from "@webpack"; +import { Clipboard, ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, i18n, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common"; +import { UnicodeEmoji } from "@webpack/types"; +import type { Guild, Role, User } from "discord-types/general"; import { settings } from ".."; import { cl, getPermissionDescription, getPermissionString } from "../utils"; @@ -42,15 +44,15 @@ export interface RoleOrUserPermission { overwriteDeny?: bigint; } -function openRolesAndUsersPermissionsModal(permissions: Array, guild: Guild, header: string) { - return openModal(modalProps => ( - - )); +type GetRoleIconData = (role: Role, size: number) => { customIconSrc?: string; unicodeEmoji?: UnicodeEmoji; }; +const getRoleIconData: GetRoleIconData = findByCodeLazy("convertSurrogateToName", "customIconSrc", "unicodeEmoji"); + +function getRoleIconSrc(role: Role) { + const icon = getRoleIconData(role, 20); + if (!icon) return; + + const { customIconSrc, unicodeEmoji } = icon; + return customIconSrc ?? unicodeEmoji?.url; } function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, header }: { permissions: Array; guild: Guild; modalProps: ModalProps; header: string; }) { @@ -86,31 +88,34 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea size={ModalSize.LARGE} > - {header} permissions: + {header} permissions: - + {!selectedItem && ( -
+
No permissions to display!
)} {selectedItem && ( -
-
+
+ {permissions.map((permission, index) => { - const user = UserStore.getUser(permission.id ?? ""); - const role = roles[permission.id ?? ""]; + const user: User | undefined = UserStore.getUser(permission.id ?? ""); + const role: Role | undefined = roles[permission.id ?? ""]; + const roleIconSrc = role != null ? getRoleIconSrc(role) : undefined; return ( - +
); })} -
-
+ +
+ {Object.entries(PermissionsBits).map(([permissionName, bit]) => ( -
-
+
+
{(() => { const { permissions, overwriteAllow, overwriteDeny } = selectedItem; @@ -192,11 +199,11 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
))} -
+
)} - + ); } @@ -208,7 +215,7 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str aria-label="Role Options" > { Clipboard.copy(roleId); @@ -217,14 +224,13 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str {(settings.store as any).unsafeViewAsRole && ( { const role = GuildStore.getRole(guild.id, roleId); if (!role) return; onClose(); - FluxDispatcher.dispatch({ type: "IMPERSONATE_UPDATE", guildId: guild.id, @@ -235,15 +241,14 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str } } }); - } - } + }} /> )} ); } -function UserContextMenu({ userId, onClose }: { userId: string; onClose: () => void; }) { +function UserContextMenu({ userId }: { userId: string; }) { return ( v aria-label="User Options" > { Clipboard.copy(userId); @@ -263,4 +268,13 @@ function UserContextMenu({ userId, onClose }: { userId: string; onClose: () => v const RolesAndUsersPermissions = ErrorBoundary.wrap(RolesAndUsersPermissionsComponent); -export default openRolesAndUsersPermissionsModal; +export default function openRolesAndUsersPermissionsModal(permissions: Array, guild: Guild, header: string) { + return openModal(modalProps => ( + + )); +} diff --git a/src/plugins/permissionsViewer/components/UserPermissions.tsx b/src/plugins/permissionsViewer/components/UserPermissions.tsx index dc2aa6fa4..7c0858f10 100644 --- a/src/plugins/permissionsViewer/components/UserPermissions.tsx +++ b/src/plugins/permissionsViewer/components/UserPermissions.tsx @@ -29,6 +29,7 @@ import openRolesAndUsersPermissionsModal, { PermissionType, type RoleOrUserPermi interface UserPermission { permission: string; + roleName: string; roleColor: string; rolePosition: number; } @@ -45,8 +46,48 @@ const { RoleRootClasses, RoleClasses, RoleBorderClasses } = proxyLazyWebpack(() return { RoleRootClasses, RoleClasses, RoleBorderClasses }; }); +interface FakeRoleProps extends React.HTMLAttributes { + text: string; + color: string; +} + +function FakeRole({ text, color, ...props }: FakeRoleProps) { + return ( +
+
+ +
+
+ + {text} + +
+
+ ); +} + +interface GrantedByTooltipProps { + roleName: string; + roleColor: string; +} + +function GrantedByTooltip({ roleName, roleColor }: GrantedByTooltipProps) { + return ( + <> + Granted By + + + ); +} + function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { guild: Guild; guildMember: GuildMember; forceOpen?: boolean; }) { - const stns = settings.use(["permissionsSortOrder"]); + const { permissionsSortOrder } = settings.use(["permissionsSortOrder"]); const [rolePermissions, userPermissions] = useMemo(() => { const userPermissions: UserPermissions = []; @@ -67,6 +108,7 @@ function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { g const OWNER = i18n.Messages.GUILD_OWNER || "Server Owner"; userPermissions.push({ permission: OWNER, + roleName: "Owner", roleColor: "var(--primary-300)", rolePosition: Infinity }); @@ -75,10 +117,11 @@ function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { g sortUserRoles(userRoles); for (const [permission, bit] of Object.entries(PermissionsBits)) { - for (const { permissions, colorString, position } of userRoles) { + for (const { permissions, colorString, position, name } of userRoles) { if ((permissions & bit) === bit) { userPermissions.push({ permission: getPermissionString(permission), + roleName: name, roleColor: colorString || "var(--primary-300)", rolePosition: position }); @@ -91,7 +134,7 @@ function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { g userPermissions.sort((a, b) => b.rolePosition - a.rolePosition); return [rolePermissions, userPermissions]; - }, [stns.permissionsSortOrder]); + }, [permissionsSortOrder]); return ( settings.store.defaultPermissionsDropdownState = !state} defaultState={settings.store.defaultPermissionsDropdownState} buttons={[ - ( + {tooltipProps => ( - +
)} - ) + ]}> {userPermissions.length > 0 && (
- {userPermissions.map(({ permission, roleColor }) => ( -
-
- -
-
- - {permission} - -
-
+ {userPermissions.map(({ permission, roleColor, roleName }) => ( + } + tooltipClassName={cl("granted-by-container")} + tooltipContentClassName={cl("granted-by-content")} + > + {tooltipProps => ( + + )} + ))}
)} diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx index 7c3967a37..44dfd5f54 100644 --- a/src/plugins/permissionsViewer/index.tsx +++ b/src/plugins/permissionsViewer/index.tsx @@ -26,7 +26,7 @@ import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common"; +import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, match, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common"; import type { Guild, GuildMember } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions"; @@ -54,12 +54,12 @@ export const settings = definePluginSettings({ options: [ { label: "Highest Role", value: PermissionsSortOrder.HighestRole, default: true }, { label: "Lowest Role", value: PermissionsSortOrder.LowestRole } - ], + ] }, defaultPermissionsDropdownState: { description: "Whether the permissions dropdown on user popouts should be open by default", type: OptionType.BOOLEAN, - default: false, + default: false } }); @@ -73,14 +73,11 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { action={() => { const guild = GuildStore.getGuild(guildId); - let permissions: RoleOrUserPermission[]; - let header: string; - - switch (type) { - case MenuItemParentType.User: { + const { permissions, header }: { permissions: RoleOrUserPermission[], header: string; } = match(type) + .with(MenuItemParentType.User, () => { const member = GuildMemberStore.getMember(guildId, id!); - permissions = getSortedRoles(guild, member) + const permissions: RoleOrUserPermission[] = getSortedRoles(guild, member) .map(role => ({ type: PermissionType.Role, ...role @@ -93,37 +90,37 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { }); } - header = member.nick ?? UserStore.getUser(member.userId).username; - - break; - } - - case MenuItemParentType.Channel: { + return { + permissions, + header: member.nick ?? UserStore.getUser(member.userId).username + }; + }) + .with(MenuItemParentType.Channel, () => { const channel = ChannelStore.getChannel(id!); - permissions = sortPermissionOverwrites(Object.values(channel.permissionOverwrites).map(({ id, allow, deny, type }) => ({ + const permissions = sortPermissionOverwrites(Object.values(channel.permissionOverwrites).map(({ id, allow, deny, type }) => ({ type: type as PermissionType, id, overwriteAllow: allow, overwriteDeny: deny })), guildId); - header = channel.name; - - break; - } - - default: { - permissions = Object.values(GuildStore.getRoles(guild.id)).map(role => ({ + return { + permissions, + header: channel.name + }; + }) + .otherwise(() => { + const permissions = Object.values(GuildStore.getRoles(guild.id)).map(role => ({ type: PermissionType.Role, ...role })); - header = guild.name; - - break; - } - } + return { + permissions, + header: guild.name + }; + }); openRolesAndUsersPermissionsModal(permissions, guild, header); }} @@ -133,32 +130,34 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback { return (children, props) => { - if (!props) return; - if ((type === MenuItemParentType.User && !props.user) || (type === MenuItemParentType.Guild && !props.guild) || (type === MenuItemParentType.Channel && (!props.channel || !props.guild))) + if ( + !props || + (type === MenuItemParentType.User && !props.user) || + (type === MenuItemParentType.Guild && !props.guild) || + (type === MenuItemParentType.Channel && (!props.channel || !props.guild)) + ) { return; + } const group = findGroupChildrenByChildId(childId, children); - const item = (() => { - switch (type) { - case MenuItemParentType.User: - return MenuItem(props.guildId, props.user.id, type); - case MenuItemParentType.Channel: - return MenuItem(props.guild.id, props.channel.id, type); - case MenuItemParentType.Guild: - return MenuItem(props.guild.id); - default: - return null; - } - })(); + const item = match(type) + .with(MenuItemParentType.User, () => MenuItem(props.guildId, props.user.id, type)) + .with(MenuItemParentType.Channel, () => MenuItem(props.guild.id, props.channel.id, type)) + .with(MenuItemParentType.Guild, () => MenuItem(props.guild.id)) + .otherwise(() => null); + if (item == null) return; - if (group) - group.push(item); - else if (childId === "roles" && props.guildId) - // "roles" may not be present due to the member not having any roles. In that case, add it above "Copy ID" + if (group) { + return group.push(item); + } + + // "roles" may not be present due to the member not having any roles. In that case, add it above "Copy ID" + if (childId === "roles" && props.guildId) { children.splice(-1, 0, {item}); + } }; } diff --git a/src/plugins/permissionsViewer/styles.css b/src/plugins/permissionsViewer/styles.css index 0ef961e5a..0123f86e2 100644 --- a/src/plugins/permissionsViewer/styles.css +++ b/src/plugins/permissionsViewer/styles.css @@ -1,20 +1,6 @@ /* User Permissions Component */ -.vc-permviewer-userperms-title-container { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 10px; - margin-bottom: 6px; -} - -.vc-permviewer-userperms-btns-container { - display: flex; - align-items: center; -} - -.vc-permviewer-userperms-sortorder-btn { - all: unset; +.vc-permviewer-user-sortorder-btn { cursor: pointer; display: flex; align-items: center; @@ -23,27 +9,17 @@ height: 24px; } -.vc-permviewer-userperms-permdetails-btn { - all: unset; - cursor: pointer; - display: flex; - align-items: center; -} - -.vc-permviewer-userperms-toggleperms-btn { - all: unset; - cursor: pointer; - display: flex; - align-items: center; -} - /* RolesAndUsersPermissions Component */ -.vc-permviewer-perms-title { +.vc-permviewer-modal-content { + padding: 16px 4px 16px 16px; +} + +.vc-permviewer-modal-title { flex-grow: 1; } -.vc-permviewer-perms-no-perms { +.vc-permviewer-modal-no-perms { width: 100%; height: 100%; display: flex; @@ -52,101 +28,103 @@ text-align: center; } -.vc-permviewer-perms-container { - display: grid; - grid-template-columns: 1fr 2fr; - grid-template-areas: "list permissions"; - padding: 16px 0; +.vc-permviewer-modal-container { + width: 100%; + height: 100%; + display: flex; + gap: 8px; } -.vc-permviewer-perms-list { - grid-area: list; +.vc-permviewer-modal-list { display: flex; flex-direction: column; gap: 2px; - border-right: 2px solid var(--background-modifier-active); + padding-right: 8px; + width: 200px; } -.vc-permviewer-perms-list-item-btn { - all: unset; +.vc-permviewer-modal-list-item-btn { cursor: pointer; } -.vc-permviewer-perms-list-item { +.vc-permviewer-modal-list-item { display: flex; align-items: center; - padding: 8px 5px; - cursor: pointer; - width: 230px; + gap: 8px; + padding: 8px; border-radius: 5px; } -.vc-permviewer-perms-list-item:hover { +.vc-permviewer-modal-list-item:hover { background-color: var(--background-modifier-hover); } -.vc-permviewer-perms-list-item-active { +.vc-permviewer-modal-list-item-active { background-color: var(--background-modifier-selected); } -.vc-permviewer-perms-list-item > div { +.vc-permviewer-modal-list-item > div { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } -.vc-permviewer-perms-role-circle { +.vc-permviewer-modal-role-circle { border-radius: 50%; width: 12px; height: 12px; - margin-left: 3px; - margin-right: 11px; flex-shrink: 0; } -.vc-permviewer-perms-user-img { +.vc-permviewer-modal-role-image { + width: 20px; + height: 20px; + object-fit: contain; +} + +.vc-permviewer-modal-user-img { border-radius: 50%; width: 20px; height: 20px; - margin-right: 6px; } -.vc-permviewer-perms-perms { - grid-area: permissions; +.vc-permviewer-modal-divider { + width: 2px; + background-color: var(--background-modifier-active); +} + +.vc-permviewer-modal-perms { display: flex; flex-direction: column; - margin-left: 5px; + padding-right: 8px; } -.vc-permviewer-perms-perms-item { - position: relative; +.vc-permviewer-modal-perms-item { display: flex; align-items: center; - padding: 10px; + gap: 5px; + padding: 10px 2px 10px 10px; border-bottom: 2px solid var(--background-modifier-active); } -.vc-permviewer-perms-perms-item:last-child { +.vc-permviewer-modal-perms-item:last-child { border: 0; } -.vc-permviewer-perms-perms-item-icon { +.vc-permviewer-modal-perms-item-icon { border: 1px solid var(--background-modifier-selected); width: 24px; height: 24px; - margin-right: 5px; } -.vc-permviewer-perms-perms-item .vc-info-icon { +.vc-permviewer-modal-perms-item .vc-info-icon { color: var(--interactive-muted); + margin-left: auto; cursor: pointer; - position: absolute; - right: 0; - scale: 0.9; transition: color ease-in 0.1s; } -.vc-permviewer-perms-perms-item .vc-info-icon:hover { +.vc-permviewer-modal-perms-item .vc-info-icon:hover { color: var(--interactive-active); } @@ -167,3 +145,14 @@ background: rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-6)); border-color: var(--profile-body-border-color) } + +.vc-permviewer-granted-by-container { + max-width: 300px; + width: auto; +} + +.vc-permviewer-granted-by-content { + display: flex; + align-items: center; + gap: 4px; +} diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index 5dcc95194..0588d5e4c 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -459,7 +459,7 @@ export type ScrollerThin = ComponentType Date: Mon, 2 Sep 2024 06:50:52 +0300 Subject: [PATCH 14/30] MutualGroupDMs: Add Mutual Groups to DM Sidebar (#2817) --- src/plugins/mutualGroupDMs/index.tsx | 75 ++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index a1e73cabf..ec52b4061 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -20,7 +20,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; import definePlugin from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common"; import { Channel, User } from "discord-types/general"; @@ -28,6 +28,7 @@ const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); const UserUtils = findByPropsLazy("getGlobalName"); const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds"); +const ExpandableList = findComponentByCodeLazy(".mutualFriendItem]"); const GuildLabelClasses = findByPropsLazy("guildNick", "guildAvatarWithoutIcon"); function getGroupDMName(channel: Channel) { @@ -50,6 +51,29 @@ function getMutualGDMCountText(user: User) { return `${count === 0 ? "No" : count} Mutual Group${count !== 1 ? "s" : ""}`; } +function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) { + return mutualDms.map(c => ( + { + onClose(); + SelectedChannelActionCreators.selectPrivateChannel(c.id); + }} + > + + +
+
{getGroupDMName(c)}
+
{c.recipients.length + 1} Members
+
+
+ )); +} + const IS_PATCHED = Symbol("MutualGroupDMs.Patched"); export default definePlugin({ @@ -70,6 +94,13 @@ export default definePlugin({ replace: "$1==='MUTUAL_GDMS'?$self.renderMutualGDMs(arguments[0]):$&" } ] + }, + { + find: 'section:"MUTUAL_FRIENDS"', + replacement: { + match: /\.openUserProfileModal.+?\)}\)}\)(?<=(\(0,\i\.jsxs?\)\(\i\.\i,{className:(\i)\.divider}\)).+?)/, + replace: "$&,$self.renderDMPageList({user: arguments[0].user, Divider: $1, listStyle: $2.list})" + } } ], @@ -84,28 +115,9 @@ export default definePlugin({ }, renderMutualGDMs: ErrorBoundary.wrap(({ user, onClose }: { user: User, onClose: () => void; }) => { - const mutualDms = useMemo(() => getMutualGroupDms(user.id), [user.id]); + const mutualGDms = useMemo(() => getMutualGroupDms(user.id), [user.id]); - const entries = mutualDms.map(c => ( - { - onClose(); - SelectedChannelActionCreators.selectPrivateChannel(c.id); - }} - > - - -
-
{getGroupDMName(c)}
-
{c.recipients.length + 1} Members
-
-
- )); + const entries = renderClickableGDMs(mutualGDms, onClose); return ( ); + }), + + renderDMPageList: ErrorBoundary.wrap(({ user, Divider, listStyle }: { user: User, Divider: JSX.Element, listStyle: string; }) => { + const mutualGDms = getMutualGroupDms(user.id); + if (mutualGDms.length === 0) return null; + + const header = getMutualGDMCountText(user); + + return ( + <> + {Divider} + { })} + /> + + ); }) }); From 27e81b20db23f09ca7334cc74bc5aee841cd6705 Mon Sep 17 00:00:00 2001 From: Maddie <52103563+maddie480@users.noreply.github.com> Date: Mon, 2 Sep 2024 06:51:29 +0200 Subject: [PATCH 15/30] Allow online themes to be applied only in dark or light mode (#2701) --- src/components/VencordSettings/ThemesTab.tsx | 19 ++++++++++++++----- src/plugins/clientTheme/index.tsx | 3 +-- src/utils/quickCss.ts | 15 ++++++++++++++- src/webpack/common/stores.ts | 2 ++ src/webpack/common/types/stores.d.ts | 8 ++++++++ 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index bb9d37894..f718ab11f 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -77,8 +77,16 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) { Validator This section will tell you whether your themes can successfully be loaded
- {themeLinks.map(link => ( - { + const { label, link } = (() => { + const match = /^@(light|dark) (.*)/.exec(rawLink); + if (!match) return { label: rawLink, link: rawLink }; + + const [, mode, link] = match; + return { label: `[${mode} mode only] ${link}`, link }; + })(); + + return - {link} + {label} - - ))} + ; + })}
); @@ -296,6 +304,7 @@ function ThemesTab() { Paste links to css files here One link per line + You can prefix lines with @light or @dark to toggle them based on your Discord theme Make sure to use direct links to files (raw or github.io)! diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 358bae017..59f3d5fe2 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -12,7 +12,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import definePlugin, { OptionType, StartAt } from "@utils/types"; import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; -import { Button, Forms, useStateFromStores } from "@webpack/common"; +import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common"; const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); @@ -36,7 +36,6 @@ function setTheme(theme: string) { saveClientTheme({ theme }); } -const ThemeStore = findStoreLazy("ThemeStore"); const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore"); function ThemeSettings() { diff --git a/src/utils/quickCss.ts b/src/utils/quickCss.ts index 99f06004c..6a18948d1 100644 --- a/src/utils/quickCss.ts +++ b/src/utils/quickCss.ts @@ -17,6 +17,7 @@ */ import { Settings, SettingsStore } from "@api/Settings"; +import { ThemeStore } from "@webpack/common"; let style: HTMLStyleElement; @@ -59,7 +60,18 @@ async function initThemes() { const { themeLinks, enabledThemes } = Settings; - const links: string[] = [...themeLinks]; + // "darker" and "midnight" both count as dark + const activeTheme = ThemeStore.theme === "light" ? "light" : "dark"; + + const links = themeLinks + .map(rawLink => { + const match = /^@(light|dark) (.*)/.exec(rawLink); + if (!match) return rawLink; + + const [, mode, link] = match; + return mode === activeTheme ? link : null; + }) + .filter(link => link !== null); if (IS_WEB) { for (const theme of enabledThemes) { @@ -85,6 +97,7 @@ document.addEventListener("DOMContentLoaded", () => { SettingsStore.addChangeListener("themeLinks", initThemes); SettingsStore.addChangeListener("enabledThemes", initThemes); + ThemeStore.addChangeListener(initThemes); if (!IS_WEB) VencordNative.quickCss.addThemeChangeListener(initThemes); diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 74813357a..8579f8b92 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -53,6 +53,7 @@ export let RelationshipStore: Stores.RelationshipStore & t.FluxStore & { }; export let EmojiStore: t.EmojiStore; +export let ThemeStore: t.ThemeStore; export let WindowStore: t.WindowStore; export let DraftStore: t.DraftStore; @@ -84,3 +85,4 @@ waitForStore("GuildChannelStore", m => GuildChannelStore = m); waitForStore("MessageStore", m => MessageStore = m); waitForStore("WindowStore", m => WindowStore = m); waitForStore("EmojiStore", m => EmojiStore = m); +waitForStore("ThemeStore", m => ThemeStore = m); diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts index 037b2d81c..9ca7dfc94 100644 --- a/src/webpack/common/types/stores.d.ts +++ b/src/webpack/common/types/stores.d.ts @@ -220,6 +220,14 @@ export class GuildStore extends FluxStore { getAllGuildRoles(): Record>; } +export class ThemeStore extends FluxStore { + theme: "light" | "dark" | "darker" | "midnight"; + darkSidebar: boolean; + isSystemThemeAvailable: boolean; + systemPrefersColorScheme: "light" | "dark"; + systemTheme: null; +} + export type useStateFromStores = ( stores: t.FluxStore[], mapper: () => T, From accfc15125c5a611cb5835815a6e36d6bd4e8f89 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 2 Sep 2024 01:54:16 -0300 Subject: [PATCH 16/30] Ban ts-pattern normal import --- scripts/build/common.mjs | 1 + src/plugins/permissionsViewer/index.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 247557e3c..e88f1e2b9 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -334,5 +334,6 @@ export const commonRendererPlugins = [ banImportPlugin(builtinModuleRegex, "Cannot import node inbuilt modules in browser code. You need to use a native.ts file"), banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"), banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"), + banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"), ...commonOpts.plugins ]; diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx index 44dfd5f54..ca28f845f 100644 --- a/src/plugins/permissionsViewer/index.tsx +++ b/src/plugins/permissionsViewer/index.tsx @@ -73,7 +73,8 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { action={() => { const guild = GuildStore.getGuild(guildId); - const { permissions, header }: { permissions: RoleOrUserPermission[], header: string; } = match(type) + const { permissions, header } = match(type) + .returnType<{ permissions: RoleOrUserPermission[], header: string; }>() .with(MenuItemParentType.User, () => { const member = GuildMemberStore.getMember(guildId, id!); From c51d7b8fb4ea38f0ada2fe210f21263d2f2dd711 Mon Sep 17 00:00:00 2001 From: June Park Date: Mon, 2 Sep 2024 17:09:45 +0900 Subject: [PATCH 17/30] ReviewDB: Fix wording in server reviews (#2826) --- src/plugins/reviewDB/components/ReviewModal.tsx | 7 +++++-- src/plugins/reviewDB/components/ReviewsView.tsx | 9 ++++++--- src/plugins/reviewDB/index.tsx | 8 ++++---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/plugins/reviewDB/components/ReviewModal.tsx b/src/plugins/reviewDB/components/ReviewModal.tsx index e12a98acd..71ac021f0 100644 --- a/src/plugins/reviewDB/components/ReviewModal.tsx +++ b/src/plugins/reviewDB/components/ReviewModal.tsx @@ -22,12 +22,13 @@ import { useForceUpdater } from "@utils/react"; import { Paginator, Text, useRef, useState } from "@webpack/common"; import { Auth } from "../auth"; +import { ReviewType } from "../entities"; import { Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; import { cl } from "../utils"; import ReviewComponent from "./ReviewComponent"; import ReviewsView, { ReviewsInputComponent } from "./ReviewsView"; -function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; modalKey: string, discordId: string; name: string; }) { +function Modal({ modalProps, modalKey, discordId, name, type }: { modalProps: any; modalKey: string, discordId: string; name: string; type: ReviewType; }) { const [data, setData] = useState(); const [signal, refetch] = useForceUpdater(true); const [page, setPage] = useState(1); @@ -58,6 +59,7 @@ function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; mod onFetchReviews={setData} scrollToTop={() => ref.current?.scrollTo({ top: 0, behavior: "smooth" })} hideOwnReview + type={type} />
@@ -95,7 +97,7 @@ function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; mod ); } -export function openReviewsModal(discordId: string, name: string) { +export function openReviewsModal(discordId: string, name: string, type: ReviewType) { const modalKey = "vc-rdb-modal-" + Date.now(); openModal(props => ( @@ -104,6 +106,7 @@ export function openReviewsModal(discordId: string, name: string) { modalProps={props} discordId={discordId} name={name} + type={type} /> ), { modalKey }); } diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx index 76a0be475..7a7d8d020 100644 --- a/src/plugins/reviewDB/components/ReviewsView.tsx +++ b/src/plugins/reviewDB/components/ReviewsView.tsx @@ -21,7 +21,7 @@ import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpa import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common"; import { Auth, authorize } from "../auth"; -import { Review } from "../entities"; +import { Review, ReviewType } from "../entities"; import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; import { settings } from "../settings"; import { cl, showToast } from "../utils"; @@ -45,6 +45,7 @@ interface Props extends UserProps { page?: number; scrollToTop?(): void; hideOwnReview?: boolean; + type: ReviewType; } export default function ReviewsView({ @@ -56,6 +57,7 @@ export default function ReviewsView({ page = 1, showInput = false, hideOwnReview = false, + type, }: Props) { const [signal, refetch] = useForceUpdater(true); @@ -80,6 +82,7 @@ export default function ReviewsView({ reviews={reviewData!.reviews} hideOwnReview={hideOwnReview} profileId={discordId} + type={type} /> {showInput && ( @@ -94,7 +97,7 @@ export default function ReviewsView({ ); } -function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; profileId: string; }) { +function ReviewList({ refetch, reviews, hideOwnReview, profileId, type }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; profileId: string; type: ReviewType; }) { const myId = UserStore.getCurrentUser().id; return ( @@ -111,7 +114,7 @@ function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch(): {reviews?.length === 0 && ( - Looks like nobody reviewed this user yet. You could be the first! + Looks like nobody reviewed this {type === ReviewType.User ? "user" : "server"} yet. You could be the first! )}
diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index caf9bacba..1164a2c5b 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -30,7 +30,7 @@ import { Guild, User } from "discord-types/general"; import { Auth, initAuth, updateAuth } from "./auth"; import { openReviewsModal } from "./components/ReviewModal"; -import { NotificationType } from "./entities"; +import { NotificationType, ReviewType } from "./entities"; import { getCurrentUserInfo, readNotification } from "./reviewDbApi"; import { settings } from "./settings"; import { showToast } from "./utils"; @@ -44,7 +44,7 @@ const guildPopoutPatch: NavContextMenuPatchCallback = (children, { guild }: { gu label="View Reviews" id="vc-rdb-server-reviews" icon={OpenExternalIcon} - action={() => openReviewsModal(guild.id, guild.name)} + action={() => openReviewsModal(guild.id, guild.name, ReviewType.Server)} /> ); }; @@ -56,7 +56,7 @@ const userContextPatch: NavContextMenuPatchCallback = (children, { user }: { use label="View Reviews" id="vc-rdb-user-reviews" icon={OpenExternalIcon} - action={() => openReviewsModal(user.id, user.username)} + action={() => openReviewsModal(user.id, user.username, ReviewType.User)} /> ); }; @@ -157,7 +157,7 @@ export default definePlugin({ return (
) } - - + ); }, { noop: true }), }); From 244cb26c328828503835c680fa294917cb7d5f9b Mon Sep 17 00:00:00 2001 From: ElectricSteve <96793824+electricsteve@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:00:49 +0200 Subject: [PATCH 23/30] Dearrow: Add option to not dearrow by default (#2818) --- src/plugins/dearrow/index.tsx | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx index 5fb438256..10175c846 100644 --- a/src/plugins/dearrow/index.tsx +++ b/src/plugins/dearrow/index.tsx @@ -46,7 +46,7 @@ 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; + const { replaceElements, dearrowByDefault } = settings.store; if (!embed || embed.dearrow || embed.provider?.name !== "YouTube" || !embed.video?.url) return; @@ -63,18 +63,22 @@ async function embedDidMount(this: Component) { if (!hasTitle && !hasThumb) return; + embed.dearrow = { - enabled: true + enabled: dearrowByDefault }; if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) { - embed.dearrow.oldTitle = embed.rawTitle; - embed.rawTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2"); - } + const replacementTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2"); + embed.dearrow.oldTitle = dearrowByDefault ? embed.rawTitle : replacementTitle; + if (dearrowByDefault) embed.rawTitle = replacementTitle; + } 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}`; + const replacementProxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`; + + embed.dearrow.oldThumb = dearrowByDefault ? embed.thumbnail.proxyURL : replacementProxyURL; + if (embed.thumbnail.proxyURL) embed.thumbnail.proxyURL = replacementProxyURL; } this.forceUpdate(); @@ -96,6 +100,7 @@ function DearrowButton({ component }: { component: Component; }) { className={"vc-dearrow-toggle-" + (embed.dearrow.enabled ? "on" : "off")} onClick={() => { const { enabled, oldThumb, oldTitle } = embed.dearrow; + settings.store.dearrowByDefault = !enabled; embed.dearrow.enabled = !enabled; if (oldTitle) { embed.dearrow.oldTitle = embed.rawTitle; @@ -153,6 +158,12 @@ const settings = definePluginSettings({ { label: "Titles", value: ReplaceElements.ReplaceTitlesOnly }, { label: "Thumbnails", value: ReplaceElements.ReplaceThumbnailsOnly }, ], + }, + dearrowByDefault: { + description: "Dearrow videos automatically", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: false } }); From 7333f40db697bc5f06694a0b1d462468b0605529 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:16:45 -0300 Subject: [PATCH 24/30] Dearrow: Fix thumbnails with default option --- src/plugins/dearrow/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx index 10175c846..b7e90e096 100644 --- a/src/plugins/dearrow/index.tsx +++ b/src/plugins/dearrow/index.tsx @@ -78,7 +78,7 @@ async function embedDidMount(this: Component) { const replacementProxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`; embed.dearrow.oldThumb = dearrowByDefault ? embed.thumbnail.proxyURL : replacementProxyURL; - if (embed.thumbnail.proxyURL) embed.thumbnail.proxyURL = replacementProxyURL; + if (dearrowByDefault) embed.thumbnail.proxyURL = replacementProxyURL; } this.forceUpdate(); From 99cd423efbaf4da9c5d1c7bf23061d933eb99d18 Mon Sep 17 00:00:00 2001 From: Haruka <96925398+nakoyasha@users.noreply.github.com> Date: Thu, 5 Sep 2024 00:26:08 +0300 Subject: [PATCH 25/30] fix crashing on canary when searching slash commands (#2844) --- src/api/Commands/index.ts | 2 ++ src/api/Commands/types.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/api/Commands/index.ts b/src/api/Commands/index.ts index ef4db171c..5e3fc8f4d 100644 --- a/src/api/Commands/index.ts +++ b/src/api/Commands/index.ts @@ -138,6 +138,8 @@ export function registerCommand(command: C, plugin: string) { throw new Error(`Command '${command.name}' already exists.`); command.isVencordCommand = true; + command.untranslatedName ??= command.name; + command.untranslatedDescription ??= command.description; command.id ??= `-${BUILT_IN.length + 1}`; command.applicationId ??= "-1"; // BUILT_IN; command.type ??= ApplicationCommandType.CHAT_INPUT; diff --git a/src/api/Commands/types.ts b/src/api/Commands/types.ts index bd349e250..70b73775a 100644 --- a/src/api/Commands/types.ts +++ b/src/api/Commands/types.ts @@ -93,8 +93,10 @@ export interface Command { isVencordCommand?: boolean; name: string; + untranslatedName?: string; displayName?: string; description: string; + untranslatedDescription?: string; displayDescription?: string; options?: Option[]; From be02baffaa8c46d499b90c6e2f638e4fe7aa28bd Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 5 Sep 2024 02:12:09 +0200 Subject: [PATCH 26/30] MemberCount: fix null safety and improve types for PopoutPosition Co-Authored-By: fres621 <126067139+fres621@users.noreply.github.com> --- src/plugins/memberCount/MemberCount.tsx | 4 ++-- .../memberCount/OnlineMemberCountStore.ts | 8 ++++---- src/plugins/memberCount/index.tsx | 6 +++--- src/utils/discord.tsx | 6 +++--- src/webpack/common/types/components.d.ts | 18 ++++++++++-------- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/plugins/memberCount/MemberCount.tsx b/src/plugins/memberCount/MemberCount.tsx index 084e7ecc4..0a3f5e620 100644 --- a/src/plugins/memberCount/MemberCount.tsx +++ b/src/plugins/memberCount/MemberCount.tsx @@ -14,7 +14,7 @@ import { OnlineMemberCountStore } from "./OnlineMemberCountStore"; export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) { const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel()); - const guildId = isTooltip ? tooltipGuildId! : currentChannel.guild_id; + const guildId = isTooltip ? tooltipGuildId! : currentChannel?.guild_id; const totalCount = useStateFromStores( [GuildMemberCountStore], @@ -33,7 +33,7 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t const threadGroups = useStateFromStores( [ThreadMemberListStore], - () => ThreadMemberListStore.getMemberListSections(currentChannel.id) + () => ThreadMemberListStore.getMemberListSections(currentChannel?.id) ); if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) { diff --git a/src/plugins/memberCount/OnlineMemberCountStore.ts b/src/plugins/memberCount/OnlineMemberCountStore.ts index 8790f5e29..d74bea2a0 100644 --- a/src/plugins/memberCount/OnlineMemberCountStore.ts +++ b/src/plugins/memberCount/OnlineMemberCountStore.ts @@ -15,8 +15,8 @@ export const OnlineMemberCountStore = proxyLazy(() => { const onlineMemberMap = new Map(); class OnlineMemberCountStore extends Flux.Store { - getCount(guildId: string) { - return onlineMemberMap.get(guildId); + getCount(guildId?: string) { + return onlineMemberMap.get(guildId!); } async _ensureCount(guildId: string) { @@ -25,8 +25,8 @@ export const OnlineMemberCountStore = proxyLazy(() => { await PrivateChannelsStore.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id); } - ensureCount(guildId: string) { - if (onlineMemberMap.has(guildId)) return; + ensureCount(guildId?: string) { + if (!guildId || onlineMemberMap.has(guildId)) return; preloadQueue.push(() => this._ensureCount(guildId) diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx index 7e591357d..85dcc4b2d 100644 --- a/src/plugins/memberCount/index.tsx +++ b/src/plugins/memberCount/index.tsx @@ -28,12 +28,12 @@ import { FluxStore } from "@webpack/types"; import { MemberCount } from "./MemberCount"; -export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; }; +export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId?: string): number | null; }; export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & { - getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; }; + getProps(guildId?: string, channelId?: string): { groups: { count: number; id: string; }[]; }; }; export const ThreadMemberListStore = findStoreLazy("ThreadMemberListStore") as FluxStore & { - getMemberListSections(channelId: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; }; + getMemberListSections(channelId?: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; }; }; diff --git a/src/utils/discord.tsx b/src/utils/discord.tsx index c1d1e2253..4c7cc38a0 100644 --- a/src/utils/discord.tsx +++ b/src/utils/discord.tsx @@ -18,7 +18,7 @@ import { MessageObject } from "@api/MessageEvents"; 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 { Channel, Guild, Message, User } from "discord-types/general"; import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal"; @@ -54,12 +54,12 @@ export async function openInviteModal(code: string) { }); } -export function getCurrentChannel() { +export function getCurrentChannel(): Channel | undefined { return ChannelStore.getChannel(SelectedChannelStore.getChannelId()); } export function getCurrentGuild(): Guild | undefined { - return GuildStore.getGuild(getCurrentChannel()?.guild_id); + return GuildStore.getGuild(getCurrentChannel()?.guild_id!); } export function openPrivateChannel(userId: string) { diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index 0588d5e4c..6c7623339 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -91,7 +91,7 @@ export type Tooltip = ComponentType<{ /** Tooltip.Colors.BLACK */ color?: string; /** TooltipPositions.TOP */ - position?: string; + position?: PopoutPosition; tooltipClassName?: string; tooltipContentClassName?: string; @@ -110,7 +110,7 @@ export type TooltipContainer = ComponentType & { From 40512d729490091f61d645cc2e9eded9390ef26d Mon Sep 17 00:00:00 2001 From: Nyako <24845294+nyakowint@users.noreply.github.com> Date: Wed, 4 Sep 2024 20:16:41 -0400 Subject: [PATCH 27/30] XSOverlay: fix profile images (#2788) Fixes https://github.com/Vendicated/Vencord/issues/2787 Co-authored-by: v --- src/plugins/xsOverlay/index.tsx | 40 +++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/plugins/xsOverlay/index.tsx b/src/plugins/xsOverlay/index.tsx index 12dbf6b61..4c5b7a80e 100644 --- a/src/plugins/xsOverlay/index.tsx +++ b/src/plugins/xsOverlay/index.tsx @@ -86,7 +86,7 @@ interface NotificationObject { title: string; content: string; useBase64Icon: boolean; - icon: ArrayBuffer | string; + icon: string; sourceApp: string; } @@ -320,23 +320,29 @@ function shouldIgnoreForChannelType(channel: Channel) { } function sendMsgNotif(titleString: string, content: string, message: Message) { - fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => { - const msgData: NotificationObject = { - type: 1, - timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout, - height: calculateHeight(content), - opacity: settings.store.opacity, - volume: settings.store.volume, - audioPath: settings.store.soundPath, - title: titleString, - content: content, - useBase64Icon: true, - icon: new TextDecoder().decode(result), - sourceApp: "Vencord" - }; + fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`) + .then(response => response.blob()) + .then(blob => new Promise(resolve => { + const r = new FileReader(); + r.onload = () => resolve((r.result as string).split(",")[1]); + r.readAsDataURL(blob); + })).then(result => { + const msgData: NotificationObject = { + type: 1, + timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout, + height: calculateHeight(content), + opacity: settings.store.opacity, + volume: settings.store.volume, + audioPath: settings.store.soundPath, + title: titleString, + content: content, + useBase64Icon: true, + icon: result, + sourceApp: "Vencord" + }; - sendToOverlay(msgData); - }); + sendToOverlay(msgData); + }); } function sendOtherNotif(content: string, titleString: string) { From e6994e1946ea2b34c0b6b842d3a323beb9cfa3ef Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 5 Sep 2024 02:24:10 +0200 Subject: [PATCH 28/30] MentionAvatars: Fix compatibility with ServerInfo plugin --- src/plugins/mentionAvatars/styles.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/mentionAvatars/styles.css b/src/plugins/mentionAvatars/styles.css index 64eb41416..cf6e2c14c 100644 --- a/src/plugins/mentionAvatars/styles.css +++ b/src/plugins/mentionAvatars/styles.css @@ -9,3 +9,8 @@ .vc-mentionAvatars-role-icon { margin: 0 2px 0.2rem 4px; } + +/** don't display inside the ServerInfo modal owner mention */ +.vc-gp-owner .vc-mentionAvatars-icon { + display: none; +} From 61d3c08f1f6b9c44bd193aab4f8568734ba60ad8 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 5 Sep 2024 02:25:27 +0200 Subject: [PATCH 29/30] bump to v1.10.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 65a2f4512..e65e1b0a7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.9.9", + "version": "1.10.1", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 8890c8c6b4201121c559e671e0110b45937467c2 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 5 Sep 2024 02:33:07 +0200 Subject: [PATCH 30/30] fix Commands, Badges, RoleColorEverywhere --- src/api/Commands/index.ts | 7 ++++--- src/plugins/_api/badges/fixBadgeOverflow.css | 3 --- src/plugins/_api/badges/index.tsx | 4 +--- src/plugins/roleColorEverywhere/index.tsx | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) delete mode 100644 src/plugins/_api/badges/fixBadgeOverflow.css diff --git a/src/api/Commands/index.ts b/src/api/Commands/index.ts index 5e3fc8f4d..e5803ba02 100644 --- a/src/api/Commands/index.ts +++ b/src/api/Commands/index.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { Logger } from "@utils/Logger"; import { makeCodeblock } from "@utils/text"; import { sendBotMessage } from "./commandHelpers"; @@ -46,10 +47,10 @@ export let RequiredMessageOption: Option = ReqPlaceholder; export const _init = function (cmds: Command[]) { try { BUILT_IN = cmds; - OptionalMessageOption = cmds.find(c => c.name === "shrug")!.options![0]; - RequiredMessageOption = cmds.find(c => c.name === "me")!.options![0]; + OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0]; + RequiredMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "me")!.options![0]; } catch (e) { - console.error("Failed to load CommandsApi"); + new Logger("CommandsAPI").error("Failed to load CommandsApi", e, " - cmds is", cmds); } return cmds; } as never; diff --git a/src/plugins/_api/badges/fixBadgeOverflow.css b/src/plugins/_api/badges/fixBadgeOverflow.css deleted file mode 100644 index 348d0ff38..000000000 --- a/src/plugins/_api/badges/fixBadgeOverflow.css +++ /dev/null @@ -1,3 +0,0 @@ -[class*="profileBadges"] { - flex: none; -} diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index cf00a0e29..c44d98b90 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -import "./fixBadgeOverflow.css"; - import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges"; import DonateButton from "@components/DonateButton"; import ErrorBoundary from "@components/ErrorBoundary"; @@ -79,7 +77,7 @@ export default definePlugin({ replace: "...$1.props,$& $1.image??" }, { - match: /(?<=text:(\i)\.description,.{0,50})children:/, + match: /(?<=text:(\i)\.description,.{0,200})children:/, replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :" }, // conditionally override their onClick with badge.onClick if it exists diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 3e7d216b7..b5f66c098 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -57,7 +57,7 @@ export default definePlugin({ patches: [ // Chat Mentions { - find: 'location:"UserMention', + find: ".USER_MENTION)", replacement: [ { match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/,