// SPDX-License-Identifier: AGPL-3.0-or-later import {DeletionReasons} from '@fluxer/constants/src/Core'; import { PremiumFlags, PremiumFlagsDescriptions, UserFlags, UserFlagsDescriptions, } from '@fluxer/constants/src/UserConstants'; import {ADMIN_ACL_COUNT, AdminAclType} from '@fluxer/schema/src/domains/admin/AdminAclType'; import {CalendarDateType} from '@fluxer/schema/src/primitives/DateValidators'; import {NSFWLevelSchema} from '@fluxer/schema/src/primitives/GuildValidators'; import {createQueryIntegerType, QueryBooleanType} from '@fluxer/schema/src/primitives/QueryValidators'; import { createBitflagInt32Type, createBitflagStringType, createInt32EnumType, createNamedStringLiteralUnion, createStringType, Int32Type, SnowflakeStringType, SnowflakeType, withFieldDescription, } from '@fluxer/schema/src/primitives/SchemaPrimitives'; import {DiscriminatorType, EmailType, UsernameType} from '@fluxer/schema/src/primitives/UserValidators'; import {z} from 'zod'; export const UserAdminResponseSchema = z.object({ id: SnowflakeStringType, username: z.string(), discriminator: Int32Type, global_name: z.string().nullable(), bot: z.boolean(), system: z.boolean(), flags: createBitflagStringType(UserFlags, UserFlagsDescriptions, 'User account flags (64-bit)', 'UserFlags'), premium_flags: createBitflagInt32Type( PremiumFlags, PremiumFlagsDescriptions, 'User premium-related flags (32-bit)', 'PremiumFlags', ), avatar: z.string().nullable(), banner: z.string().nullable(), bio: z.string().nullable(), pronouns: z.string().nullable(), accent_color: Int32Type.nullable(), email: z.string().nullable(), email_verified: z.boolean(), email_bounced: z.boolean(), date_of_birth: z.string().nullable(), locale: z.string().nullable(), premium_type: Int32Type.nullable(), premium_since: z.string().nullable(), premium_until: z.string().nullable(), premium_grace_ends_at: z.string().nullable(), premium_lifetime_sequence: Int32Type.nullable(), temp_banned_until: z.string().nullable(), pending_deletion_at: z.string().nullable(), pending_bulk_message_deletion_at: z.string().nullable(), deletion_reason_code: Int32Type.nullable(), deletion_public_reason: z.string().nullable(), deletion_audit_log_reason: z .string() .nullable() .describe('Private reason recorded with the pending deletion, null without the audit log view permission'), deletion_scheduled_by: SnowflakeStringType.nullable().describe( 'ID of the account that scheduled the pending deletion, null when it was not recorded', ), deletion_scheduled_at: z.string().nullable().describe('ISO 8601 timestamp when the pending deletion was scheduled'), acls: z.array(z.string()).max(ADMIN_ACL_COUNT), traits: z.array(z.string()).max(100), has_totp: z.boolean(), authenticator_types: z.array(Int32Type).max(10), last_active_at: z.string().nullable(), last_active_ip: z.string().nullable(), last_active_ip_reverse: z.string().nullable(), last_active_location: z.string().nullable(), }); export type UserAdminResponse = z.infer; const LookupUserByQueryRequest = z.object({ query: createStringType(1, 1024), }); const LookupUserByIdsRequest = z.object({ user_ids: z.array(SnowflakeType).max(100), }); export const LookupUserRequest = z.union([LookupUserByQueryRequest, LookupUserByIdsRequest]); export type LookupUserRequest = z.infer; const UserContactChangeLogEntrySchema = z.object({ event_id: z.string(), field: z.string(), old_value: z.string().nullable(), new_value: z.string().nullable(), reason: z.string().nullable(), actor_user_id: z.string().nullable(), event_at: z.string(), }); export const ListUserChangeLogResponseSchema = z.object({ entries: z.array(UserContactChangeLogEntrySchema).max(200), next_page_token: z.string().nullable(), }); export const AdminUsersMeResponse = z.object({ user: UserAdminResponseSchema, }); export type AdminUsersMeResponse = z.infer; export const UserMutationResponse = AdminUsersMeResponse; export type UserMutationResponse = z.infer; export const LookupUserResponse = z.object({ users: z.array(UserAdminResponseSchema), }); export type LookupUserResponse = z.infer; const UserSessionResponse = z.object({ session_id_hash: createStringType(8, 256).describe('Hashed session identifier (base64url)'), created_at: z.string().describe('ISO timestamp when the session was created'), approx_last_used_at: z.string().describe('ISO timestamp of the session last usage (approximate)'), client_ip: createStringType(1, 64).describe('Client IP address'), client_ip_reverse: z.string().nullable().describe('Reverse DNS hostname for the client IP (PTR), if available'), client_os: z.string().nullable().describe('Client operating system, if detected'), client_platform: z.string().nullable().describe('Client platform, if detected'), client_location: z.string().nullable().describe('Approximate geo location label for the client IP, if available'), deleted_at: z.string().nullable().describe('ISO timestamp when the session was terminated, or null if still active'), }); export const ListUserSessionsResponse = z.object({ sessions: z.array(UserSessionResponse), }); export type ListUserSessionsResponse = z.infer; export const ListUserDmChannelsRequest = z .object({ user_id: SnowflakeType.describe('ID of the user to list DM channels for'), before: SnowflakeType.optional().describe('Return channels with IDs lower than this channel ID'), after: SnowflakeType.optional().describe('Return channels with IDs higher than this channel ID'), limit: z.number().int().min(1).max(200).default(50).describe('Maximum number of DM channels to return'), }) .refine((value) => value.before === undefined || value.after === undefined, { error: 'before and after cannot both be provided', }); export type ListUserDmChannelsRequest = z.infer; const AdminResolvedUserSchema = z.object({ id: SnowflakeStringType, username: z.string(), discriminator: z.string(), global_name: z.string().nullable(), avatar: z.string().nullable(), }); const AdminUserDmChannelSchema = z.object({ channel_id: SnowflakeStringType, channel_type: Int32Type.nullable(), channel_nsfw: z.boolean().nullable(), guild_nsfw_level: NSFWLevelSchema.nullable(), recipient_ids: z.array(SnowflakeStringType).max(100), recipients: z.array(AdminResolvedUserSchema).max(100), last_message_id: SnowflakeStringType.nullable(), is_open: z.boolean(), name: z.string().nullable(), icon: z.string().nullable(), owner_id: SnowflakeStringType.nullable(), }); const ListUserDmChannelsResponse = z.object({ channels: z.array(AdminUserDmChannelSchema).max(200), }); export const ListUserGroupDmChannelsRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to list group DM channels for'), }); export type ListUserGroupDmChannelsRequest = z.infer; const ListUserGroupDmChannelsResponse = z.object({ channels: z.array(AdminUserDmChannelSchema).max(500), }); export const TerminateSessionsResponse = z.object({ terminated_count: Int32Type, }); export type TerminateSessionsResponse = z.infer; const UserFlagValueType = createBitflagStringType( UserFlags, UserFlagsDescriptions, 'A single user flag value to add or remove', 'UserFlags', ); const PremiumFlagValueType = createBitflagInt32Type( PremiumFlags, PremiumFlagsDescriptions, 'A single premium flag value to add or remove', 'PremiumFlags', ); const UpdateUserFlagsRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to update'), add_flags: z.array(UserFlagValueType).max(64).default([]).describe('User flags to add'), remove_flags: z.array(UserFlagValueType).max(64).default([]).describe('User flags to remove'), }); const UpdatePremiumFlagsRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to update'), add_flags: z.array(PremiumFlagValueType).max(64).default([]).describe('Premium flags to add'), remove_flags: z.array(PremiumFlagValueType).max(64).default([]).describe('Premium flags to remove'), }); export const DisableMfaRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to disable MFA for'), }); export type DisableMfaRequest = z.infer; export const CancelBulkMessageDeletionRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to cancel bulk message deletion for'), }); export type CancelBulkMessageDeletionRequest = z.infer; const UserProfileFieldEnum = createNamedStringLiteralUnion( [ ['avatar', 'avatar', 'User profile avatar image'], ['banner', 'banner', 'User profile banner image'], ['bio', 'bio', 'User biography text'], ['pronouns', 'pronouns', 'User pronouns'], ['global_name', 'global_name', 'User display name'], ], 'User profile field that can be cleared', ); export const ClearUserFieldsRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to clear fields for'), fields: z.array(UserProfileFieldEnum).max(10).describe('List of profile fields to clear'), }); export type ClearUserFieldsRequest = z.infer; export const SetUserBotStatusRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to update'), bot: z.boolean().describe('Whether the user should be marked as a bot'), }); export type SetUserBotStatusRequest = z.infer; export const SetUserSystemStatusRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to update'), system: z.boolean().describe('Whether the user should be marked as a system user'), }); export type SetUserSystemStatusRequest = z.infer; export const VerifyUserEmailRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to verify email for'), }); export type VerifyUserEmailRequest = z.infer; export const ResendVerificationEmailRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to resend verification email to'), }); export type ResendVerificationEmailRequest = z.infer; export const SendPasswordResetRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to send password reset to'), }); export type SendPasswordResetRequest = z.infer; export const AdminPasswordResetLinkResponse = z.object({ url: z.string().describe('Password reset link to hand to the user. It is shown only once'), expires_at: z.iso.datetime().describe('ISO 8601 timestamp when the link stops working'), }); export type AdminPasswordResetLinkResponse = z.infer; export const ChangeUsernameRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to change username for'), username: UsernameType.describe('New username for the user'), discriminator: DiscriminatorType.optional().describe('Legacy discriminator value'), }); export type ChangeUsernameRequest = z.infer; export const ChangeEmailRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to change email for'), email: EmailType.describe('New email address for the user'), }); export type ChangeEmailRequest = z.infer; export const TerminateSessionsRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to terminate sessions for'), }); export type TerminateSessionsRequest = z.infer; export const TempBanUserRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to temporarily ban'), duration_hours: z .number() .int() .min(0) .max(8760) .describe('Duration of the ban in hours. Use 0 for a permanent ban (until manually unbanned).'), reason: createStringType(0, 512).optional().describe('Reason shown to the user in the ban email'), notify_user: z .boolean() .default(true) .describe('Whether to email the user about a temporary ban. Permanent bans (duration_hours 0) are never emailed'), }); export type TempBanUserRequest = z.infer; const DeletionReasonCodeType = createInt32EnumType( Object.entries(DeletionReasons).map(([name, value]) => [value, name] as const), 'Reason the account was scheduled for deletion', 'DeletionReasonCode', ); export const ScheduleAccountDeletionRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to schedule deletion for'), reason_code: withFieldDescription(DeletionReasonCodeType, 'Code indicating the reason for deletion'), public_reason: createStringType(0, 512).optional().describe('Public-facing reason for the deletion'), days_until_deletion: z .number() .int() .min(1) .max(365) .default(60) .describe('Number of days until the account is deleted'), replace_pending_deletion_at: z.iso .datetime() .optional() .describe( 'pending_deletion_at of the deletion this request replaces. Required when a deletion is already scheduled for the account', ), notify_user: z.boolean().default(true).describe('Whether to email the user about the scheduled deletion'), }); export type ScheduleAccountDeletionRequest = z.infer; export const SetUserAclsRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to set ACLs for'), acls: z.array(AdminAclType).max(ADMIN_ACL_COUNT).describe('List of access control permissions to assign'), }); export type SetUserAclsRequest = z.infer; export const SetUserTraitsRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to set traits for'), traits: z.array(createStringType(1, 128)).max(100).describe('List of traits to assign to the user'), }); export type SetUserTraitsRequest = z.infer; export const ChangeDobRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to change date of birth for'), date_of_birth: createStringType(10, 10) .refine((value) => CalendarDateType.safeParse(value).success, 'Invalid date format') .describe('New date of birth in YYYY-MM-DD format'), }); export type ChangeDobRequest = z.infer; export const BulkUpdateUserFlagsRequest = z.object({ user_ids: z.array(SnowflakeType).max(1000).describe('List of user IDs to update'), add_flags: z.array(UserFlagValueType).max(64).default([]).describe('User flags to add to all specified users'), remove_flags: z .array(UserFlagValueType) .max(64) .default([]) .describe('User flags to remove from all specified users'), }); export type BulkUpdateUserFlagsRequest = z.infer; export const BulkScheduleUserDeletionRequest = ScheduleAccountDeletionRequest.omit({ user_id: true, replace_pending_deletion_at: true, }).extend({ user_ids: z.array(SnowflakeType).max(1000).describe('List of user IDs to schedule deletion for'), days_until_deletion: ScheduleAccountDeletionRequest.shape.days_until_deletion.describe( 'Number of days until the accounts are deleted', ), }); export type BulkScheduleUserDeletionRequest = z.infer; export const ListWebAuthnCredentialsRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to list WebAuthn credentials for'), }); export type ListWebAuthnCredentialsRequest = z.infer; export const DeleteWebAuthnCredentialRequest = z.object({ user_id: SnowflakeType.describe('ID of the user who owns the credential'), credential_id: createStringType(1, 512).describe('ID of the WebAuthn credential to delete'), }); export type DeleteWebAuthnCredentialRequest = z.infer; export const ListUserChangeLogRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to list change logs for'), limit: z.number().int().min(1).max(200).default(50).describe('Maximum number of entries to return'), page_token: z.string().optional().describe('Pagination token for the next page of results'), }); export type ListUserChangeLogRequest = z.infer; export const RelationshipCategoryEnum = z.enum(['friend', 'incoming_request', 'outgoing_request', 'blocked']); export type RelationshipCategory = z.infer; export const ListUserRelationshipsRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to list relationships for'), }); export type ListUserRelationshipsRequest = z.infer; export const AdminRelationshipEntrySchema = z.object({ target_user_id: SnowflakeStringType, category: RelationshipCategoryEnum, nickname: z.string().nullable(), since: z.string().nullable(), target: AdminResolvedUserSchema.nullable(), }); export type AdminRelationshipEntry = z.infer; export const ListUserRelationshipsResponse = z.object({ friends: z.array(AdminRelationshipEntrySchema), incoming_requests: z.array(AdminRelationshipEntrySchema), outgoing_requests: z.array(AdminRelationshipEntrySchema), blocked: z.array(AdminRelationshipEntrySchema), }); export type ListUserRelationshipsResponse = z.infer; export const RemoveUserRelationshipRequest = z.object({ user_id: SnowflakeType.describe('ID of the user whose relationship is being removed'), target_user_id: SnowflakeType.describe('ID of the other party in the relationship'), category: RelationshipCategoryEnum.describe( 'Which relationship to remove. Friend and outgoing_request also remove the mirror entry on the target user.', ), }); export type RemoveUserRelationshipRequest = z.infer; export const RemoveUserRelationshipsByCategoryRequest = z.object({ user_id: SnowflakeType.describe('ID of the user to bulk-remove relationships for'), category: RelationshipCategoryEnum.describe('Category of relationships to remove for this user'), }); export type RemoveUserRelationshipsByCategoryRequest = z.infer; export const RemoveUserRelationshipsResponse = z.object({ removed_count: Int32Type, }); export type RemoveUserRelationshipsResponse = z.infer; export const AdminAclListResponse = z.object({ acls: z .array(createStringType(1, 64)) .max(ADMIN_ACL_COUNT) .describe('Every admin access control permission the admin API recognises'), }); export type AdminAclListResponse = z.infer; export const AdminUserListQuery = z.object({ q: createStringType(1, 1024).optional().describe('Restrict the results to the users matching this free-text query'), user_id: z .union([SnowflakeStringType, z.array(SnowflakeStringType).max(100)]) .optional() .describe('Restrict the results to these users. Repeat the parameter to pass more than one.'), resolve: createStringType(1, 1024) .optional() .describe( 'Resolve one exact identifier: a username#discriminator tag, a user ID, an email address, or a Stripe subscription ID', ), email: createStringType(1, 320).optional().describe('Restrict the results to the user with this exact email address'), last_active_ip: createStringType(1, 64) .optional() .describe('Restrict the results to the users whose last active IP address matches this one exactly'), limit: createQueryIntegerType({defaultValue: 50, minValue: 1, maxValue: 200}).describe( 'Maximum number of users to return', ), offset: createQueryIntegerType({defaultValue: 0, minValue: 0, maxValue: 100000}).describe( 'Number of users to skip before returning results', ), }); export type AdminUserListQuery = z.infer; export const AdminUserGuildListQuery = z.object({ before: SnowflakeType.optional().describe('Return guilds with IDs lower than this guild ID'), after: SnowflakeType.optional().describe('Return guilds with IDs higher than this guild ID'), limit: createQueryIntegerType({defaultValue: 200, minValue: 1, maxValue: 200}).describe( 'Maximum number of guilds to return', ), with_counts: QueryBooleanType.describe('Whether to resolve live member and presence counts from the gateway'), }); export type AdminUserGuildListQuery = z.infer; export const AdminUserDmChannelType = createNamedStringLiteralUnion( [ ['dm', 'DM', 'One-to-one direct message channels'], ['group_dm', 'GROUP_DM', 'Group direct message channels'], ], 'Kind of direct message channel to list', ); export const AdminUserDmChannelListQuery = z .object({ type: AdminUserDmChannelType.optional() .default('dm') .describe('The kind of direct message channel to list. Defaults to the one-to-one direct message channels.'), before: SnowflakeType.optional().describe('Return channels with IDs lower than this channel ID'), after: SnowflakeType.optional().describe('Return channels with IDs higher than this channel ID'), limit: createQueryIntegerType({defaultValue: 50, minValue: 1, maxValue: 200}).describe( 'Maximum number of DM channels to return', ), }) .refine((value) => value.before === undefined || value.after === undefined, { error: 'before and after cannot both be provided', }); export type AdminUserDmChannelListQuery = z.infer; export const AdminUserChangeLogQuery = z.object({ limit: createQueryIntegerType({defaultValue: 50, minValue: 1, maxValue: 200}).describe( 'Maximum number of entries to return', ), page_token: createStringType(1, 64).optional().describe('Pagination token for the next page of results'), }); export type AdminUserChangeLogQuery = z.infer; export const AdminUserRelationshipCategoryQuery = z.object({ category: RelationshipCategoryEnum.describe('Category of relationships the operation applies to'), }); export type AdminUserRelationshipCategoryQuery = z.infer; export const AdminUserRelationshipParam = z.object({ user_id: SnowflakeType.describe('The ID of the user'), target_user_id: SnowflakeType.describe('The ID of the target user'), }); export type AdminUserRelationshipParam = z.infer; export const AdminUserWebAuthnCredentialParam = z.object({ user_id: SnowflakeType.describe('The ID of the user'), credential_id: createStringType(1, 512).describe('The ID of the WebAuthn credential'), }); export type AdminUserWebAuthnCredentialParam = z.infer; export const AdminUserClearFieldsRequest = ClearUserFieldsRequest.omit({user_id: true}); export type AdminUserClearFieldsRequest = z.infer; export const AdminUserBotStatusRequest = SetUserBotStatusRequest.omit({user_id: true}); export type AdminUserBotStatusRequest = z.infer; export const AdminUserSystemStatusRequest = SetUserSystemStatusRequest.omit({user_id: true}); export type AdminUserSystemStatusRequest = z.infer; export const AdminUserUsernameUpdateRequest = ChangeUsernameRequest.omit({user_id: true}); export type AdminUserUsernameUpdateRequest = z.infer; export const AdminUserEmailUpdateRequest = ChangeEmailRequest.omit({user_id: true}); export type AdminUserEmailUpdateRequest = z.infer; export const AdminUserBanRequest = TempBanUserRequest.omit({user_id: true}); export type AdminUserBanRequest = z.infer; export const AdminUserDeletionScheduleRequest = ScheduleAccountDeletionRequest.omit({user_id: true}); export type AdminUserDeletionScheduleRequest = z.infer; export const AdminUserDeletionCancelRequest = z.object({ expected_pending_deletion_at: z.iso .datetime() .describe('pending_deletion_at of the deletion being cancelled, as shown on the account'), notify_user: z.boolean().default(false).describe('Whether to email the user that the deletion was cancelled'), }); export type AdminUserDeletionCancelRequest = z.infer; export const AdminUserUnbanRequest = z.object({ notify_user: z.boolean().default(true).describe('Whether to email the user that the suspension was lifted'), public_reason: createStringType(0, 512) .optional() .describe('Reason shown to the user in the unban email. The audit log reason is never emailed'), }); export type AdminUserUnbanRequest = z.infer; export const AdminUserBanNoteRequest = z.object({ ban_audit_log_id: SnowflakeType.describe('Audit log entry of the current ban that the note refers to'), note: createStringType(1, 512).describe('Note to append to the ban. Recorded as the reason of a new audit log entry'), }); export type AdminUserBanNoteRequest = z.infer; export const AdminUserAclsRequest = SetUserAclsRequest.omit({user_id: true}); export type AdminUserAclsRequest = z.infer; export const AdminUserTraitsRequest = SetUserTraitsRequest.omit({user_id: true}); export type AdminUserTraitsRequest = z.infer; export const AdminUserFlagsUpdateRequest = UpdateUserFlagsRequest.omit({user_id: true}); export type AdminUserFlagsUpdateRequest = z.infer; export const AdminUserPremiumFlagsUpdateRequest = UpdatePremiumFlagsRequest.omit({user_id: true}); export type AdminUserPremiumFlagsUpdateRequest = z.infer; export const AdminUserDobUpdateRequest = ChangeDobRequest.omit({user_id: true}); export type AdminUserDobUpdateRequest = z.infer; export const AdminUserDmChannelListResponse = z.union([ListUserDmChannelsResponse, ListUserGroupDmChannelsResponse]); export type AdminUserDmChannelListResponse = z.infer;