import { Column , Entity , FindOneOptions , JoinColumn , ManyToMany , OneToMany , RelationId } from "typeorm" ; import { BaseClass } from "./BaseClass" ; import { BitField } from "../util/BitField" ; import { Relationship } from "./Relationship" ; import { ConnectedAccount } from "./ConnectedAccount" ; import { Config , FieldErrors , Snowflake , trimSpecial } from ".." ; import { Member , Session } from "." ; export enum PublicUserEnum { username , discriminator , id , public_flags , avatar , accent_color , banner , bio , bot } export type PublicUserKeys = keyof typeof PublicUserEnum ; export enum PrivateUserEnum { flags , mfa_enabled , email , phone , verified , nsfw_allowed , premium , premium_type , disabled , settings } export type PrivateUserKeys = keyof typeof PrivateUserEnum | PublicUserKeys ; export const PublicUserProjection = Object . values ( PublicUserEnum ) . filter ( ( x ) => typeof x === "string" ) as PublicUserKeys [ ] ; export const PrivateUserProjection = [ ... PublicUserProjection , ... Object . values ( PrivateUserEnum ) . filter ( ( x ) => typeof x === "string" ) , ] as PrivateUserKeys [ ] ; export type PublicUser = Pick < User , PublicUserKeys > ; export interface UserPublic extends Pick < User , PublicUserKeys > { } export interface UserPrivate extends Pick < User , PrivateUserKeys > { locale : string ; } @ Entity ( "users" ) export class User extends BaseClass { @ Column ( ) username : string ; @ Column ( ) discriminator : string ; setDiscriminator ( val ) { const number = Number ( val ) ; if ( isNaN ( number ) ) throw new Error ( "invalid discriminator" ) ; if ( number <= 0 || number >= 10000 ) throw new Error ( "discriminator must be between 1 and 9999" ) ; this . discriminator = val . toString ( ) . padStart ( 4 , "0" ) ; } @ Column ( { nullable : true } ) avatar ? : string ; @ Column ( { nullable : true } ) accent_color ? : number ; @ Column ( { nullable : true } ) banner ? : string ; @ Column ( { nullable : true , select : false } ) phone ? : string ; @ Column ( { select : false } ) desktop : boolean ; @ Column ( { select : false } ) mobile : boolean ; @ Column ( ) premium : boolean ; @ Column ( ) premium_type : number ; @ Column ( ) bot : boolean ; @ Column ( ) bio : string ; @ Column ( ) system : boolean ; @ Column ( { select : false } ) nsfw_allowed : boolean ; @ Column ( { select : false } ) mfa_enabled : boolean ; @ Column ( ) created_at : Date ; @ Column ( { select : false } ) verified : boolean ; @ Column ( ) disabled : boolean ; @ Column ( ) deleted : boolean ; @ Column ( { nullable : true , select : false } ) email ? : string ; @ Column ( ) flags : string ; @ Column ( ) public_flags : number ; @ Column ( ) rights : string ; @ OneToMany ( ( ) => Session , ( session ) => session . user ) sessions : Session [ ] ; @ JoinColumn ( { name : "relationship_ids" } ) @ OneToMany ( ( ) => Relationship , ( relationship ) => relationship . from , { cascade : true , orphanedRowAction : "delete" , } ) relationships : Relationship [ ] ; @ JoinColumn ( { name : "connected_account_ids" } ) @ OneToMany ( ( ) => ConnectedAccount , ( account ) => account . user , { cascade : true , orphanedRowAction : "delete" , } ) connected_accounts : ConnectedAccount [ ] ; @ Column ( { type : "simple-json" , select : false } ) data : { valid_tokens_since : Date ; hash ? : string ; } ; @ Column ( { type : "simple-array" , select : false } ) fingerprints : string [ ] ; @ Column ( { type : "simple-json" , select : false } ) settings : UserSettings ; toPublicUser ( ) { const user = { } ; PublicUserProjection . forEach ( ( x ) => { user [ x ] = this [ x ] ; } ) ; return user as PublicUser ; } static async getPublicUser ( user_id , opts ? ) { return await User . findOneOrFail ( { id : user_id } , { ... opts , select : [ ... PublicUserProjection , ... ( opts ?. select || [ ] ) ] , } ) ; } private static async generateDiscriminator ( username ) { if ( Config . get ( ) . register . incrementingDiscriminators ) { const users = await User . find ( { where : { username } , select : [ "discriminator" ] } ) ; const highestDiscriminator = Math . max ( 0 , ... users . map ( ( u ) => Number ( u . discriminator ) ) ) ; const discriminator = highestDiscriminator + 1 ; if ( discriminator >= 10000 ) { return undefined ; } return discriminator . toString ( ) . padStart ( 4 , "0" ) ; } else { for ( let tries = 0 ; tries < 5 ; tries ++ ) { const discriminator = Math . randomIntBetween ( 1 , 9999 ) . toString ( ) . padStart ( 4 , "0" ) ; const exists = await User . findOne ( { where : { discriminator , username : username } , select : [ "id" ] } ) ; if ( ! exists ) return discriminator ; } return undefined ; } } static async register ( { email , username , password , date_of_birth , req , } ) { username = trimSpecial ( username ) ; const discriminator = await User . generateDiscriminator ( username ) ; if ( ! discriminator ) { throw FieldErrors ( { username : { code : "USERNAME_TOO_MANY_USERS" , message : req . t ( "auth:register.USERNAME_TOO_MANY_USERS" ) , } , } ) ; } const language = req . language === "en" ? "en-US" : req . language || "en-US" ; const user = new User ( { created_at : new Date ( ) , username : username , discriminator , id : Snowflake . generate ( ) , bot : false , system : false , desktop : false , mobile : false , premium : true , premium_type : 2 , bio : "" , mfa_enabled : false , verified : true , disabled : false , deleted : false , email : email , rights : "0" , nsfw_allowed : true , public_flags : "0" , flags : "0" , data : { hash : password , valid_tokens_since : new Date ( ) , } , settings : { ... defaultSettings , locale : language } , fingerprints : [ ] , } ) ; await user . save ( ) ; setImmediate ( async ( ) => { if ( Config . get ( ) . guild . autoJoin . enabled ) { for ( const guild of Config . get ( ) . guild . autoJoin . guilds || [ ] ) { await Member . addToGuild ( user . id , guild ) . catch ( ( e ) => { } ) ; } } } ) ; return user ; } } export const defaultSettings = { afk_timeout : 300 , allow_accessibility_detection : true , animate_emoji : true , animate_stickers : 0 , contact_sync_enabled : false , convert_emoticons : false , custom_status : { emoji_id : undefined , emoji_name : undefined , expires_at : undefined , text : undefined , } , default_guilds_restricted : false , detect_platform_accounts : true , developer_mode : false , disable_games_tab : false , enable_tts_command : true , explicit_content_filter : 0 , friend_source_flags : { all : true } , gateway_connected : false , gif_auto_play : true , guild_folders : [ ] , guild_positions : [ ] , inline_attachment_media : true , inline_embed_media : true , locale : "en-US" , message_display_compact : false , native_phone_integration_enabled : true , render_embeds : true , render_reactions : true , restricted_guilds : [ ] , show_current_game : true , status : "online" , stream_notifications_enabled : true , theme : "dark" , timezone_offset : 0 , } ; export interface UserSettings { afk_timeout : number ; allow_accessibility_detection : boolean ; animate_emoji : boolean ; animate_stickers : number ; contact_sync_enabled : boolean ; convert_emoticons : boolean ; custom_status : { emoji_id ? : string ; emoji_name ? : string ; expires_at ? : number ; text ? : string ; } ; default_guilds_restricted : boolean ; detect_platform_accounts : boolean ; developer_mode : boolean ; disable_games_tab : boolean ; enable_tts_command : boolean ; explicit_content_filter : number ; friend_source_flags : { all : boolean ; } ; gateway_connected : boolean ; gif_auto_play : boolean ; guild_folders : { color : number ; guild_ids : string [ ] ; id : number ; name : string ; } [ ] ; guild_positions : string [ ] ; inline_attachment_media : boolean ; inline_embed_media : boolean ; locale : string ; message_display_compact : boolean ; native_phone_integration_enabled : boolean ; render_embeds : boolean ; render_reactions : boolean ; restricted_guilds : string [ ] ; show_current_game : boolean ; status : "online" | "offline" | "dnd" | "idle" ; stream_notifications_enabled : boolean ; theme : "dark" | "white" ; timezone_offset : number ; } export const CUSTOM_USER_FLAG_OFFSET = BigInt ( 1 ) << BigInt ( 32 ) ; export class UserFlags extends BitField { static FLAGS = { DISCORD_EMPLOYEE : BigInt ( 1 ) << BigInt ( 0 ) , PARTNERED_SERVER_OWNER : BigInt ( 1 ) << BigInt ( 1 ) , HYPESQUAD_EVENTS : BigInt ( 1 ) << BigInt ( 2 ) , BUGHUNTER_LEVEL_1 : BigInt ( 1 ) << BigInt ( 3 ) , MFA_SMS : BigInt ( 1 ) << BigInt ( 4 ) , PREMIUM_PROMO_DISMISSED : BigInt ( 1 ) << BigInt ( 5 ) , HOUSE_BRAVERY : BigInt ( 1 ) << BigInt ( 6 ) , HOUSE_BRILLIANCE : BigInt ( 1 ) << BigInt ( 7 ) , HOUSE_BALANCE : BigInt ( 1 ) << BigInt ( 8 ) , EARLY_SUPPORTER : BigInt ( 1 ) << BigInt ( 9 ) , TEAM_USER : BigInt ( 1 ) << BigInt ( 10 ) , TRUST_AND_SAFETY : BigInt ( 1 ) << BigInt ( 11 ) , SYSTEM : BigInt ( 1 ) << BigInt ( 12 ) , HAS_UNREAD_URGENT_MESSAGES : BigInt ( 1 ) << BigInt ( 13 ) , BUGHUNTER_LEVEL_2 : BigInt ( 1 ) << BigInt ( 14 ) , UNDERAGE_DELETED : BigInt ( 1 ) << BigInt ( 15 ) , VERIFIED_BOT : BigInt ( 1 ) << BigInt ( 16 ) , EARLY_VERIFIED_BOT_DEVELOPER : BigInt ( 1 ) << BigInt ( 17 ) , CERTIFIED_MODERATOR : BigInt ( 1 ) << BigInt ( 18 ) , BOT_HTTP_INTERACTIONS : BigInt ( 1 ) << BigInt ( 19 ) , } ; }