buddy

node MVC discord bot
Log | Files | Refs | README

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;