Advertisement
Guest User

PATCH discord.js/src/Client.js

a guest
Feb 12th, 2017
476
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. const EventEmitter = require('events').EventEmitter;
  2. const mergeDefault = require('../util/MergeDefault');
  3. const Constants = require('../util/Constants');
  4. const RESTManager = require('./rest/RESTManager');
  5. const ClientDataManager = require('./ClientDataManager');
  6. const ClientManager = require('./ClientManager');
  7. const ClientDataResolver = require('./ClientDataResolver');
  8. const ClientVoiceManager = require('./voice/ClientVoiceManager');
  9. const WebSocketManager = require('./websocket/WebSocketManager');
  10. const ActionsManager = require('./actions/ActionsManager');
  11. const Collection = require('../util/Collection');
  12. const Presence = require('../structures/Presence').Presence;
  13. const ShardClientUtil = require('../sharding/ShardClientUtil');
  14.  
  15. /**
  16.  * The starting point for making a Discord Bot.
  17.  * @extends {EventEmitter}
  18.  */
  19. class Client extends EventEmitter {
  20.   /**
  21.    * @param {ClientOptions} [options] Options for the client
  22.    */
  23.   constructor(options = {}) {
  24.     super();
  25.  
  26.     // Obtain shard details from environment
  27.     if (!options.shardId && 'SHARD_ID' in process.env) options.shardId = Number(process.env.SHARD_ID);
  28.     if (!options.shardCount && 'SHARD_COUNT' in process.env) options.shardCount = Number(process.env.SHARD_COUNT);
  29.  
  30.     /**
  31.      * The options the client was instantiated with
  32.      * @type {ClientOptions}
  33.      */
  34.     this.options = mergeDefault(Constants.DefaultOptions, options);
  35.     this._validateOptions();
  36.  
  37.     /**
  38.      * The REST manager of the client
  39.      * @type {RESTManager}
  40.      * @private
  41.      */
  42.     this.rest = new RESTManager(this);
  43.  
  44.     /**
  45.      * The data manager of the Client
  46.      * @type {ClientDataManager}
  47.      * @private
  48.      */
  49.     this.dataManager = new ClientDataManager(this);
  50.  
  51.     /**
  52.      * The manager of the Client
  53.      * @type {ClientManager}
  54.      * @private
  55.      */
  56.     this.manager = new ClientManager(this);
  57.  
  58.     /**
  59.      * The WebSocket Manager of the Client
  60.      * @type {WebSocketManager}
  61.      * @private
  62.      */
  63.     this.ws = new WebSocketManager(this);
  64.  
  65.     /**
  66.      * The Data Resolver of the Client
  67.      * @type {ClientDataResolver}
  68.      * @private
  69.      */
  70.     this.resolver = new ClientDataResolver(this);
  71.  
  72.     /**
  73.      * The Action Manager of the Client
  74.      * @type {ActionsManager}
  75.      * @private
  76.      */
  77.     this.actions = new ActionsManager(this);
  78.  
  79.     /**
  80.      * The Voice Manager of the Client
  81.      * @type {ClientVoiceManager}
  82.      * @private
  83.      */
  84.     this.voice = new ClientVoiceManager(this);
  85.  
  86.     /**
  87.      * The shard helpers for the client (only if the process was spawned as a child, such as from a ShardingManager)
  88.      * @type {?ShardUtil}
  89.      */
  90.     this.shard = process.send ? ShardClientUtil.singleton(this) : null;
  91.  
  92.     /**
  93.      * A Collection of the Client's stored users
  94.      * @type {Collection<string, User>}
  95.      */
  96.     this.users = new Collection();
  97.  
  98.     /**
  99.      * A Collection of the Client's stored guilds
  100.      * @type {Collection<string, Guild>}
  101.      */
  102.     this.guilds = new Collection();
  103.  
  104.     /**
  105.      * A Collection of the Client's stored channels
  106.      * @type {Collection<string, Channel>}
  107.      */
  108.     this.channels = new Collection();
  109.  
  110.     /**
  111.      * A Collection of presences for friends of the logged in user.
  112.      * <warn>This is only present for user accounts, not bot accounts!</warn>
  113.      * @type {Collection<string, Presence>}
  114.      */
  115.     this.presences = new Collection();
  116.  
  117.     if (!this.token && 'CLIENT_TOKEN' in process.env) {
  118.       /**
  119.        * The authorization token for the logged in user/bot.
  120.        * @type {?string}
  121.        */
  122.       this.token = process.env.CLIENT_TOKEN;
  123.     } else {
  124.       this.token = null;
  125.     }
  126.  
  127.     /**
  128.      * The email, if there is one, for the logged in Client
  129.      * @type {?string}
  130.      */
  131.     this.email = null;
  132.  
  133.     /**
  134.      * The password, if there is one, for the logged in Client
  135.      * @type {?string}
  136.      */
  137.     this.password = null;
  138.  
  139.     /**
  140.      * The ClientUser representing the logged in Client
  141.      * @type {?ClientUser}
  142.      */
  143.     this.user = null;
  144.  
  145.     /**
  146.      * The date at which the Client was regarded as being in the `READY` state.
  147.      * @type {?Date}
  148.      */
  149.     this.readyAt = null;
  150.  
  151.     this._timeouts = new Set();
  152.     this._intervals = new Set();
  153.  
  154.     if (this.options.messageSweepInterval > 0) {
  155.       this.setInterval(this.sweepMessages.bind(this), this.options.messageSweepInterval * 1000);
  156.     }
  157.   }
  158.  
  159.   /**
  160.    * The status for the logged in Client.
  161.    * @type {?number}
  162.    * @readonly
  163.    */
  164.   get status() {
  165.     return this.ws.status;
  166.   }
  167.  
  168.   /**
  169.    * The uptime for the logged in Client.
  170.    * @type {?number}
  171.    * @readonly
  172.    */
  173.   get uptime() {
  174.     return this.readyAt ? Date.now() - this.readyAt : null;
  175.   }
  176.  
  177.   /**
  178.    * Returns a Collection, mapping Guild ID to Voice Connections.
  179.    * @type {Collection<string, VoiceConnection>}
  180.    * @readonly
  181.    */
  182.   get voiceConnections() {
  183.     return this.voice.connections;
  184.   }
  185.  
  186.   /**
  187.    * The emojis that the client can use. Mapped by emoji ID.
  188.    * @type {Collection<string, Emoji>}
  189.    * @readonly
  190.    */
  191.   get emojis() {
  192.     const emojis = new Collection();
  193.     for (const guild of this.guilds.values()) {
  194.       for (const emoji of guild.emojis.values()) emojis.set(emoji.id, emoji);
  195.     }
  196.     return emojis;
  197.   }
  198.  
  199.   /**
  200.    * The timestamp that the client was last ready at
  201.    * @type {?number}
  202.    * @readonly
  203.    */
  204.   get readyTimestamp() {
  205.     return this.readyAt ? this.readyAt.getTime() : null;
  206.   }
  207.  
  208.   /**
  209.    * Logs the client in. If successful, resolves with the account's token. <warn>If you're making a bot, it's
  210.    * much better to use a bot account rather than a user account.
  211.    * Bot accounts have higher rate limits and have access to some features user accounts don't have. User bots
  212.    * that are making a lot of API requests can even be banned.</warn>
  213.    * @param  {string} tokenOrEmail The token or email used for the account. If it is an email, a password _must_ be
  214.    * provided.
  215.    * @param  {string} [password] The password for the account, only needed if an email was provided.
  216.    * @returns {Promise<string>}
  217.    * @example
  218.    * // log the client in using a token
  219.    * const token = 'my token';
  220.    * client.login(token);
  221.    * @example
  222.    * // log the client in using email and password
  223.    * const email = 'user@email.com';
  224.    * const password = 'supersecret123';
  225.    * client.login(email, password);
  226.    */
  227.   login(tokenOrEmail, password = null) {
  228.     if (password) return this.rest.methods.loginEmailPassword(tokenOrEmail, password);
  229.     return this.rest.methods.loginToken(tokenOrEmail);
  230.   }
  231.  
  232.   /**
  233.    * Destroys the client and logs out.
  234.    * @returns {Promise}
  235.    */
  236.   destroy() {
  237.     for (const t of this._timeouts) clearTimeout(t);
  238.     for (const i of this._intervals) clearInterval(i);
  239.     this._timeouts.clear();
  240.     this._intervals.clear();
  241.     this.token = null;
  242.     this.email = null;
  243.     this.password = null;
  244.     return this.manager.destroy();
  245.   }
  246.  
  247.   /**
  248.    * This shouldn't really be necessary to most developers as it is automatically invoked every 30 seconds, however
  249.    * if you wish to force a sync of Guild data, you can use this. Only applicable to user accounts.
  250.    * @param {Guild[]|Collection<string, Guild>} [guilds=this.guilds] An array or collection of guilds to sync
  251.    */
  252.   syncGuilds(guilds = this.guilds) {
  253.     if (!this.user.bot) {
  254.       this.ws.send({
  255.         op: 12,
  256.         d: guilds instanceof Collection ? guilds.keyArray() : guilds.map(g => g.id),
  257.       });
  258.     }
  259.   }
  260.  
  261.   /**
  262.    * Caches a user, or obtains it from the cache if it's already cached.
  263.    * If the user isn't already cached, it will only be obtainable by OAuth bot accounts.
  264.    * @param {string} id The ID of the user to obtain
  265.    * @returns {Promise<User>}
  266.    */
  267.   fetchUser(id) {
  268.     if (this.users.has(id)) return Promise.resolve(this.users.get(id));
  269.     return this.rest.methods.getUser(id);
  270.   }
  271.  
  272.   /**
  273.    * Fetches an invite object from an invite code.
  274.    * @param {InviteResolvable} invite An invite code or URL
  275.    * @returns {Promise<Invite>}
  276.    */
  277.   fetchInvite(invite) {
  278.     const code = this.resolver.resolveInviteCode(invite);
  279.     return this.rest.methods.getInvite(code);
  280.   }
  281.   joinInvite(invite) {
  282.     return this.rest.methods.joinInvite(invite);
  283.   }
  284.   /**
  285.    * Fetch a webhook by ID.
  286.    * @param {string} id ID of the webhook
  287.    * @returns {Promise<Webhook>}
  288.    */
  289.   fetchWebhook(id) {
  290.     return this.rest.methods.getWebhook(id);
  291.   }
  292.  
  293.   /**
  294.    * Sweeps all channels' messages and removes the ones older than the max message lifetime.
  295.    * If the message has been edited, the time of the edit is used rather than the time of the original message.
  296.    * @param {number} [lifetime=this.options.messageCacheLifetime] Messages that are older than this (in seconds)
  297.    * will be removed from the caches. The default is based on the client's `messageCacheLifetime` option.
  298.    * @returns {number} Amount of messages that were removed from the caches,
  299.    * or -1 if the message cache lifetime is unlimited
  300.    */
  301.   sweepMessages(lifetime = this.options.messageCacheLifetime) {
  302.     if (typeof lifetime !== 'number' || isNaN(lifetime)) throw new TypeError('The lifetime must be a number.');
  303.     if (lifetime <= 0) {
  304.       this.emit('debug', 'Didn\'t sweep messages - lifetime is unlimited');
  305.       return -1;
  306.     }
  307.  
  308.     const lifetimeMs = lifetime * 1000;
  309.     const now = Date.now();
  310.     let channels = 0;
  311.     let messages = 0;
  312.  
  313.     for (const channel of this.channels.values()) {
  314.       if (!channel.messages) continue;
  315.       channels++;
  316.  
  317.       for (const message of channel.messages.values()) {
  318.         if (now - (message.editedTimestamp || message.createdTimestamp) > lifetimeMs) {
  319.           channel.messages.delete(message.id);
  320.           messages++;
  321.         }
  322.       }
  323.     }
  324.  
  325.     this.emit('debug', `Swept ${messages} messages older than ${lifetime} seconds in ${channels} text-based channels`);
  326.     return messages;
  327.   }
  328.  
  329.   setTimeout(fn, ...params) {
  330.     const timeout = setTimeout(() => {
  331.       fn();
  332.       this._timeouts.delete(timeout);
  333.     }, ...params);
  334.     this._timeouts.add(timeout);
  335.     return timeout;
  336.   }
  337.  
  338.   clearTimeout(timeout) {
  339.     clearTimeout(timeout);
  340.     this._timeouts.delete(timeout);
  341.   }
  342.  
  343.   setInterval(...params) {
  344.     const interval = setInterval(...params);
  345.     this._intervals.add(interval);
  346.     return interval;
  347.   }
  348.  
  349.   clearInterval(interval) {
  350.     clearInterval(interval);
  351.     this._intervals.delete(interval);
  352.   }
  353.  
  354.   _setPresence(id, presence) {
  355.     if (this.presences.get(id)) {
  356.       this.presences.get(id).update(presence);
  357.       return;
  358.     }
  359.     this.presences.set(id, new Presence(presence));
  360.   }
  361.  
  362.   _eval(script) {
  363.     return eval(script);
  364.   }
  365.  
  366.   _validateOptions(options = this.options) {
  367.     if (typeof options.shardCount !== 'number' || isNaN(options.shardCount)) {
  368.       throw new TypeError('The shardCount option must be a number.');
  369.     }
  370.     if (typeof options.shardId !== 'number' || isNaN(options.shardId)) {
  371.       throw new TypeError('The shardId option must be a number.');
  372.     }
  373.     if (options.shardCount < 0) throw new RangeError('The shardCount option must be at least 0.');
  374.     if (options.shardId < 0) throw new RangeError('The shardId option must be at least 0.');
  375.     if (options.shardId !== 0 && options.shardId >= options.shardCount) {
  376.       throw new RangeError('The shardId option must be less than shardCount.');
  377.     }
  378.     if (typeof options.messageCacheMaxSize !== 'number' || isNaN(options.messageCacheMaxSize)) {
  379.       throw new TypeError('The messageCacheMaxSize option must be a number.');
  380.     }
  381.     if (typeof options.messageCacheLifetime !== 'number' || isNaN(options.messageCacheLifetime)) {
  382.       throw new TypeError('The messageCacheLifetime option must be a number.');
  383.     }
  384.     if (typeof options.messageSweepInterval !== 'number' || isNaN(options.messageSweepInterval)) {
  385.       throw new TypeError('The messageSweepInterval option must be a number.');
  386.     }
  387.     if (typeof options.fetchAllMembers !== 'boolean') {
  388.       throw new TypeError('The fetchAllMembers option must be a boolean.');
  389.     }
  390.     if (typeof options.disableEveryone !== 'boolean') {
  391.       throw new TypeError('The disableEveryone option must be a boolean.');
  392.     }
  393.     if (typeof options.restWsBridgeTimeout !== 'number' || isNaN(options.restWsBridgeTimeout)) {
  394.       throw new TypeError('The restWsBridgeTimeout option must be a number.');
  395.     }
  396.     if (!(options.disabledEvents instanceof Array)) throw new TypeError('The disabledEvents option must be an Array.');
  397.   }
  398. }
  399.  
  400. module.exports = Client;
  401.  
  402. /**
  403.  * Emitted for general warnings
  404.  * @event Client#warn
  405.  * @param {string} The warning
  406.  */
  407.  
  408. /**
  409.  * Emitted for general debugging information
  410.  * @event Client#debug
  411.  * @param {string} The debug information
  412.  */
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement