User.js (8359B)
1 'use strict'; 2 3 const Base = require('./Base'); 4 const { Presence } = require('./Presence'); 5 const TextBasedChannel = require('./interfaces/TextBasedChannel'); 6 const { Error } = require('../errors'); 7 const Snowflake = require('../util/Snowflake'); 8 const UserFlags = require('../util/UserFlags'); 9 10 /** 11 * Represents a user on Discord. 12 * @implements {TextBasedChannel} 13 * @extends {Base} 14 */ 15 class User extends Base { 16 /** 17 * @param {Client} client The instantiating client 18 * @param {Object} data The data for the user 19 */ 20 constructor(client, data) { 21 super(client); 22 23 /** 24 * The ID of the user 25 * @type {Snowflake} 26 */ 27 this.id = data.id; 28 29 /** 30 * Whether or not the user is a bot 31 * @type {boolean} 32 * @name User#bot 33 */ 34 this.bot = Boolean(data.bot); 35 36 this._patch(data); 37 } 38 39 _patch(data) { 40 /** 41 * The username of the user 42 * @type {?string} 43 * @name User#username 44 */ 45 if (data.username) this.username = data.username; 46 47 /** 48 * A discriminator based on username for the user 49 * @type {?string} 50 * @name User#discriminator 51 */ 52 if (data.discriminator) this.discriminator = data.discriminator; 53 54 /** 55 * The ID of the user's avatar 56 * @type {?string} 57 * @name User#avatar 58 */ 59 if (typeof data.avatar !== 'undefined') this.avatar = data.avatar; 60 61 if (typeof data.bot !== 'undefined') this.bot = Boolean(data.bot); 62 63 /** 64 * Whether the user is an Official Discord System user (part of the urgent message system) 65 * @type {?boolean} 66 * @name User#system 67 */ 68 if (typeof data.system !== 'undefined') this.system = Boolean(data.system); 69 70 /** 71 * The locale of the user's client (ISO 639-1) 72 * @type {?string} 73 * @name User#locale 74 */ 75 if (data.locale) this.locale = data.locale; 76 77 /** 78 * The flags for this user 79 * @type {?UserFlags} 80 */ 81 if (typeof data.public_flags !== 'undefined') this.flags = new UserFlags(data.public_flags); 82 83 /** 84 * The ID of the last message sent by the user, if one was sent 85 * @type {?Snowflake} 86 */ 87 this.lastMessageID = null; 88 89 /** 90 * The ID of the channel for the last message sent by the user, if one was sent 91 * @type {?Snowflake} 92 */ 93 this.lastMessageChannelID = null; 94 } 95 96 /** 97 * Whether this User is a partial 98 * @type {boolean} 99 * @readonly 100 */ 101 get partial() { 102 return typeof this.username !== 'string'; 103 } 104 105 /** 106 * The timestamp the user was created at 107 * @type {number} 108 * @readonly 109 */ 110 get createdTimestamp() { 111 return Snowflake.deconstruct(this.id).timestamp; 112 } 113 114 /** 115 * The time the user was created at 116 * @type {Date} 117 * @readonly 118 */ 119 get createdAt() { 120 return new Date(this.createdTimestamp); 121 } 122 123 /** 124 * The Message object of the last message sent by the user, if one was sent 125 * @type {?Message} 126 * @readonly 127 */ 128 get lastMessage() { 129 const channel = this.client.channels.cache.get(this.lastMessageChannelID); 130 return (channel && channel.messages.cache.get(this.lastMessageID)) || null; 131 } 132 133 /** 134 * The presence of this user 135 * @type {Presence} 136 * @readonly 137 */ 138 get presence() { 139 for (const guild of this.client.guilds.cache.values()) { 140 if (guild.presences.cache.has(this.id)) return guild.presences.cache.get(this.id); 141 } 142 return new Presence(this.client, { user: { id: this.id } }); 143 } 144 145 /** 146 * A link to the user's avatar. 147 * @param {ImageURLOptions} [options={}] Options for the Image URL 148 * @returns {?string} 149 */ 150 avatarURL({ format, size, dynamic } = {}) { 151 if (!this.avatar) return null; 152 return this.client.rest.cdn.Avatar(this.id, this.avatar, format, size, dynamic); 153 } 154 155 /** 156 * A link to the user's default avatar 157 * @type {string} 158 * @readonly 159 */ 160 get defaultAvatarURL() { 161 return this.client.rest.cdn.DefaultAvatar(this.discriminator % 5); 162 } 163 164 /** 165 * A link to the user's avatar if they have one. 166 * Otherwise a link to their default avatar will be returned. 167 * @param {ImageURLOptions} [options={}] Options for the Image URL 168 * @returns {string} 169 */ 170 displayAvatarURL(options) { 171 return this.avatarURL(options) || this.defaultAvatarURL; 172 } 173 174 /** 175 * The Discord "tag" (e.g. `hydrabolt#0001`) for this user 176 * @type {?string} 177 * @readonly 178 */ 179 get tag() { 180 return typeof this.username === 'string' ? `${this.username}#${this.discriminator}` : null; 181 } 182 183 /** 184 * Checks whether the user is typing in a channel. 185 * @param {ChannelResolvable} channel The channel to check in 186 * @returns {boolean} 187 */ 188 typingIn(channel) { 189 channel = this.client.channels.resolve(channel); 190 return channel._typing.has(this.id); 191 } 192 193 /** 194 * Gets the time that the user started typing. 195 * @param {ChannelResolvable} channel The channel to get the time in 196 * @returns {?Date} 197 */ 198 typingSinceIn(channel) { 199 channel = this.client.channels.resolve(channel); 200 return channel._typing.has(this.id) ? new Date(channel._typing.get(this.id).since) : null; 201 } 202 203 /** 204 * Gets the amount of time the user has been typing in a channel for (in milliseconds), or -1 if they're not typing. 205 * @param {ChannelResolvable} channel The channel to get the time in 206 * @returns {number} 207 */ 208 typingDurationIn(channel) { 209 channel = this.client.channels.resolve(channel); 210 return channel._typing.has(this.id) ? channel._typing.get(this.id).elapsedTime : -1; 211 } 212 213 /** 214 * The DM between the client's user and this user 215 * @type {?DMChannel} 216 * @readonly 217 */ 218 get dmChannel() { 219 return this.client.channels.cache.find(c => c.type === 'dm' && c.recipient.id === this.id) || null; 220 } 221 222 /** 223 * Creates a DM channel between the client and the user. 224 * @returns {Promise<DMChannel>} 225 */ 226 async createDM() { 227 const { dmChannel } = this; 228 if (dmChannel && !dmChannel.partial) return dmChannel; 229 const data = await this.client.api.users(this.client.user.id).channels.post({ 230 data: { 231 recipient_id: this.id, 232 }, 233 }); 234 return this.client.actions.ChannelCreate.handle(data).channel; 235 } 236 237 /** 238 * Deletes a DM channel (if one exists) between the client and the user. Resolves with the channel if successful. 239 * @returns {Promise<DMChannel>} 240 */ 241 async deleteDM() { 242 const { dmChannel } = this; 243 if (!dmChannel) throw new Error('USER_NO_DMCHANNEL'); 244 const data = await this.client.api.channels(dmChannel.id).delete(); 245 return this.client.actions.ChannelDelete.handle(data).channel; 246 } 247 248 /** 249 * Checks if the user is equal to another. It compares ID, username, discriminator, avatar, and bot flags. 250 * It is recommended to compare equality by using `user.id === user2.id` unless you want to compare all properties. 251 * @param {User} user User to compare with 252 * @returns {boolean} 253 */ 254 equals(user) { 255 let equal = 256 user && 257 this.id === user.id && 258 this.username === user.username && 259 this.discriminator === user.discriminator && 260 this.avatar === user.avatar; 261 262 return equal; 263 } 264 265 /** 266 * Fetches this user's flags. 267 * @returns {Promise<UserFlags>} 268 */ 269 async fetchFlags() { 270 if (this.flags) return this.flags; 271 const data = await this.client.api.users(this.id).get(); 272 this._patch(data); 273 return this.flags; 274 } 275 276 /** 277 * Fetches this user. 278 * @returns {Promise<User>} 279 */ 280 fetch() { 281 return this.client.users.fetch(this.id, true); 282 } 283 284 /** 285 * When concatenated with a string, this automatically returns the user's mention instead of the User object. 286 * @returns {string} 287 * @example 288 * // Logs: Hello from <@123456789012345678>! 289 * console.log(`Hello from ${user}!`); 290 */ 291 toString() { 292 return `<@${this.id}>`; 293 } 294 295 toJSON(...props) { 296 const json = super.toJSON( 297 { 298 createdTimestamp: true, 299 defaultAvatarURL: true, 300 tag: true, 301 lastMessage: false, 302 lastMessageID: false, 303 }, 304 ...props, 305 ); 306 json.avatarURL = this.avatarURL(); 307 json.displayAvatarURL = this.displayAvatarURL(); 308 return json; 309 } 310 311 // These are here only for documentation purposes - they are implemented by TextBasedChannel 312 /* eslint-disable no-empty-function */ 313 send() {} 314 } 315 316 TextBasedChannel.applyToClass(User); 317 318 module.exports = User;