// SPDX-License-Identifier: AGPL-3.0-or-later import type { GuildEmojiResponse, GuildEmojiWithUserResponse, GuildStickerResponse, GuildStickerWithUserResponse, } from '@fluxer/schema/src/domains/guild/GuildEmojiSchemas'; import type {GuildBanResponse, GuildMemberResponse} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas'; import type {GuildPartialResponse, GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas'; import type {GuildRoleResponse} from '@fluxer/schema/src/domains/guild/GuildRoleSchemas'; import type {UserPartialResponse} from '@fluxer/schema/src/domains/user/UserResponseSchemas'; import type {z} from 'zod'; import { stripGuildBannerForFeatures, stripGuildIconForFeatures, stripGuildSplashForFeatures, } from '../infrastructure/AssetEntitlementUtils'; import type {UserCacheService} from '../infrastructure/UserCacheService'; import type {RequestCache} from '../middleware/RequestCacheMiddleware'; import type {Guild} from '../models/Guild'; import type {GuildBan} from '../models/GuildBan'; import type {GuildEmoji} from '../models/GuildEmoji'; import type {GuildMember} from '../models/GuildMember'; import type {GuildRole} from '../models/GuildRole'; import type {GuildSticker} from '../models/GuildSticker'; import {getCachedUserPartialResponse, getCachedUserPartialResponses} from '../user/UserCacheHelpers'; import {mapGuildFeatures} from './GuildFeatureUtils'; export function mapGuildToPartialResponse(guild: Guild): z.infer { const guildId = guild.id.toString(); const iconHash = stripGuildIconForFeatures(guild.iconHash, guild.features); const bannerHash = stripGuildBannerForFeatures(guild.bannerHash, guild.features); const splashHash = stripGuildSplashForFeatures(guild.splashHash, guild.features); const embedSplashHash = stripGuildSplashForFeatures(guild.embedSplashHash, guild.features); return { id: guildId, name: guild.name, icon: iconHash, banner: bannerHash, banner_width: bannerHash ? guild.bannerWidth : null, banner_height: bannerHash ? guild.bannerHeight : null, splash: splashHash, splash_width: splashHash ? guild.splashWidth : null, splash_height: splashHash ? guild.splashHeight : null, embed_splash: embedSplashHash, embed_splash_width: embedSplashHash ? guild.embedSplashWidth : null, embed_splash_height: embedSplashHash ? guild.embedSplashHeight : null, splash_card_alignment: guild.splashCardAlignment, features: mapGuildFeatures(guild.features), }; } export function mapGuildToGuildResponse( guild: Guild, options?: { permissions?: bigint | null; }, ): z.infer { const iconHash = stripGuildIconForFeatures(guild.iconHash, guild.features); const bannerHash = stripGuildBannerForFeatures(guild.bannerHash, guild.features); const splashHash = stripGuildSplashForFeatures(guild.splashHash, guild.features); const embedSplashHash = stripGuildSplashForFeatures(guild.embedSplashHash, guild.features); return { id: guild.id.toString(), name: guild.name, icon: iconHash, banner: bannerHash, banner_width: bannerHash ? guild.bannerWidth : null, banner_height: bannerHash ? guild.bannerHeight : null, splash: splashHash, splash_width: splashHash ? guild.splashWidth : null, splash_height: splashHash ? guild.splashHeight : null, embed_splash: embedSplashHash, embed_splash_width: embedSplashHash ? guild.embedSplashWidth : null, embed_splash_height: embedSplashHash ? guild.embedSplashHeight : null, splash_card_alignment: guild.splashCardAlignment, vanity_url_code: guild.vanityUrlCode, owner_id: guild.ownerId.toString(), system_channel_id: guild.systemChannelId ? guild.systemChannelId.toString() : null, system_channel_flags: guild.systemChannelFlags, rules_channel_id: guild.rulesChannelId ? guild.rulesChannelId.toString() : null, afk_channel_id: guild.afkChannelId ? guild.afkChannelId.toString() : null, afk_timeout: guild.afkTimeout, features: mapGuildFeatures(guild.features), verification_level: guild.verificationLevel, mfa_level: guild.mfaLevel, nsfw_level: guild.nsfwLevel, nsfw: guild.nsfw, content_warning_level: guild.contentWarningLevel as 0 | 1, content_warning_text: guild.contentWarningText, explicit_content_filter: guild.explicitContentFilter, default_message_notifications: guild.defaultMessageNotifications, disabled_operations: guild.disabledOperations, message_history_cutoff: guild.messageHistoryCutoff ? guild.messageHistoryCutoff.toISOString() : null, permissions: options?.permissions != null ? options.permissions.toString() : undefined, }; } export function mapGuildRoleToResponse(role: GuildRole): z.infer { return { id: role.id.toString(), name: role.name, color: role.color, position: role.position, hoist_position: role.hoistPosition, permissions: role.permissions.toString(), hoist: role.isHoisted, mentionable: role.isMentionable, }; } export function mapGuildEmojiToResponse(emoji: GuildEmoji): z.infer { const id = emoji.id.toString(); return { id, name: emoji.name, animated: emoji.isAnimated, nsfw: emoji.isNsfw, }; } export function mapGuildStickerToResponse(sticker: GuildSticker): z.infer { const id = sticker.id.toString(); return { id, name: sticker.name, description: sticker.description ?? '', tags: sticker.tags, animated: sticker.animated, nsfw: sticker.isNsfw, }; } function mapMemberWithUser( member: GuildMember, userPartial: z.infer, ): z.infer { const now = Date.now(); const isTimedOut = member.communicationDisabledUntil != null && member.communicationDisabledUntil.getTime() > now; return { user: userPartial, nick: member.nickname, avatar: member.isPremiumSanitized ? null : member.avatarHash, banner: member.isPremiumSanitized ? null : member.bannerHash, accent_color: member.accentColor, roles: Array.from(member.roleIds).map((id) => id.toString()), joined_at: member.joinedAt.toISOString(), mute: isTimedOut ? true : member.isMute, deaf: member.isDeaf, communication_disabled_until: member.communicationDisabledUntil?.toISOString() ?? null, profile_flags: member.profileFlags || undefined, mention_flags: member.mentionFlags || undefined, }; } export function isGuildMemberTimedOut(member?: z.infer | null): boolean { if (!member?.communication_disabled_until) { return false; } const timestamp = Date.parse(member.communication_disabled_until); return !Number.isNaN(timestamp) && timestamp > Date.now(); } export async function mapGuildMemberToResponse( member: GuildMember, userCacheService: Pick, requestCache: RequestCache, ): Promise> { const userPartial = await getCachedUserPartialResponse({userId: member.userId, userCacheService, requestCache}); return mapMemberWithUser(member, userPartial); } function mapEmojiWithUser( emoji: GuildEmoji, userPartial: z.infer, ): z.infer { const id = emoji.id.toString(); return { id, name: emoji.name, animated: emoji.isAnimated, nsfw: emoji.isNsfw, user: userPartial, }; } export async function mapGuildEmojisWithUsersToResponse( emojis: Array, userCacheService: Pick, requestCache: RequestCache, ): Promise>> { const userIds = [...new Set(emojis.map((emoji) => emoji.creatorId))]; const userPartials = await getCachedUserPartialResponses({userIds, userCacheService, requestCache}); return emojis .filter((emoji) => userPartials.has(emoji.creatorId)) .map((emoji) => mapEmojiWithUser(emoji, userPartials.get(emoji.creatorId)!)); } function mapStickerWithUser( sticker: GuildSticker, userPartial: z.infer, ): z.infer { return { id: sticker.id.toString(), name: sticker.name, description: sticker.description ?? '', tags: sticker.tags, animated: sticker.animated, nsfw: sticker.isNsfw, user: userPartial, }; } export async function mapGuildStickersWithUsersToResponse( stickers: Array, userCacheService: Pick, requestCache: RequestCache, ): Promise>> { const userIds = [...new Set(stickers.map((sticker) => sticker.creatorId))]; const userPartials = await getCachedUserPartialResponses({userIds, userCacheService, requestCache}); return stickers .filter((sticker) => userPartials.has(sticker.creatorId)) .map((sticker) => mapStickerWithUser(sticker, userPartials.get(sticker.creatorId)!)); } function mapBanWithUser( ban: GuildBan, userPartial: z.infer, ): z.infer { return { user: userPartial, reason: ban.reason, moderator_id: ban.moderatorId.toString(), banned_at: ban.bannedAt.toISOString(), expires_at: ban.expiresAt ? ban.expiresAt.toISOString() : null, }; } export async function mapGuildBansToResponse( bans: Array, userCacheService: Pick, requestCache: RequestCache, ): Promise>> { const userIds = [...new Set(bans.map((ban) => ban.userId))]; const userPartials = await getCachedUserPartialResponses({userIds, userCacheService, requestCache}); return bans .filter((ban) => userPartials.has(ban.userId)) .map((ban) => mapBanWithUser(ban, userPartials.get(ban.userId)!)); }