UNPKG

detritus-client

Version:

A Typescript NodeJS library to interact with Discord's API, both Rest and Gateway.

271 lines (270 loc) 11.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ClusterClient = void 0; const detritus_client_rest_1 = require("detritus-client-rest"); const detritus_utils_1 = require("detritus-utils"); const bucket_1 = require("./bucket"); const client_1 = require("./client"); const processchild_1 = require("./cluster/processchild"); const basecollection_1 = require("./collections/basecollection"); const constants_1 = require("./constants"); class ClusterClient extends detritus_utils_1.EventSpewer { constructor(token, options = {}) { super(); this._refresh = { applications: { last: 0, time: 4 * (60 * 60) * 1000 }, oauth2Application: { last: 0, time: 4 * (60 * 60) * 1000 }, }; this._shardsWaiting = new basecollection_1.BaseCollection(); this.commandClient = null; this.manager = null; this.interactionCommandClient = null; this.buckets = new basecollection_1.BaseCollection(); this.maxConcurrency = 1; this.ran = false; this.shardCount = 0; this.shardEnd = -1; this.shardStart = 0; this.shards = new basecollection_1.BaseCollection(); this.shardOptions = {}; options = Object.assign({}, options); const isUsingClusterManager = process.env.CLUSTER_MANAGER === 'true'; if (isUsingClusterManager) { const { CLUSTER_SHARD_COUNT, CLUSTER_SHARD_END, CLUSTER_SHARD_START, CLUSTER_TOKEN, MAX_CONCURRENCY, } = process.env; token = CLUSTER_TOKEN; options.maxConcurrency = +MAX_CONCURRENCY; options.shardCount = +CLUSTER_SHARD_COUNT; options.shards = [ +CLUSTER_SHARD_START, +CLUSTER_SHARD_END, ]; } if (!token) { throw new Error('Token is required for this library to work.'); } this.token = token; this.maxConcurrency = options.maxConcurrency || this.maxConcurrency; this.shardCount = +(options.shardCount || this.shardCount); if (Array.isArray(options.shards)) { if (options.shards.length !== 2) { throw new Error('Shards need to be in the format of [shardStart, shardEnd]'); } const [shardStart, shardEnd] = options.shards; this.shardEnd = +shardEnd; this.shardStart = +shardStart; } Object.assign(this.shardOptions, options); this.shardOptions.isBot = true; this.shardOptions.rest = Object.assign({}, this.shardOptions.rest); this.shardOptions.rest.authType = constants_1.AuthTypes.BOT; this.rest = new detritus_client_rest_1.Client(token, this.shardOptions.rest); this.shardOptions.rest.globalBucket = this.rest.globalBucket; this.shardOptions.rest.routesCollection = this.rest.routes; this.shardOptions.pass = Object.assign({}, this.shardOptions.pass); this.shardOptions.pass.cluster = this; if (this.shardOptions.pass.commandClient) { this.commandClient = this.shardOptions.pass.commandClient; } if (this.shardOptions.pass.interactionCommandClient) { this.interactionCommandClient = this.shardOptions.pass.interactionCommandClient; } if (isUsingClusterManager) { this.manager = new processchild_1.ClusterProcessChild(this); } Object.defineProperties(this, { commandClient: { configurable: true, enumerable: false, writable: false }, manager: { configurable: false, writable: false }, ran: { configurable: true, writable: false }, rest: { enumerable: false, writable: false }, shardCount: { writable: false }, shardEnd: { configurable: true, writable: false }, shardStart: { configurable: true, writable: false }, shards: { writable: false }, shardOptions: { enumerable: false, writable: false }, interactionCommandClient: { enumerable: false, writable: false }, token: { enumerable: false, writable: false }, }); } get applicationId() { return (this.shards.length) ? this.shards.first().applicationId : ''; } get clusterId() { return (this.manager) ? this.manager.clusterId : 0; } setShardCount(value) { Object.defineProperty(this, 'shardCount', { value }); } setShardEnd(value) { Object.defineProperty(this, 'shardEnd', { value }); } setShardStart(value) { Object.defineProperty(this, 'shardStart', { value }); } /** @hidden */ _eval(code) { return eval(code); } kill(error) { for (let [shardId, shard] of this.shards) { shard.kill(error); } this.shards.clear(); Object.defineProperty(this, 'ran', { value: false }); this.emit(constants_1.ClientEvents.KILLED, { error }); this.removeAllListeners(); } hookedHasEventListener(shard, name) { return super.hasEventListener(name) || super.hasEventListener.call(shard, name); } hookedEmit(shard, name, event) { if (name !== constants_1.ClientEvents.READY) { if (this.hasEventListener(name)) { const clusterEvent = Object.assign({}, event, { shard }); this.emit(name, clusterEvent); } } return super.emit.call(shard, name, event); } async fillApplications() { const refresh = this._refresh.applications; if (Date.now() - refresh.last < refresh.time) { return; } const firstShard = this.shards.first(); const enabled = (firstShard) ? firstShard.applications.enabled : false; if (enabled) { refresh.last = Date.now(); let applications; if (this.manager && this.manager.hasMultipleClusters) { applications = await this.manager.sendRestRequest('fetchApplicationsDetectable'); } else { applications = await this.rest.fetchApplicationsDetectable(); } for (let [shardId, shard] of this.shards) { shard.applications.fill(applications); } } } async fillOauth2Application() { const refresh = this._refresh.oauth2Application; if (Date.now() - refresh.last < refresh.time) { return; } const data = await this.rest.fetchOauth2Application(); refresh.last = Date.now(); for (let [shardId, shard] of this.shards) { shard._mergeOauth2Application(data); } } async run(options = {}) { if (this.ran) { return this; } Object.defineProperty(this, 'ran', { value: true }); options = Object.assign({ delay: constants_1.DEFAULT_SHARD_LAUNCH_DELAY, url: process.env.GATEWAY_URL, }, options, { wait: false, }); const delay = +options.delay; let maxConcurrency = +(options.maxConcurrency || this.maxConcurrency); let shardCount = options.shardCount || this.shardCount || 0; if (options.url === undefined || !shardCount || !maxConcurrency) { const data = await this.rest.fetchGatewayBot(); maxConcurrency = data.session_start_limit.max_concurrency; shardCount = shardCount || data.shards; options.url = options.url || data.url; } if (!shardCount) { throw new Error('Shard Count cannot be 0, pass in one via the options or the constructor.'); } this.maxConcurrency = maxConcurrency; this.setShardCount(shardCount); if (this.shardEnd === -1) { this.setShardEnd(shardCount - 1); } for (let shardId = this.shardStart; shardId <= this.shardEnd; shardId++) { const ratelimitKey = this.getRatelimitKey(shardId); if (!this.buckets.has(ratelimitKey)) { const bucket = new bucket_1.Bucket(1, delay, true); this.buckets.set(ratelimitKey, bucket); } const shardOptions = Object.assign({}, this.shardOptions); shardOptions.gateway = Object.assign({}, shardOptions.gateway, { shardCount, shardId }); if (this.commandClient || this.interactionCommandClient) { shardOptions.pass = Object.assign({}, shardOptions.pass); if (this.commandClient) { shardOptions.pass.commandClient = this.commandClient; } if (this.interactionCommandClient) { shardOptions.pass.interactionCommandClient = this.interactionCommandClient; } } const shard = new client_1.ShardClient(this.token, shardOptions); Object.defineProperties(shard, { hasEventListener: { value: this.hookedHasEventListener.bind(this, shard) }, emit: { value: this.hookedEmit.bind(this, shard) }, }); this.shards.set(shardId, shard); if (!this.manager) { shard.gateway.on('state', ({ state }) => { switch (state) { case constants_1.SocketStates.READY: { const waiting = this._shardsWaiting.get(shardId); if (waiting) { waiting.resolve(); } this._shardsWaiting.delete(shardId); } ; break; } }); shard.gateway.onIdentifyCheck = () => { const bucket = this.buckets.get(ratelimitKey); if (bucket) { const waiting = this._shardsWaiting.get(shardId); if (waiting) { shard.gateway.identify(); } else { bucket.add(() => { shard.gateway.identify(); return new Promise((resolve, reject) => { this._shardsWaiting.set(shardId, { resolve, reject }); }); }); } } return false; }; } this.emit(constants_1.ClientEvents.SHARD, { shard }); } await this.fillApplications(); await this.fillOauth2Application(); for (let [shardId, shard] of this.shards) { await shard.run(options); } this.emit(constants_1.ClientEvents.READY); return this; } getRatelimitKey(shardId) { return shardId % this.maxConcurrency; } on(event, listener) { super.on(event, listener); return this; } once(event, listener) { super.once(event, listener); return this; } subscribe(event, listener) { return super.subscribe(event, listener); } } exports.ClusterClient = ClusterClient;