mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-19 05:55:34 +09:00
fix(app): backport rendering improvements (#1580)
This commit is contained in:
1 parent
0c291a01da
commit
42cdc1f877
513 files changed
+98743
-70397
No files matched your search
@@ -0,0 +1,482 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
|
||||
import DeveloperOptions from '@app/features/devtools/state/DeveloperOptions';
|
||||
import {buildMediaProxyURL} from '@app/features/messaging/utils/MediaProxyUtils';
|
||||
import {cdnUrl, mediaUrl, setPathQueryParams} from '@app/features/messaging/utils/MessagingUrlUtils';
|
||||
import type {User} from '@app/features/user/models/User';
|
||||
import {
|
||||
getDefaultAvatarIndex,
|
||||
getDefaultAvatarPrimaryColor as getSharedDefaultAvatarPrimaryColor,
|
||||
normalizeEndpoint,
|
||||
parseAvatarHash,
|
||||
} from '@app/features/user/utils/AvatarMediaUtils';
|
||||
import {
|
||||
MEDIA_PROXY_AVATAR_SIZE_DEFAULT,
|
||||
MEDIA_PROXY_ICON_SIZE_DEFAULT,
|
||||
} from '@fluxer/constants/src/MediaProxyAssetSizes';
|
||||
import type {MediaProxyImageSize} from '@fluxer/constants/src/MediaProxyImageSizes';
|
||||
import {SOUNDBOARD_SOUND_PATH_PREFIX} from '@fluxer/constants/src/SoundboardConstants';
|
||||
|
||||
const getDefaultAvatar = (index: number): string => cdnUrl(`avatars/${index}.png`);
|
||||
|
||||
export function getDefaultAvatarPrimaryColor(id: string) {
|
||||
return getSharedDefaultAvatarPrimaryColor(id);
|
||||
}
|
||||
|
||||
export function getDefaultAvatarURL(id: string) {
|
||||
return getDefaultAvatar(getDefaultAvatarIndex(id));
|
||||
}
|
||||
|
||||
type AvatarOptions = Pick<User, 'id' | 'avatar'>;
|
||||
type BannerOptions = Pick<User, 'id' | 'banner'>;
|
||||
|
||||
interface IconOptions {
|
||||
id: string;
|
||||
icon: string | null;
|
||||
}
|
||||
|
||||
type MediaURLParams = {
|
||||
path: string;
|
||||
id: string;
|
||||
hash: string;
|
||||
size?: MediaProxyImageSize;
|
||||
format: string;
|
||||
animated?: boolean;
|
||||
endpoint?: string;
|
||||
};
|
||||
|
||||
const parseMediaHashForRequest = (value: string, animated = false) => {
|
||||
const {animated: isAnimated} = parseAvatarHash(value);
|
||||
return {
|
||||
hash: value,
|
||||
animated: isAnimated ? animated : undefined,
|
||||
};
|
||||
};
|
||||
const getMediaURL = ({path, id, hash, size, format, animated, endpoint}: MediaURLParams) => {
|
||||
if (DeveloperOptions.forceRenderPlaceholders) {
|
||||
return '';
|
||||
}
|
||||
const baseEndpoint = endpoint === undefined ? RuntimeConfig.mediaEndpoint : endpoint;
|
||||
if (!baseEndpoint) {
|
||||
return '';
|
||||
}
|
||||
const basePath = `${path}/${id}/${hash}.${format}`;
|
||||
const url = size ? setPathQueryParams(basePath, {size}) : basePath;
|
||||
const proxyOptions = animated === undefined ? undefined : {animated};
|
||||
return buildMediaProxyURL(`${normalizeEndpoint(baseEndpoint)}/${url}`, proxyOptions);
|
||||
};
|
||||
|
||||
type GuildMemberMediaURLParams = {
|
||||
path: string;
|
||||
guildId: string;
|
||||
userId: string;
|
||||
hash: string;
|
||||
size?: MediaProxyImageSize;
|
||||
format: string;
|
||||
animated?: boolean;
|
||||
};
|
||||
|
||||
const getGuildMemberMediaURL = ({path, guildId, userId, hash, size, format, animated}: GuildMemberMediaURLParams) => {
|
||||
if (DeveloperOptions.forceRenderPlaceholders) {
|
||||
return '';
|
||||
}
|
||||
const baseEndpoint = RuntimeConfig.mediaEndpoint;
|
||||
if (!baseEndpoint) {
|
||||
return '';
|
||||
}
|
||||
const basePath = `guilds/${guildId}/users/${userId}/${path}/${hash}.${format}`;
|
||||
const url = size ? setPathQueryParams(basePath, {size}) : basePath;
|
||||
const proxyOptions = animated === undefined ? undefined : {animated};
|
||||
return buildMediaProxyURL(`${normalizeEndpoint(baseEndpoint)}/${url}`, proxyOptions);
|
||||
};
|
||||
const buildWebpMediaUrl = (params: Omit<MediaURLParams, 'format'>) => getMediaURL({...params, format: 'webp'});
|
||||
const buildPngMediaUrl = (params: Omit<MediaURLParams, 'format'>) => getMediaURL({...params, format: 'png'});
|
||||
const buildGuildMemberWebpUrl = (params: Omit<GuildMemberMediaURLParams, 'format'>) =>
|
||||
getGuildMemberMediaURL({...params, format: 'webp'});
|
||||
|
||||
export function getUserAvatarURL(
|
||||
{id, avatar}: AvatarOptions,
|
||||
animated = false,
|
||||
size: MediaProxyImageSize = MEDIA_PROXY_AVATAR_SIZE_DEFAULT,
|
||||
) {
|
||||
if (!avatar) {
|
||||
return getDefaultAvatar(getDefaultAvatarIndex(id));
|
||||
}
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(avatar, animated);
|
||||
return buildWebpMediaUrl({
|
||||
path: 'avatars',
|
||||
id,
|
||||
hash,
|
||||
size,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserNotificationAvatarURL(
|
||||
{id, avatar}: AvatarOptions,
|
||||
size: MediaProxyImageSize = MEDIA_PROXY_AVATAR_SIZE_DEFAULT,
|
||||
) {
|
||||
if (!avatar) {
|
||||
return getDefaultAvatar(getDefaultAvatarIndex(id));
|
||||
}
|
||||
const {hash, animated} = parseMediaHashForRequest(avatar, false);
|
||||
return buildPngMediaUrl({
|
||||
path: 'avatars',
|
||||
id,
|
||||
hash,
|
||||
size,
|
||||
animated,
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserBannerURL({id, banner}: BannerOptions, animated = false, size: MediaProxyImageSize = 1024) {
|
||||
if (!banner) {
|
||||
return '';
|
||||
}
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(banner, animated);
|
||||
return buildWebpMediaUrl({
|
||||
path: 'banners',
|
||||
id,
|
||||
hash,
|
||||
size,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
|
||||
const SOUNDBOARD_SOUND_URL_CACHE = new Map<string, string>();
|
||||
const SOUNDBOARD_SOUND_URL_CACHE_LIMIT = 4096;
|
||||
|
||||
export function getSoundboardSoundURL(soundId: string): string {
|
||||
if (DeveloperOptions.forceRenderPlaceholders) {
|
||||
return '';
|
||||
}
|
||||
const key = `${RuntimeConfig.mediaEndpoint}:${soundId}`;
|
||||
const cached = SOUNDBOARD_SOUND_URL_CACHE.get(key);
|
||||
if (cached !== undefined) return cached;
|
||||
const result = mediaUrl(`${SOUNDBOARD_SOUND_PATH_PREFIX}/${soundId}`);
|
||||
if (SOUNDBOARD_SOUND_URL_CACHE.size >= SOUNDBOARD_SOUND_URL_CACHE_LIMIT) SOUNDBOARD_SOUND_URL_CACHE.clear();
|
||||
SOUNDBOARD_SOUND_URL_CACHE.set(key, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getGuildIconURL({id, icon}: IconOptions, animated = false) {
|
||||
if (!icon) {
|
||||
return '';
|
||||
}
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(icon, animated);
|
||||
return buildWebpMediaUrl({
|
||||
path: 'icons',
|
||||
id,
|
||||
hash,
|
||||
size: MEDIA_PROXY_ICON_SIZE_DEFAULT,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
|
||||
export function getGuildIconURLWithProxy({id, icon}: IconOptions, endpoint: string, animated = false) {
|
||||
if (!endpoint || !icon) {
|
||||
return '';
|
||||
}
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(icon, animated);
|
||||
return buildWebpMediaUrl({
|
||||
path: 'icons',
|
||||
id,
|
||||
hash,
|
||||
size: MEDIA_PROXY_ICON_SIZE_DEFAULT,
|
||||
animated: shouldAnimate,
|
||||
endpoint,
|
||||
});
|
||||
}
|
||||
|
||||
export function getGuildSplashURL(
|
||||
{
|
||||
id,
|
||||
splash,
|
||||
}: {
|
||||
id: string;
|
||||
splash: string | null;
|
||||
},
|
||||
size: MediaProxyImageSize = 1024,
|
||||
) {
|
||||
if (!splash) {
|
||||
return '';
|
||||
}
|
||||
return buildWebpMediaUrl({
|
||||
path: 'splashes',
|
||||
id,
|
||||
hash: splash,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
export function getGuildDiscoverySplashURL({id, splash}: {id: string; splash: string | null}) {
|
||||
if (!splash) {
|
||||
return '';
|
||||
}
|
||||
return buildWebpMediaUrl({
|
||||
path: 'discovery-splashes',
|
||||
id,
|
||||
hash: splash,
|
||||
size: 1024,
|
||||
});
|
||||
}
|
||||
|
||||
export function getGuildBannerURL(
|
||||
{
|
||||
id,
|
||||
banner,
|
||||
}: {
|
||||
id: string;
|
||||
banner: string | null;
|
||||
},
|
||||
animated = false,
|
||||
) {
|
||||
if (!banner) {
|
||||
return '';
|
||||
}
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(banner, animated);
|
||||
return buildWebpMediaUrl({
|
||||
path: 'banners',
|
||||
id,
|
||||
hash,
|
||||
size: 1024,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
|
||||
export function getGuildMemberAvatarURL({
|
||||
guildId,
|
||||
userId,
|
||||
avatar,
|
||||
memberAvatar,
|
||||
animated = false,
|
||||
size = MEDIA_PROXY_AVATAR_SIZE_DEFAULT,
|
||||
}: {
|
||||
guildId: string;
|
||||
userId: string;
|
||||
avatar: string | null;
|
||||
memberAvatar?: string | null;
|
||||
animated?: boolean;
|
||||
size?: MediaProxyImageSize;
|
||||
}) {
|
||||
if (memberAvatar) {
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(memberAvatar, animated);
|
||||
return buildGuildMemberWebpUrl({
|
||||
path: 'avatars',
|
||||
guildId,
|
||||
userId,
|
||||
hash,
|
||||
size,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
if (avatar) {
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(avatar, animated);
|
||||
return buildWebpMediaUrl({
|
||||
path: 'avatars',
|
||||
id: userId,
|
||||
hash,
|
||||
size,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
return getDefaultAvatar(getDefaultAvatarIndex(userId));
|
||||
}
|
||||
|
||||
export function getGuildMemberDisplayAvatarURL({
|
||||
guildId,
|
||||
user,
|
||||
memberAvatar,
|
||||
avatarUnset = false,
|
||||
animated = false,
|
||||
size = MEDIA_PROXY_AVATAR_SIZE_DEFAULT,
|
||||
}: {
|
||||
guildId: string;
|
||||
user: AvatarOptions;
|
||||
memberAvatar?: string | null;
|
||||
avatarUnset?: boolean;
|
||||
animated?: boolean;
|
||||
size?: MediaProxyImageSize;
|
||||
}) {
|
||||
if (avatarUnset) {
|
||||
return getUserAvatarURL({id: user.id, avatar: null}, animated, size);
|
||||
}
|
||||
return getGuildMemberAvatarURL({
|
||||
guildId,
|
||||
userId: user.id,
|
||||
avatar: user.avatar,
|
||||
memberAvatar,
|
||||
animated,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
export function getGuildMemberBannerURL({
|
||||
guildId,
|
||||
userId,
|
||||
banner,
|
||||
memberBanner,
|
||||
animated = false,
|
||||
size = 1024,
|
||||
}: {
|
||||
guildId: string;
|
||||
userId: string;
|
||||
banner: string | null;
|
||||
memberBanner?: string | null;
|
||||
animated?: boolean;
|
||||
size?: MediaProxyImageSize;
|
||||
}) {
|
||||
if (memberBanner) {
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(memberBanner, animated);
|
||||
return buildGuildMemberWebpUrl({
|
||||
path: 'banners',
|
||||
guildId,
|
||||
userId,
|
||||
hash,
|
||||
size,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
if (banner) {
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(banner, animated);
|
||||
return buildWebpMediaUrl({
|
||||
path: 'banners',
|
||||
id: userId,
|
||||
hash,
|
||||
size,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getUserAvatarURLWithProxy(
|
||||
options: AvatarOptions,
|
||||
endpoint: string,
|
||||
animated = false,
|
||||
size: MediaProxyImageSize = MEDIA_PROXY_AVATAR_SIZE_DEFAULT,
|
||||
) {
|
||||
const {id, avatar} = options;
|
||||
if (!endpoint || !avatar) {
|
||||
return getDefaultAvatar(getDefaultAvatarIndex(id));
|
||||
}
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(avatar, animated);
|
||||
return buildWebpMediaUrl({
|
||||
path: 'avatars',
|
||||
id,
|
||||
hash,
|
||||
size,
|
||||
animated: shouldAnimate,
|
||||
endpoint,
|
||||
});
|
||||
}
|
||||
|
||||
export function getGuildEmbedSplashURL(
|
||||
{
|
||||
id,
|
||||
embedSplash,
|
||||
}: {
|
||||
id: string;
|
||||
embedSplash: string | null;
|
||||
},
|
||||
size: MediaProxyImageSize = 1024,
|
||||
) {
|
||||
if (!embedSplash) {
|
||||
return '';
|
||||
}
|
||||
return buildWebpMediaUrl({
|
||||
path: 'embed-splashes',
|
||||
id,
|
||||
hash: embedSplash,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
export function getChannelIconURL(
|
||||
{id, icon}: IconOptions,
|
||||
size: MediaProxyImageSize = MEDIA_PROXY_ICON_SIZE_DEFAULT,
|
||||
animated = false,
|
||||
) {
|
||||
if (!icon) {
|
||||
return '';
|
||||
}
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(icon, animated);
|
||||
return buildWebpMediaUrl({
|
||||
path: 'icons',
|
||||
id,
|
||||
hash,
|
||||
size,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
|
||||
export function getChannelIconURLWithProxy(
|
||||
{id, icon}: IconOptions,
|
||||
endpoint: string,
|
||||
size: MediaProxyImageSize = MEDIA_PROXY_ICON_SIZE_DEFAULT,
|
||||
animated = false,
|
||||
) {
|
||||
if (!endpoint || !icon) {
|
||||
return '';
|
||||
}
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(icon, animated);
|
||||
return buildWebpMediaUrl({
|
||||
path: 'icons',
|
||||
id,
|
||||
hash,
|
||||
size,
|
||||
animated: shouldAnimate,
|
||||
endpoint,
|
||||
});
|
||||
}
|
||||
|
||||
export function getWebhookAvatarURL({id, avatar}: AvatarOptions, animated = false) {
|
||||
if (!avatar) {
|
||||
return getDefaultAvatar(getDefaultAvatarIndex(id));
|
||||
}
|
||||
const {hash, animated: shouldAnimate} = parseMediaHashForRequest(avatar, animated);
|
||||
return buildWebpMediaUrl({
|
||||
path: 'avatars',
|
||||
id,
|
||||
hash,
|
||||
size: MEDIA_PROXY_AVATAR_SIZE_DEFAULT,
|
||||
animated: shouldAnimate,
|
||||
});
|
||||
}
|
||||
|
||||
const EMOJI_URL_CACHE = new Map<string, string>();
|
||||
const EMOJI_URL_CACHE_LIMIT = 4096;
|
||||
|
||||
export function getEmojiURL({id, animated}: {id: string; animated?: boolean}) {
|
||||
if (DeveloperOptions.forceRenderPlaceholders) {
|
||||
return '';
|
||||
}
|
||||
const animatedFlag = Boolean(animated);
|
||||
const key = `${RuntimeConfig.mediaEndpoint}:${animatedFlag ? 'a' : 's'}:${id}`;
|
||||
const cached = EMOJI_URL_CACHE.get(key);
|
||||
if (cached !== undefined) return cached;
|
||||
const result = mediaUrl(setPathQueryParams(`emojis/${id}.webp`, {v: 5}), {animated: animatedFlag});
|
||||
if (EMOJI_URL_CACHE.size >= EMOJI_URL_CACHE_LIMIT) EMOJI_URL_CACHE.clear();
|
||||
EMOJI_URL_CACHE.set(key, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
type StickerSize = 160 | 320;
|
||||
|
||||
export function getStickerURL({id, animated, size = 320}: {id: string; animated?: boolean; size?: StickerSize}) {
|
||||
if (DeveloperOptions.forceRenderPlaceholders) {
|
||||
return '';
|
||||
}
|
||||
const safeSize: StickerSize = size === 320 ? 320 : 160;
|
||||
return mediaUrl(setPathQueryParams(`stickers/${id}.webp`, {size: safeSize}), {animated: Boolean(animated)});
|
||||
}
|
||||
|
||||
export function fileToBase64(file: File) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve(reader.result as string);
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import Channels from '@app/features/channel/state/Channels';
|
||||
import GuildMembers from '@app/features/member/state/GuildMembers';
|
||||
import SelectedGuild from '@app/features/navigation/state/SelectedGuild';
|
||||
import Relationships from '@app/features/relationship/state/Relationships';
|
||||
import StreamerMode from '@app/features/streamer_mode/state/StreamerMode';
|
||||
import type {User} from '@app/features/user/models/User';
|
||||
|
||||
export interface UserDisplayNameLike {
|
||||
username: string;
|
||||
displayName?: string | null;
|
||||
globalName?: string | null;
|
||||
global_name?: string | null;
|
||||
}
|
||||
|
||||
export function truncateStreamerModeName(name: string): string {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) return '…';
|
||||
return `${Array.from(trimmed)[0]}…`;
|
||||
}
|
||||
|
||||
export function formatNameForStreamerMode(name: string): string {
|
||||
return StreamerMode.shouldTruncateUsernames ? truncateStreamerModeName(name) : name;
|
||||
}
|
||||
|
||||
export function formatTagForStreamerMode(tag: string): string {
|
||||
return StreamerMode.shouldTruncateUsernames ? truncateStreamerModeName(tag) : tag;
|
||||
}
|
||||
|
||||
export function formatUserTagForStreamerMode(user: Pick<User, 'tag' | 'username' | 'discriminator'>): string {
|
||||
return formatTagForStreamerMode(user.tag || `${user.username}#${user.discriminator}`);
|
||||
}
|
||||
|
||||
export function getDisplayName(user: UserDisplayNameLike): string {
|
||||
return formatNameForStreamerMode(user.displayName || user.globalName || user.global_name || user.username || '');
|
||||
}
|
||||
|
||||
function resolveRelationshipNickname(userId: string): string | null {
|
||||
const relationship = Relationships.getRelationship(userId);
|
||||
if (relationship === null || relationship === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (!relationship.nickname) {
|
||||
return null;
|
||||
}
|
||||
return relationship.nickname;
|
||||
}
|
||||
|
||||
function resolveGuildMemberNickname(guildId: string, userId: string): string | null {
|
||||
const member = GuildMembers.getMember(guildId, userId);
|
||||
if (member === null || member === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (!member.nick) {
|
||||
return null;
|
||||
}
|
||||
return member.nick;
|
||||
}
|
||||
|
||||
export function getUntruncatedNickname(user: User, guildId?: string | null, channelId?: string): string {
|
||||
let name = user.displayName || user.globalName || user.username || user.id || '';
|
||||
const relationshipNickname = resolveRelationshipNickname(user.id);
|
||||
if (relationshipNickname !== null) {
|
||||
name = relationshipNickname;
|
||||
}
|
||||
const channel = channelId ? Channels.getChannel(channelId) : null;
|
||||
let selectedGuildId: string | undefined;
|
||||
if (!channelId && SelectedGuild.selectedGuildId !== null) {
|
||||
selectedGuildId = SelectedGuild.selectedGuildId;
|
||||
}
|
||||
let resolvedGuildId: string | undefined;
|
||||
if (guildId === null) {
|
||||
resolvedGuildId = undefined;
|
||||
} else if (guildId !== undefined) {
|
||||
resolvedGuildId = guildId;
|
||||
} else if (channel !== null && channel !== undefined && channel.guildId !== undefined) {
|
||||
resolvedGuildId = channel.guildId;
|
||||
} else {
|
||||
resolvedGuildId = selectedGuildId;
|
||||
}
|
||||
if (resolvedGuildId) {
|
||||
const memberNickname = resolveGuildMemberNickname(resolvedGuildId, user.id);
|
||||
if (memberNickname !== null) {
|
||||
name = memberNickname;
|
||||
}
|
||||
} else if (channel !== null && channel !== undefined) {
|
||||
const channelNickname = channel.nicks[user.id];
|
||||
if (channelNickname) {
|
||||
name = channelNickname;
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
export function getUntruncatedGuildMemberNickname(member: {guildId: string; nick: string | null; user: User}): string {
|
||||
const relationshipNickname = resolveRelationshipNickname(member.user.id);
|
||||
if (relationshipNickname !== null) {
|
||||
return relationshipNickname;
|
||||
}
|
||||
if (member.nick !== null && member.nick.length > 0) {
|
||||
return member.nick;
|
||||
}
|
||||
return member.user.displayName || member.user.globalName || member.user.username || member.user.id || '';
|
||||
}
|
||||
|
||||
export function getGuildMemberNickname(member: {guildId: string; nick: string | null; user: User}): string {
|
||||
return formatNameForStreamerMode(getUntruncatedGuildMemberNickname(member));
|
||||
}
|
||||
|
||||
export function getNickname(user: User, guildId?: string | null, channelId?: string): string {
|
||||
return formatNameForStreamerMode(getUntruncatedNickname(user, guildId, channelId));
|
||||
}
|
||||
Reference in new issue
Block a user