refactor progress

This commit is contained in:
Hampus Kraft committed 2026-02-17 12:22:36 +00:00
1 parent cb31608523
commit d5abd1a7e4
8257 files changed
+1190205 -761038

No files matched your search

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {
createNamedStringLiteralUnion,
createStringType,
SnowflakeStringType,
withOpenApiType,
} from '@fluxer/schema/src/primitives/SchemaPrimitives';
import {z} from 'zod';
export const HarvestStatusEnum = withOpenApiType(
createNamedStringLiteralUnion(
[
['pending', 'pending', 'The harvest job is waiting to be processed'],
['processing', 'processing', 'The harvest job is currently being processed'],
['completed', 'completed', 'The harvest job has finished successfully'],
['failed', 'failed', 'The harvest job encountered an error and could not complete'],
],
'Current status of the harvest request',
),
'HarvestStatus',
);
export const HarvestCreationResponseSchema = z.object({
harvest_id: SnowflakeStringType.describe('Unique identifier for the harvest request'),
status: HarvestStatusEnum,
created_at: z.string().describe('ISO 8601 timestamp when the harvest request was created'),
});
export const HarvestStatusResponseSchema = HarvestCreationResponseSchema.extend({
started_at: z.string().nullable().describe('ISO 8601 timestamp when the harvest started, or null if pending'),
completed_at: z.string().nullable().describe('ISO 8601 timestamp when the harvest completed, or null otherwise'),
failed_at: z.string().nullable().describe('ISO 8601 timestamp when the harvest failed, or null otherwise'),
file_size: z
.string()
.nullable()
.describe('Final file size of the downloaded data, expressed as a string, or null if not available'),
progress_percent: z.number().describe('Harvest progress as a percentage value between 0 and 100'),
progress_step: z.string().nullable().describe('Textual description of the current harvest step, if available'),
error_message: z.string().nullable().describe('Error message when the harvest fails, or null otherwise'),
download_url_expires_at: z
.string()
.nullable()
.describe('ISO 8601 timestamp when the download URL expires, or null if unavailable'),
expires_at: z
.string()
.nullable()
.describe('ISO 8601 timestamp when the harvest download expires, or null if unavailable'),
});
export type HarvestCreationResponse = z.infer<typeof HarvestCreationResponseSchema>;
export type HarvestStatusResponse = z.infer<typeof HarvestStatusResponseSchema>;
export const HarvestStatusResponseSchemaNullable = HarvestStatusResponseSchema.nullable();
export const HarvestDownloadUrlResponse = z.object({
download_url: createStringType(1, 2048).describe('The presigned URL to download the harvest archive'),
expires_at: z.string().describe('ISO 8601 timestamp when the harvest download expires'),
});
export type HarvestDownloadUrlResponse = z.infer<typeof HarvestDownloadUrlResponse>;
@@ -0,0 +1,444 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {AVATAR_MAX_SIZE} from '@fluxer/constants/src/LimitConstants';
import {StatusTypes} from '@fluxer/constants/src/StatusConstants';
import {
DEFAULT_GUILD_FOLDER_ICON,
FriendSourceFlags,
FriendSourceFlagsDescriptions,
GroupDmAddPermissionFlags,
GroupDmAddPermissionFlagsDescriptions,
GuildFolderFlags,
GuildFolderFlagsDescriptions,
GuildFolderIcons,
IncomingCallFlags,
IncomingCallFlagsDescriptions,
ThemeTypes,
} from '@fluxer/constants/src/UserConstants';
import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
import {SudoVerificationSchema} from '@fluxer/schema/src/domains/auth/AuthSchemas';
import {isValidSingleUnicodeEmoji} from '@fluxer/schema/src/primitives/EmojiValidators';
import {createBase64StringType} from '@fluxer/schema/src/primitives/FileValidators';
import {LocaleSchema} from '@fluxer/schema/src/primitives/LocaleSchema';
import {createQueryIntegerType, DateTimeType, QueryBooleanType} from '@fluxer/schema/src/primitives/QueryValidators';
import {
ColorType,
createBitflagInt32Type,
createNamedStringLiteralUnion,
createStringType,
Int32Type,
SignedInt32Type,
SnowflakeStringType,
SnowflakeType,
withFieldDescription,
withOpenApiType,
} from '@fluxer/schema/src/primitives/SchemaPrimitives';
import {URLType} from '@fluxer/schema/src/primitives/UrlValidators';
import {
RelationshipTypesSchema,
RenderSpoilersSchema,
StickerAnimationOptionsSchema,
TimeFormatTypesSchema,
UserNotificationSettingsSchema,
} from '@fluxer/schema/src/primitives/UserSettingsValidators';
import {
DiscriminatorType,
EmailType,
GlobalNameType,
PasswordType,
UsernameType,
} from '@fluxer/schema/src/primitives/UserValidators';
import {z} from 'zod';
export const UserUpdateRequest = z
.object({
username: UsernameType.describe('The username for the account (1-32 characters)'),
discriminator: DiscriminatorType.describe('The 4-digit discriminator tag'),
global_name: GlobalNameType.nullish().describe('The display name shown to other users'),
email: EmailType.describe('The email address for the account'),
new_password: PasswordType.describe('The new password to set'),
password: PasswordType.describe('The current password for verification'),
avatar: createBase64StringType(1, AVATAR_MAX_SIZE * 1.33)
.nullish()
.describe('Base64-encoded avatar image'),
banner: createBase64StringType(1, AVATAR_MAX_SIZE * 1.33)
.nullish()
.describe('Base64-encoded profile banner image'),
bio: createStringType(1, 320).nullish().describe('User biography text (max 320 characters)'),
pronouns: createStringType(1, 40).nullish().describe('User pronouns (max 40 characters)'),
accent_color: ColorType.nullish().describe('Profile accent color as integer'),
premium_badge_hidden: z.boolean().describe('Whether to hide the premium badge'),
premium_badge_masked: z.boolean().describe('Whether to mask the premium badge'),
premium_badge_timestamp_hidden: z.boolean().describe('Whether to hide premium badge timestamp'),
premium_badge_sequence_hidden: z.boolean().describe('Whether to hide premium badge sequence'),
premium_enabled_override: z.boolean().describe('Override premium enabled state'),
has_dismissed_premium_onboarding: z.boolean().describe('Whether user dismissed premium onboarding'),
has_unread_gift_inventory: z.boolean().describe('Whether user has unread gifts'),
used_mobile_client: z.boolean().describe('Whether user has used mobile client'),
})
.partial();
export type UserUpdateRequest = z.infer<typeof UserUpdateRequest>;
const EmailTokenType = createStringType(1, 256);
export const UserUpdateWithVerificationRequest = UserUpdateRequest.merge(
z.object({
email_token: EmailTokenType.optional().describe('Email change token for updating email'),
}),
)
.merge(SudoVerificationSchema)
.superRefine((data, ctx) => {
if (data.email !== undefined) {
ctx.addIssue({
code: 'custom',
message: ValidationErrorCodes.EMAIL_MUST_BE_CHANGED_VIA_TOKEN,
path: ['email'],
});
}
});
export type UserUpdateWithVerificationRequest = z.infer<typeof UserUpdateWithVerificationRequest>;
export const EmailChangeTicketRequest = z.object({
ticket: createStringType().describe('Email change ticket identifier'),
});
export type EmailChangeTicketRequest = z.infer<typeof EmailChangeTicketRequest>;
export const EmailChangeVerifyOriginalRequest = EmailChangeTicketRequest.extend({
code: createStringType().describe('Verification code sent to the original email address'),
});
export type EmailChangeVerifyOriginalRequest = z.infer<typeof EmailChangeVerifyOriginalRequest>;
export const EmailChangeRequestNewRequest = EmailChangeTicketRequest.extend({
new_email: EmailType.describe('New email address to switch to'),
original_proof: createStringType().describe('Proof token obtained from verifying the original email'),
});
export type EmailChangeRequestNewRequest = z.infer<typeof EmailChangeRequestNewRequest>;
export const EmailChangeVerifyNewRequest = EmailChangeVerifyOriginalRequest.extend({
original_proof: createStringType().describe('Proof token obtained from verifying the original email'),
});
export type EmailChangeVerifyNewRequest = z.infer<typeof EmailChangeVerifyNewRequest>;
export const EmailChangeBouncedRequestNewRequest = z.object({
new_email: EmailType.describe('Replacement email address used when the current email has bounced'),
});
export type EmailChangeBouncedRequestNewRequest = z.infer<typeof EmailChangeBouncedRequestNewRequest>;
export const EmailChangeBouncedVerifyNewRequest = EmailChangeTicketRequest.extend({
code: createStringType().describe('Verification code sent to the replacement email address'),
});
export type EmailChangeBouncedVerifyNewRequest = z.infer<typeof EmailChangeBouncedVerifyNewRequest>;
export const PasswordChangeTicketRequest = z.object({
ticket: createStringType().describe('Password change ticket identifier'),
});
export type PasswordChangeTicketRequest = z.infer<typeof PasswordChangeTicketRequest>;
export const PasswordChangeVerifyRequest = PasswordChangeTicketRequest.extend({
code: createStringType().describe('Verification code sent to the email address'),
});
export type PasswordChangeVerifyRequest = z.infer<typeof PasswordChangeVerifyRequest>;
export const PasswordChangeCompleteRequest = PasswordChangeTicketRequest.extend({
verification_proof: createStringType().describe('Proof token obtained from verifying the email code'),
new_password: PasswordType.describe('The new password to set'),
});
export type PasswordChangeCompleteRequest = z.infer<typeof PasswordChangeCompleteRequest>;
export const FriendRequestByTagRequest = z.object({
username: UsernameType.describe('Username of the user to send friend request'),
discriminator: DiscriminatorType.describe('Discriminator tag of the user'),
});
export type FriendRequestByTagRequest = z.infer<typeof FriendRequestByTagRequest>;
export const RelationshipNicknameUpdateRequest = z.object({
nickname: createStringType(0, 256).nullable().describe('Custom nickname for this friend (max 256 characters)'),
});
export type RelationshipNicknameUpdateRequest = z.infer<typeof RelationshipNicknameUpdateRequest>;
export const RelationshipTypePutRequest = z
.object({
type: withFieldDescription(RelationshipTypesSchema, 'Type of relationship to create').optional(),
})
.optional();
export type RelationshipTypePutRequest = z.infer<typeof RelationshipTypePutRequest>;
export const CustomStatusPayload = z
.object({
text: createStringType(1, 128).nullish().describe('Custom status text (max 128 characters)'),
expires_at: DateTimeType.nullish().describe('When the custom status expires'),
emoji_id: SnowflakeType.nullish().describe('ID of custom emoji to display'),
emoji_name: createStringType(2, 32).nullish().describe('Unicode emoji or custom emoji name'),
})
.transform((value) => {
if (value.emoji_id != null) {
return {...value, emoji_name: undefined};
}
return value;
})
.refine((value) => value.emoji_name == null || isValidSingleUnicodeEmoji(value.emoji_name), {
message: 'Emoji name must be a valid Unicode emoji',
path: ['emoji_name'],
})
.refine((value) => value.expires_at == null || value.expires_at.getTime() > Date.now(), {
message: 'expires_at must be in the future',
path: ['expires_at'],
});
export type CustomStatusPayload = z.infer<typeof CustomStatusPayload>;
const GuildFolderIconSchema = withOpenApiType(
createNamedStringLiteralUnion(
[
[GuildFolderIcons.FOLDER, 'FOLDER', 'Classic folder icon'],
[GuildFolderIcons.STAR, 'STAR', 'Star icon'],
[GuildFolderIcons.HEART, 'HEART', 'Heart icon'],
[GuildFolderIcons.BOOKMARK, 'BOOKMARK', 'Bookmark icon'],
[GuildFolderIcons.GAME_CONTROLLER, 'GAME_CONTROLLER', 'Game controller icon'],
[GuildFolderIcons.SHIELD, 'SHIELD', 'Shield icon'],
[GuildFolderIcons.MUSIC_NOTE, 'MUSIC_NOTE', 'Music note icon'],
] as const,
'Guild folder icon',
),
'GuildFolderIconType',
);
export const CreatePrivateChannelRequest = z
.object({
recipient_id: SnowflakeType.optional().describe('User ID for creating a DM channel'),
recipients: z.array(SnowflakeType).max(9).optional().describe('Array of user IDs for creating a group DM (max 9)'),
})
.refine((data) => (data.recipient_id && !data.recipients) || (!data.recipient_id && data.recipients), {
message: 'Either recipient_id or recipients must be provided, but not both',
});
export type CreatePrivateChannelRequest = z.infer<typeof CreatePrivateChannelRequest>;
const GuildFolderSchema = z.object({
id: SignedInt32Type.describe('Unique identifier for the folder (-1 for uncategorized)'),
name: createStringType(0, 100).nullish().describe('Display name of the folder'),
color: Int32Type.nullish().describe('Color of the folder as integer'),
flags: createBitflagInt32Type(
GuildFolderFlags,
GuildFolderFlagsDescriptions,
'Bitfield for guild folder display behaviour',
'GuildFolderFlags',
)
.default(0)
.describe('Bitfield for guild folder display behaviour'),
icon: GuildFolderIconSchema.default(DEFAULT_GUILD_FOLDER_ICON).describe('Selected icon for the guild folder'),
guild_ids: z.array(SnowflakeType).max(200).describe('Guild IDs in this folder'),
});
export const UserStatusType = withOpenApiType(
createNamedStringLiteralUnion(
[
[StatusTypes.ONLINE, 'ONLINE', 'User is online and available'],
[StatusTypes.DND, 'DND', 'Do not disturb – notifications are suppressed'],
[StatusTypes.IDLE, 'IDLE', 'User is away or inactive'],
[StatusTypes.INVISIBLE, 'INVISIBLE', 'User appears offline but can still receive messages'],
] as const,
'User online status',
),
'UserStatusType',
);
export const UserThemeType = withOpenApiType(
createNamedStringLiteralUnion(
[
[ThemeTypes.DARK, 'DARK', 'Dark colour theme'],
[ThemeTypes.COAL, 'COAL', 'Coal/darker colour theme'],
[ThemeTypes.LIGHT, 'LIGHT', 'Light colour theme'],
[ThemeTypes.SYSTEM, 'SYSTEM', 'Follow system colour preference'],
] as const,
'UI theme preference',
),
'UserThemeType',
);
export const UserSettingsUpdateRequest = z
.object({
flags: createBitflagInt32Type(
FriendSourceFlags,
FriendSourceFlagsDescriptions,
'Friend source flags',
'FriendSourceFlags',
),
status: UserStatusType,
status_resets_at: DateTimeType.nullish().describe('When status resets'),
status_resets_to: UserStatusType.nullish(),
theme: UserThemeType,
locale: LocaleSchema,
restricted_guilds: z.array(SnowflakeType).max(200).describe('Guilds with DM restrictions'),
bot_restricted_guilds: z.array(SnowflakeType).max(200).describe('Guilds with bot DM restrictions'),
default_guilds_restricted: z.boolean().describe('Default DM restriction for new guilds'),
bot_default_guilds_restricted: z.boolean().describe('Default bot DM restriction for new guilds'),
inline_attachment_media: z.boolean().describe('Display attachments inline'),
inline_embed_media: z.boolean().describe('Display embed media inline'),
gif_auto_play: z.boolean().describe('Auto-play GIFs'),
render_embeds: z.boolean().describe('Render message embeds'),
render_reactions: z.boolean().describe('Display reactions'),
animate_emoji: z.boolean().describe('Animate custom emoji'),
animate_stickers: withFieldDescription(StickerAnimationOptionsSchema, 'Sticker animation preference'),
render_spoilers: withFieldDescription(RenderSpoilersSchema, 'Spoiler rendering preference'),
message_display_compact: z.boolean().describe('Compact message display'),
friend_source_flags: createBitflagInt32Type(
FriendSourceFlags,
FriendSourceFlagsDescriptions,
'Friend request source permissions',
'FriendSourceFlags',
),
incoming_call_flags: createBitflagInt32Type(
IncomingCallFlags,
IncomingCallFlagsDescriptions,
'Incoming call settings',
'IncomingCallFlags',
),
group_dm_add_permission_flags: createBitflagInt32Type(
GroupDmAddPermissionFlags,
GroupDmAddPermissionFlagsDescriptions,
'Group DM add permissions',
'GroupDmAddPermissionFlags',
),
guild_folders: z.array(GuildFolderSchema).max(200).describe('Guild folder organization'),
custom_status: CustomStatusPayload.nullish().describe('Custom status'),
afk_timeout: z.number().int().describe('AFK timeout in seconds'),
time_format: withFieldDescription(TimeFormatTypesSchema, 'Time format preference'),
developer_mode: z.boolean().describe('Developer mode enabled'),
trusted_domains: z
.array(z.string().min(1).max(253))
.max(1000)
.describe('Trusted external link domains. Use "*" to trust all domains.'),
default_hide_muted_channels: z.boolean().describe('Hide muted channels by default in new guilds'),
})
.partial();
export type UserSettingsUpdateRequest = z.infer<typeof UserSettingsUpdateRequest>;
const MuteConfigSchema = z
.object({
end_time: DateTimeType.nullish().describe('When the mute expires'),
selected_time_window: z.number().int().describe('Selected mute duration'),
})
.nullish();
const ChannelOverrideSchema = z.object({
collapsed: z.boolean().describe('Channel category collapsed'),
message_notifications: withFieldDescription(UserNotificationSettingsSchema, 'Channel notification level'),
muted: z.boolean().describe('Channel muted'),
mute_config: MuteConfigSchema.describe('Channel mute configuration'),
});
export const UserGuildSettingsUpdateRequest = z
.object({
message_notifications: withFieldDescription(UserNotificationSettingsSchema, 'Default guild notification level'),
muted: z.boolean().describe('Guild muted'),
mute_config: MuteConfigSchema.describe('Guild mute configuration'),
mobile_push: z.boolean().describe('Mobile push notifications enabled'),
suppress_everyone: z.boolean().describe('Suppress @everyone mentions'),
suppress_roles: z.boolean().describe('Suppress role mentions'),
hide_muted_channels: z.boolean().describe('Hide muted channels'),
channel_overrides: z
.record(SnowflakeStringType, ChannelOverrideSchema)
.nullable()
.describe('Per-channel overrides'),
})
.partial();
export type UserGuildSettingsUpdateRequest = z.infer<typeof UserGuildSettingsUpdateRequest>;
export const EmptyBodyRequest = z.object({}).optional();
export type EmptyBodyRequest = z.infer<typeof EmptyBodyRequest>;
export const UserTagCheckQueryRequest = z.object({
username: UsernameType.describe('The username to check'),
discriminator: DiscriminatorType.describe('The discriminator to check'),
});
export type UserTagCheckQueryRequest = z.infer<typeof UserTagCheckQueryRequest>;
export const UserProfileQueryRequest = z.object({
guild_id: SnowflakeType.optional().describe('Optional guild ID for guild-specific profile'),
with_mutual_friends: QueryBooleanType.describe('Whether to include mutual friends'),
with_mutual_guilds: QueryBooleanType.describe('Whether to include mutual guilds'),
});
export type UserProfileQueryRequest = z.infer<typeof UserProfileQueryRequest>;
export const UserNoteUpdateRequest = z.object({
note: createStringType(1, 256).nullish().describe('The note text (max 256 characters)'),
});
export type UserNoteUpdateRequest = z.infer<typeof UserNoteUpdateRequest>;
export const PushSubscribeRequest = z.object({
endpoint: URLType.describe('The push subscription endpoint URL'),
keys: z.object({
p256dh: createStringType(1, 1024).describe('The P-256 ECDH public key'),
auth: createStringType(1, 1024).describe('The authentication secret'),
}),
user_agent: createStringType(1, 1024).optional().describe('The user agent string'),
});
export type PushSubscribeRequest = z.infer<typeof PushSubscribeRequest>;
export const SubscriptionIdParam = z.object({
subscription_id: createStringType(1, 256).describe('The ID of the push subscription'),
});
export type SubscriptionIdParam = z.infer<typeof SubscriptionIdParam>;
export const PreloadMessagesRequest = z.object({
channels: z.array(SnowflakeType).max(100).describe('Array of channel IDs to preload messages from (max 100)'),
});
export type PreloadMessagesRequest = z.infer<typeof PreloadMessagesRequest>;
export const UserMentionsQueryRequest = z.object({
limit: createQueryIntegerType({minValue: 1, maxValue: 100, defaultValue: 25}).describe(
'Maximum number of mentions to return (1-100, default 25)',
),
roles: QueryBooleanType.optional().default(true).describe('Whether to include role mentions'),
everyone: QueryBooleanType.optional().default(true).describe('Whether to include @everyone mentions'),
guilds: QueryBooleanType.optional().default(true).describe('Whether to include guild mentions'),
before: SnowflakeType.optional().describe('Get mentions before this message ID'),
});
export type UserMentionsQueryRequest = z.infer<typeof UserMentionsQueryRequest>;
export const UserSavedMessagesQueryRequest = z.object({
limit: createQueryIntegerType({minValue: 1, maxValue: 100, defaultValue: 25}).describe(
'Maximum number of saved messages to return (1-100, default 25)',
),
});
export type UserSavedMessagesQueryRequest = z.infer<typeof UserSavedMessagesQueryRequest>;
export const SaveMessageRequest = z.object({
channel_id: SnowflakeType.describe('The ID of the channel containing the message'),
message_id: SnowflakeType.describe('The ID of the message to save'),
});
export type SaveMessageRequest = z.infer<typeof SaveMessageRequest>;
@@ -0,0 +1,521 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {
DEFAULT_GUILD_FOLDER_ICON,
FriendSourceFlags,
FriendSourceFlagsDescriptions,
GroupDmAddPermissionFlags,
GroupDmAddPermissionFlagsDescriptions,
GuildFolderFlags,
GuildFolderFlagsDescriptions,
GuildFolderIcons,
IncomingCallFlags,
IncomingCallFlagsDescriptions,
PublicUserFlags,
PublicUserFlagsDescriptions,
} from '@fluxer/constants/src/UserConstants';
import {ConnectionResponse} from '@fluxer/schema/src/domains/connection/ConnectionSchemas';
import {GuildMemberResponse} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
import {MessageResponseSchema} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
import {LocaleSchema} from '@fluxer/schema/src/primitives/LocaleSchema';
import {
createBitflagInt32Type,
createNamedStringLiteralUnion,
createStringType,
HexString32Type,
Int32Type,
SignedInt32Type,
SnowflakeStringType,
withFieldDescription,
withOpenApiType,
} from '@fluxer/schema/src/primitives/SchemaPrimitives';
import {
RelationshipTypesSchema,
RenderSpoilersSchema,
StickerAnimationOptionsSchema,
TimeFormatTypesSchema,
UserAuthenticatorTypesSchema,
UserNotificationSettingsSchema,
UserPremiumTypesSchema,
} from '@fluxer/schema/src/primitives/UserSettingsValidators';
import {z} from 'zod';
export const UserPartialResponse = z.object({
id: SnowflakeStringType.describe('The unique identifier (snowflake) for this user'),
username: z.string().describe('The username of the user, not unique across the platform'),
discriminator: z.string().describe('The four-digit discriminator tag of the user'),
global_name: z.string().nullable().describe('The display name of the user, if set'),
avatar: z.string().nullable().describe('The hash of the user avatar image'),
avatar_color: Int32Type.nullable().describe('The dominant avatar color of the user as an integer'),
bot: z.boolean().optional().describe('Whether the user is a bot account'),
system: z.boolean().optional().describe('Whether the user is an official system user'),
flags: createBitflagInt32Type(
PublicUserFlags,
PublicUserFlagsDescriptions,
'The public flags on the user account',
'PublicUserFlags',
),
});
export type UserPartialResponse = z.infer<typeof UserPartialResponse>;
export const UserPrivateResponse = UserPartialResponse.extend({
is_staff: z.boolean().describe('Whether the user has staff permissions'),
acls: z.array(z.string()).max(100).describe('Access control list entries for the user'),
traits: z.array(z.string()).max(100).describe('Special traits assigned to the user account'),
email: z.string().nullable().describe('The email address associated with the account'),
email_bounced: z
.boolean()
.optional()
.describe('Whether the current email address is marked as bounced by the mail provider'),
phone: z.string().nullable().describe('The phone number associated with the account'),
bio: z.string().nullable().describe('The user biography text'),
pronouns: z.string().nullable().describe('The preferred pronouns of the user'),
accent_color: Int32Type.nullable().describe('The user-selected accent color as an integer'),
banner: z.string().nullable().describe('The hash of the user profile banner image'),
banner_color: Int32Type.nullable().describe('The default banner color if no custom banner is set'),
mfa_enabled: z.boolean().describe('Whether multi-factor authentication is enabled'),
authenticator_types: z
.array(UserAuthenticatorTypesSchema)
.max(10)
.optional()
.describe('The types of authenticators configured for MFA'),
verified: z.boolean().describe('Whether the email address has been verified'),
premium_type: withFieldDescription(UserPremiumTypesSchema, 'The type of premium subscription').nullable(),
premium_since: z.string().nullable().describe('ISO8601 timestamp of when premium was first activated'),
premium_until: z.string().nullable().describe('ISO8601 timestamp of when the current premium period ends'),
premium_will_cancel: z.boolean().describe('Whether premium is set to cancel at the end of the billing period'),
premium_billing_cycle: z.string().nullable().describe('The billing cycle for the premium subscription'),
premium_lifetime_sequence: Int32Type.nullable().describe('The sequence number for lifetime premium subscribers'),
premium_badge_hidden: z.boolean().describe('Whether the premium badge is hidden on the profile'),
premium_badge_masked: z.boolean().describe('Whether the premium badge shows a masked appearance'),
premium_badge_timestamp_hidden: z.boolean().describe('Whether the premium start timestamp is hidden'),
premium_badge_sequence_hidden: z.boolean().describe('Whether the lifetime sequence number is hidden'),
premium_purchase_disabled: z.boolean().describe('Whether premium purchases are disabled for this account'),
premium_enabled_override: z.boolean().describe('Whether premium features are enabled via override'),
password_last_changed_at: z.string().nullable().describe('ISO8601 timestamp of the last password change'),
required_actions: z
.array(z.string())
.max(20)
.nullable()
.describe('Actions the user must complete before full access'),
nsfw_allowed: z.boolean().describe('Whether the user is allowed to view NSFW content'),
has_dismissed_premium_onboarding: z.boolean().describe('Whether the user has dismissed the premium onboarding flow'),
has_ever_purchased: z.boolean().describe('Whether the user has ever made a purchase'),
has_unread_gift_inventory: z.boolean().describe('Whether there are unread items in the gift inventory'),
unread_gift_inventory_count: Int32Type.describe('The number of unread gift inventory items'),
used_mobile_client: z.boolean().describe('Whether the user has ever used the mobile client'),
pending_bulk_message_deletion: z
.object({
scheduled_at: z.string().describe('ISO8601 timestamp of when the deletion was scheduled'),
channel_count: Int32Type.describe('The number of channels with messages to delete'),
message_count: Int32Type.describe('The total number of messages to delete'),
})
.nullable()
.describe('Information about a pending bulk message deletion request'),
});
export type UserPrivateResponse = z.infer<typeof UserPrivateResponse>;
export const EmailChangeStartResponse = z.object({
ticket: z.string().describe('Ticket returned for email change actions'),
require_original: z.boolean().describe('Whether verification of the original email is required'),
original_email: z.string().nullable().describe('The original email address on record'),
original_proof: z
.string()
.nullable()
.describe('Proof token generated when original email verification is not required'),
original_code_expires_at: z
.string()
.nullable()
.describe('ISO8601 timestamp when the original verification code expires'),
resend_available_at: z
.string()
.nullable()
.describe('ISO8601 timestamp when the original verification code can be resent'),
});
export type EmailChangeStartResponse = z.infer<typeof EmailChangeStartResponse>;
export const EmailChangeVerifyOriginalResponse = z.object({
original_proof: z.string().describe('Proof token issued after verifying the original email'),
});
export type EmailChangeVerifyOriginalResponse = z.infer<typeof EmailChangeVerifyOriginalResponse>;
export const EmailChangeRequestNewResponse = z.object({
ticket: z.string().describe('Ticket associated with the email change attempt'),
new_email: z.string().describe('The new email address the user wants to verify'),
new_code_expires_at: z.string().describe('ISO8601 timestamp when the new email code expires'),
resend_available_at: z.string().nullable().describe('ISO8601 timestamp when the new email code can be resent'),
});
export type EmailChangeRequestNewResponse = z.infer<typeof EmailChangeRequestNewResponse>;
export const PasswordChangeStartResponse = z.object({
ticket: z.string().describe('Ticket for password change actions'),
code_expires_at: z.string().describe('ISO8601 timestamp when the verification code expires'),
resend_available_at: z.string().nullable().describe('ISO8601 timestamp when the code can be resent'),
});
export type PasswordChangeStartResponse = z.infer<typeof PasswordChangeStartResponse>;
export const PasswordChangeVerifyResponse = z.object({
verification_proof: z.string().describe('Proof token issued after verifying the email code'),
});
export type PasswordChangeVerifyResponse = z.infer<typeof PasswordChangeVerifyResponse>;
export interface UserProfileResponse {
bio: string | null;
pronouns: string | null;
banner: string | null;
banner_color?: number | null;
accent_color: number | null;
}
export const CustomStatusResponse = z.object({
text: z.string().nullish().describe('The custom status message text'),
expires_at: z.iso.datetime().nullish().describe('ISO8601 timestamp of when the custom status expires'),
emoji_id: SnowflakeStringType.nullish().describe('The ID of the custom emoji used in the status'),
emoji_name: z.string().nullish().describe('The name of the emoji used in the status'),
emoji_animated: z.boolean().describe('Whether the status emoji is animated'),
});
export type CustomStatusResponse = z.infer<typeof CustomStatusResponse>;
const GuildFolderIconSchema = withOpenApiType(
createNamedStringLiteralUnion(
[
[GuildFolderIcons.FOLDER, 'FOLDER', 'Classic folder icon'],
[GuildFolderIcons.STAR, 'STAR', 'Star icon'],
[GuildFolderIcons.HEART, 'HEART', 'Heart icon'],
[GuildFolderIcons.BOOKMARK, 'BOOKMARK', 'Bookmark icon'],
[GuildFolderIcons.GAME_CONTROLLER, 'GAME_CONTROLLER', 'Game controller icon'],
[GuildFolderIcons.SHIELD, 'SHIELD', 'Shield icon'],
[GuildFolderIcons.MUSIC_NOTE, 'MUSIC_NOTE', 'Music note icon'],
] as const,
'Guild folder icon',
),
'GuildFolderIconType',
);
export const UserSettingsResponse = z.object({
status: z.string().describe('The current online status of the user'),
status_resets_at: z.iso.datetime().nullish().describe('ISO8601 timestamp of when the status will reset'),
status_resets_to: z.string().nullish().describe('The status to reset to after the scheduled reset'),
theme: z.string().describe('The UI theme preference'),
locale: LocaleSchema,
restricted_guilds: z.array(SnowflakeStringType).max(200).describe('Guild IDs where direct messages are restricted'),
bot_restricted_guilds: z
.array(SnowflakeStringType)
.max(200)
.describe('Guild IDs where bot direct messages are restricted'),
default_guilds_restricted: z.boolean().describe('Whether new guilds have DM restrictions by default'),
bot_default_guilds_restricted: z.boolean().describe('Whether new guilds have bot DM restrictions by default'),
inline_attachment_media: z.boolean().describe('Whether to display attachments inline in chat'),
inline_embed_media: z.boolean().describe('Whether to display embed media inline in chat'),
gif_auto_play: z.boolean().describe('Whether GIFs auto-play in chat'),
render_embeds: z.boolean().describe('Whether to render message embeds'),
render_reactions: z.boolean().describe('Whether to display reactions on messages'),
animate_emoji: z.boolean().describe('Whether to animate custom emoji'),
animate_stickers: withFieldDescription(StickerAnimationOptionsSchema, 'Sticker animation preference setting'),
render_spoilers: withFieldDescription(RenderSpoilersSchema, 'Spoiler rendering preference setting'),
message_display_compact: z.boolean().describe('Whether to use compact message display mode'),
friend_source_flags: createBitflagInt32Type(
FriendSourceFlags,
FriendSourceFlagsDescriptions,
'Bitfield for friend request source permissions',
'FriendSourceFlags',
),
incoming_call_flags: createBitflagInt32Type(
IncomingCallFlags,
IncomingCallFlagsDescriptions,
'Bitfield for incoming call notification settings',
'IncomingCallFlags',
),
group_dm_add_permission_flags: createBitflagInt32Type(
GroupDmAddPermissionFlags,
GroupDmAddPermissionFlagsDescriptions,
'Bitfield for group DM add permissions',
'GroupDmAddPermissionFlags',
),
guild_folders: z
.array(
z.object({
id: SignedInt32Type.nullish().describe('The unique identifier for the folder (-1 for uncategorized)'),
name: z.string().nullish().describe('The display name of the folder'),
color: Int32Type.nullish().describe('The color of the folder as an integer'),
flags: createBitflagInt32Type(
GuildFolderFlags,
GuildFolderFlagsDescriptions,
'Bitfield for guild folder display behaviour',
'GuildFolderFlags',
)
.default(0)
.describe('Bitfield for guild folder display behaviour'),
icon: GuildFolderIconSchema.default(DEFAULT_GUILD_FOLDER_ICON).describe('Selected icon for the guild folder'),
guild_ids: z.array(SnowflakeStringType).max(200).describe('The IDs of guilds contained in this folder'),
}),
)
.max(200)
.describe('The folder structure for organizing guilds in the sidebar'),
custom_status: CustomStatusResponse.nullable().describe('The custom status set by the user'),
afk_timeout: Int32Type.describe('The idle timeout in seconds before going AFK'),
time_format: withFieldDescription(TimeFormatTypesSchema, 'The preferred time format setting'),
developer_mode: z.boolean().describe('Whether developer mode is enabled'),
trusted_domains: z.array(z.string()).max(1000).describe('List of trusted external link domains'),
default_hide_muted_channels: z.boolean().describe('Whether muted channels are hidden by default in new guilds'),
});
export type UserSettingsResponse = z.infer<typeof UserSettingsResponse>;
const UserGuildMuteConfig = z
.object({
end_time: z.string().nullable().describe('ISO8601 timestamp of when the mute expires'),
selected_time_window: Int32Type.describe('The selected mute duration in seconds'),
})
.nullable();
const UserGuildChannelOverride = z.object({
collapsed: z.boolean().describe('Whether the channel category is collapsed in the sidebar'),
message_notifications: withFieldDescription(
UserNotificationSettingsSchema,
'The notification level override for this channel',
),
muted: z.boolean().describe('Whether notifications are muted for this channel'),
mute_config: UserGuildMuteConfig.describe('The mute configuration for this channel'),
});
export const UserGuildSettingsResponse = z.object({
guild_id: SnowflakeStringType.nullable().describe('The ID of the guild these settings apply to'),
message_notifications: withFieldDescription(
UserNotificationSettingsSchema,
'The default notification level for the guild',
),
muted: z.boolean().describe('Whether the guild is muted'),
mute_config: UserGuildMuteConfig.describe('The mute configuration for the guild'),
mobile_push: z.boolean().describe('Whether mobile push notifications are enabled'),
suppress_everyone: z.boolean().describe('Whether @everyone mentions are suppressed'),
suppress_roles: z.boolean().describe('Whether role mentions are suppressed'),
hide_muted_channels: z.boolean().describe('Whether muted channels are hidden in the sidebar'),
channel_overrides: z
.record(SnowflakeStringType, UserGuildChannelOverride)
.nullable()
.describe('Per-channel notification overrides'),
version: Int32Type.describe('The version number of these settings for sync'),
});
export type UserGuildSettingsResponse = z.infer<typeof UserGuildSettingsResponse>;
export const RelationshipResponse = z.object({
id: SnowflakeStringType.describe('The unique identifier for the relationship'),
type: withFieldDescription(RelationshipTypesSchema, 'The type of relationship (friend, blocked, pending, etc.)'),
user: z.lazy(() => UserPartialResponse).describe('The user involved in this relationship'),
since: z.iso.datetime().optional().describe('ISO8601 timestamp of when the relationship was established'),
nickname: z.string().nullable().describe('A custom nickname set for the related user'),
});
export type RelationshipResponse = z.infer<typeof RelationshipResponse>;
export const RelationshipListResponse = z
.array(RelationshipResponse)
.max(10000)
.describe('A list of user relationships');
export type RelationshipListResponse = z.infer<typeof RelationshipListResponse>;
export type RequiredAction =
| 'REQUIRE_VERIFIED_EMAIL'
| 'REQUIRE_REVERIFIED_EMAIL'
| 'REQUIRE_VERIFIED_PHONE'
| 'REQUIRE_REVERIFIED_PHONE'
| 'REQUIRE_VERIFIED_EMAIL_OR_VERIFIED_PHONE'
| 'REQUIRE_REVERIFIED_EMAIL_OR_VERIFIED_PHONE'
| 'REQUIRE_VERIFIED_EMAIL_OR_REVERIFIED_PHONE'
| 'REQUIRE_REVERIFIED_EMAIL_OR_REVERIFIED_PHONE';
export interface BackupCode {
readonly code: string;
readonly consumed: boolean;
}
export interface PendingBulkMessageDeletion {
readonly scheduled_at: string;
readonly channel_count: number;
readonly message_count: number;
}
export interface UserProfile {
readonly bio: string | null;
readonly banner: string | null;
readonly banner_color?: number | null;
readonly pronouns: string | null;
readonly accent_color: number | null;
}
export interface UserPartial {
readonly id: string;
readonly username: string;
readonly discriminator: string;
readonly global_name: string | null;
readonly avatar: string | null;
readonly avatar_color: number | null;
readonly bot?: boolean;
readonly system?: boolean;
readonly flags: number;
}
export interface UserPrivate extends UserPartial, UserProfile {
readonly is_staff: boolean;
readonly email: string | null;
readonly email_bounced?: boolean;
readonly mfa_enabled: boolean;
readonly phone: string | null;
readonly authenticator_types: ReadonlyArray<number>;
readonly verified: boolean;
readonly premium_type: number | null;
readonly premium_since: string | null;
readonly premium_until: string | null;
readonly premium_will_cancel: boolean;
readonly premium_billing_cycle: string | null;
readonly premium_lifetime_sequence: number | null;
readonly premium_badge_hidden: boolean;
readonly premium_badge_masked: boolean;
readonly premium_badge_timestamp_hidden: boolean;
readonly premium_badge_sequence_hidden: boolean;
readonly premium_purchase_disabled: boolean;
readonly premium_enabled_override: boolean;
readonly password_last_changed_at: string | null;
readonly required_actions: ReadonlyArray<RequiredAction> | null;
readonly nsfw_allowed: boolean;
readonly pending_bulk_message_deletion: PendingBulkMessageDeletion | null;
readonly has_dismissed_premium_onboarding: boolean;
readonly has_ever_purchased: boolean;
readonly has_unread_gift_inventory: boolean;
readonly unread_gift_inventory_count: number;
readonly used_mobile_client: boolean;
readonly traits: ReadonlyArray<string>;
}
export type User = UserPartial & Partial<UserPrivate>;
export const SavedMessageStatusSchema = withOpenApiType(
createNamedStringLiteralUnion(
[
['available', 'Available', 'The saved message is available and can be retrieved'],
['missing_permissions', 'Missing Permissions', 'The user no longer has permission to view the message'],
],
'Availability status of a saved message',
),
'SavedMessageStatus',
);
export type SavedMessageStatus = z.infer<typeof SavedMessageStatusSchema>;
export const SavedMessageEntryResponse = z.object({
id: SnowflakeStringType.describe('Unique identifier for the saved message entry'),
channel_id: SnowflakeStringType.describe('ID of the channel containing the message'),
message_id: SnowflakeStringType.describe('ID of the saved message'),
status: SavedMessageStatusSchema.describe('Availability status of the saved message'),
message: MessageResponseSchema.nullable().describe('The message content if available'),
});
export type SavedMessageEntryResponse = z.infer<typeof SavedMessageEntryResponse>;
export const SavedMessageEntryListResponse = z.array(SavedMessageEntryResponse);
export type SavedMessageEntryListResponse = z.infer<typeof SavedMessageEntryListResponse>;
export const EmailTokenResponse = z.object({
email_token: createStringType(1, 256).describe('The email change token to use for updating email'),
});
export type EmailTokenResponse = z.infer<typeof EmailTokenResponse>;
export const UserTagCheckResponse = z.object({
taken: z.boolean().describe('Whether the username/discriminator combination is already taken'),
});
export type UserTagCheckResponse = z.infer<typeof UserTagCheckResponse>;
export const UserProfileDataResponse = z.object({
bio: z.string().nullable().describe('User biography text'),
pronouns: z.string().nullable().describe('User pronouns'),
banner: z.string().nullable().describe('Hash of the profile banner image'),
banner_color: Int32Type.nullable().optional().describe('Default banner color if no custom banner'),
accent_color: Int32Type.nullable().describe('User-selected accent color'),
});
export type UserProfileDataResponse = z.infer<typeof UserProfileDataResponse>;
export const GuildMemberProfileDataResponse = z
.object({
bio: z.string().nullable().describe('Guild-specific biography text'),
pronouns: z.string().nullable().describe('Guild-specific pronouns'),
banner: z.string().nullable().describe('Hash of the guild-specific banner image'),
accent_color: Int32Type.nullable().describe('Guild-specific accent color'),
})
.nullable()
.optional();
export type GuildMemberProfileDataResponse = z.infer<typeof GuildMemberProfileDataResponse>;
export const MutualGuildResponse = z.object({
id: SnowflakeStringType.describe('The ID of the mutual guild'),
nick: z.string().nullable().describe('The nickname of the target user in this guild'),
});
export type MutualGuildResponse = z.infer<typeof MutualGuildResponse>;
export const UserProfileFullResponse = z.object({
user: UserPartialResponse.describe('The user object'),
user_profile: UserProfileDataResponse.describe('The user profile data'),
guild_member: z
.lazy(() => GuildMemberResponse)
.optional()
.describe('The guild member data if guild_id was provided'),
guild_member_profile: GuildMemberProfileDataResponse.describe('Guild-specific profile data'),
premium_type: withFieldDescription(UserPremiumTypesSchema, 'The type of premium subscription').optional(),
premium_since: z.string().optional().describe('ISO8601 timestamp of when premium was activated'),
premium_lifetime_sequence: Int32Type.optional().describe('Sequence number for lifetime premium'),
mutual_friends: z.array(UserPartialResponse).optional().describe('Array of mutual friends'),
mutual_guilds: z.array(MutualGuildResponse).optional().describe('Array of mutual guilds'),
connected_accounts: z.array(ConnectionResponse).optional().describe('Array of verified external connections'),
});
export type UserProfileFullResponse = z.infer<typeof UserProfileFullResponse>;
export const UserNotesRecordResponse = z
.record(SnowflakeStringType, z.string())
.describe('A map of user IDs to note text');
export type UserNotesRecordResponse = z.infer<typeof UserNotesRecordResponse>;
export const UserNoteResponse = z.object({
note: z.string().describe('The note text for this user'),
});
export type UserNoteResponse = z.infer<typeof UserNoteResponse>;
export const PushSubscribeResponse = z.object({
subscription_id: HexString32Type.describe('The unique identifier for the push subscription'),
});
export type PushSubscribeResponse = z.infer<typeof PushSubscribeResponse>;
export const PushSubscriptionItemResponse = z.object({
subscription_id: HexString32Type.describe('The unique identifier for the push subscription'),
user_agent: z.string().nullable().describe('The user agent that registered this subscription'),
});
export type PushSubscriptionItemResponse = z.infer<typeof PushSubscriptionItemResponse>;
export const PushSubscriptionsListResponse = z.object({
subscriptions: z.array(PushSubscriptionItemResponse).describe('Array of push subscriptions'),
});
export type PushSubscriptionsListResponse = z.infer<typeof PushSubscriptionsListResponse>;
export const PreloadMessagesResponse = z
.record(SnowflakeStringType, MessageResponseSchema.nullable())
.describe('A map of channel IDs to the latest message in each channel');
export type PreloadMessagesResponse = z.infer<typeof PreloadMessagesResponse>;