fix(app): apply streamer mode to member nicknames and stop guild scope leaking into global surfaces (#1697)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-17 14:58:56 +02:00
1 parent 3fa766c39c
commit 3ff7189566
32 files changed
+82 -61

No files matched your search

@@ -138,7 +138,7 @@ export const RecipientList = observer((props: RecipientListProps) => {
const query = searchQuery.toLowerCase(); const query = searchQuery.toLowerCase();
return props.recipients.filter((item) => { return props.recipients.filter((item) => {
const username = item.user.username.toLowerCase(); const username = item.user.username.toLowerCase();
const displayName = NicknameUtils.getNickname(item.user).toLowerCase(); const displayName = NicknameUtils.getNickname(item.user, null).toLowerCase();
const channelName = (item.channelName || '').toLowerCase(); const channelName = (item.channelName || '').toLowerCase();
return username.includes(query) || displayName.includes(query) || channelName.includes(query); return username.includes(query) || displayName.includes(query) || channelName.includes(query);
}); });
@@ -186,7 +186,8 @@ export const RecipientList = observer((props: RecipientListProps) => {
const userId = item.type === 'group_dm' ? item.id : item.user.id; const userId = item.type === 'group_dm' ? item.id : item.user.id;
const isSending = props.sendingTo.has(userId); const isSending = props.sendingTo.has(userId);
const isSent = props.sentTo.has(userId); const isSent = props.sentTo.has(userId);
const displayName = item.type === 'group_dm' ? item.channelName : NicknameUtils.getNickname(item.user); const displayName =
item.type === 'group_dm' ? item.channelName : NicknameUtils.getNickname(item.user, null);
const secondaryText = (() => { const secondaryText = (() => {
if (item.type === 'dm') return i18n._(DIRECT_MESSAGE_DESCRIPTOR); if (item.type === 'dm') return i18n._(DIRECT_MESSAGE_DESCRIPTOR);
if (item.type === 'group_dm') return i18n._(GROUP_DM_DESCRIPTOR); if (item.type === 'group_dm') return i18n._(GROUP_DM_DESCRIPTOR);
@@ -518,7 +518,7 @@ export const UserAreaPopout = observer(() => {
const accentColor = getUserAccentColor(currentUser, profileData?.accent_color); const accentColor = getUserAccentColor(currentUser, profileData?.accent_color);
const borderColor = accentColor; const borderColor = accentColor;
const bannerColor = accentColor; const bannerColor = accentColor;
const displayName = currentUser ? NicknameUtils.getNickname(currentUser) : ''; const displayName = currentUser ? NicknameUtils.getNickname(currentUser, null) : '';
const customStatus = currentUserId ? Presence.getCustomStatus(currentUserId) : null; const customStatus = currentUserId ? Presence.getCustomStatus(currentUserId) : null;
const hasCustomStatus = Boolean(normalizeCustomStatus(customStatus)); const hasCustomStatus = Boolean(normalizeCustomStatus(customStatus));
const popoutContainerRef = useRef<HTMLDivElement | null>(null); const popoutContainerRef = useRef<HTMLDivElement | null>(null);
@@ -243,7 +243,7 @@ const UserAreaInner = observer(
if (isDeafened) return i18n._(VOICE_UNDEAFEN_SELF_DESCRIPTOR); if (isDeafened) return i18n._(VOICE_UNDEAFEN_SELF_DESCRIPTOR);
return i18n._(VOICE_DEAFEN_SELF_DESCRIPTOR); return i18n._(VOICE_DEAFEN_SELF_DESCRIPTOR);
})(); })();
const displayName = NicknameUtils.getNickname(user); const displayName = NicknameUtils.getNickname(user, null);
return ( return (
<section <section
className={wrapperClassName} className={wrapperClassName}
@@ -81,7 +81,7 @@ export const FriendSelector: React.FC<FriendSelectorProps> = observer(
return friends return friends
.map((relationship) => Users.getUser(relationship.id)) .map((relationship) => Users.getUser(relationship.id))
.filter((user): user is User => Boolean(user)) .filter((user): user is User => Boolean(user))
.sort((a, b) => NicknameUtils.getNickname(a).localeCompare(NicknameUtils.getNickname(b))); .sort((a, b) => NicknameUtils.getNickname(a, null).localeCompare(NicknameUtils.getNickname(b, null)));
}, [relationships, excludeUserIds]); }, [relationships, excludeUserIds]);
const activeStickyUserIds = useMemo(() => { const activeStickyUserIds = useMemo(() => {
return stickyUserIds.filter((id) => selectedUserIds.includes(id)); return stickyUserIds.filter((id) => selectedUserIds.includes(id));
@@ -89,13 +89,13 @@ export const FriendSelector: React.FC<FriendSelectorProps> = observer(
const groupedFriends = useMemo(() => { const groupedFriends = useMemo(() => {
const filtered = friendUsers.filter((user) => { const filtered = friendUsers.filter((user) => {
if (!searchQuery) return true; if (!searchQuery) return true;
return NicknameUtils.getNickname(user).toLowerCase().includes(searchQuery.toLowerCase()); return NicknameUtils.getNickname(user, null).toLowerCase().includes(searchQuery.toLowerCase());
}); });
const stickySet = new Set(activeStickyUserIds); const stickySet = new Set(activeStickyUserIds);
const groups: Record<string, Array<User>> = {}; const groups: Record<string, Array<User>> = {};
filtered.forEach((user) => { filtered.forEach((user) => {
if (stickySet.has(user.id)) return; if (stickySet.has(user.id)) return;
const firstLetter = NicknameUtils.getNickname(user)[0].toUpperCase(); const firstLetter = NicknameUtils.getNickname(user, null)[0].toUpperCase();
if (!groups[firstLetter]) { if (!groups[firstLetter]) {
groups[firstLetter] = []; groups[firstLetter] = [];
} }
@@ -153,14 +153,16 @@ export const FriendSelector: React.FC<FriendSelectorProps> = observer(
data-flx="app.friend-selector.render-search-input.selected-pill" data-flx="app.friend-selector.render-search-input.selected-pill"
> >
<Avatar user={user} size={16} data-flx="app.friend-selector.render-search-input.avatar" /> <Avatar user={user} size={16} data-flx="app.friend-selector.render-search-input.avatar" />
<span data-flx="app.friend-selector.render-search-input.span">{NicknameUtils.getNickname(user)}</span> <span data-flx="app.friend-selector.render-search-input.span">
{NicknameUtils.getNickname(user, null)}
</span>
<FocusRing offset={-2} data-flx="app.friend-selector.render-search-input.focus-ring"> <FocusRing offset={-2} data-flx="app.friend-selector.render-search-input.focus-ring">
<button <button
type="button" type="button"
onClick={() => handleRemovePill(userId)} onClick={() => handleRemovePill(userId)}
className={styles.removeButton} className={styles.removeButton}
aria-label={i18n._(REMOVE_DESCRIPTOR, { aria-label={i18n._(REMOVE_DESCRIPTOR, {
displayName: NicknameUtils.getNickname(user), displayName: NicknameUtils.getNickname(user, null),
})} })}
data-flx="app.friend-selector.render-search-input.remove-button.remove-pill" data-flx="app.friend-selector.render-search-input.remove-button.remove-pill"
> >
@@ -252,7 +254,7 @@ export const FriendSelector: React.FC<FriendSelectorProps> = observer(
data-flx="app.friend-selector.status-aware-avatar" data-flx="app.friend-selector.status-aware-avatar"
/> />
<span className={styles.friendName} data-flx="app.friend-selector.friend-name"> <span className={styles.friendName} data-flx="app.friend-selector.friend-name">
{NicknameUtils.getNickname(user)} {NicknameUtils.getNickname(user, null)}
</span> </span>
</div> </div>
<div className={styles.checkboxContainer} data-flx="app.friend-selector.checkbox-container"> <div className={styles.checkboxContainer} data-flx="app.friend-selector.checkbox-container">
@@ -306,7 +308,7 @@ export const FriendSelector: React.FC<FriendSelectorProps> = observer(
data-flx="app.friend-selector.status-aware-avatar--2" data-flx="app.friend-selector.status-aware-avatar--2"
/> />
<span className={styles.friendName} data-flx="app.friend-selector.friend-name--2"> <span className={styles.friendName} data-flx="app.friend-selector.friend-name--2">
{NicknameUtils.getNickname(user)} {NicknameUtils.getNickname(user, null)}
</span> </span>
</div> </div>
<div <div
@@ -487,7 +487,9 @@ const LazyMemberList = observer(function LazyMemberList({guild, channel}: LazyMe
continue; continue;
} }
const user = member.user; const user = member.user;
const displayName = member.nick ?? NicknameUtils.getNickname(user, guild.id); const displayName = member.nick
? NicknameUtils.formatNicknameForStreamerMode(member.nick)
: NicknameUtils.getNickname(user, guild.id);
const status = resolveMemberListPresence({guildId: guild.id, channelId: channel.id, userId: user.id}); const status = resolveMemberListPresence({guildId: guild.id, channelId: channel.id, userId: user.id});
const customStatus = resolveMemberListCustomStatus({ const customStatus = resolveMemberListCustomStatus({
guildId: guild.id, guildId: guild.id,
@@ -599,7 +601,9 @@ const LazyMemberList = observer(function LazyMemberList({guild, channel}: LazyMe
continue; continue;
} }
const user = member.user; const user = member.user;
const displayName = member.nick ?? NicknameUtils.getNickname(user, guild.id); const displayName = member.nick
? NicknameUtils.formatNicknameForStreamerMode(member.nick)
: NicknameUtils.getNickname(user, guild.id);
const status = resolveMemberListPresence({guildId: guild.id, channelId: channel.id, userId: user.id}); const status = resolveMemberListPresence({guildId: guild.id, channelId: channel.id, userId: user.id});
const customStatus = resolveMemberListCustomStatus({ const customStatus = resolveMemberListCustomStatus({
guildId: guild.id, guildId: guild.id,
@@ -305,7 +305,7 @@ export const ChannelDetailsBottomSheet: React.FC<ChannelDetailsBottomSheetProps>
<ConfirmModal <ConfirmModal
title={i18n._(CLOSE_DM_DESCRIPTOR)} title={i18n._(CLOSE_DM_DESCRIPTOR)}
description={i18n._(ARE_YOU_SURE_YOU_WANT_TO_CLOSE_YOUR_DESCRIPTOR, { description={i18n._(ARE_YOU_SURE_YOU_WANT_TO_CLOSE_YOUR_DESCRIPTOR, {
recipientUsername: recipient ? NicknameUtils.getNickname(recipient) : '', recipientUsername: recipient ? NicknameUtils.getNickname(recipient, null) : '',
})} })}
primaryText={i18n._(CLOSE_DM_DESCRIPTOR)} primaryText={i18n._(CLOSE_DM_DESCRIPTOR)}
primaryVariant="danger" primaryVariant="danger"
@@ -129,7 +129,9 @@ export const MobileMemberListItem = observer(
const handleLongPress = useCallback(() => { const handleLongPress = useCallback(() => {
onLongPress?.(member); onLongPress?.(member);
}, [member, onLongPress]); }, [member, onLongPress]);
const displayName = member.nick ?? NicknameUtils.getNickname(member.user, guild.id); const displayName = member.nick
? NicknameUtils.formatNicknameForStreamerMode(member.nick)
: NicknameUtils.getNickname(member.user, guild.id);
const avatarUrl = useMemo( const avatarUrl = useMemo(
() => () =>
AvatarUtils.getGuildMemberDisplayAvatarURL({ AvatarUtils.getGuildMemberDisplayAvatarURL({
@@ -491,7 +491,7 @@ export const DMChannelView = observer(({channelId}: DMChannelViewProps) => {
) : isDM && isRecipientBlocked && recipient ? ( ) : isDM && isRecipientBlocked && recipient ? (
<BlockedUserBarrier <BlockedUserBarrier
userId={recipient.id} userId={recipient.id}
username={NicknameUtils.getNickname(recipient)} username={NicknameUtils.getNickname(recipient, null)}
data-flx="channel.channel-view.dm-channel-view.blocked-user-barrier" data-flx="channel.channel-view.dm-channel-view.blocked-user-barrier"
/> />
) : isCurrentUserUnclaimed && isDM && !isPersonalNotes && !isGroupDM ? ( ) : isCurrentUserUnclaimed && isDM && !isPersonalNotes && !isGroupDM ? (
@@ -72,7 +72,7 @@ export const CallParticipantsRow = observer(
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
const participantEntry = participantEntryByUserId.get(user.id); const participantEntry = participantEntryByUserId.get(user.id);
const participantName = NicknameUtils.getNickname(user, channel.guildId ?? undefined, channel.id) || user.id; const participantName = NicknameUtils.getNickname(user, channel.guildId ?? null, channel.id) || user.id;
ContextMenuCommands.openFromEvent(event, ({onClose}) => ContextMenuCommands.openFromEvent(event, ({onClose}) =>
participantEntry ? ( participantEntry ? (
<VoiceParticipantContextMenu <VoiceParticipantContextMenu
@@ -155,7 +155,7 @@ export const CallParticipantsRow = observer(
> >
{participants.map(({user, isRinging}) => { {participants.map(({user, isRinging}) => {
const participantEntry = participantEntryByUserId.get(user.id); const participantEntry = participantEntryByUserId.get(user.id);
const displayName = NicknameUtils.getNickname(user, channel.guildId ?? undefined, channel.id); const displayName = NicknameUtils.getNickname(user, channel.guildId ?? null, channel.id);
return ( return (
<motion.button <motion.button
type="button" type="button"
@@ -175,7 +175,7 @@ export function useDMListItemHandlers({
closeAllSheets(); closeAllSheets();
}, [channel.id, closeAllSheets]); }, [channel.id, closeAllSheets]);
const handleCloseDm = useCallback(() => { const handleCloseDm = useCallback(() => {
const username = recipient ? NicknameUtils.getNickname(recipient) : ''; const username = recipient ? NicknameUtils.getNickname(recipient, null) : '';
const description = isGroupDM const description = isGroupDM
? i18n._(ARE_YOU_SURE_YOU_WANT_TO_CLOSE_THIS_DESCRIPTOR) ? i18n._(ARE_YOU_SURE_YOU_WANT_TO_CLOSE_THIS_DESCRIPTOR)
: i18n._(ARE_YOU_SURE_YOU_WANT_TO_CLOSE_YOUR_DESCRIPTOR, {username}); : i18n._(ARE_YOU_SURE_YOU_WANT_TO_CLOSE_YOUR_DESCRIPTOR, {username});
@@ -367,7 +367,7 @@ export const FriendListItem: React.FC<FriendListItemProps> = observer((props) =>
<div className={styles.friendDetails} data-flx="channel.friends.friend-list-item.friend-details"> <div className={styles.friendDetails} data-flx="channel.friends.friend-list-item.friend-details">
<div className={styles.friendNameRow} data-flx="channel.friends.friend-list-item.friend-name-row"> <div className={styles.friendNameRow} data-flx="channel.friends.friend-list-item.friend-name-row">
<span className={styles.friendName} data-flx="channel.friends.friend-list-item.friend-name"> <span className={styles.friendName} data-flx="channel.friends.friend-list-item.friend-name">
{NicknameUtils.getNickname(user)} {NicknameUtils.getNickname(user, null)}
</span> </span>
<span className={styles.friendTag} data-flx="channel.friends.friend-list-item.friend-tag"> <span className={styles.friendTag} data-flx="channel.friends.friend-list-item.friend-tag">
{NicknameUtils.formatTagForStreamerMode(user.tag)} {NicknameUtils.formatTagForStreamerMode(user.tag)}
@@ -167,7 +167,7 @@ export const MobileFriendRequestItem: React.FC<MobileFriendRequestItemProps> = o
/> />
<div className={styles.userInfo} data-flx="channel.friends.mobile-friend-request-item.user-info"> <div className={styles.userInfo} data-flx="channel.friends.mobile-friend-request-item.user-info">
<span className={styles.userName} data-flx="channel.friends.mobile-friend-request-item.user-name"> <span className={styles.userName} data-flx="channel.friends.mobile-friend-request-item.user-name">
{NicknameUtils.getNickname(user)} {NicknameUtils.getNickname(user, null)}
</span> </span>
<span className={styles.requestStatus} data-flx="channel.friends.mobile-friend-request-item.request-status"> <span className={styles.requestStatus} data-flx="channel.friends.mobile-friend-request-item.request-status">
{statusText} {statusText}
@@ -179,7 +179,7 @@ export const MobileFriendRequestItem: React.FC<MobileFriendRequestItemProps> = o
onClick={() => setMenuOpen(true)} onClick={() => setMenuOpen(true)}
className={styles.actionButton} className={styles.actionButton}
aria-label={i18n._(FRIEND_REQUEST_ACTIONS_FOR_DESCRIPTOR, { aria-label={i18n._(FRIEND_REQUEST_ACTIONS_FOR_DESCRIPTOR, {
displayName: NicknameUtils.getNickname(user), displayName: NicknameUtils.getNickname(user, null),
})} })}
aria-haspopup="dialog" aria-haspopup="dialog"
data-flx="channel.friends.mobile-friend-request-item.action-button.set-menu-open" data-flx="channel.friends.mobile-friend-request-item.action-button.set-menu-open"
@@ -70,7 +70,7 @@ export const FriendsList: React.FC<FriendsListProps> = observer(({showOnlineOnly
return true; return true;
} }
const user = Users.getUser(userId); const user = Users.getUser(userId);
const nickname = user ? NicknameUtils.getNickname(user) : ''; const nickname = user ? NicknameUtils.getNickname(user, null) : '';
const username = user?.username ?? ''; const username = user?.username ?? '';
return `${nickname} ${username}`.toLowerCase().includes(normalizedQuery); return `${nickname} ${username}`.toLowerCase().includes(normalizedQuery);
}; };
@@ -84,7 +84,7 @@ export const FriendsList: React.FC<FriendsListProps> = observer(({showOnlineOnly
const userA = Users.getUser(a); const userA = Users.getUser(a);
const userB = Users.getUser(b); const userB = Users.getUser(b);
if (!userA || !userB) return 0; if (!userA || !userB) return 0;
return NicknameUtils.getNickname(userA).localeCompare(NicknameUtils.getNickname(userB)); return NicknameUtils.getNickname(userA, null).localeCompare(NicknameUtils.getNickname(userB, null));
}); });
const reportedTab = showOnlineOnly ? SkeletonFriendsTab.ONLINE : SkeletonFriendsTab.ALL; const reportedTab = showOnlineOnly ? SkeletonFriendsTab.ONLINE : SkeletonFriendsTab.ALL;
useEffect(() => { useEffect(() => {
@@ -68,7 +68,7 @@ export const PendingFriendsView: React.FC<PendingFriendsViewProps> = observer(
return true; return true;
} }
const user = Users.getUser(userId); const user = Users.getUser(userId);
const nickname = user ? NicknameUtils.getNickname(user) : ''; const nickname = user ? NicknameUtils.getNickname(user, null) : '';
const username = user?.username ?? ''; const username = user?.username ?? '';
return `${nickname} ${username}`.toLowerCase().includes(normalizedQuery); return `${nickname} ${username}`.toLowerCase().includes(normalizedQuery);
}; };
@@ -77,7 +77,7 @@ export const PendingFriendsView: React.FC<PendingFriendsViewProps> = observer(
const userA = Users.getUser(a.id); const userA = Users.getUser(a.id);
const userB = Users.getUser(b.id); const userB = Users.getUser(b.id);
if (!userA || !userB) return 0; if (!userA || !userB) return 0;
return NicknameUtils.getNickname(userA).localeCompare(NicknameUtils.getNickname(userB)); return NicknameUtils.getNickname(userA, null).localeCompare(NicknameUtils.getNickname(userB, null));
}); });
const visibleIncoming = sortByDisplayName( const visibleIncoming = sortByDisplayName(
hasSearch ? incomingRequests.filter((request) => matchesSearch(request.id)) : incomingRequests, hasSearch ? incomingRequests.filter((request) => matchesSearch(request.id)) : incomingRequests,
@@ -268,7 +268,7 @@ export const MemberTableRow: React.FC<MemberTableRowProps> = observer(
}, },
[topRole], [topRole],
); );
const displayName = member?.nick ?? data.nickname ?? data.displayName; const displayName = NicknameUtils.formatNicknameForStreamerMode(member?.nick || data.nickname || data.displayName);
const tag = user?.tag ?? data.tag; const tag = user?.tag ?? data.tag;
return ( return (
<div <div
@@ -13,6 +13,7 @@ import GuildMembers from '@app/features/member/state/GuildMembers';
import {http} from '@app/features/platform/transport/RestTransport'; import {http} from '@app/features/platform/transport/RestTransport';
import {Logger} from '@app/features/platform/utils/AppLogger'; import {Logger} from '@app/features/platform/utils/AppLogger';
import Users from '@app/features/user/state/Users'; import Users from '@app/features/user/state/Users';
import * as NicknameUtils from '@app/features/user/utils/NicknameUtils';
import {extractTimestampFromSnowflakeAsDate} from '@fluxer/snowflake/src/SnowflakeUtils'; import {extractTimestampFromSnowflakeAsDate} from '@fluxer/snowflake/src/SnowflakeUtils';
export const logger = new Logger('GuildMembersPage'); export const logger = new Logger('GuildMembersPage');
@@ -27,7 +28,8 @@ export async function searchGuildMembers(guildId: string, params: SearchParams):
export function toMemberDisplayData(searchMember: SearchableGuildMember, guildId: string): MemberDisplayData { export function toMemberDisplayData(searchMember: SearchableGuildMember, guildId: string): MemberDisplayData {
const user = Users.getUser(searchMember.user_id); const user = Users.getUser(searchMember.user_id);
const member = GuildMembers.getMember(guildId, searchMember.user_id); const member = GuildMembers.getMember(guildId, searchMember.user_id);
const displayName = member?.nick ?? searchMember.nickname ?? searchMember.global_name ?? searchMember.username; const resolvedName = member?.nick || searchMember.nickname || searchMember.global_name || searchMember.username;
const displayName = NicknameUtils.formatNicknameForStreamerMode(resolvedName);
const tag = user ? user.tag : `${searchMember.username}#${searchMember.discriminator}`; const tag = user ? user.tag : `${searchMember.username}#${searchMember.discriminator}`;
return { return {
userId: searchMember.user_id, userId: searchMember.user_id,
@@ -63,14 +63,14 @@ const useFriendGroups = (friendIds: Array<string>, searchQuery: string) => {
const user = Users.getUser(userId); const user = Users.getUser(userId);
if (!user) return false; if (!user) return false;
if (!searchQuery) return true; if (!searchQuery) return true;
const nickname = NicknameUtils.getNickname(user).toLowerCase(); const nickname = NicknameUtils.getNickname(user, null).toLowerCase();
return nickname.includes(searchQuery.toLowerCase()); return nickname.includes(searchQuery.toLowerCase());
}); });
const groups: Record<string, Array<string>> = {}; const groups: Record<string, Array<string>> = {};
for (const userId of filtered) { for (const userId of filtered) {
const user = Users.getUser(userId); const user = Users.getUser(userId);
if (!user) continue; if (!user) continue;
const firstLetter = NicknameUtils.getNickname(user)[0].toUpperCase(); const firstLetter = NicknameUtils.getNickname(user, null)[0].toUpperCase();
if (!groups[firstLetter]) { if (!groups[firstLetter]) {
groups[firstLetter] = []; groups[firstLetter] = [];
} }
@@ -81,7 +81,7 @@ const useFriendGroups = (friendIds: Array<string>, searchQuery: string) => {
const userA = Users.getUser(a); const userA = Users.getUser(a);
const userB = Users.getUser(b); const userB = Users.getUser(b);
if (!userA || !userB) return 0; if (!userA || !userB) return 0;
return NicknameUtils.getNickname(userA).localeCompare(NicknameUtils.getNickname(userB)); return NicknameUtils.getNickname(userA, null).localeCompare(NicknameUtils.getNickname(userB, null));
}); });
} }
const groupArray: Array<FriendGroup> = Object.keys(groups) const groupArray: Array<FriendGroup> = Object.keys(groups)
@@ -143,7 +143,7 @@ const FriendItem = observer(({userId}: {userId: string}) => {
className={styles.friendItemName} className={styles.friendItemName}
data-flx="relationship.friends-list-utils.friend-item.friend-item-name" data-flx="relationship.friends-list-utils.friend-item.friend-item-name"
> >
{NicknameUtils.getNickname(user)} {NicknameUtils.getNickname(user, null)}
</div> </div>
{statusLabel && ( {statusLabel && (
<div <div
@@ -184,7 +184,7 @@ export function showSendFriendRequestConfirmation(i18n: I18n, user: User): void
<ConfirmModal <ConfirmModal
title={i18n._(ADD_FRIEND_DESCRIPTOR)} title={i18n._(ADD_FRIEND_DESCRIPTOR)}
description={i18n._(SEND_A_FRIEND_REQUEST_TO_OR_USE_THE_DESCRIPTOR, { description={i18n._(SEND_A_FRIEND_REQUEST_TO_OR_USE_THE_DESCRIPTOR, {
userName: NicknameUtils.getNickname(user), userName: NicknameUtils.getNickname(user, null),
})} })}
primaryText={i18n._(ADD_FRIEND_DESCRIPTOR)} primaryText={i18n._(ADD_FRIEND_DESCRIPTOR)}
onPrimary={async (staffForceAccept) => { onPrimary={async (staffForceAccept) => {
@@ -225,7 +225,7 @@ export function showAcceptFriendRequestConfirmation(
<ConfirmModal <ConfirmModal
title={i18n._(ACCEPT_FRIEND_REQUEST_DESCRIPTOR)} title={i18n._(ACCEPT_FRIEND_REQUEST_DESCRIPTOR)}
description={i18n._(ARE_YOU_SURE_YOU_WANT_TO_ACCEPT_THE_DESCRIPTOR, { description={i18n._(ARE_YOU_SURE_YOU_WANT_TO_ACCEPT_THE_DESCRIPTOR, {
userName: NicknameUtils.getNickname(user), userName: NicknameUtils.getNickname(user, null),
})} })}
primaryText={i18n._(ACCEPT_FRIEND_REQUEST_ACTION_DESCRIPTOR)} primaryText={i18n._(ACCEPT_FRIEND_REQUEST_ACTION_DESCRIPTOR)}
primaryVariant="primary" primaryVariant="primary"
@@ -273,7 +273,7 @@ export function showRemoveFriendConfirmation(
<ConfirmModal <ConfirmModal
title={i18n._(REMOVE_FRIEND_DESCRIPTOR)} title={i18n._(REMOVE_FRIEND_DESCRIPTOR)}
description={i18n._(ARE_YOU_SURE_YOU_WANT_TO_REMOVE_AS_DESCRIPTOR, { description={i18n._(ARE_YOU_SURE_YOU_WANT_TO_REMOVE_AS_DESCRIPTOR, {
userName: NicknameUtils.getNickname(user), userName: NicknameUtils.getNickname(user, null),
})} })}
primaryText={i18n._(REMOVE_FRIEND_DESCRIPTOR)} primaryText={i18n._(REMOVE_FRIEND_DESCRIPTOR)}
primaryVariant="danger" primaryVariant="danger"
@@ -307,7 +307,7 @@ export function showBlockUserConfirmation(i18n: I18n, user: User, options: Relat
<ConfirmModal <ConfirmModal
title={i18n._(BLOCK_USER_DESCRIPTOR)} title={i18n._(BLOCK_USER_DESCRIPTOR)}
description={i18n._(ARE_YOU_SURE_YOU_WANT_TO_BLOCK_THEY_DESCRIPTOR, { description={i18n._(ARE_YOU_SURE_YOU_WANT_TO_BLOCK_THEY_DESCRIPTOR, {
userName: NicknameUtils.getNickname(user), userName: NicknameUtils.getNickname(user, null),
})} })}
primaryText={i18n._(BLOCK_DESCRIPTOR)} primaryText={i18n._(BLOCK_DESCRIPTOR)}
primaryVariant="danger" primaryVariant="danger"
@@ -345,7 +345,7 @@ export function showUnblockUserConfirmation(
<ConfirmModal <ConfirmModal
title={i18n._(UNBLOCK_USER_DESCRIPTOR)} title={i18n._(UNBLOCK_USER_DESCRIPTOR)}
description={i18n._(ARE_YOU_SURE_YOU_WANT_TO_UNBLOCK_DESCRIPTOR, { description={i18n._(ARE_YOU_SURE_YOU_WANT_TO_UNBLOCK_DESCRIPTOR, {
userName: NicknameUtils.getNickname(user), userName: NicknameUtils.getNickname(user, null),
})} })}
primaryText={i18n._(UNBLOCK_USER_ACTION_DESCRIPTOR)} primaryText={i18n._(UNBLOCK_USER_ACTION_DESCRIPTOR)}
primaryVariant="primary" primaryVariant="primary"
@@ -107,7 +107,7 @@ export function buildChannelCandidate(
const participantNames = channel.recipientIds const participantNames = channel.recipientIds
.map((recipientId) => { .map((recipientId) => {
const user = Users.getUser(recipientId); const user = Users.getUser(recipientId);
return user ? NicknameUtils.getNickname(user) : null; return user ? NicknameUtils.getNickname(user, null) : null;
}) })
.filter(Boolean) as Array<string>; .filter(Boolean) as Array<string>;
const subtitle = participantNames.length > 0 ? participantNames.join(', ') : i18n._(GROUP_MESSAGE_DESCRIPTOR); const subtitle = participantNames.length > 0 ? participantNames.join(', ') : i18n._(GROUP_MESSAGE_DESCRIPTOR);
@@ -233,7 +233,7 @@ export function buildCandidateSets(i18n: I18n): CandidateSets {
} }
const user = relationship.user; const user = relationship.user;
if (!userCandidates.has(user.id)) { if (!userCandidates.has(user.id)) {
const title = NicknameUtils.getNickname(user); const title = NicknameUtils.getNickname(user, null);
const subtitle = NicknameUtils.formatUserTagForStreamerMode(user); const subtitle = NicknameUtils.formatUserTagForStreamerMode(user);
const searchValues = [title, subtitle, user.username, user.id].filter(Boolean); const searchValues = [title, subtitle, user.username, user.id].filter(Boolean);
userCandidates.set(user.id, { userCandidates.set(user.id, {
@@ -251,7 +251,7 @@ export function buildCandidateSets(i18n: I18n): CandidateSets {
for (const user of Users.getUsers()) { for (const user of Users.getUsers()) {
if (user.id === currentUserId) continue; if (user.id === currentUserId) continue;
if (!userCandidates.has(user.id)) { if (!userCandidates.has(user.id)) {
const title = NicknameUtils.getNickname(user); const title = NicknameUtils.getNickname(user, null);
const subtitle = NicknameUtils.formatUserTagForStreamerMode(user); const subtitle = NicknameUtils.formatUserTagForStreamerMode(user);
const searchValues = [title, subtitle, user.username, user.id].filter(Boolean); const searchValues = [title, subtitle, user.username, user.id].filter(Boolean);
userCandidates.set(user.id, { userCandidates.set(user.id, {
@@ -272,7 +272,9 @@ export function buildCandidateSets(i18n: I18n): CandidateSets {
for (const member of guildMembers) { for (const member of guildMembers) {
if (member.user.id === currentUserId) continue; if (member.user.id === currentUserId) continue;
if (!userCandidates.has(member.user.id)) { if (!userCandidates.has(member.user.id)) {
const title = member.nick ?? NicknameUtils.getNickname(member.user); const title = member.nick
? NicknameUtils.formatNicknameForStreamerMode(member.nick)
: NicknameUtils.getNickname(member.user, member.guildId);
const subtitle = NicknameUtils.formatUserTagForStreamerMode(member.user); const subtitle = NicknameUtils.formatUserTagForStreamerMode(member.user);
const searchValues = [title, subtitle, member.user.username, member.user.id, member.nick].filter( const searchValues = [title, subtitle, member.user.username, member.user.id, member.nick].filter(
Boolean, Boolean,
@@ -235,7 +235,9 @@ function buildUserCandidatesWithMemberSearch(
} }
function createUserCandidateFromMember(member: GuildMember): UserCandidate { function createUserCandidateFromMember(member: GuildMember): UserCandidate {
const title = member.nick ?? NicknameUtils.getNickname(member.user); const title = member.nick
? NicknameUtils.formatNicknameForStreamerMode(member.nick)
: NicknameUtils.getNickname(member.user, member.guildId);
const subtitle = NicknameUtils.formatUserTagForStreamerMode(member.user); const subtitle = NicknameUtils.formatUserTagForStreamerMode(member.user);
const searchValues = [title, subtitle, member.user.username, member.user.id, member.nick].filter( const searchValues = [title, subtitle, member.user.username, member.user.id, member.nick].filter(
Boolean, Boolean,
@@ -197,7 +197,7 @@ export const UserContextMenu: React.FC<UserContextMenuProps> = observer(
)), )),
); );
}, [channel, onClose, user]); }, [channel, onClose, user]);
const userDisplayName = NicknameUtils.getNickname(user, channel?.guildId ?? undefined, channel?.id); const userDisplayName = NicknameUtils.getNickname(user, channel?.guildId ?? null, channel?.id);
const handleRemoveFromGroup = useCallback(() => { const handleRemoveFromGroup = useCallback(() => {
if (!channel) return; if (!channel) return;
ModalCommands.pushAfterBottomSheetClose( ModalCommands.pushAfterBottomSheetClose(
@@ -401,7 +401,7 @@ export function useDMMenuData(
<ConfirmModal <ConfirmModal
title={i18n._(CLOSE_DM_DESCRIPTOR)} title={i18n._(CLOSE_DM_DESCRIPTOR)}
description={i18n._(ARE_YOU_SURE_YOU_WANT_TO_CLOSE_YOUR_DESCRIPTOR, { description={i18n._(ARE_YOU_SURE_YOU_WANT_TO_CLOSE_YOUR_DESCRIPTOR, {
recipientUsername: recipient ? NicknameUtils.getNickname(recipient) : '', recipientUsername: recipient ? NicknameUtils.getNickname(recipient, null) : '',
})} })}
primaryText={i18n._(CLOSE_DM_DESCRIPTOR)} primaryText={i18n._(CLOSE_DM_DESCRIPTOR)}
primaryVariant="danger" primaryVariant="danger"
@@ -59,7 +59,7 @@ export const AvatarStack: React.FC<AvatarStackProps> = observer(
const childArray = React.Children.toArray(children).filter(Boolean); const childArray = React.Children.toArray(children).filter(Boolean);
const userChildren = const userChildren =
users?.map((user, index) => { users?.map((user, index) => {
const displayName = NicknameUtils.getNickname(user, guildId ?? undefined, channelId ?? undefined); const displayName = NicknameUtils.getNickname(user, guildId ?? null, channelId ?? undefined);
const avatarNode = renderAvatar?.(user, size, index) ?? ( const avatarNode = renderAvatar?.(user, size, index) ?? (
<Avatar user={user} size={size} guildId={guildId ?? undefined} data-flx="ui.avatars.avatar-stack.avatar" /> <Avatar user={user} size={size} guildId={guildId ?? undefined} data-flx="ui.avatars.avatar-stack.avatar" />
); );
@@ -118,7 +118,7 @@ export const AvatarStack: React.FC<AvatarStackProps> = observer(
} as React.CSSProperties; } as React.CSSProperties;
const wrapWithContextMenu = (node: React.ReactNode, user: User, index: number) => { const wrapWithContextMenu = (node: React.ReactNode, user: User, index: number) => {
if (!onUserContextMenu) return node; if (!onUserContextMenu) return node;
const displayName = NicknameUtils.getNickname(user, guildId ?? undefined, channelId ?? undefined); const displayName = NicknameUtils.getNickname(user, guildId ?? null, channelId ?? undefined);
return ( return (
<div <div
className={styles.avatarContextMenuWrap} className={styles.avatarContextMenuWrap}
@@ -206,7 +206,7 @@ const AvatarComponent = React.forwardRef<HTMLDivElement, AvatarProps>(
}); });
const safeHoverAvatarUrl = isAnimatedNeeded ? hoverAvatarUrl || undefined : undefined; const safeHoverAvatarUrl = isAnimatedNeeded ? hoverAvatarUrl || undefined : undefined;
const normalizedStatusAttr = status != null ? normalizeStatus(status) : undefined; const normalizedStatusAttr = status != null ? normalizeStatus(status) : undefined;
const displayName = NicknameUtils.getNickname(user, guildId ?? undefined); const displayName = NicknameUtils.getNickname(user, guildId ?? null);
const avatarRefs = useMemo(() => [ref, hoverRef], [ref, hoverRef]); const avatarRefs = useMemo(() => [ref, hoverRef], [ref, hoverRef]);
const mergedRef = useMergeRefs(avatarRefs); const mergedRef = useMergeRefs(avatarRefs);
return ( return (
@@ -174,7 +174,9 @@ export const UserProfilePopout: React.FC<UserProfilePopoutProps> = observer(
const handleClosePopout = useCallback(() => { const handleClosePopout = useCallback(() => {
requestClose(); requestClose();
}, [requestClose]); }, [requestClose]);
const displayName = guildMember?.nick ?? NicknameUtils.getNickname(user, guildId); const displayName = guildMember?.nick
? NicknameUtils.formatNicknameForStreamerMode(guildMember.nick)
: NicknameUtils.getNickname(user, guildId);
const handleMessage = async () => { const handleMessage = async () => {
try { try {
requestClose(); requestClose();
@@ -206,7 +206,7 @@ export const VoiceActivityCard: React.FC<VoiceActivityCardProps> = observer(({ac
return i18n._(PARTICIPANTS_DESCRIPTOR); return i18n._(PARTICIPANTS_DESCRIPTOR);
} }
const names = avatarStackUsers const names = avatarStackUsers
.map((user) => NicknameUtils.getNickname(user, guildId ?? undefined, channelId ?? undefined)) .map((user) => NicknameUtils.getNickname(user, guildId ?? null, channelId ?? undefined))
.join(', '); .join(', ');
return i18n._(PARTICIPANTS_2_DESCRIPTOR, {names}); return i18n._(PARTICIPANTS_2_DESCRIPTOR, {names});
}, [avatarStackUsers, guildId, channelId, i18n.locale]); }, [avatarStackUsers, guildId, channelId, i18n.locale]);
@@ -421,7 +421,7 @@ const VoiceActivityContext: React.FC<VoiceActivityContextProps> = observer(({cha
const recipientId = channel.recipientIds.find((id) => id !== voiceState.user_id); const recipientId = channel.recipientIds.find((id) => id !== voiceState.user_id);
const recipientUser = recipientId ? Users.getUser(recipientId) : undefined; const recipientUser = recipientId ? Users.getUser(recipientId) : undefined;
if (recipientUser) { if (recipientUser) {
const displayName = NicknameUtils.getNickname(recipientUser); const displayName = NicknameUtils.getNickname(recipientUser, null);
return ( return (
<button <button
type="button" type="button"
@@ -33,6 +33,12 @@ export function formatUserTagForStreamerMode(user: Pick<User, 'tag' | 'username'
return formatTagForStreamerMode(user.tag || `${user.username}#${user.discriminator}`); return formatTagForStreamerMode(user.tag || `${user.username}#${user.discriminator}`);
} }
export function formatNicknameForStreamerMode(nickname: string): string {
const resolved = formatNameForStreamerMode(nickname);
noteText(resolved);
return resolved;
}
export function getDisplayName(user: UserDisplayNameLike): string { export function getDisplayName(user: UserDisplayNameLike): string {
const name = formatNameForStreamerMode( const name = formatNameForStreamerMode(
user.displayName || user.globalName || user.global_name || user.username || '', user.displayName || user.globalName || user.global_name || user.username || '',
@@ -60,7 +66,5 @@ export function getNickname(user: User, guildId?: string | null, channelId?: str
name = channel.nicks[user.id]; name = channel.nicks[user.id];
} }
} }
const resolved = formatNameForStreamerMode(name); return formatNicknameForStreamerMode(name);
noteText(resolved);
return resolved;
} }
@@ -262,7 +262,7 @@ interface VoiceParticipantPopoutRowProps {
function VoiceParticipantPopoutRow({entry, guildId, channelId}: VoiceParticipantPopoutRowProps) { function VoiceParticipantPopoutRow({entry, guildId, channelId}: VoiceParticipantPopoutRowProps) {
const {i18n} = useLingui(); const {i18n} = useLingui();
const displayName = NicknameUtils.getNickname(entry.user, guildId ?? undefined, channelId ?? undefined); const displayName = NicknameUtils.getNickname(entry.user, guildId ?? null, channelId ?? undefined);
const participantName = displayName || i18n._(UNKNOWN_USER_DESCRIPTOR); const participantName = displayName || i18n._(UNKNOWN_USER_DESCRIPTOR);
const {scheduleProfilePreload, cancelProfilePreload} = useUserProfileHoverPreload({ const {scheduleProfilePreload, cancelProfilePreload} = useUserProfileHoverPreload({
userId: entry.user.id, userId: entry.user.id,
@@ -405,7 +405,7 @@ export const VoiceParticipantSpeakingAvatarStack: React.FC<VoiceParticipantSpeak
event.stopPropagation(); event.stopPropagation();
const entry = sortedEntries[index]; const entry = sortedEntries[index];
if (!entry) return; if (!entry) return;
const displayName = NicknameUtils.getNickname(user, guildId ?? undefined, channelId ?? undefined); const displayName = NicknameUtils.getNickname(user, guildId ?? null, channelId ?? undefined);
const participantName = displayName || i18n._(UNKNOWN_USER_DESCRIPTOR); const participantName = displayName || i18n._(UNKNOWN_USER_DESCRIPTOR);
ContextMenuCommands.openFromEvent(event, ({onClose}) => ( ContextMenuCommands.openFromEvent(event, ({onClose}) => (
<VoiceParticipantContextMenu <VoiceParticipantContextMenu
@@ -554,7 +554,7 @@ export const VoiceParticipantWrappedAvatarList: React.FC<VoiceParticipantWrapped
(event: React.MouseEvent<HTMLElement>, entry: VoiceParticipantAvatarEntry) => { (event: React.MouseEvent<HTMLElement>, entry: VoiceParticipantAvatarEntry) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
const displayName = NicknameUtils.getNickname(entry.user, guildId ?? undefined, channelId ?? undefined); const displayName = NicknameUtils.getNickname(entry.user, guildId ?? null, channelId ?? undefined);
const participantName = displayName || i18n._(UNKNOWN_USER_DESCRIPTOR); const participantName = displayName || i18n._(UNKNOWN_USER_DESCRIPTOR);
ContextMenuCommands.openFromEvent(event, ({onClose}) => ( ContextMenuCommands.openFromEvent(event, ({onClose}) => (
<VoiceParticipantContextMenu <VoiceParticipantContextMenu
@@ -44,7 +44,7 @@ function getUserDisplayName(userId: string, guildId?: string | null, channelId?:
if (!userId) return ''; if (!userId) return '';
const user = Users.getUser(userId); const user = Users.getUser(userId);
if (!user) return ''; if (!user) return '';
return NicknameUtils.getNickname(user, guildId ?? undefined, channelId ?? undefined) || ''; return NicknameUtils.getNickname(user, guildId ?? null, channelId ?? undefined) || '';
} }
function buildSnapshotFallbackDisplayName(userId: string, participantKey: string): string { function buildSnapshotFallbackDisplayName(userId: string, participantKey: string): string {
@@ -169,7 +169,7 @@ const PiPOverlayInnerBase = observer(function PiPOverlayInnerBase({content, room
const {viewerUsers} = useStreamSpectators(isScreenShare ? streamKey : '', content.userId); const {viewerUsers} = useStreamSpectators(isScreenShare ? streamKey : '', content.userId);
const displayName = useMemo(() => { const displayName = useMemo(() => {
if (!participantUser) return ''; if (!participantUser) return '';
return NicknameUtils.getNickname(participantUser, content.guildId ?? undefined, content.channelId); return NicknameUtils.getNickname(participantUser, content.guildId ?? null, content.channelId);
}, [participantUser, content.guildId, content.channelId]); }, [participantUser, content.guildId, content.channelId]);
const disconnectLabel = i18n._(VOICE_DISCONNECT_DESCRIPTOR); const disconnectLabel = i18n._(VOICE_DISCONNECT_DESCRIPTOR);
const channelName = channel?.name ?? ''; const channelName = channel?.name ?? '';
@@ -85,7 +85,7 @@ function formatUserMention(userId: string, guildId: string | null, i18n: I18n):
if (!user) { if (!user) {
return i18n._(UNKNOWN_USER_DESCRIPTOR); return i18n._(UNKNOWN_USER_DESCRIPTOR);
} }
return NicknameUtils.getNickname(user, guildId ?? undefined); return NicknameUtils.getNickname(user, guildId ?? null);
} }
function formatRoleMention(roleId: string, guildId: string | null, i18n: I18n): string { function formatRoleMention(roleId: string, guildId: string | null, i18n: I18n): string {
@@ -438,7 +438,7 @@ function handleIncomingTtsMessage(message: Message): void {
if (!author) { if (!author) {
return; return;
} }
const authorName = NicknameUtils.getNickname(author, channel.guildId ?? undefined); const authorName = NicknameUtils.getNickname(author, channel.guildId ?? null);
if (!message.content.trim()) { if (!message.content.trim()) {
const description = describeNonTextContent(message, localI18n); const description = describeNonTextContent(message, localI18n);
if (!description) { if (!description) {
@@ -457,7 +457,7 @@ function handleIncomingTtsMessage(message: Message): void {
if (ref.message) { if (ref.message) {
const replyAuthor = Users.getUser(ref.message.author.id); const replyAuthor = Users.getUser(ref.message.author.id);
if (replyAuthor) { if (replyAuthor) {
replyAuthorName = NicknameUtils.getNickname(replyAuthor, channel.guildId ?? undefined); replyAuthorName = NicknameUtils.getNickname(replyAuthor, channel.guildId ?? null);
} }
} }
} }