feat: port to typed EventEmitter
This commit is contained in:
		
							parent
							
								
									3f436b2b3f
								
							
						
					
					
						commit
						e7b0804616
					
				
					 13 changed files with 318 additions and 198 deletions
				
			
		
							
								
								
									
										2
									
								
								deps.ts
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								deps.ts
									
										
									
									
									
								
							|  | @ -1,4 +1,4 @@ | ||||||
| export { EventEmitter } from 'https://deno.land/std@0.82.0/node/events.ts' | export { EventEmitter } from 'https://deno.land/x/event@0.2.1/mod.ts' | ||||||
| export { unzlib } from 'https://deno.land/x/denoflate@1.1/mod.ts' | export { unzlib } from 'https://deno.land/x/denoflate@1.1/mod.ts' | ||||||
| export { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts' | export { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts' | ||||||
| export { parse } from 'https://deno.land/x/mutil@0.1.2/mod.ts' | export { parse } from 'https://deno.land/x/mutil@0.1.2/mod.ts' | ||||||
|  |  | ||||||
|  | @ -1,5 +1,9 @@ | ||||||
| import { GatewayEventHandler } from '../index.ts' | import { GatewayEventHandler } from '../index.ts' | ||||||
| import { GatewayEvents, TypingStartGuildData } from '../../types/gateway.ts' | import { | ||||||
|  |   GatewayEvents, | ||||||
|  |   MessageDeletePayload, | ||||||
|  |   TypingStartGuildData | ||||||
|  | } from '../../types/gateway.ts' | ||||||
| import { channelCreate } from './channelCreate.ts' | import { channelCreate } from './channelCreate.ts' | ||||||
| import { channelDelete } from './channelDelete.ts' | import { channelDelete } from './channelDelete.ts' | ||||||
| import { channelUpdate } from './channelUpdate.ts' | import { channelUpdate } from './channelUpdate.ts' | ||||||
|  | @ -55,6 +59,10 @@ import { | ||||||
| } from '../../utils/getChannelByType.ts' | } from '../../utils/getChannelByType.ts' | ||||||
| import { interactionCreate } from './interactionCreate.ts' | import { interactionCreate } from './interactionCreate.ts' | ||||||
| import { Interaction } from '../../structures/slash.ts' | import { Interaction } from '../../structures/slash.ts' | ||||||
|  | import { CommandContext } from '../../models/command.ts' | ||||||
|  | import { RequestMethods } from '../../models/rest.ts' | ||||||
|  | import { PartialInvitePayload } from '../../types/invite.ts' | ||||||
|  | import { GuildChannel } from '../../managers/guildChannels.ts' | ||||||
| 
 | 
 | ||||||
