Presence.js (8671B)
1 'use strict'; 2 3 const Emoji = require('./Emoji'); 4 const ActivityFlags = require('../util/ActivityFlags'); 5 const { ActivityTypes } = require('../util/Constants'); 6 const Util = require('../util/Util'); 7 8 /** 9 * Activity sent in a message. 10 * @typedef {Object} MessageActivity 11 * @property {string} [partyID] Id of the party represented in activity 12 * @property {number} [type] Type of activity sent 13 */ 14 15 /** 16 * The status of this presence: 17 * * **`online`** - user is online 18 * * **`idle`** - user is AFK 19 * * **`offline`** - user is offline or invisible 20 * * **`dnd`** - user is in Do Not Disturb 21 * @typedef {string} PresenceStatus 22 */ 23 24 /** 25 * The status of this presence: 26 * * **`online`** - user is online 27 * * **`idle`** - user is AFK 28 * * **`dnd`** - user is in Do Not Disturb 29 * @typedef {string} ClientPresenceStatus 30 */ 31 32 /** 33 * Represents a user's presence. 34 */ 35 class Presence { 36 /** 37 * @param {Client} client The instantiating client 38 * @param {Object} [data={}] The data for the presence 39 */ 40 constructor(client, data = {}) { 41 /** 42 * The client that instantiated this 43 * @name Presence#client 44 * @type {Client} 45 * @readonly 46 */ 47 Object.defineProperty(this, 'client', { value: client }); 48 /** 49 * The user ID of this presence 50 * @type {Snowflake} 51 */ 52 this.userID = data.user.id; 53 54 /** 55 * The guild of this presence 56 * @type {?Guild} 57 */ 58 this.guild = data.guild || null; 59 60 this.patch(data); 61 } 62 63 /** 64 * The user of this presence 65 * @type {?User} 66 * @readonly 67 */ 68 get user() { 69 return this.client.users.cache.get(this.userID) || null; 70 } 71 72 /** 73 * The member of this presence 74 * @type {?GuildMember} 75 * @readonly 76 */ 77 get member() { 78 return this.guild.members.cache.get(this.userID) || null; 79 } 80 81 patch(data) { 82 /** 83 * The status of this presence 84 * @type {PresenceStatus} 85 */ 86 this.status = data.status || this.status || 'offline'; 87 88 if (data.activities) { 89 /** 90 * The activities of this presence 91 * @type {Activity[]} 92 */ 93 this.activities = data.activities.map(activity => new Activity(this, activity)); 94 } else if (data.activity || data.game) { 95 this.activities = [new Activity(this, data.game || data.activity)]; 96 } else { 97 this.activities = []; 98 } 99 100 /** 101 * The devices this presence is on 102 * @type {?Object} 103 * @property {?ClientPresenceStatus} web The current presence in the web application 104 * @property {?ClientPresenceStatus} mobile The current presence in the mobile application 105 * @property {?ClientPresenceStatus} desktop The current presence in the desktop application 106 */ 107 this.clientStatus = data.client_status || null; 108 109 return this; 110 } 111 112 _clone() { 113 const clone = Object.assign(Object.create(this), this); 114 if (this.activities) clone.activities = this.activities.map(activity => activity._clone()); 115 return clone; 116 } 117 118 /** 119 * Whether this presence is equal to another. 120 * @param {Presence} presence The presence to compare with 121 * @returns {boolean} 122 */ 123 equals(presence) { 124 return ( 125 this === presence || 126 (presence && 127 this.status === presence.status && 128 this.activities.length === presence.activities.length && 129 this.activities.every((activity, index) => activity.equals(presence.activities[index])) && 130 this.clientStatus.web === presence.clientStatus.web && 131 this.clientStatus.mobile === presence.clientStatus.mobile && 132 this.clientStatus.desktop === presence.clientStatus.desktop) 133 ); 134 } 135 136 toJSON() { 137 return Util.flatten(this); 138 } 139 } 140 141 /** 142 * Represents an activity that is part of a user's presence. 143 */ 144 class Activity { 145 constructor(presence, data) { 146 Object.defineProperty(this, 'presence', { value: presence }); 147 148 /** 149 * The name of the activity being played 150 * @type {string} 151 */ 152 this.name = data.name; 153 154 /** 155 * The type of the activity status 156 * @type {ActivityType} 157 */ 158 this.type = ActivityTypes[data.type]; 159 160 /** 161 * If the activity is being streamed, a link to the stream 162 * @type {?string} 163 */ 164 this.url = data.url || null; 165 166 /** 167 * Details about the activity 168 * @type {?string} 169 */ 170 this.details = data.details || null; 171 172 /** 173 * State of the activity 174 * @type {?string} 175 */ 176 this.state = data.state || null; 177 178 /** 179 * Application ID associated with this activity 180 * @type {?Snowflake} 181 */ 182 this.applicationID = data.application_id || null; 183 184 /** 185 * Timestamps for the activity 186 * @type {?Object} 187 * @prop {?Date} start When the activity started 188 * @prop {?Date} end When the activity will end 189 */ 190 this.timestamps = data.timestamps 191 ? { 192 start: data.timestamps.start ? new Date(Number(data.timestamps.start)) : null, 193 end: data.timestamps.end ? new Date(Number(data.timestamps.end)) : null, 194 } 195 : null; 196 197 /** 198 * Party of the activity 199 * @type {?Object} 200 * @prop {?string} id ID of the party 201 * @prop {number[]} size Size of the party as `[current, max]` 202 */ 203 this.party = data.party || null; 204 205 /** 206 * Assets for rich presence 207 * @type {?RichPresenceAssets} 208 */ 209 this.assets = data.assets ? new RichPresenceAssets(this, data.assets) : null; 210 211 this.syncID = data.sync_id; 212 213 /** 214 * Flags that describe the activity 215 * @type {Readonly<ActivityFlags>} 216 */ 217 this.flags = new ActivityFlags(data.flags).freeze(); 218 219 /** 220 * Emoji for a custom activity 221 * @type {?Emoji} 222 */ 223 this.emoji = data.emoji ? new Emoji(presence.client, data.emoji) : null; 224 225 /** 226 * Creation date of the activity 227 * @type {number} 228 */ 229 this.createdTimestamp = new Date(data.created_at).getTime(); 230 } 231 232 /** 233 * Whether this activity is equal to another activity. 234 * @param {Activity} activity The activity to compare with 235 * @returns {boolean} 236 */ 237 equals(activity) { 238 return ( 239 this === activity || 240 (activity && this.name === activity.name && this.type === activity.type && this.url === activity.url) 241 ); 242 } 243 244 /** 245 * The time the activity was created at 246 * @type {Date} 247 * @readonly 248 */ 249 get createdAt() { 250 return new Date(this.createdTimestamp); 251 } 252 253 /** 254 * When concatenated with a string, this automatically returns the activities' name instead of the Activity object. 255 * @returns {string} 256 */ 257 toString() { 258 return this.name; 259 } 260 261 _clone() { 262 return Object.assign(Object.create(this), this); 263 } 264 } 265 266 /** 267 * Assets for a rich presence 268 */ 269 class RichPresenceAssets { 270 constructor(activity, assets) { 271 Object.defineProperty(this, 'activity', { value: activity }); 272 273 /** 274 * Hover text for the large image 275 * @type {?string} 276 */ 277 this.largeText = assets.large_text || null; 278 279 /** 280 * Hover text for the small image 281 * @type {?string} 282 */ 283 this.smallText = assets.small_text || null; 284 285 /** 286 * ID of the large image asset 287 * @type {?Snowflake} 288 */ 289 this.largeImage = assets.large_image || null; 290 291 /** 292 * ID of the small image asset 293 * @type {?Snowflake} 294 */ 295 this.smallImage = assets.small_image || null; 296 } 297 298 /** 299 * Gets the URL of the small image asset 300 * @param {Object} [options] Options for the image url 301 * @param {string} [options.format] Format of the image 302 * @param {number} [options.size] Size of the image 303 * @returns {?string} The small image URL 304 */ 305 smallImageURL({ format, size } = {}) { 306 if (!this.smallImage) return null; 307 return this.activity.presence.client.rest.cdn.AppAsset(this.activity.applicationID, this.smallImage, { 308 format, 309 size, 310 }); 311 } 312 313 /** 314 * Gets the URL of the large image asset 315 * @param {Object} [options] Options for the image url 316 * @param {string} [options.format] Format of the image 317 * @param {number} [options.size] Size of the image 318 * @returns {?string} The large image URL 319 */ 320 largeImageURL({ format, size } = {}) { 321 if (!this.largeImage) return null; 322 if (/^spotify:/.test(this.largeImage)) { 323 return `https://i.scdn.co/image/${this.largeImage.slice(8)}`; 324 } else if (/^twitch:/.test(this.largeImage)) { 325 return `https://static-cdn.jtvnw.net/previews-ttv/live_user_${this.largeImage.slice(7)}.png`; 326 } 327 return this.activity.presence.client.rest.cdn.AppAsset(this.activity.applicationID, this.largeImage, { 328 format, 329 size, 330 }); 331 } 332 } 333 334 exports.Presence = Presence; 335 exports.Activity = Activity; 336 exports.RichPresenceAssets = RichPresenceAssets;