diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index 8970f7ade..1d92d4a42 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -21,12 +21,35 @@ import { classNameFactory } from "@utils/index"; import definePlugin, { OptionType } from "@utils/types"; import { GuildMember, Message, RenderModalProps, User } from "@vencord/discord-types"; import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack"; -import { AccessibilityStore, ChannelStore, GuildMemberStore, GuildStore, Menu, MessageStore, Modal, openModal, RelationshipStore, StreamerModeStore, TextInput, useEffect, UserStore, useState } from "@webpack/common"; +import { AccessibilityStore, ChannelStore, GuildMemberStore, GuildStore, Menu, MessageStore, Modal, openModal, RelationshipStore, StreamerModeStore, TextInput, useEffect, UsernameUtils, UserStore, useState } from "@webpack/common"; import { JSX } from "react"; const SMYNC = classNameFactory(); const wrapEmojis = findByCodeLazy("lastIndex;return"); +interface SearchUserResult { + text: string; + user: User; +} + +interface UserAutocompleteResult { + record: User; +} + +interface UserAutocompleteUtils { + queryUsers(options: { + query: string; + users: User[]; + limit: number; + }): UserAutocompleteResult[]; +} + +const AutocompleteUtils: UserAutocompleteUtils = findByPropsLazy( + "queryUsers", + "queryGuildUsers", + "queryChannelUsers" +); + interface DisplayNameStyles { colors: number[]; effectId: number; @@ -332,6 +355,16 @@ function getProcessedNames( return { username, display, nick, friend, custom }; } +interface SearchAutocompleteProps { + currentToken?: { + getFullMatch(): string | undefined; + } | null; + maxResults?: number; + searchContext: { + guildId: string; + }; +} + interface mentionProps { userId: string; channelId?: string; @@ -352,7 +385,7 @@ interface messageProps { interface memberListProfileReactionProps { user: User | null | undefined; - type: "typingIndicator" | "membersList" | "profilesPopout" | "profilesTooltip" | "reactionsTooltip" | "reactionsPopout" | "voiceChannel"; + type: "typingIndicator" | "membersList" | "profilesPopout" | "profilesTooltip" | "reactionsTooltip" | "reactionsPopout" | "voiceChannel" | "searchAutocomplete"; guildId?: string; tags?: any; isHovered?: boolean; @@ -366,6 +399,55 @@ interface activeNowNameProps { type colorStringsType = { primaryColor: string | null, secondaryColor: string | null, tertiaryColor: string | null; } | null | undefined; +function addCustomNameResults( + results: SearchUserResult[], + { currentToken, maxResults = 10, searchContext }: SearchAutocompleteProps +) { + const query = currentToken?.getFullMatch()?.trim(); + if (!query) return results; + + const candidates = new Map( + results.map(({ user }) => [user.id, user]) + ); + + let hasCustomCandidates = false; + + for (const userId in customNicknames) { + if (!matchableCustomName(userId)) continue; + if (!GuildMemberStore.getMember(searchContext.guildId, userId)) continue; + + const user = UserStore.getUser(userId); + if (!user || user.isNonUserBot()) continue; + + candidates.set(userId, user); + hasCustomCandidates = true; + } + + if (!hasCustomCandidates) return results; + + const ranked = AutocompleteUtils.queryUsers({ + query, + users: [...candidates.values()], + limit: maxResults + }); + + const rerankedResults: SearchUserResult[] = []; + + for (const { record: user } of ranked) { + const text = UsernameUtils.getUserTag(user); + if (text != null) rerankedResults.push({ text, user }); + } + + const rerankedIds = new Set( + rerankedResults.map(({ user }) => user.id) + ); + + return [ + ...rerankedResults, + ...results.filter(({ user }) => !rerankedIds.has(user.id)) + ].slice(0, maxResults); +} + function getTypingMemberListProfilesReactionsVoiceName( props: memberListProfileReactionProps, ): [string | null, JSX.Element | null, string | null] { @@ -582,7 +664,7 @@ function renderUsername( author: User | GuildMember | null, channelId: string | null, messageId: string | null, - type: "messages" | "replies" | "typingIndicator" | "mentions" | "membersList" | "profilesPopout" | "profilesTooltip" | "reactionsTooltip" | "reactionsPopout" | "voiceChannel", + type: "messages" | "replies" | "typingIndicator" | "mentions" | "membersList" | "profilesPopout" | "profilesTooltip" | "reactionsTooltip" | "reactionsPopout" | "voiceChannel" | "searchAutocomplete", mentionSymbol: string, hookless: boolean, inGuild: boolean, @@ -595,14 +677,15 @@ function renderUsername( const isMention = type === "mentions"; const isTyping = type === "typingIndicator"; const isMember = type === "membersList"; + const isAutocomplete = type === "searchAutocomplete"; const isProfile = type === "profilesPopout"; const isReactionsPopout = type === "reactionsPopout"; const isReactionsTooltip = type === "reactionsTooltip"; const isReaction = isReactionsTooltip || isReactionsPopout; const isVoice = type === "voiceChannel"; - const config = hookless ? settings.store : settings.use(["messages", "replies", "mentions", "typingIndicator", "memberList", "styleDirectMessagesList", "styleDirectMessagesMessages", "styleFriendsList", "styleActiveNow", "profilePopout", "reactions", "friendNameOnlyInDirectMessages", "customNameOnlyInDirectMessages", "discriminators", "hideDefaultAtSign", "truncateAllNamesWithStreamerMode", "removeDuplicates", "ignoreEffects", "ignoreFonts", "animateEffects", "alwaysAnimateEffects", "gradientGlow", "includedNames", "customNameColor", "friendNameColor", "nicknameColor", "displayNameColor", "usernameColor", "nameSeparator", "triggerNameRerender"]); - const { messages, replies, mentions, typingIndicator, memberList, styleDirectMessagesMessages, profilePopout, reactions, friendNameOnlyInDirectMessages, customNameOnlyInDirectMessages, discriminators, truncateAllNamesWithStreamerMode, removeDuplicates, ignoreEffects, ignoreFonts, animateEffects, includedNames, customNameColor, friendNameColor, nicknameColor, displayNameColor, usernameColor, nameSeparator, triggerNameRerender } = config; + const config = hookless ? settings.store : settings.use(["messages", "replies", "mentions", "typingIndicator", "memberList", "searchAutocomplete", "styleDirectMessagesList", "styleDirectMessagesMessages", "styleFriendsList", "styleActiveNow", "profilePopout", "reactions", "friendNameOnlyInDirectMessages", "customNameOnlyInDirectMessages", "discriminators", "hideDefaultAtSign", "truncateAllNamesWithStreamerMode", "removeDuplicates", "ignoreEffects", "ignoreFonts", "animateEffects", "alwaysAnimateEffects", "gradientGlow", "includedNames", "customNameColor", "friendNameColor", "nicknameColor", "displayNameColor", "usernameColor", "nameSeparator", "triggerNameRerender"]); + const { messages, replies, mentions, typingIndicator, memberList, searchAutocomplete, styleDirectMessagesMessages, profilePopout, reactions, friendNameOnlyInDirectMessages, customNameOnlyInDirectMessages, discriminators, truncateAllNamesWithStreamerMode, removeDuplicates, ignoreEffects, ignoreFonts, animateEffects, includedNames, customNameColor, friendNameColor, nicknameColor, displayNameColor, usernameColor, nameSeparator, triggerNameRerender } = config; const channel = channelId ? ChannelStore.getChannel(channelId) || null : null; const message = channelId && messageId ? MessageStore.getMessage(channelId, messageId) : null; @@ -750,6 +833,8 @@ function renderUsername( return [null, null, null]; } else if (isMember && !memberList) { return [null, null, null]; + } else if (isAutocomplete && !searchAutocomplete) { + return [null, null, null]; } else if (isProfile && !profilePopout) { return [null, null, null]; } else if (isReaction && !reactions) { @@ -1116,29 +1201,10 @@ const settings = definePluginSettings({ default: true, description: "Display the first available name listed in your custom name format in the members list, DMs list, friends list, and Active Now.", }, - styleDirectMessagesList: { + searchAutocomplete: { type: OptionType.BOOLEAN, default: true, - displayName: "Style Direct Messages List", - description: "Show display name effects on unselected entries without requiring hover. Selected and hovered entries are still styled when disabled.", - }, - styleDirectMessagesMessages: { - type: OptionType.BOOLEAN, - default: true, - displayName: "Style Direct Messages Messages", - description: "Show display name effects on message authors without requiring hover. Hovered names are still styled when disabled.", - }, - styleFriendsList: { - type: OptionType.BOOLEAN, - default: true, - displayName: "Style Friends List", - description: "Show display name effects without requiring hover. Hovered entries are still styled when disabled.", - }, - styleActiveNow: { - type: OptionType.BOOLEAN, - default: true, - displayName: "Style Active Now", - description: "Show display name effects without requiring hover. Hovered entries are still styled when disabled.", + description: "Display the first available name listed in your custom name format in mention and search autocomplete", }, profilePopout: { type: OptionType.BOOLEAN, @@ -1155,6 +1221,26 @@ const settings = definePluginSettings({ default: true, description: "Display the first available name listed in your custom name format in reaction tooltips, and the full name in reaction popouts.", }, + styleDirectMessagesList: { + type: OptionType.BOOLEAN, + default: true, + description: "Show display name effects on unselected entries without requiring hover. Selected and hovered entries are still styled when disabled.", + }, + styleDirectMessagesMessages: { + type: OptionType.BOOLEAN, + default: true, + description: "Show display name effects on message authors without requiring hover. Hovered names are still styled when disabled.", + }, + styleFriendsList: { + type: OptionType.BOOLEAN, + default: true, + description: "Show display name effects without requiring hover. Hovered entries are still styled when disabled.", + }, + styleActiveNow: { + type: OptionType.BOOLEAN, + default: true, + description: "Show display name effects without requiring hover. Hovered entries are still styled when disabled.", + }, discriminators: { type: OptionType.BOOLEAN, default: true, @@ -1263,7 +1349,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "ShowMeYourName", description: "Display any permutation of custom nicknames, friend nicknames, server nicknames, display names, and usernames in chat.", - authors: [EquicordDevs.Etorix, Devs.Rini, Devs.TheKodeToad, Devs.sadan, Devs.prism], + authors: [EquicordDevs.Etorix, Devs.Rini, Devs.TheKodeToad, Devs.sadan, Devs.prism, EquicordDevs.penguinwokrs], tags: ["Appearance", "Customisation"], searchTerms: ["SMYN", "Nicknames", "Custom Nicknames"], isModified: true, @@ -1509,28 +1595,45 @@ export default definePlugin({ } }, { - // Let the mention autocomplete match custom names too. + // Allow custom names to be considered in the mention auto complete. find: "queryGuildMentionResults(", group: true, replacement: [ { - // Rank a leading match the same as one on a username, nickname or display name. + // Rank a leading match the same as friend names, global names, nicknames, and usernames. match: /\i&&\i===(\i)\.id\|\|\i\.substring\(0,(\i)\.length\)===\2\|\|/, replace: "$&$self.matchableCustomName($1.id).startsWith($2)||" }, { - // Rank a looser match with the same matcher and tier as those names. + // Rank a looser match using the same matcher and tie breaker as friend names, global names, nicknames, and usernames. match: /\i<50&&\((\i)\(\)\((\i),\i\).{0,120}?(?=\)&&\(\i\.push\(\{type:\i\.\i\.\i,record:(\i),)/, replace: "$&||$1()($2,$self.matchableCustomName($3.id))" } ] }, { - // Let the mention autocomplete ui have our custom names, support our formatting, etc. + // Allow custom names to be considered in the search autocomplete by + // inserting custom names into the results and re-sorting them by rank. + find: '="SearchAutocompleteStore"', + replacement: { + match: /(?<=&&!\i\)\i=)\i\(\i\)\.results/, + replace: "$self.addCustomNameResults($&,arguments[0])" + } + }, + { + // Replace names in the from: and mentions: search filter suggestions. + find: "hasOtherSearchFiltersVisible", + replacement: { + match: /(\i)=(\i\.\i\.useName\((\i),\i,(\i)\))/, + replace: '$1=$self.getTypingMemberListProfilesReactionsVoiceNameText({user:$4,guildId:$3,type:"searchAutocomplete"})??$2' + } + }, + { + // Replace names in the mention autocomplete. find: "#{intl::COMMANDS_OPTIONAL_COUNT}", replacement: { - match: /\i\?\?\i\?\?\i\.\i\.getName\(\i\)/, - replace: '$self.getTypingMemberListProfilesReactionsVoiceNameText({user:this.props.user,guildId:this.props.guildId,type:"membersList"})' + match: /(\i\?\?\i\?\?\i\.\i\.getName\(\i\))/, + replace: '$self.getTypingMemberListProfilesReactionsVoiceNameText({user:this.props.user,guildId:this.props.guildId,type:"searchAutocomplete"})??$1' } } ], @@ -1584,5 +1687,6 @@ export default definePlugin({ shouldAnimateNameEffects, getTypingMemberListProfilesReactionsVoiceNameText, getTypingMemberListProfilesReactionsVoiceNameElement, - matchableCustomName + matchableCustomName, + addCustomNameResults, }); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 7a4d864bc..5e7c3a7dd 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1430,6 +1430,10 @@ export const EquicordDevs = Object.freeze({ name: "ELJoOker", id: 605894319408283678n }, + penguinwokrs: { + name: "penguinwokrs", + id: 385266832136863746n + }, } satisfies Record); // iife so #__PURE__ works correctly