import { ClusterWorkerIPC } from './ClusterWorkerIPC' ; import { ShardManager } from '../sharding/ShardManager' ; import { Client , ClientOptions , Guild , AnyChannel , User , Shard } from 'eris' ; import { IPCEvents } from '../util/constants' ; import { sleep , transformError } from '../util/util' ; import { IPCResult } from '../' ; import ErisShardManager from '../sharding/ErisShardManager' ; export abstract class BaseClusterWorker { /** The worker's Eris client */ public readonly client : Client ; /** The worker's cluster id */ public readonly id : number ; /** The worker's IPC client */ public readonly ipc : ClusterWorkerIPC ; public constructor ( public manager ) { const clientConfig = Object . assign ( { } , manager . clientOptions , { firstShardID : Number ( process . env . FIRST_SHARD ) , lastShardID : Number ( process . env . LAST_SHARD ) , maxShards : Number ( process . env . TOTAL_SHARDS ) } ) ; this . client = new Client ( manager . token , clientConfig ) ; this . client . shards = new ErisShardManager ( this . client , this . manager . maxConcurrency ) ; this . id = Number ( process . env . CLUSTER_ID ) ; this . ipc = new ClusterWorkerIPC ( this , this . manager . ipcSocket ) ; } public async init ( ) { await this . ipc . init ( ) ; this . client . once ( 'ready' , async ( ) => { this . ipc . send ( { op : IPCEvents . READY , d : { id : this . id } } ) ; if ( ! this . manager . cacheAllMembers || this . client . guilds . size === 0 ) return ; await Promise . all ( this . client . shards . map ( shard => this . cacheShardMembers ( shard ) ) ) ; this . ipc . send ( { op : IPCEvents . ALL_MEMBERS_CACHED , d : this . id } ) ; if ( this . allMembersCached ) this . allMembersCached ( false ) ; } ) ; this . client . on ( 'connect' , shardId => this . ipc . send ( { op : IPCEvents . SHARD_CONNECTED , d : { id : this . id , shardId } } ) ) ; this . client . on ( 'shardReady' , shardId => this . ipc . send ( { op : IPCEvents . SHARD_READY , d : { id : this . id , shardId } } ) ) ; this . client . on ( 'shardResume' , shardId => this . ipc . send ( { op : IPCEvents . SHARD_RESUMED , d : { id : this . id , shardId } } ) ) ; this . client . on ( 'shardDisconnect' , ( error , id ) => this . ipc . send ( { op : IPCEvents . SHARD_DISCONNECTED , d : { id : this . id , shardId : id , error : transformError ( error ) } } ) ) ; this . client . on ( 'error' , ( error , shardId ) => this . ipc . send ( { op : IPCEvents . ERROR , d : { id : this . id , shardId , error : transformError ( error ) } } ) ) ; if ( this . manager . cacheAllMembers ) this . client . on ( 'guildCreate' , guild => guild . fetchAllMembers ( 2 * 60e3 ) ) ; await this . launch ( ) ; } private async cacheShardMembers ( shard ) { const guilds = this . client . guilds . filter ( guild => guild . shard . id === shard . id ) ; if ( guilds . length === 0 ) return ; const chunkedGuilds = guilds . reduce ( ( chunked , guild ) => { if ( chunked [ chunked . length - 1 ] . length > 110 ) chunked . push ( [ guild ] ) ; else chunked [ chunked . length - 1 ] . push ( guild ) ; return chunked ; } , [ [ ] ] ) ; for ( let i = 0 ; i < chunkedGuilds . length ; i ++ ) { chunkedGuilds [ i ] . forEach ( guild => { guild . fetchAllMembers ( 20 * 60e3 ) . catch ( error => this . ipc . send ( { op : IPCEvents . ERROR , d : { id : this . id , error : transformError ( error ) } } ) ) ; } ) ; if ( i < chunkedGuilds . length - 1 ) await sleep ( 60100 ) ; } return ; } /**
     * Called for graceful shutdown of the worker. Disconnects the Eris client.
     *
     * You must call this method if you overwrite it using `super.shutdown()`.
     */ public shutdown ( ) { this . client . disconnect ( { reconnect : false } ) ; } /**
     * Is called after the worker is initialized with an IPC client. This method must be implemented.
     * This is where you should usually connect the Eris client.
     */ protected async launch ( ) { await this . client . connect ( ) ; } abstract public abstract allClustersReady ( ) ; abstract public abstract allMembersCached ( allClusters ) ; abstract public abstract getStats ( ) ; /**
     * Is called when a CLUSTER_COMMAND event is received.
     * If the event is receptive then an IPCResult must be returned.
     */ public async handleCommand ( data , receptive ) { if ( receptive ) return this . asError ( new Error ( 'Clusters are not set up to handle commands' ) ) ; } public async eval ( script ) { return await eval ( script ) ; } public getUser ( query , idOnly ) { query = query . toLowerCase ( ) . trim ( ) ; if ( idOnly ) return this . client . users . get ( query ) || null ; if ( /^[0-9]{16,19}$/ . test ( query ) ) { const user = this . client . users . get ( query ) ; if ( user ) return user ; } return this . client . users . find ( user => user . username . toLowerCase ( ) === query ) || this . client . users . find ( user => user . username . toLowerCase ( ) . includes ( query ) ) || null ; } public getChannel ( id ) { const guildId = this . client . channelGuildMap [ id ] ; if ( ! guildId ) return null ; return this . client . guilds . get ( guildId ) ?. channels . get ( id ) || null ; } public getGuild ( id ) { return this . client . guilds . get ( id ) || null ; } /** Formats data as a response to an IPC event or command */ public asResponse ( data ) { return { success : true , d : data } ; } /** Formats an error as a response to an IPC event or command */ public asError ( error ) { return { success : false , d : transformError ( error ) } ; } }