| export const gatewayHandlers: { | export const gatewayHandlers: { | ||||||
|   [eventCode in GatewayEvents]: GatewayEventHandler | undefined |   [eventCode in GatewayEvents]: GatewayEventHandler | undefined | ||||||
|  | @ -105,7 +113,8 @@ export interface VoiceServerUpdateData { | ||||||
|   guild: Guild |   guild: Guild | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface ClientEvents { | // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
 | ||||||
|  | export type ClientEvents = { | ||||||
|   /** When Client has successfully connected to Discord */ |   /** When Client has successfully connected to Discord */ | ||||||
|   ready: [] |   ready: [] | ||||||
|   /** When a successful reconnect has been made */ |   /** When a successful reconnect has been made */ | ||||||
|  | @ -355,4 +364,40 @@ export interface ClientEvents { | ||||||
|    * @param payload Payload JSON of the event |    * @param payload Payload JSON of the event | ||||||
|    */ |    */ | ||||||
|   raw: [evt: string, payload: any] |   raw: [evt: string, payload: any] | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * An uncached Message was deleted. | ||||||
|  |    * @param payload Message Delete Payload | ||||||
|  |    */ | ||||||
|  |   messageDeleteUncached: [payload: MessageDeletePayload] | ||||||
|  | 
 | ||||||
|  |   guildMembersChunk: [ | ||||||
|  |     guild: Guild, | ||||||
|  |     info: { | ||||||
|  |       chunkIndex: number | ||||||
|  |       chunkCount: number | ||||||
|  |       members: string[] | ||||||
|  |       presences: string[] | undefined | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  |   guildMembersChunked: [guild: Guild, chunks: number] | ||||||
|  |   rateLimit: [data: { method: RequestMethods; url: string; body: any }] | ||||||
|  |   inviteDeleteUncached: [invite: PartialInvitePayload] | ||||||
|  |   voiceStateRemoveUncached: [data: { guild: Guild; member: Member }] | ||||||
|  |   userUpdateUncached: [user: User] | ||||||
|  |   webhooksUpdateUncached: [guild: Guild, channelID: string] | ||||||
|  |   guildRoleUpdateUncached: [role: Role] | ||||||
|  |   guildMemberUpdateUncached: [member: Member] | ||||||
|  |   guildMemberRemoveUncached: [member: Member] | ||||||
|  |   channelUpdateUncached: [channel: GuildChannel] | ||||||
|  | 
 | ||||||
|  |   commandOwnerOnly: [ctx: CommandContext] | ||||||
|  |   commandGuildOnly: [ctx: CommandContext] | ||||||
|  |   commandDmOnly: [ctx: CommandContext] | ||||||
|  |   commandNSFW: [ctx: CommandContext] | ||||||
|  |   commandBotMissingPermissions: [ctx: CommandContext, missing: string[]] | ||||||
|  |   commandUserMissingPermissions: [ctx: CommandContext, missing: string[]] | ||||||
|  |   commandMissingArgs: [ctx: CommandContext] | ||||||
|  |   commandUsed: [ctx: CommandContext] | ||||||
|  |   commandError: [ctx: CommandContext, err: Error] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ export const ready: GatewayEventHandler = async ( | ||||||
|   gateway: Gateway, |   gateway: Gateway, | ||||||
|   d: Ready |   d: Ready | ||||||
| ) => { | ) => { | ||||||
|  |   gateway.client.upSince = new Date() | ||||||
|   await gateway.client.guilds.flush() |   await gateway.client.guilds.flush() | ||||||
| 
 | 
 | ||||||
|   await gateway.client.users.set(d.user.id, d.user) |   await gateway.client.users.set(d.user.id, d.user) | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ export const resume: GatewayEventHandler = async ( | ||||||
|   d: Resume |   d: Resume | ||||||
| ) => { | ) => { | ||||||
|   gateway.debug(`Session Resumed!`) |   gateway.debug(`Session Resumed!`) | ||||||
|   gateway.client.emit('resume') |   gateway.client.emit('resumed') | ||||||
|   if (gateway.client.user === undefined) |   if (gateway.client.user === undefined) | ||||||
|     gateway.client.user = new User( |     gateway.client.user = new User( | ||||||
|       gateway.client, |       gateway.client, | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { unzlib, EventEmitter } from '../../deps.ts' | import { unzlib } from '../../deps.ts' | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { | import { | ||||||
|   DISCORD_GATEWAY_URL, |   DISCORD_GATEWAY_URL, | ||||||
|  | @ -10,14 +10,15 @@ import { | ||||||
|   GatewayIntents, |   GatewayIntents, | ||||||
|   GatewayCloseCodes, |   GatewayCloseCodes, | ||||||
|   IdentityPayload, |   IdentityPayload, | ||||||
|   StatusUpdatePayload |   StatusUpdatePayload, | ||||||
|  |   GatewayEvents | ||||||
| } from '../types/gateway.ts' | } from '../types/gateway.ts' | ||||||
| import { gatewayHandlers } from './handlers/index.ts' | import { gatewayHandlers } from './handlers/index.ts' | ||||||
| import { GATEWAY_BOT } from '../types/endpoint.ts' |  | ||||||
| import { GatewayCache } from '../managers/gatewayCache.ts' | import { GatewayCache } from '../managers/gatewayCache.ts' | ||||||
| import { delay } from '../utils/delay.ts' | import { delay } from '../utils/delay.ts' | ||||||
| import { VoiceChannel } from '../structures/guildVoiceChannel.ts' | import { VoiceChannel } from '../structures/guildVoiceChannel.ts' | ||||||
| import { Guild } from '../structures/guild.ts' | import { Guild } from '../structures/guild.ts' | ||||||
|  | import { HarmonyEventEmitter } from '../utils/events.ts' | ||||||
| 
 | 
 | ||||||
| export interface RequestMembersOptions { | export interface RequestMembersOptions { | ||||||
|   limit?: number |   limit?: number | ||||||
|  | @ -33,15 +34,31 @@ export interface VoiceStateOptions { | ||||||
| 
 | 
 | ||||||
| export const RECONNECT_REASON = 'harmony-reconnect' | export const RECONNECT_REASON = 'harmony-reconnect' | ||||||
| 
 | 
 | ||||||
|  | // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
 | ||||||
|  | export type GatewayTypedEvents = { | ||||||
|  |   [name in GatewayEvents]: [any] | ||||||
|  | } & { | ||||||
|  |   connect: [] | ||||||
|  |   ping: [number] | ||||||
|  |   resume: [] | ||||||
|  |   reconnectRequired: [] | ||||||
|  |   close: [number, string] | ||||||
|  |   error: [Error, ErrorEvent] | ||||||
|  |   sentIdentify: [] | ||||||
|  |   sentResume: [] | ||||||
|  |   reconnecting: [] | ||||||
|  |   init: [] | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Handles Discord Gateway connection. |  * Handles Discord Gateway connection. | ||||||
|  * |  * | ||||||
|  * You should not use this and rather use Client class. |  * You should not use this and rather use Client class. | ||||||
|  */ |  */ | ||||||
| export class Gateway extends EventEmitter { | export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> { | ||||||
|   websocket: WebSocket |   websocket?: WebSocket | ||||||
|   token: string |   token?: string | ||||||
|   intents: GatewayIntents[] |   intents?: GatewayIntents[] | ||||||
|   connected = false |   connected = false | ||||||
|   initialized = false |   initialized = false | ||||||
|   heartbeatInterval = 0 |   heartbeatInterval = 0 | ||||||
|  | @ -53,23 +70,13 @@ export class Gateway extends EventEmitter { | ||||||
|   client: Client |   client: Client | ||||||
|   cache: GatewayCache |   cache: GatewayCache | ||||||
|   private timedIdentify: number | null = null |   private timedIdentify: number | null = null | ||||||
|  |   shards?: number[] | ||||||
| 
 | 
 | ||||||
|   constructor(client: Client, token: string, intents: GatewayIntents[]) { |   constructor(client: Client, shards?: number[]) { | ||||||
|     super() |     super() | ||||||
|     this.token = token |  | ||||||
|     this.intents = intents |  | ||||||
|     this.client = client |     this.client = client | ||||||
|     this.cache = new GatewayCache(client) |     this.cache = new GatewayCache(client) | ||||||
|     this.websocket = new WebSocket( |     this.shards = shards | ||||||
|       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
 |  | ||||||
|       `${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`, |  | ||||||
|       [] |  | ||||||
|     ) |  | ||||||
|     this.websocket.binaryType = 'arraybuffer' |  | ||||||
|     this.websocket.onopen = this.onopen.bind(this) |  | ||||||
|     this.websocket.onmessage = this.onmessage.bind(this) |  | ||||||
|     this.websocket.onclose = this.onclose.bind(this) |  | ||||||
|     this.websocket.onerror = this.onerror.bind(this) |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private onopen(): void { |   private onopen(): void { | ||||||
|  | @ -145,7 +152,7 @@ export class Gateway extends EventEmitter { | ||||||
|           await this.cache.set('seq', s) |           await this.cache.set('seq', s) | ||||||
|         } |         } | ||||||
|         if (t !== null && t !== undefined) { |         if (t !== null && t !== undefined) { | ||||||
|           this.emit(t, d) |           this.emit(t as any, d) | ||||||
|           this.client.emit('raw', t, d) |           this.client.emit('raw', t, d) | ||||||
| 
 | 
 | ||||||
|           const handler = gatewayHandlers[t] |           const handler = gatewayHandlers[t] | ||||||
|  | @ -236,14 +243,29 @@ export class Gateway extends EventEmitter { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private onerror(event: Event | ErrorEvent): void { |   private async onerror(event: ErrorEvent): Promise<void> { | ||||||
|     const eventError = event as ErrorEvent |     const error = new Error( | ||||||
|     this.emit('error', eventError) |       Deno.inspect({ | ||||||
|  |         message: event.message, | ||||||
|  |         error: event.error, | ||||||
|  |         type: event.type, | ||||||
|  |         target: event.target | ||||||
|  |       }) | ||||||
|  |     ) | ||||||
|  |     error.name = 'ErrorEvent' | ||||||
|  |     console.log(error) | ||||||
|  |     this.emit('error', error, event) | ||||||
|  |     await this.reconnect() | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private async sendIdentify(forceNewSession?: boolean): Promise<void> { |   private async sendIdentify(forceNewSession?: boolean): Promise<void> { | ||||||
|  |     if (typeof this.token !== 'string') throw new Error('Token not specified') | ||||||
|  |     if (typeof this.intents !== 'object') | ||||||
|  |       throw new Error('Intents not specified') | ||||||
|  | 
 | ||||||
|  |     if (this.client.fetchGatewayInfo === true) { | ||||||
|       this.debug('Fetching /gateway/bot...') |       this.debug('Fetching /gateway/bot...') | ||||||
|     const info = await this.client.rest.get(GATEWAY_BOT()) |       const info = await this.client.rest.api.gateway.bot.get() | ||||||
|       if (info.session_start_limit.remaining === 0) |       if (info.session_start_limit.remaining === 0) | ||||||
|         throw new Error( |         throw new Error( | ||||||
|           `Session Limit Reached. Retry After ${info.session_start_limit.reset_after}ms` |           `Session Limit Reached. Retry After ${info.session_start_limit.reset_after}ms` | ||||||
|  | @ -254,6 +276,7 @@ export class Gateway extends EventEmitter { | ||||||
|         `Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}` |         `Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}` | ||||||
|       ) |       ) | ||||||
|       this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`) |       this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`) | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if (forceNewSession === undefined || !forceNewSession) { |     if (forceNewSession === undefined || !forceNewSession) { | ||||||
|       const sessionIDCached = await this.cache.get('session_id') |       const sessionIDCached = await this.cache.get('session_id') | ||||||
|  | @ -272,7 +295,10 @@ export class Gateway extends EventEmitter { | ||||||
|         $device: this.client.clientProperties.device ?? 'harmony' |         $device: this.client.clientProperties.device ?? 'harmony' | ||||||
|       }, |       }, | ||||||
|       compress: true, |       compress: true, | ||||||
|       shard: [0, 1], // TODO: Make sharding possible
 |       shard: | ||||||
|  |         this.shards === undefined | ||||||
|  |           ? [0, 1] | ||||||
|  |           : [this.shards[0] ?? 0, this.shards[1] ?? 1], | ||||||
|       intents: this.intents.reduce( |       intents: this.intents.reduce( | ||||||
|         (previous, current) => previous | current, |         (previous, current) => previous | current, | ||||||
|         0 |         0 | ||||||
|  | @ -289,6 +315,10 @@ export class Gateway extends EventEmitter { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private async sendResume(): Promise<void> { |   private async sendResume(): Promise<void> { | ||||||
|  |     if (typeof this.token !== 'string') throw new Error('Token not specified') | ||||||
|  |     if (typeof this.intents !== 'object') | ||||||
|  |       throw new Error('Intents not specified') | ||||||
|  | 
 | ||||||
|     if (this.sessionID === undefined) { |     if (this.sessionID === undefined) { | ||||||
|       this.sessionID = await this.cache.get('session_id') |       this.sessionID = await this.cache.get('session_id') | ||||||
|       if (this.sessionID === undefined) return await this.sendIdentify() |       if (this.sessionID === undefined) return await this.sendIdentify() | ||||||
|  | @ -380,22 +410,22 @@ export class Gateway extends EventEmitter { | ||||||
|     this.websocket.onopen = this.onopen.bind(this) |     this.websocket.onopen = this.onopen.bind(this) | ||||||
|     this.websocket.onmessage = this.onmessage.bind(this) |     this.websocket.onmessage = this.onmessage.bind(this) | ||||||
|     this.websocket.onclose = this.onclose.bind(this) |     this.websocket.onclose = this.onclose.bind(this) | ||||||
|     this.websocket.onerror = this.onerror.bind(this) |     this.websocket.onerror = this.onerror.bind(this) as any | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   close(code: number = 1000, reason?: string): void { |   close(code: number = 1000, reason?: string): void { | ||||||
|     return this.websocket.close(code, reason) |     return this.websocket?.close(code, reason) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   send(data: GatewayResponse): boolean { |   send(data: GatewayResponse): boolean { | ||||||
|     if (this.websocket.readyState !== this.websocket.OPEN) return false |     if (this.websocket?.readyState !== this.websocket?.OPEN) return false | ||||||
|     const packet = JSON.stringify({ |     const packet = JSON.stringify({ | ||||||
|       op: data.op, |       op: data.op, | ||||||
|       d: data.d, |       d: data.d, | ||||||
|       s: typeof data.s === 'number' ? data.s : null, |       s: typeof data.s === 'number' ? data.s : null, | ||||||
|       t: data.t === undefined ? null : data.t |       t: data.t === undefined ? null : data.t | ||||||
|     }) |     }) | ||||||
|     this.websocket.send(packet) |     this.websocket?.send(packet) | ||||||
|     return true |     return true | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ import { User } from '../structures/user.ts' | ||||||
| import { GatewayIntents } from '../types/gateway.ts' | import { GatewayIntents } from '../types/gateway.ts' | ||||||
| import { Gateway } from '../gateway/index.ts' | import { Gateway } from '../gateway/index.ts' | ||||||
| import { RESTManager, RESTOptions, TokenType } from './rest.ts' | import { RESTManager, RESTOptions, TokenType } from './rest.ts' | ||||||
| import { EventEmitter } from '../../deps.ts' |  | ||||||
| import { DefaultCacheAdapter, ICacheAdapter } from './cacheAdapter.ts' | import { DefaultCacheAdapter, ICacheAdapter } from './cacheAdapter.ts' | ||||||
| import { UsersManager } from '../managers/users.ts' | import { UsersManager } from '../managers/users.ts' | ||||||
| import { GuildManager } from '../managers/guilds.ts' | import { GuildManager } from '../managers/guilds.ts' | ||||||
|  | @ -21,6 +20,7 @@ import { Invite } from '../structures/invite.ts' | ||||||
| import { INVITE } from '../types/endpoint.ts' | import { INVITE } from '../types/endpoint.ts' | ||||||
| import { ClientEvents } from '../gateway/handlers/index.ts' | import { ClientEvents } from '../gateway/handlers/index.ts' | ||||||
| import type { Collector } from './collectors.ts' | import type { Collector } from './collectors.ts' | ||||||
|  | import { HarmonyEventEmitter } from '../utils/events.ts' | ||||||
| 
 | 
 | ||||||
| /** OS related properties sent with Gateway Identify */ | /** OS related properties sent with Gateway Identify */ | ||||||
| export interface ClientProperties { | export interface ClientProperties { | ||||||
|  | @ -59,40 +59,20 @@ export interface ClientOptions { | ||||||
|   disableEnvToken?: boolean |   disableEnvToken?: boolean | ||||||
|   /** Override REST Options */ |   /** Override REST Options */ | ||||||
|   restOptions?: RESTOptions |   restOptions?: RESTOptions | ||||||
| } |   /** Whether to fetch Gateway info or not */ | ||||||
| 
 |   fetchGatewayInfo?: boolean | ||||||
| export declare interface Client { |   /** ADVANCED: Shard ID to launch on */ | ||||||
|   on<K extends keyof ClientEvents>( |   shard?: number | ||||||
|     event: K, |   /** Shard count. Set to 'auto' for automatic sharding */ | ||||||
|     listener: (...args: ClientEvents[K]) => void |   shardCount?: number | 'auto' | ||||||
|   ): this |  | ||||||
|   on(event: string | symbol, listener: (...args: any[]) => void): this |  | ||||||
| 
 |  | ||||||
|   once<K extends keyof ClientEvents>( |  | ||||||
|     event: K, |  | ||||||
|     listener: (...args: ClientEvents[K]) => void |  | ||||||
|   ): this |  | ||||||
|   once(event: string | symbol, listener: (...args: any[]) => void): this |  | ||||||
| 
 |  | ||||||
|   emit<K extends keyof ClientEvents>( |  | ||||||
|     event: K, |  | ||||||
|     ...args: ClientEvents[K] |  | ||||||
|   ): boolean |  | ||||||
|   emit(event: string | symbol, ...args: any[]): boolean |  | ||||||
| 
 |  | ||||||
|   off<K extends keyof ClientEvents>( |  | ||||||
|     event: K, |  | ||||||
|     listener: (...args: ClientEvents[K]) => void |  | ||||||
|   ): this |  | ||||||
|   off(event: string | symbol, listener: (...args: any[]) => void): this |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Discord Client. |  * Discord Client. | ||||||
|  */ |  */ | ||||||
| export class Client extends EventEmitter { | export class Client extends HarmonyEventEmitter<ClientEvents> { | ||||||
|   /** Gateway object */ |   /** Gateway object */ | ||||||
|   gateway?: Gateway |   gateway: Gateway | ||||||
|   /** REST Manager - used to make all requests */ |   /** REST Manager - used to make all requests */ | ||||||
|   rest: RESTManager |   rest: RESTManager | ||||||
|   /** User which Client logs in to, undefined until logs in */ |   /** User which Client logs in to, undefined until logs in */ | ||||||
|  | @ -117,12 +97,21 @@ export class Client extends EventEmitter { | ||||||
|   clientProperties: ClientProperties |   clientProperties: ClientProperties | ||||||
|   /** Slash-Commands Management client */ |   /** Slash-Commands Management client */ | ||||||
|   slash: SlashClient |   slash: SlashClient | ||||||
|  |   /** Whether to fetch Gateway info or not */ | ||||||
|  |   fetchGatewayInfo: boolean = true | ||||||
| 
 | 
 | ||||||
|  |   /** Users Manager, containing all Users cached */ | ||||||
|   users: UsersManager = new UsersManager(this) |   users: UsersManager = new UsersManager(this) | ||||||
|  |   /** Guilds Manager, providing cache & API interface to Guilds */ | ||||||
|   guilds: GuildManager = new GuildManager(this) |   guilds: GuildManager = new GuildManager(this) | ||||||
|  |   /** Channels Manager, providing cache interface to Channels */ | ||||||
|   channels: ChannelsManager = new ChannelsManager(this) |   channels: ChannelsManager = new ChannelsManager(this) | ||||||
|  |   /** Channels Manager, providing cache interface to Channels */ | ||||||
|   emojis: EmojisManager = new EmojisManager(this) |   emojis: EmojisManager = new EmojisManager(this) | ||||||
| 
 | 
 | ||||||
|  |   /** Last READY timestamp */ | ||||||
|  |   upSince?: Date | ||||||
|  | 
 | ||||||
|   /** Client's presence. Startup one if set before connecting */ |   /** Client's presence. Startup one if set before connecting */ | ||||||
|   presence: ClientPresence = new ClientPresence() |   presence: ClientPresence = new ClientPresence() | ||||||
|   _decoratedEvents?: { |   _decoratedEvents?: { | ||||||
|  | @ -141,10 +130,23 @@ export class Client extends EventEmitter { | ||||||
| 
 | 
 | ||||||
|   /** Shard on which this Client is */ |   /** Shard on which this Client is */ | ||||||
|   shard: number = 0 |   shard: number = 0 | ||||||
|  |   /** Shard Count */ | ||||||
|  |   shardCount: number | 'auto' = 1 | ||||||
|   /** Shard Manager of this Client if Sharded */ |   /** Shard Manager of this Client if Sharded */ | ||||||
|   shardManager?: ShardManager |   shards?: ShardManager | ||||||
|  |   /** Collectors set */ | ||||||
|   collectors: Set<Collector> = new Set() |   collectors: Set<Collector> = new Set() | ||||||
| 
 | 
 | ||||||
|  |   /** Since when is Client online (ready). */ | ||||||
|  |   get uptime(): number { | ||||||
|  |     if (this.upSince === undefined) return 0 | ||||||
|  |     else { | ||||||
|  |       const dif = Date.now() - this.upSince.getTime() | ||||||
|  |       if (dif < 0) return dif | ||||||
|  |       else return dif | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   constructor(options: ClientOptions = {}) { |   constructor(options: ClientOptions = {}) { | ||||||
|     super() |     super() | ||||||
|     this._id = options.id |     this._id = options.id | ||||||
|  | @ -169,7 +171,7 @@ export class Client extends EventEmitter { | ||||||
|       Object.keys(this._decoratedEvents).length !== 0 |       Object.keys(this._decoratedEvents).length !== 0 | ||||||
|     ) { |     ) { | ||||||
|       Object.entries(this._decoratedEvents).forEach((entry) => { |       Object.entries(this._decoratedEvents).forEach((entry) => { | ||||||
|         this.on(entry[0], entry[1]) |         this.on(entry[0] as keyof ClientEvents, entry[1]) | ||||||
|       }) |       }) | ||||||
|       this._decoratedEvents = undefined |       this._decoratedEvents = undefined | ||||||
|     } |     } | ||||||
|  | @ -183,12 +185,17 @@ export class Client extends EventEmitter { | ||||||
|           } |           } | ||||||
|         : options.clientProperties |         : options.clientProperties | ||||||
| 
 | 
 | ||||||
|  |     if (options.shard !== undefined) this.shard = options.shard | ||||||
|  |     if (options.shardCount !== undefined) this.shardCount = options.shardCount | ||||||
|  | 
 | ||||||
|     this.slash = new SlashClient({ |     this.slash = new SlashClient({ | ||||||
|       id: () => this.getEstimatedID(), |       id: () => this.getEstimatedID(), | ||||||
|       client: this, |       client: this, | ||||||
|       enabled: options.enableSlash |       enabled: options.enableSlash | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|  |     if (options.fetchGatewayInfo === true) this.fetchGatewayInfo = true | ||||||
|  | 
 | ||||||
|     if (this.token === undefined) { |     if (this.token === undefined) { | ||||||
|       try { |       try { | ||||||
|         const token = Deno.env.get('DISCORD_TOKEN') |         const token = Deno.env.get('DISCORD_TOKEN') | ||||||
|  | @ -209,6 +216,7 @@ export class Client extends EventEmitter { | ||||||
|     if (options.restOptions !== undefined) |     if (options.restOptions !== undefined) | ||||||
|       Object.assign(restOptions, options.restOptions) |       Object.assign(restOptions, options.restOptions) | ||||||
|     this.rest = new RESTManager(restOptions) |     this.rest = new RESTManager(restOptions) | ||||||
|  |     this.gateway = new Gateway(this) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|  | @ -232,6 +240,7 @@ export class Client extends EventEmitter { | ||||||
| 
 | 
 | ||||||
|   /** Emits debug event */ |   /** Emits debug event */ | ||||||
|   debug(tag: string, msg: string): void { |   debug(tag: string, msg: string): void { | ||||||
|  |     // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||||
|     this.emit('debug', `[${tag}] ${msg}`) |     this.emit('debug', `[${tag}] ${msg}`) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -271,7 +280,7 @@ export class Client extends EventEmitter { | ||||||
|    * @param token Your token. This is required if not given in ClientOptions. |    * @param token Your token. This is required if not given in ClientOptions. | ||||||
|    * @param intents Gateway intents in array. This is required if not given in ClientOptions. |    * @param intents Gateway intents in array. This is required if not given in ClientOptions. | ||||||
|    */ |    */ | ||||||
|   connect(token?: string, intents?: GatewayIntents[]): void { |   async connect(token?: string, intents?: GatewayIntents[]): Promise<Client> { | ||||||
|     if (token === undefined && this.token !== undefined) token = this.token |     if (token === undefined && this.token !== undefined) token = this.token | ||||||
|     else if (this.token === undefined && token !== undefined) { |     else if (this.token === undefined && token !== undefined) { | ||||||
|       this.token = token |       this.token = token | ||||||
|  | @ -288,7 +297,30 @@ export class Client extends EventEmitter { | ||||||
|     } else throw new Error('No Gateway Intents were provided') |     } else throw new Error('No Gateway Intents were provided') | ||||||
| 
 | 
 | ||||||
|     this.rest.token = token |     this.rest.token = token | ||||||
|     this.gateway = new Gateway(this, token, intents) |     this.gateway.token = token | ||||||
|  |     this.gateway.intents = intents | ||||||
|  |     this.gateway.initWebsocket() | ||||||
|  |     return this.waitFor('ready', () => true).then(() => this) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Destroy the Gateway connection */ | ||||||
|  |   async destroy(): Promise<Client> { | ||||||
|  |     this.gateway.initialized = false | ||||||
|  |     this.gateway.sequenceID = undefined | ||||||
|  |     this.gateway.sessionID = undefined | ||||||
|  |     await this.gateway.cache.delete('seq') | ||||||
|  |     await this.gateway.cache.delete('session_id') | ||||||
|  |     this.gateway.close() | ||||||
|  |     this.user = undefined | ||||||
|  |     this.upSince = undefined | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Attempt to Close current Gateway connection and Resume */ | ||||||
|  |   async reconnect(): Promise<Client> { | ||||||
|  |     this.gateway.close() | ||||||
|  |     this.gateway.initWebsocket() | ||||||
|  |     return this.waitFor('ready', () => true).then(() => this) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** Add a new Collector */ |   /** Add a new Collector */ | ||||||
|  | @ -309,7 +341,7 @@ export class Client extends EventEmitter { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   emit(event: keyof ClientEvents, ...args: any[]): boolean { |   async emit(event: keyof ClientEvents, ...args: any[]): Promise<void> { | ||||||
|     const collectors: Collector[] = [] |     const collectors: Collector[] = [] | ||||||
|     for (const collector of this.collectors.values()) { |     for (const collector of this.collectors.values()) { | ||||||
|       if (collector.event === event) collectors.push(collector) |       if (collector.event === event) collectors.push(collector) | ||||||
|  | @ -317,33 +349,11 @@ export class Client extends EventEmitter { | ||||||
|     if (collectors.length !== 0) { |     if (collectors.length !== 0) { | ||||||
|       this.collectors.forEach((collector) => collector._fire(...args)) |       this.collectors.forEach((collector) => collector._fire(...args)) | ||||||
|     } |     } | ||||||
|  |     // TODO(DjDeveloperr): Fix this ts-ignore
 | ||||||
|  |     // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
 | ||||||
|  |     // @ts-ignore
 | ||||||
|     return super.emit(event, ...args) |     return super.emit(event, ...args) | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   /** Wait for an Event (optionally satisfying an event) to occur */ |  | ||||||
|   async waitFor<K extends keyof ClientEvents>( |  | ||||||
|     event: K, |  | ||||||
|     checkFunction: (...args: ClientEvents[K]) => boolean, |  | ||||||
|     timeout?: number |  | ||||||
|   ): Promise<ClientEvents[K] | []> { |  | ||||||
|     return await new Promise((resolve) => { |  | ||||||
|       let timeoutID: number | undefined |  | ||||||
|       if (timeout !== undefined) { |  | ||||||
|         timeoutID = setTimeout(() => { |  | ||||||
|           this.off(event, eventFunc) |  | ||||||
|           resolve([]) |  | ||||||
|         }, timeout) |  | ||||||
|       } |  | ||||||
|       const eventFunc = (...args: ClientEvents[K]): void => { |  | ||||||
|         if (checkFunction(...args)) { |  | ||||||
|           resolve(args) |  | ||||||
|           this.off(event, eventFunc) |  | ||||||
|           if (timeoutID !== undefined) clearTimeout(timeoutID) |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       this.on(event, eventFunc) |  | ||||||
|     }) |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** Event decorator to create an Event handler from function */ | /** Event decorator to create an Event handler from function */ | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { Collection } from '../utils/collection.ts' | import { Collection } from '../utils/collection.ts' | ||||||
| import { EventEmitter } from '../../deps.ts' |  | ||||||
| import type { Client } from './client.ts' | import type { Client } from './client.ts' | ||||||
|  | import { HarmonyEventEmitter } from '../utils/events.ts' | ||||||
| 
 | 
 | ||||||
| export type CollectorFilter = (...args: any[]) => boolean | Promise<boolean> | export type CollectorFilter = (...args: any[]) => boolean | Promise<boolean> | ||||||
| 
 | 
 | ||||||
|  | @ -19,7 +19,14 @@ export interface CollectorOptions { | ||||||
|   timeout?: number |   timeout?: number | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class Collector extends EventEmitter { | // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
 | ||||||
|  | export type CollectorEvents = { | ||||||
|  |   start: [] | ||||||
|  |   end: [] | ||||||
|  |   collect: any | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class Collector extends HarmonyEventEmitter<CollectorEvents> { | ||||||
|   client?: Client |   client?: Client | ||||||
|   private _started: boolean = false |   private _started: boolean = false | ||||||
|   event: string |   event: string | ||||||
|  | @ -146,14 +153,14 @@ export class Collector extends EventEmitter { | ||||||
|       let done = false |       let done = false | ||||||
|       const onend = (): void => { |       const onend = (): void => { | ||||||
|         done = true |         done = true | ||||||
|         this.removeListener('end', onend) |         this.off('end', onend) | ||||||
|         resolve(this) |         resolve(this) | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       this.on('end', onend) |       this.on('end', onend) | ||||||
|       setTimeout(() => { |       setTimeout(() => { | ||||||
|         if (!done) { |         if (!done) { | ||||||
|           this.removeListener('end', onend) |           this.off('end', onend) | ||||||
|           reject(new Error('Timeout')) |           reject(new Error('Timeout')) | ||||||
|         } |         } | ||||||
|       }, timeout) |       }, timeout) | ||||||
|  |  | ||||||
|  | @ -259,7 +259,7 @@ export class CommandClient extends Client implements CommandClientOptions { | ||||||
|         : category.ownerOnly) === true && |         : category.ownerOnly) === true && | ||||||
|       !this.owners.includes(msg.author.id) |       !this.owners.includes(msg.author.id) | ||||||
|     ) |     ) | ||||||
|       return this.emit('commandOwnerOnly', ctx, command) |       return this.emit('commandOwnerOnly', ctx) | ||||||
| 
 | 
 | ||||||
|     // Checks if Command is only for Guild
 |     // Checks if Command is only for Guild
 | ||||||
|     if ( |     if ( | ||||||
|  | @ -268,7 +268,7 @@ export class CommandClient extends Client implements CommandClientOptions { | ||||||
|         : category.guildOnly) === true && |         : category.guildOnly) === true && | ||||||
|       msg.guild === undefined |       msg.guild === undefined | ||||||
|     ) |     ) | ||||||
|       return this.emit('commandGuildOnly', ctx, command) |       return this.emit('commandGuildOnly', ctx) | ||||||
| 
 | 
 | ||||||
|     // Checks if Command is only for DMs
 |     // Checks if Command is only for DMs
 | ||||||
|     if ( |     if ( | ||||||
|  | @ -277,14 +277,14 @@ export class CommandClient extends Client implements CommandClientOptions { | ||||||
|         : category.dmOnly) === true && |         : category.dmOnly) === true && | ||||||
|       msg.guild !== undefined |       msg.guild !== undefined | ||||||
|     ) |     ) | ||||||
|       return this.emit('commandDmOnly', ctx, command) |       return this.emit('commandDmOnly', ctx) | ||||||
| 
 | 
 | ||||||
|     if ( |     if ( | ||||||
|       command.nsfw === true && |       command.nsfw === true && | ||||||
|       (msg.guild === undefined || |       (msg.guild === undefined || | ||||||
|         ((msg.channel as unknown) as GuildTextChannel).nsfw !== true) |         ((msg.channel as unknown) as GuildTextChannel).nsfw !== true) | ||||||
|     ) |     ) | ||||||
|       return this.emit('commandNSFW', ctx, command) |       return this.emit('commandNSFW', ctx) | ||||||
| 
 | 
 | ||||||
|     const allPermissions = |     const allPermissions = | ||||||
|       command.permissions !== undefined |       command.permissions !== undefined | ||||||
|  | @ -316,12 +316,7 @@ export class CommandClient extends Client implements CommandClientOptions { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (missing.length !== 0) |         if (missing.length !== 0) | ||||||
|           return this.emit( |           return this.emit('commandBotMissingPermissions', ctx, missing) | ||||||
|             'commandBotMissingPermissions', |  | ||||||
|             ctx, |  | ||||||
|             command, |  | ||||||
|             missing |  | ||||||
|           ) |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -349,27 +344,22 @@ export class CommandClient extends Client implements CommandClientOptions { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (missing.length !== 0) |         if (missing.length !== 0) | ||||||
|           return this.emit( |           return this.emit('commandUserMissingPermissions', ctx, missing) | ||||||
|             'commandUserMissingPermissions', |  | ||||||
|             command, |  | ||||||
|             missing, |  | ||||||
|             ctx |  | ||||||
|           ) |  | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (command.args !== undefined) { |     if (command.args !== undefined) { | ||||||
|       if (typeof command.args === 'boolean' && parsed.args.length === 0) |       if (typeof command.args === 'boolean' && parsed.args.length === 0) | ||||||
|         return this.emit('commandMissingArgs', ctx, command) |         return this.emit('commandMissingArgs', ctx) | ||||||
|       else if ( |       else if ( | ||||||
|         typeof command.args === 'number' && |         typeof command.args === 'number' && | ||||||
|         parsed.args.length < command.args |         parsed.args.length < command.args | ||||||
|       ) |       ) | ||||||
|         this.emit('commandMissingArgs', ctx, command) |         this.emit('commandMissingArgs', ctx) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|       this.emit('commandUsed', ctx, command) |       this.emit('commandUsed', ctx) | ||||||
| 
 | 
 | ||||||
|       const beforeExecute = await awaitSync(command.beforeExecute(ctx)) |       const beforeExecute = await awaitSync(command.beforeExecute(ctx)) | ||||||
|       if (beforeExecute === false) return |       if (beforeExecute === false) return | ||||||
|  | @ -377,7 +367,7 @@ export class CommandClient extends Client implements CommandClientOptions { | ||||||
|       const result = await awaitSync(command.execute(ctx)) |       const result = await awaitSync(command.execute(ctx)) | ||||||
|       command.afterExecute(ctx, result) |       command.afterExecute(ctx, result) | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       this.emit('commandError', command, ctx, e) |       this.emit('commandError', ctx, e) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | import { ClientEvents } from '../../mod.ts' | ||||||
| import { Collection } from '../utils/collection.ts' | import { Collection } from '../utils/collection.ts' | ||||||
| import { Command } from './command.ts' | import { Command } from './command.ts' | ||||||
| import { CommandClient } from './commandClient.ts' | import { CommandClient } from './commandClient.ts' | ||||||
|  | @ -90,14 +91,14 @@ export class Extension { | ||||||
|       Object.keys(this._decoratedEvents).length !== 0 |       Object.keys(this._decoratedEvents).length !== 0 | ||||||
|     ) { |     ) { | ||||||
|       Object.entries(this._decoratedEvents).forEach((entry) => { |       Object.entries(this._decoratedEvents).forEach((entry) => { | ||||||
|         this.listen(entry[0], entry[1]) |         this.listen(entry[0] as keyof ClientEvents, entry[1]) | ||||||
|       }) |       }) | ||||||
|       this._decoratedEvents = undefined |       this._decoratedEvents = undefined | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** Listens for an Event through Extension. */ |   /** Listens for an Event through Extension. */ | ||||||
|   listen(event: string, cb: ExtensionEventCallback): boolean { |   listen(event: keyof ClientEvents, cb: ExtensionEventCallback): boolean { | ||||||
|     if (this.events[event] !== undefined) return false |     if (this.events[event] !== undefined) return false | ||||||
|     else { |     else { | ||||||
|       const fn = (...args: any[]): any => { |       const fn = (...args: any[]): any => { | ||||||
|  | @ -152,7 +153,7 @@ export class ExtensionsManager { | ||||||
|     if (extension === undefined) return false |     if (extension === undefined) return false | ||||||
|     extension.commands.deleteAll() |     extension.commands.deleteAll() | ||||||
|     for (const [k, v] of Object.entries(extension.events)) { |     for (const [k, v] of Object.entries(extension.events)) { | ||||||
|       this.client.removeListener(k, v) |       this.client.off(k as keyof ClientEvents, v) | ||||||
|       // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
 |       // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
 | ||||||
|       delete extension.events[k] |       delete extension.events[k] | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,69 +1,75 @@ | ||||||
| import { Collection } from '../utils/collection.ts' | import { Collection } from '../utils/collection.ts' | ||||||
| import { Client, ClientOptions } from './client.ts' | import { Client } from './client.ts' | ||||||
| import {EventEmitter} from '../../deps.ts' |  | ||||||
| import { RESTManager } from './rest.ts' | import { RESTManager } from './rest.ts' | ||||||
| // import { GATEWAY_BOT } from '../types/endpoint.ts'
 | import { Gateway } from '../gateway/index.ts' | ||||||
| // import { GatewayBotPayload } from '../types/gatewayBot.ts'
 | import { HarmonyEventEmitter } from '../utils/events.ts' | ||||||
|  | import { GatewayEvents } from '../types/gateway.ts' | ||||||
| 
 | 
 | ||||||
| // TODO(DjDeveloperr)
 | // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
 | ||||||
| // I'm kinda confused; will continue on this later once
 | export type ShardManagerEvents = { | ||||||
| // Deno namespace in Web Worker is stable!
 |   launch: [number] | ||||||
| export interface ShardManagerOptions { |   shardReady: [number] | ||||||
|   client: Client | typeof Client |   shardDisconnect: [number, number | undefined, string | undefined] | ||||||
|   token?: string |   shardError: [number, Error, ErrorEvent] | ||||||
|   intents?: number[] |   shardResume: [number] | ||||||
|   options?: ClientOptions |  | ||||||
|   shards: number |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface ShardManagerInitOptions { | export class ShardManager extends HarmonyEventEmitter<ShardManagerEvents> { | ||||||
|   file: string |   list: Collection<string, Gateway> = new Collection() | ||||||
|   token?: string |   client: Client | ||||||
|   intents?: number[] |   cachedShardCount?: number | ||||||
|   options?: ClientOptions |  | ||||||
|   shards?: number |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export class ShardManager extends EventEmitter { |  | ||||||
|   workers: Collection<string, Worker> = new Collection() |  | ||||||
|   token: string |  | ||||||
|   intents: number[] |  | ||||||
|   shardCount: number |  | ||||||
|   private readonly __client: Client |  | ||||||
| 
 | 
 | ||||||
|   get rest(): RESTManager { |   get rest(): RESTManager { | ||||||
|     return this.__client.rest |     return this.client.rest | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   constructor(options: ShardManagerOptions) { |   constructor(client: Client) { | ||||||
|     super() |     super() | ||||||
|     this.__client = |     this.client = client | ||||||
|       options.client instanceof Client |   } | ||||||
|         ? options.client |  | ||||||
|         : // eslint-disable-next-line new-cap
 |  | ||||||
|           new options.client(options.options) |  | ||||||
| 
 | 
 | ||||||
|     if (this.__client.token === undefined || options.token === undefined) |   async getShardCount(): Promise<number> { | ||||||
|       throw new Error('Token should be provided when constructing ShardManager') |     let shardCount: number | ||||||
|     if (this.__client.intents === undefined || options.intents === undefined) |     if (this.cachedShardCount !== undefined) shardCount = this.cachedShardCount | ||||||
|       throw new Error( |     else { | ||||||
|         'Intents should be provided when constructing ShardManager' |       if (this.client.shardCount === 'auto') { | ||||||
|  |         const info = await this.client.rest.api.gateway.bot.get() | ||||||
|  |         shardCount = info.shards as number | ||||||
|  |       } else shardCount = this.client.shardCount ?? 1 | ||||||
|  |     } | ||||||
|  |     this.cachedShardCount = shardCount | ||||||
|  |     return this.cachedShardCount | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Launches a new Shard */ | ||||||
|  |   async launch(id: number): Promise<ShardManager> { | ||||||
|  |     if (this.list.has(id.toString()) === true) | ||||||
|  |       throw new Error(`Shard ${id} already launched`) | ||||||
|  | 
 | ||||||
|  |     const shardCount = await this.getShardCount() | ||||||
|  | 
 | ||||||
|  |     const gw = new Gateway(this.client, [Number(id), shardCount]) | ||||||
|  |     this.list.set(id.toString(), gw) | ||||||
|  |     gw.initWebsocket() | ||||||
|  |     this.emit('launch', id) | ||||||
|  | 
 | ||||||
|  |     gw.on(GatewayEvents.Ready, () => this.emit('shardReady', id)) | ||||||
|  |     gw.on('error', (err: Error, evt: ErrorEvent) => | ||||||
|  |       this.emit('shardError', id, err, evt) | ||||||
|  |     ) | ||||||
|  |     gw.on(GatewayEvents.Resumed, () => this.emit('shardResume', id)) | ||||||
|  |     gw.on('close', (code: number, reason: string) => | ||||||
|  |       this.emit('shardDisconnect', id, code, reason) | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     this.token = this.__client.token ?? options.token |     return gw.waitFor(GatewayEvents.Ready, () => true).then(() => this) | ||||||
|     this.intents = this.__client.intents ?? options.intents |  | ||||||
|     this.shardCount = options.shards |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // static async init(): Promise<ShardManager> {}
 |   async start(): Promise<ShardManager> { | ||||||
| 
 |     const shardCount = await this.getShardCount() | ||||||
|   // async start(): Promise<ShardManager> {
 |     for (let i = 0; i <= shardCount; i++) { | ||||||
|   //   const info = ((await this.rest.get(
 |       await this.launch(i) | ||||||
|   //     GATEWAY_BOT()
 |     } | ||||||
|   //   )) as unknown) as GatewayBotPayload
 |     return this | ||||||
| 
 |   } | ||||||
|   //   const totalShards = this.__shardCount ?? info.shards
 |  | ||||||
| 
 |  | ||||||
|   //   return this
 |  | ||||||
|   // }
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -292,14 +292,14 @@ export class Guild extends Base { | ||||||
|         const listener = (guild: Guild): void => { |         const listener = (guild: Guild): void => { | ||||||
|           if (guild.id === this.id) { |           if (guild.id === this.id) { | ||||||
|             chunked = true |             chunked = true | ||||||
|             this.client.removeListener('guildMembersChunked', listener) |             this.client.off('guildMembersChunked', listener) | ||||||
|             resolve(this) |             resolve(this) | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|         this.client.on('guildMembersChunked', listener) |         this.client.on('guildMembersChunked', listener) | ||||||
|         setTimeout(() => { |         setTimeout(() => { | ||||||
|           if (!chunked) { |           if (!chunked) { | ||||||
|             this.client.removeListener('guildMembersChunked', listener) |             this.client.off('guildMembersChunked', listener) | ||||||
|           } |           } | ||||||
|         }, timeout) |         }, timeout) | ||||||
|       } |       } | ||||||
|  | @ -312,19 +312,19 @@ export class Guild extends Base { | ||||||
|    */ |    */ | ||||||
|   async awaitAvailability(timeout: number = 1000): Promise<Guild> { |   async awaitAvailability(timeout: number = 1000): Promise<Guild> { | ||||||
|     return await new Promise((resolve, reject) => { |     return await new Promise((resolve, reject) => { | ||||||
|       if(!this.unavailable) resolve(this); |       if (!this.unavailable) resolve(this) | ||||||
|       const listener = (guild: Guild): void => { |       const listener = (guild: Guild): void => { | ||||||
|         if (guild.id === this.id) { |         if (guild.id === this.id) { | ||||||
|           this.client.removeListener('guildLoaded', listener); |           this.client.off('guildLoaded', listener) | ||||||
|           resolve(this); |           resolve(this) | ||||||
|         } |         } | ||||||
|       }; |       } | ||||||
|       this.client.on('guildLoaded', listener); |       this.client.on('guildLoaded', listener) | ||||||
|       setTimeout(() => { |       setTimeout(() => { | ||||||
|         this.client.removeListener('guildLoaded', listener); |         this.client.off('guildLoaded', listener) | ||||||
|         reject(Error("Timeout. Guild didn't arrive in time.")); |         reject(Error("Timeout. Guild didn't arrive in time.")) | ||||||
|       }, timeout); |       }, timeout) | ||||||
|     }); |     }) | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -44,7 +44,7 @@ export class VoiceChannel extends Channel { | ||||||
|       const onVoiceStateAdd = (state: VoiceState): void => { |       const onVoiceStateAdd = (state: VoiceState): void => { | ||||||
|         if (state.user.id !== this.client.user?.id) return |         if (state.user.id !== this.client.user?.id) return | ||||||
|         if (state.channel?.id !== this.id) return |         if (state.channel?.id !== this.id) return | ||||||
|         this.client.removeListener('voiceStateAdd', onVoiceStateAdd) |         this.client.off('voiceStateAdd', onVoiceStateAdd) | ||||||
|         done++ |         done++ | ||||||
|         if (done >= 2) resolve((vcdata as unknown) as VoiceServerUpdateData) |         if (done >= 2) resolve((vcdata as unknown) as VoiceServerUpdateData) | ||||||
|       } |       } | ||||||
|  | @ -52,7 +52,7 @@ export class VoiceChannel extends Channel { | ||||||
|       const onVoiceServerUpdate = (data: VoiceServerUpdateData): void => { |       const onVoiceServerUpdate = (data: VoiceServerUpdateData): void => { | ||||||
|         if (data.guild.id !== this.guild.id) return |         if (data.guild.id !== this.guild.id) return | ||||||
|         vcdata = data |         vcdata = data | ||||||
|         this.client.removeListener('voiceServerUpdate', onVoiceServerUpdate) |         this.client.off('voiceServerUpdate', onVoiceServerUpdate) | ||||||
|         done++ |         done++ | ||||||
|         if (done >= 2) resolve(vcdata) |         if (done >= 2) resolve(vcdata) | ||||||
|       } |       } | ||||||
|  | @ -64,8 +64,8 @@ export class VoiceChannel extends Channel { | ||||||
| 
 | 
 | ||||||
|       setTimeout(() => { |       setTimeout(() => { | ||||||
|         if (done < 2) { |         if (done < 2) { | ||||||
|           this.client.removeListener('voiceServerUpdate', onVoiceServerUpdate) |           this.client.off('voiceServerUpdate', onVoiceServerUpdate) | ||||||
|           this.client.removeListener('voiceStateAdd', onVoiceStateAdd) |           this.client.off('voiceStateAdd', onVoiceStateAdd) | ||||||
|           reject( |           reject( | ||||||
|             new Error( |             new Error( | ||||||
|               "Connection timed out - couldn't connect to Voice Channel" |               "Connection timed out - couldn't connect to Voice Channel" | ||||||
|  |  | ||||||
							
								
								
									
										30
									
								
								src/utils/events.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/utils/events.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | ||||||
|  | import { EventEmitter } from '../../deps.ts' | ||||||
|  | 
 | ||||||
|  | export class HarmonyEventEmitter< | ||||||
|  |   T extends Record<string, unknown[]> | ||||||
|  | > extends EventEmitter<T> { | ||||||
|  |   /** Wait for an Event to fire with given condition. */ | ||||||
|  |   async waitFor<K extends keyof T>( | ||||||
|  |     event: K, | ||||||
|  |     checkFunction: (...args: T[K]) => boolean = () => true, | ||||||
|  |     timeout?: number | ||||||
|  |   ): Promise<T[K] | []> { | ||||||
|  |     return await new Promise((resolve) => { | ||||||
|  |       let timeoutID: number | undefined | ||||||
|  |       if (timeout !== undefined) { | ||||||
|  |         timeoutID = setTimeout(() => { | ||||||
|  |           this.off(event, eventFunc) | ||||||
|  |           resolve([]) | ||||||
|  |         }, timeout) | ||||||
|  |       } | ||||||
|  |       const eventFunc = (...args: T[K]): void => { | ||||||
|  |         if (checkFunction(...args)) { | ||||||
|  |           resolve(args) | ||||||
|  |           this.off(event, eventFunc) | ||||||
|  |           if (timeoutID !== undefined) clearTimeout(timeoutID) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       this.on(event, eventFunc) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue