UNPKG

seyfert

Version:

The most advanced framework for discord bots

499 lines (498 loc) 19.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WorkerClient = void 0; exports.generateShardInfo = generateShardInfo; const node_crypto_1 = require("node:crypto"); const __1 = require(".."); const cache_1 = require("../cache"); const common_1 = require("../common"); const events_1 = require("../events"); const websocket_1 = require("../websocket"); const base_1 = require("./base"); const memberUpdate_1 = require("../websocket/discord/events/memberUpdate"); const presenceUpdate_1 = require("../websocket/discord/events/presenceUpdate"); const collectors_1 = require("./collectors"); const transformers_1 = require("./transformers"); let workerData; let manager; try { workerData = { debug: String(process.env.SEYFERT_WORKER_DEBUG) === 'true', intents: Number(process.env.SEYFERT_WORKER_INTENTS), path: process.env.SEYFERT_WORKER_PATH, shards: JSON.parse(process.env.SEYFERT_WORKER_SHARDS), token: process.env.SEYFERT_WORKER_TOKEN, workerId: Number(process.env.SEYFERT_WORKER_WORKERID), workerProxy: String(process.env.SEYFERT_WORKER_WORKERPROXY) === 'true', totalShards: Number(process.env.SEYFERT_WORKER_TOTALSHARDS), mode: process.env.SEYFERT_WORKER_MODE, resharding: String(process.env.SEYFERT_WORKER_RESHARDING) === 'true', totalWorkers: Number(process.env.SEYFERT_WORKER_TOTALWORKERS), info: JSON.parse(process.env.SEYFERT_WORKER_INFO), compress: String(process.env.SEYFERT_WORKER_COMPRESS) === 'true', }; } catch { // } class WorkerClient extends base_1.BaseClient { memberUpdateHandler = new memberUpdate_1.MemberUpdateHandler(); presenceUpdateHandler = new presenceUpdate_1.PresenceUpdateHandler(); collectors = new collectors_1.Collectors(); events = new events_1.EventHandler(this); me; promises = new Map(); shards = new Map(); resharding = new Map(); constructor(options) { super(options); if (options?.postMessage) { this.postMessage = options.postMessage; } if (this.options.handleManagerMessages) { const oldFn = this.handleManagerMessages.bind(this); this.handleManagerMessages = async (message) => { await this.options.handleManagerMessages(message); return oldFn(message); }; } } get workerId() { return workerData.workerId; } get latency() { let acc = 0; this.shards.forEach(s => (acc += s.latency)); return acc / this.shards.size; } setServices(rest) { super.setServices(rest); if (this.options.postMessage && rest.cache?.adapter instanceof cache_1.WorkerAdapter) { rest.cache.adapter.postMessage = this.options.postMessage; } } setWorkerData(data) { workerData = data; } get workerData() { return workerData; } async start(options = {}) { const worker_threads = (0, common_1.lazyLoadPackage)('node:worker_threads'); if (worker_threads?.parentPort) { manager = worker_threads?.parentPort; } if (workerData.mode !== 'custom') (manager ?? process).on('message', (data) => this.handleManagerMessages(data)); this.logger = new __1.Logger({ name: `[Worker #${workerData.workerId}]`, }); if (workerData.debug) { this.debugger = new __1.Logger({ name: `[Worker #${workerData.workerId}]`, logLevel: common_1.LogLevels.Debug, }); } if (workerData.workerProxy) { this.setServices({ rest: new __1.ApiHandler({ token: workerData.token, workerProxy: true, debug: workerData.debug, }), }); } this.cache.intents = workerData.intents; this.rest.workerData = workerData; this.postMessage({ type: workerData.resharding ? 'WORKER_START_RESHARDING' : 'WORKER_START', workerId: workerData.workerId, }); await super.start(options); await this.loadEvents(options.eventsDir); } async loadEvents(dir) { dir ??= await this.getRC().then(x => x.locations.events); if (dir) { await this.events.load(dir); this.logger.info('EventHandler loaded'); } } postMessage(body) { if (manager) return manager.postMessage(body); return process.send(body); } async handleManagerMessages(data) { switch (data.type) { case 'CACHE_RESULT': if (this.cache.adapter instanceof cache_1.WorkerAdapter && this.cache.adapter.promises.has(data.nonce)) { const cacheData = this.cache.adapter.promises.get(data.nonce); clearTimeout(cacheData.timeout); cacheData.resolve(data.result); this.cache.adapter.promises.delete(data.nonce); } break; case 'SEND_PAYLOAD': { const shard = this.shards.get(data.shardId); if (!shard) { this.logger.fatal('Worker trying send payload by non-existent shard'); return; } await shard.send(true, { ...data, }); this.postMessage({ type: 'RESULT_PAYLOAD', nonce: data.nonce, workerId: this.workerId, }); } break; case 'ALLOW_CONNECT_RESHARDING': { const shard = this.resharding.get(data.shardId); if (!shard) { this.logger.fatal('Worker trying reshard non-existent shard'); return; } shard.options.presence = data.presence; await shard.connect(); } break; case 'ALLOW_CONNECT': { const shard = this.shards.get(data.shardId); if (!shard) { this.logger.fatal('Worker trying connect non-existent shard'); return; } shard.options.presence = data.presence; await shard.connect(); } break; case 'SPAWN_SHARDS_RESHARDING': { let shardsConnected = 0; const self = this; for (const id of workerData.shards) { const existsShard = this.resharding.has(id); if (existsShard) { this.logger.warn(`Trying to re-spawn existing shard #${id}`); continue; } const shard = new websocket_1.Shard(id, { token: workerData.token, intents: workerData.intents, info: data.info, compress: data.compress, debugger: this.debugger, properties: { ...websocket_1.properties, ...this.options.gateway?.properties, }, handlePayload(_, payload) { if (payload.t !== 'GUILDS_READY') return; if (++shardsConnected === workerData.shards.length) { self.postMessage({ type: 'WORKER_READY_RESHARDING', workerId: workerData.workerId, }); } }, }); this.resharding.set(id, shard); this.postMessage({ type: 'CONNECT_QUEUE_RESHARDING', shardId: id, workerId: workerData.workerId, }); } } break; case 'SPAWN_SHARDS': { for (const id of workerData.shards) { const existsShard = this.shards.has(id); if (existsShard) { this.logger.warn(`Trying to spawn existing shard #${id}`); continue; } const shard = this.createShard(id, data); this.shards.set(id, shard); this.postMessage({ type: 'CONNECT_QUEUE', shardId: id, workerId: workerData.workerId, }); } } break; case 'SHARD_INFO': { const shard = this.shards.get(data.shardId); if (!shard) { this.logger.fatal('Worker trying get non-existent shard'); return; } this.postMessage({ ...generateShardInfo(shard), nonce: data.nonce, type: 'SHARD_INFO', workerId: this.workerId, }); } break; case 'WORKER_INFO': { this.postMessage({ shards: [...this.shards.values()].map(generateShardInfo), workerId: workerData.workerId, type: 'WORKER_INFO', nonce: data.nonce, }); } break; case 'BOT_READY': await this.events.runEvent('BOT_READY', this, this.me, -1); break; case 'API_RESPONSE': { const promise = this.rest.workerPromises.get(data.nonce); if (!promise) return; this.rest.workerPromises.delete(data.nonce); if (data.error) return promise.reject(data.error); promise.resolve(data.response); } break; case 'EXECUTE_EVAL': case 'EXECUTE_EVAL_TO_WORKER': { let result; try { result = await eval(` (${data.func})(this, ${data.vars}) `); } catch (e) { result = e; } this.postMessage({ type: 'EVAL_RESPONSE', response: result, workerId: workerData.workerId, nonce: data.nonce, }); } break; case 'EVAL_RESPONSE': { const evalResponse = this.promises.get(data.nonce); if (!evalResponse) return; this.promises.delete(data.nonce); clearTimeout(evalResponse.timeout); evalResponse.resolve(data.response); } break; case 'WORKER_ALREADY_EXISTS_RESHARDING': { this.postMessage({ type: 'WORKER_START_RESHARDING', workerId: workerData.workerId, }); } break; case 'DISCONNECT_ALL_SHARDS_RESHARDING': { for (const i of this.shards.values()) { await i.disconnect(); } this.postMessage({ type: 'DISCONNECTED_ALL_SHARDS_RESHARDING', workerId: workerData.workerId, }); } break; case 'CONNECT_ALL_SHARDS_RESHARDING': { this.shards.clear(); const handlePayload = this.options?.handlePayload?.bind(this); for (const [id, shard] of this.resharding) { this.shards.set(id, shard); shard.options.handlePayload = async (shardId, packet) => { await handlePayload?.(shardId, packet); return this.onPacket(packet, shardId); }; } this.resharding.clear(); } break; } } generateNonce() { const uuid = (0, node_crypto_1.randomUUID)(); if (this.promises.has(uuid)) return this.generateNonce(); return uuid; } generateSendPromise(nonce, message = 'Timeout') { return new Promise((res, rej) => { const timeout = setTimeout(() => { this.promises.delete(nonce); rej(new Error(message)); }, 60e3); this.promises.set(nonce, { resolve: res, timeout }); }); } tellWorker(workerId, func, vars) { const nonce = this.generateNonce(); this.postMessage({ type: 'EVAL_TO_WORKER', func: func.toString(), toWorkerId: workerId, workerId: workerData.workerId, nonce, vars: JSON.stringify(vars), }); return this.generateSendPromise(nonce); } tellWorkers(func, vars) { const promises = []; for (let i = 0; i < workerData.totalWorkers; i++) { promises.push(this.tellWorker(i, func, vars)); } return Promise.all(promises); } createShard(id, data) { const onPacket = this.onPacket.bind(this); const handlePayload = this.options?.handlePayload?.bind(this); const self = this; const shard = new websocket_1.Shard(id, { token: workerData.token, intents: workerData.intents, info: data.info, compress: data.compress, debugger: this.debugger, properties: { ...websocket_1.properties, ...this.options.gateway?.properties, }, async handlePayload(shardId, payload) { await handlePayload?.(shardId, payload); await onPacket(payload, shardId); if (self.options.sendPayloadToParent) self.postMessage({ workerId: workerData.workerId, shardId, type: 'RECEIVE_PAYLOAD', payload, }); }, }); return shard; } async resumeShard(shardId, shardData) { const exists = (await this.tellWorkers((r, vars) => r.shards.has(vars.shardId), { shardId, })).some(x => x); if (exists) throw new Error('Cannot override existing shard'); const shard = this.createShard(shardId, { info: this.workerData.info, compress: this.workerData.compress, }); shard.data = shardData; this.shards.set(shardId, shard); return this.postMessage({ workerId: this.workerId, shardId, type: 'CONNECT_QUEUE', }); } async onPacket(packet, shardId) { Promise.allSettled([ this.events.runEvent('RAW', this, packet, shardId, false), this.collectors.run('RAW', packet, this), ]); //ignore promise switch (packet.t) { case 'GUILD_MEMBER_UPDATE': { if (!this.memberUpdateHandler.check(packet.d)) { return; } await this.events.execute(packet, this, shardId); } break; case 'PRESENCE_UPDATE': { if (!this.presenceUpdateHandler.check(packet.d)) { return; } await this.events.execute(packet, this, shardId); } break; default: { switch (packet.t) { case 'INTERACTION_CREATE': { await this.events.execute(packet, this, shardId); await this.handleCommand.interaction(packet.d, shardId); } break; case 'MESSAGE_CREATE': { await this.events.execute(packet, this, shardId); await this.handleCommand.message(packet.d, shardId); } break; case 'READY': { this.botId = packet.d.user.id; this.applicationId = packet.d.application.id; this.me = transformers_1.Transformers.ClientUser(this, packet.d.user, packet.d.application); if ([...this.shards.values()].every(shard => shard.data.session_id)) { this.postMessage({ type: 'WORKER_SHARDS_CONNECTED', workerId: this.workerId, }); await this.events.runEvent('WORKER_SHARDS_CONNECTED', this, this.me, -1); } await this.events.execute(packet, this, shardId); this.debugger?.debug(`#${shardId}[${packet.d.user.username}](${this.botId}) is online...`); break; } case 'GUILDS_READY': { if ([...this.shards.values()].every(shard => shard.isReady)) { this.postMessage({ type: 'WORKER_READY', workerId: this.workerId, }); await this.events.runEvent('WORKER_READY', this, this.me, -1); } await this.events.execute(packet, this, shardId); } break; default: await this.events.execute(packet, this, shardId); break; } break; } } } } exports.WorkerClient = WorkerClient; function generateShardInfo(shard) { return { open: shard.isOpen, shardId: shard.id, latency: shard.latency, resumable: shard.resumable, workerId: workerData.workerId, }; }