From 2569c39ddf352cc12e2df064f916d348fe16671b Mon Sep 17 00:00:00 2001 From: ImBanana Date: Fri, 23 Aug 2024 18:54:12 +0300 Subject: [PATCH 1/5] MemberCount: add thread support (#2785) Co-authored-by: v --- src/plugins/memberCount/MemberCount.tsx | 12 +++++++++++- src/plugins/memberCount/index.tsx | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/plugins/memberCount/MemberCount.tsx b/src/plugins/memberCount/MemberCount.tsx index 3231d01ce..084e7ecc4 100644 --- a/src/plugins/memberCount/MemberCount.tsx +++ b/src/plugins/memberCount/MemberCount.tsx @@ -5,9 +5,10 @@ */ import { getCurrentChannel } from "@utils/discord"; +import { isObjectEmpty } from "@utils/misc"; import { SelectedChannelStore, Tooltip, useEffect, useStateFromStores } from "@webpack/common"; -import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat } from "."; +import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat, ThreadMemberListStore } from "."; import { OnlineMemberCountStore } from "./OnlineMemberCountStore"; export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) { @@ -30,10 +31,19 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t () => ChannelMemberStore.getProps(guildId, currentChannel?.id) ); + const threadGroups = useStateFromStores( + [ThreadMemberListStore], + () => ThreadMemberListStore.getMemberListSections(currentChannel.id) + ); + if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) { onlineCount = groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0); } + if (!isTooltip && threadGroups && !isObjectEmpty(threadGroups)) { + onlineCount = Object.values(threadGroups).reduce((total, curr) => total + (curr.sectionId === "offline" ? 0 : curr.userIds.length), 0); + } + useEffect(() => { OnlineMemberCountStore.ensureCount(guildId); }, [guildId]); diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx index 28ecb9db7..7e591357d 100644 --- a/src/plugins/memberCount/index.tsx +++ b/src/plugins/memberCount/index.tsx @@ -32,6 +32,10 @@ export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as F export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & { 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[]; }; }; +}; + const settings = definePluginSettings({ toolTip: { From 8afcb8e4dd58efdb886fd7efb9890f64e6dc2bbe Mon Sep 17 00:00:00 2001 From: Supertiger Date: Fri, 23 Aug 2024 14:36:47 -0700 Subject: [PATCH 2/5] new plugin NoMaskedLinkPaste (#2782) Co-authored-by: v --- src/plugins/noMaskedUrlPaste/README.md | 3 +++ src/plugins/noMaskedUrlPaste/index.ts | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/plugins/noMaskedUrlPaste/README.md create mode 100644 src/plugins/noMaskedUrlPaste/index.ts diff --git a/src/plugins/noMaskedUrlPaste/README.md b/src/plugins/noMaskedUrlPaste/README.md new file mode 100644 index 000000000..36707ac93 --- /dev/null +++ b/src/plugins/noMaskedUrlPaste/README.md @@ -0,0 +1,3 @@ +# NoMaskedUrlPaste + +Pasting a link while you have text selected will NOT paste your link as a masked link. diff --git a/src/plugins/noMaskedUrlPaste/index.ts b/src/plugins/noMaskedUrlPaste/index.ts new file mode 100644 index 000000000..64f522cae --- /dev/null +++ b/src/plugins/noMaskedUrlPaste/index.ts @@ -0,0 +1,23 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants.js"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "NoMaskedUrlPaste", + authors: [Devs.CatNoir], + description: "Pasting a link while having text selected will not paste as masked URL", + patches: [ + { + find: ".selection,preventEmojiSurrogates:", + replacement: { + match: /if\(null!=\i.selection&&\i.\i.isExpanded\(\i.selection\)\)/, + replace: "if(false)" + } + } + ], +}); From f0e6986835050dca5f6a73a693ee77c299e7763c Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:41:34 -0300 Subject: [PATCH 3/5] PronounDB: Fix on user profiles --- src/plugins/noProfileThemes/index.ts | 15 --------------- src/plugins/pronoundb/index.ts | 24 ++++++++++++++++++------ src/plugins/pronoundb/pronoundbUtils.ts | 17 +++++++++++------ 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/plugins/noProfileThemes/index.ts b/src/plugins/noProfileThemes/index.ts index d4737d43e..7c440df80 100644 --- a/src/plugins/noProfileThemes/index.ts +++ b/src/plugins/noProfileThemes/index.ts @@ -19,7 +19,6 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { UserStore } from "@webpack/common"; -import virtualMerge from "virtual-merge"; export default definePlugin({ name: "NoProfileThemes", @@ -33,21 +32,7 @@ export default definePlugin({ replace: "$&$self.isCurrentUser(this.userId)&&" } }, - { - find: "UserProfileStore", - replacement: { - match: /(?<=getUserProfile\(\i\){return )(.+?)(?=})/, - replace: "$self.removeProfileThemes($1)" - } - } ], isCurrentUser: (userId: string) => userId === UserStore.getCurrentUser()?.id, - removeProfileThemes: (displayProfile: any) => { - if (displayProfile == null) return displayProfile; - - return displayProfile.userId === UserStore.getCurrentUser()?.id - ? displayProfile - : virtualMerge(displayProfile, { banner: undefined, profileEffectId: undefined }); - } }); diff --git a/src/plugins/pronoundb/index.ts b/src/plugins/pronoundb/index.ts index 51486e6ec..b0c5bfe6f 100644 --- a/src/plugins/pronoundb/index.ts +++ b/src/plugins/pronoundb/index.ts @@ -26,11 +26,6 @@ import { CompactPronounsChatComponentWrapper, PronounsChatComponentWrapper } fro import { useProfilePronouns } from "./pronoundbUtils"; import { settings } from "./settings"; -const PRONOUN_TOOLTIP_PATCH = { - match: /text:(.{0,10}.Messages\.USER_PROFILE_PRONOUNS)(?=,)/, - replace: '$& + (typeof vcPronounSource !== "undefined" ? ` (${vcPronounSource})` : "")' -}; - export default definePlugin({ name: "PronounDB", authors: [Devs.Tyman, Devs.TheKodeToad, Devs.Ven, Devs.Elvyra], @@ -52,7 +47,24 @@ export default definePlugin({ ] }, - // @TODO Patch discord pronoun hook in user profiles (useProfilePronouns) + { + find: ".Messages.USER_PROFILE_PRONOUNS", + group: true, + replacement: [ + { + match: /\.PANEL},/, + replace: "$&[vcPronoun,vcPronounSource,vcHasPendingPronouns]=$self.useProfilePronouns(arguments[0].user?.id)," + }, + { + match: /text:\i\.\i.Messages.USER_PROFILE_PRONOUNS/, + replace: '$&+vcHasPendingPronouns?"":` (${vcPronounSource})`' + }, + { + match: /(\.pronounsText.+?children:)(\i)/, + replace: "$1vcHasPendingPronouns?$2:vcPronoun" + } + ] + } ], settings, diff --git a/src/plugins/pronoundb/pronoundbUtils.ts b/src/plugins/pronoundb/pronoundbUtils.ts index d4fdb09d3..991e9031a 100644 --- a/src/plugins/pronoundb/pronoundbUtils.ts +++ b/src/plugins/pronoundb/pronoundbUtils.ts @@ -21,13 +21,16 @@ import { debounce } from "@shared/debounce"; import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent"; import { getCurrentChannel } from "@utils/discord"; import { useAwaiter } from "@utils/react"; +import { findStoreLazy } from "@webpack"; import { UserProfileStore, UserStore } from "@webpack/common"; import { settings } from "./settings"; import { CachePronouns, PronounCode, PronounMapping, PronounsResponse } from "./types"; -type PronounsWithSource = [string | null, string]; -const EmptyPronouns: PronounsWithSource = [null, ""]; +const UserSettingsAccountStore = findStoreLazy("UserSettingsAccountStore"); + +type PronounsWithSource = [pronouns: string | null, source: string, hasPendingPronouns: boolean]; +const EmptyPronouns: PronounsWithSource = [null, "", false]; export const enum PronounsFormat { Lowercase = "LOWERCASE", @@ -75,13 +78,15 @@ export function useFormattedPronouns(id: string, useGlobalProfile: boolean = fal onError: e => console.error("Fetching pronouns failed: ", e) }); + const hasPendingPronouns = UserSettingsAccountStore.getPendingPronouns() != null; + if (settings.store.pronounSource === PronounSource.PreferDiscord && discordPronouns) - return [discordPronouns, "Discord"]; + return [discordPronouns, "Discord", hasPendingPronouns]; if (result && result !== PronounMapping.unspecified) - return [result, "PronounDB"]; + return [result, "PronounDB", hasPendingPronouns]; - return [discordPronouns, "Discord"]; + return [discordPronouns, "Discord", hasPendingPronouns]; } export function useProfilePronouns(id: string, useGlobalProfile: boolean = false): PronounsWithSource { @@ -147,7 +152,7 @@ async function bulkFetchPronouns(ids: string[]): Promise { } } -export function extractPronouns(pronounSet?: { [locale: string]: PronounCode[] }): 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; From 1e8f59f13d73b361a5a726bdbe733a102747da22 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:50:05 -0300 Subject: [PATCH 4/5] ReviewDB: Add view review button to other profiles types --- src/plugins/reviewDB/index.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index 8f24131c0..caf9bacba 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -79,8 +79,22 @@ export default definePlugin({ { find: ".BITE_SIZE,user:", replacement: { - match: /(?<=\.BITE_SIZE,children:\[)\(0,\i\.jsx\)\(\i\.\i,\{user:(\i),/, - replace: "$self.BiteSizeReviewsButton({user:$1}),$&" + match: /{profileType:\i\.\i\.BITE_SIZE,children:\[/, + replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," + } + }, + { + find: ".FULL_SIZE,user:", + replacement: { + match: /{profileType:\i\.\i\.FULL_SIZE,children:\[/, + replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," + } + }, + { + find: ".PANEL,isInteractionSource:", + replacement: { + match: /{profileType:\i\.\i\.PANEL,children:\[/, + replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," } } ], From 1fb5e8df99442b959cfd55be6586d7ea5f92fa80 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 23 Aug 2024 19:00:26 -0300 Subject: [PATCH 5/5] Add missing methods to ExpressionPickerStore --- src/plugins/showConnections/index.tsx | 2 +- src/webpack/common/types/utils.d.ts | 19 ++++++++++++++++++- src/webpack/common/utils.ts | 10 ++++++---- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/plugins/showConnections/index.tsx b/src/plugins/showConnections/index.tsx index d712027e3..a946e0433 100644 --- a/src/plugins/showConnections/index.tsx +++ b/src/plugins/showConnections/index.tsx @@ -168,7 +168,7 @@ export default definePlugin({ patches: [ { - find: '"BiteSizeProfileBody"', + find: ".hasAvatarForGuild(null==", replacement: { match: /currentUser:\i,guild:\i}\)(?<=user:(\i),bio:null==(\i)\?.+?)/, replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2 })" diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts index ce1e3e268..dd76d1ade 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/src/webpack/common/types/utils.d.ts @@ -223,9 +223,26 @@ export interface Constants { FriendsSections: Record; } +export type ActiveView = LiteralUnion<"emoji" | "gif" | "sticker" | "soundboard", string>; + +export interface ExpressionPickerStoreState extends Record { + activeView: ActiveView | null; + lastActiveView: ActiveView | null; + activeViewType: any | null; + searchQuery: string; + isSearchSuggestion: boolean, + pickerId: string; +} + export interface ExpressionPickerStore { + openExpressionPicker(activeView: ActiveView, activeViewType?: any): void; closeExpressionPicker(activeViewType?: any): void; - openExpressionPicker(activeView: LiteralUnion<"emoji" | "gif" | "sticker", string>, activeViewType?: any): void; + toggleMultiExpressionPicker(activeViewType?: any): void; + toggleExpressionPicker(activeView: ActiveView, activeViewType?: any): void; + setExpressionPickerView(activeView: ActiveView): void; + setSearchQuery(searchQuery: string, isSearchSuggestion?: boolean): void; + useExpressionPickerStore(): ExpressionPickerStoreState; + useExpressionPickerStore(selector: (state: ExpressionPickerStoreState) => T): T; } export interface BrowserWindowFeatures { diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index 280b2ba90..f9cce556b 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import { canonicalizeMatch } from "@utils/patches"; import type { Channel } from "discord-types/general"; // eslint-disable-next-line path-alias/no-relative @@ -162,11 +161,14 @@ export const InviteActions = findByPropsLazy("resolveInvite"); export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL"); -const openExpressionPickerMatcher = canonicalizeMatch(/setState\({activeView:\i,activeViewType:/); -// TODO: type export const ExpressionPickerStore: t.ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", { + openExpressionPicker: filters.byCode(/setState\({activeView:(?:(?!null)\i),activeViewType:/), closeExpressionPicker: filters.byCode("setState({activeView:null"), - openExpressionPicker: m => typeof m === "function" && openExpressionPickerMatcher.test(m.toString()), + toggleMultiExpressionPicker: filters.byCode(".EMOJI,"), + toggleExpressionPicker: filters.byCode(/getState\(\)\.activeView===\i\?\i\(\):\i\(/), + setExpressionPickerView: filters.byCode(/setState\({activeView:\i,lastActiveView:/), + setSearchQuery: filters.byCode("searchQuery:"), + useExpressionPickerStore: filters.byCode("Object.is") }); export const PopoutActions: t.PopoutActions = mapMangledModuleLazy('type:"POPOUT_WINDOW_OPEN"', {