UNPKG

seyfert

Version:

The most advanced framework for discord bots

235 lines (234 loc) 9.06 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ShardManager = void 0; const common_1 = require("../../common"); const types_1 = require("../../types"); const constants_1 = require("../constants"); const structures_1 = require("../structures"); const timeout_1 = require("../structures/timeout"); const shard_1 = require("./shard"); let parentPort; let workerData; class ShardManager extends Map { connectQueue; options; debugger; constructor(options) { super(); this.options = (0, common_1.MergeOptions)(constants_1.ShardManagerDefaults, { totalShards: options.info.shards, }, options); this.connectQueue = new timeout_1.ConnectQueue(5.5e3, this.concurrency); if (this.options.debug) { this.debugger = new common_1.Logger({ name: '[ShardManager]', logLevel: common_1.LogLevels.Debug, }); } const worker_threads = (0, common_1.lazyLoadPackage)('node:worker_threads'); if (worker_threads) { workerData = worker_threads.workerData; if (worker_threads.parentPort) parentPort = worker_threads.parentPort; } } get totalShards() { return this.options.totalShards ?? this.options.info.shards; } get shardStart() { return this.options.shardStart ?? 0; } get shardEnd() { return this.options.shardEnd ?? this.totalShards; } get remaining() { return this.options.info.session_start_limit.remaining; } get concurrency() { return this.options.info.session_start_limit.max_concurrency; } get latency() { let acc = 0; this.forEach(s => (acc += s.latency)); return acc / this.size; } calculateShardId(guildId) { return (0, common_1.calculateShardId)(guildId, this.totalShards); } create(shardId) { this.debugger?.info(`Creating shard ${shardId}`); let shard = this.get(shardId); shard ??= new shard_1.Shard(shardId, { token: this.options.token, intents: this.options.intents, info: { ...this.options.info, shards: this.totalShards }, handlePayload: this.options.handlePayload, properties: this.options.properties, debugger: this.debugger, compress: this.options.compress ?? false, presence: this.options.presence?.(shardId, -1), }); this.set(shardId, shard); return shard; } async spawnShards() { const buckets = this.spawnBuckets(); this.debugger?.info('Spawning shards'); for (const bucket of buckets) { for (const shard of bucket) { if (!shard) { break; } this.debugger?.info(`${shard.id} add to connect queue`); this.connectQueue.push(shard.connect.bind(shard)); } } await this.startResharder(); } async startResharder() { if (this.options.resharding.interval <= 0) return; if (this.shardStart !== 0 || this.shardEnd !== this.totalShards) return this.debugger?.debug('Cannot start resharder'); this.debugger?.debug('Resharder enabled'); setInterval(async () => { this.debugger?.debug('Checking if reshard is needed'); const info = await this.options.resharding.getInfo(); if (info.shards <= this.totalShards) return this.debugger?.debug('Resharding not needed'); //https://github.com/discordeno/discordeno/blob/6a5f446c0651b9fad9f1550ff1857fe7a026426b/packages/gateway/src/manager.ts#L106C8-L106C94 const percentage = (info.shards / ((this.totalShards * 2500) / 1000)) * 100; if (percentage < this.options.resharding.percentage) return this.debugger?.debug(`Percentage is not enough to reshard ${percentage}/${this.options.resharding.percentage}`); this.debugger?.info('Starting resharding process'); this.connectQueue.concurrency = info.session_start_limit.max_concurrency; this.options.info.session_start_limit.max_concurrency = info.session_start_limit.max_concurrency; //waiting for all shards to connect let shardsConnected = 0; let handlePayload = (sharder, _, packet) => { if (packet.t !== 'GUILDS_READY') return; if (++shardsConnected !== info.shards) return; cleanProcess(sharder); // dont listen more events when all shards are ready }; const cleanProcess = (sharder) => { handlePayload = () => { // }; this.disconnectAll(); this.clear(); this.options.totalShards = this.options.shardEnd = this.options.info.shards = info.shards; for (const [id, shard] of sharder) { shard.options.handlePayload = (shardId, packet) => { return this.options.handlePayload(shardId, packet); }; this.set(id, shard); } sharder.clear(); }; const options = (0, common_1.MergeOptions)(this.options, { totalShards: info.shards, shardEnd: info.shards, }); const resharder = new ShardManager({ ...options, resharding: { // getInfo mock, we don't need it getInfo: () => ({}), interval: 0, percentage: 0, }, handlePayload: (shardId, packet) => { return handlePayload(resharder, shardId, packet); }, }); // share ratelimit resharder.connectQueue = this.connectQueue; await resharder.spawnShards(); }, this.options.resharding.interval); } /* * spawns buckets in order * https://discord.com/developers/docs/topics/gateway#sharding-max-concurrency */ spawnBuckets() { this.debugger?.info('#0 Preparing buckets'); const chunks = structures_1.DynamicBucket.chunk(new Array(this.shardEnd - this.shardStart), this.concurrency); chunks.forEach((arr, index) => { for (let i = 0; i < arr.length; i++) { const id = i + (index > 0 ? index * this.concurrency : 0) + this.shardStart; chunks[index][i] = this.create(id); } }); this.debugger?.info(`${chunks.length} buckets created`); return chunks; } forceIdentify(shardId) { this.debugger?.info(`Shard #${shardId} force identify`); return this.create(shardId).identify(); } disconnect(shardId) { this.debugger?.info(`Shard #${shardId} force disconnect`); return this.get(shardId)?.disconnect(); } disconnectAll() { this.debugger?.info('Disconnect all shards'); this.forEach(shard => shard.disconnect()); } setShardPresence(shardId, payload) { this.debugger?.info(`Shard #${shardId} update presence`); return this.send(shardId, { op: types_1.GatewayOpcodes.PresenceUpdate, d: payload, }); } setPresence(payload) { this.forEach(shard => { this.setShardPresence(shard.id, payload); }, this); } joinVoice(guild_id, channel_id, options) { const shardId = this.calculateShardId(guild_id); this.debugger?.info(`Shard #${shardId} join voice ${channel_id} in ${guild_id}`); return this.send(shardId, { op: types_1.GatewayOpcodes.VoiceStateUpdate, d: { guild_id, channel_id, ...options, }, }); } leaveVoice(guild_id) { const shardId = this.calculateShardId(guild_id); return this.send(shardId, { op: types_1.GatewayOpcodes.VoiceStateUpdate, d: { guild_id, channel_id: null, self_mute: false, self_deaf: false, }, }); } send(shardId, payload) { if (workerData?.__USING_WATCHER__) { return parentPort?.postMessage({ type: 'SEND_TO_SHARD', shardId, payload, }); } this.get(shardId)?.send(false, payload); } resume(shardId, shardData) { if (this.has(shardId)) throw new Error('Cannot override existing shard'); const shard = this.create(shardId); shard.data = shardData; return this.connectQueue.push(shard.connect.bind(shard)); } } exports.ShardManager = ShardManager;