Merge pull request #88 from DjDeveloperr/slash
BREAKING: Adds rest of the REST and migrate to Typed EventEmitter, better Gateway interfacing
This commit is contained in:
		
						commit
						b2a93769ff
					
				
					 19 changed files with 560 additions and 189 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' | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								mod.ts
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								mod.ts
									
										
									
									
									
								
							|  | @ -1,6 +1,7 @@ | ||||||
| export { GatewayIntents } from './src/types/gateway.ts' | export { GatewayIntents } from './src/types/gateway.ts' | ||||||
| export { Base } from './src/structures/base.ts' | export { Base } from './src/structures/base.ts' | ||||||
| export { Gateway } from './src/gateway/index.ts' | export { Gateway } from './src/gateway/index.ts' | ||||||
|  | export type { GatewayTypedEvents } from './src/gateway/index.ts' | ||||||
| export type { ClientEvents } from './src/gateway/handlers/index.ts' | export type { ClientEvents } from './src/gateway/handlers/index.ts' | ||||||
| export * from './src/models/client.ts' | export * from './src/models/client.ts' | ||||||
| export * from './src/models/slashClient.ts' | export * from './src/models/slashClient.ts' | ||||||
|  | @ -124,3 +125,4 @@ export type { UserPayload } from './src/types/user.ts' | ||||||
| export { UserFlags } from './src/types/userFlags.ts' | export { UserFlags } from './src/types/userFlags.ts' | ||||||
| export type { VoiceStatePayload } from './src/types/voice.ts' | export type { VoiceStatePayload } from './src/types/voice.ts' | ||||||
| export type { WebhookPayload } from './src/types/webhook.ts' | export type { WebhookPayload } from './src/types/webhook.ts' | ||||||
|  | export * from './src/models/collectors.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 { GuildChannels } from '../../types/guild.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: GuildChannels] | ||||||
|  | 
 | ||||||
|  |   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,24 +243,40 @@ 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> { | ||||||
|     this.debug('Fetching /gateway/bot...') |     if (typeof this.token !== 'string') throw new Error('Token not specified') | ||||||
|     const info = await this.client.rest.get(GATEWAY_BOT()) |     if (typeof this.intents !== 'object') | ||||||
|     if (info.session_start_limit.remaining === 0) |       throw new Error('Intents not specified') | ||||||
|       throw new Error( | 
 | ||||||
|         `Session Limit Reached. Retry After ${info.session_start_limit.reset_after}ms` |     if (this.client.fetchGatewayInfo === true) { | ||||||
|  |       this.debug('Fetching /gateway/bot...') | ||||||
|  |       const info = await this.client.rest.api.gateway.bot.get() | ||||||
|  |       if (info.session_start_limit.remaining === 0) | ||||||
|  |         throw new Error( | ||||||
|  |           `Session Limit Reached. Retry After ${info.session_start_limit.reset_after}ms` | ||||||
|  |         ) | ||||||
|  |       this.debug(`Recommended Shards: ${info.shards}`) | ||||||
|  |       this.debug('=== Session Limit Info ===') | ||||||
|  |       this.debug( | ||||||
|  |         `Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}` | ||||||
|       ) |       ) | ||||||
|     this.debug(`Recommended Shards: ${info.shards}`) |       this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`) | ||||||
|     this.debug('=== Session Limit Info ===') |     } | ||||||
|     this.debug( |  | ||||||
|       `Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}` |  | ||||||
|     ) |  | ||||||
|     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 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -89,4 +89,20 @@ export class GuildChannelsManager extends BaseChildManager< | ||||||
|     const channel = await this.get(res.id) |     const channel = await this.get(res.id) | ||||||
|     return (channel as unknown) as GuildChannels |     return (channel as unknown) as GuildChannels | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   /** Modify the positions of a set of channel positions for the guild. */ | ||||||
|  |   async editPositions( | ||||||
|  |     ...positions: Array<{ id: string | GuildChannels; position: number | null }> | ||||||
|  |   ): Promise<GuildChannelsManager> { | ||||||
|  |     if (positions.length === 0) | ||||||
|  |       throw new Error('No channel positions to change specified') | ||||||
|  | 
 | ||||||
|  |     await this.client.rest.api.guilds[this.guild.id].channels.patch( | ||||||
|  |       positions.map((e) => ({ | ||||||
|  |         id: typeof e.id === 'string' ? e.id : e.id.id, | ||||||
|  |         position: e.position ?? null | ||||||
|  |       })) | ||||||
|  |     ) | ||||||
|  |     return this | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
|  | import { fetchAuto } from '../../deps.ts' | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { Guild } from '../structures/guild.ts' | import { Guild } from '../structures/guild.ts' | ||||||
|  | import { Template } from '../structures/template.ts' | ||||||
| import { Role } from '../structures/role.ts' | import { Role } from '../structures/role.ts' | ||||||
| import { GUILD, GUILDS, GUILD_PREVIEW } from '../types/endpoint.ts' | import { GUILD, GUILDS, GUILD_PREVIEW } from '../types/endpoint.ts' | ||||||
| import { | import { | ||||||
|  | @ -16,7 +18,6 @@ import { | ||||||
| } from '../types/guild.ts' | } from '../types/guild.ts' | ||||||
| import { BaseManager } from './base.ts' | import { BaseManager } from './base.ts' | ||||||
| import { MembersManager } from './members.ts' | import { MembersManager } from './members.ts' | ||||||
| import { fetchAuto } from '../../deps.ts' |  | ||||||
| import { Emoji } from '../structures/emoji.ts' | import { Emoji } from '../structures/emoji.ts' | ||||||
| 
 | 
 | ||||||
| export class GuildManager extends BaseManager<GuildPayload, Guild> { | export class GuildManager extends BaseManager<GuildPayload, Guild> { | ||||||
|  | @ -47,6 +48,19 @@ export class GuildManager extends BaseManager<GuildPayload, Guild> { | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** Create a new guild based on a template. */ | ||||||
|  |   async createFromTemplate( | ||||||
|  |     template: Template | string, | ||||||
|  |     name: string, | ||||||
|  |     icon?: string | ||||||
|  |   ): Promise<Guild> { | ||||||
|  |     if (icon?.startsWith('http') === true) icon = await fetchAuto(icon) | ||||||
|  |     const guild = await this.client.rest.api.guilds.templates[ | ||||||
|  |       typeof template === 'object' ? template.code : template | ||||||
|  |     ].post({ name, icon }) | ||||||
|  |     return new Guild(this.client, guild) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /** |   /** | ||||||
|    * Creates a guild. Returns Guild. Fires guildCreate event. |    * Creates a guild. Returns Guild. Fires guildCreate event. | ||||||
|    * @param options Options for creating a guild |    * @param options Options for creating a guild | ||||||
|  |  | ||||||
|  | @ -101,4 +101,20 @@ export class RolesManager extends BaseManager<RolePayload, Role> { | ||||||
| 
 | 
 | ||||||
|     return new Role(this.client, resp, this.guild) |     return new Role(this.client, resp, this.guild) | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   /** Modify the positions of a set of role positions for the guild. */ | ||||||
|  |   async editPositions( | ||||||
|  |     ...positions: Array<{ id: string | Role; position: number | null }> | ||||||
|  |   ): Promise<RolesManager> { | ||||||
|  |     if (positions.length === 0) | ||||||
|  |       throw new Error('No role positions to change specified') | ||||||
|  | 
 | ||||||
|  |     await this.client.rest.api.guilds[this.guild.id].roles.patch( | ||||||
|  |       positions.map((e) => ({ | ||||||
|  |         id: typeof e.id === 'string' ? e.id : e.id.id, | ||||||
|  |         position: e.position ?? null | ||||||
|  |       })) | ||||||
|  |     ) | ||||||
|  |     return this | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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,11 @@ 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' | ||||||
|  | import { VoiceRegion } from '../types/voice.ts' | ||||||
|  | import { fetchAuto } from '../../deps.ts' | ||||||
|  | import { DMChannel } from '../structures/dmChannel.ts' | ||||||
|  | import { Template } from '../structures/template.ts' | ||||||
| 
 | 
 | ||||||
| /** OS related properties sent with Gateway Identify */ | /** OS related properties sent with Gateway Identify */ | ||||||
| export interface ClientProperties { | export interface ClientProperties { | ||||||
|  | @ -59,40 +63,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 +101,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 +134,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 +175,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 +189,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 | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|  |     this.fetchGatewayInfo = options.fetchGatewayInfo ?? false | ||||||
|  | 
 | ||||||
|     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 +220,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 +244,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 +284,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 +301,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 +345,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,32 +353,62 @@ 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 */ |   /** Returns an array of voice region objects that can be used when creating servers. */ | ||||||
|   async waitFor<K extends keyof ClientEvents>( |   async fetchVoiceRegions(): Promise<VoiceRegion[]> { | ||||||
|     event: K, |     return this.rest.api.voice.regions.get() | ||||||
|     checkFunction: (...args: ClientEvents[K]) => boolean, |   } | ||||||
|     timeout?: number | 
 | ||||||
|   ): Promise<ClientEvents[K] | []> { |   /** Modify current (Client) User. */ | ||||||
|     return await new Promise((resolve) => { |   async editUser(data: { | ||||||
|       let timeoutID: number | undefined |     username?: string | ||||||
|       if (timeout !== undefined) { |     avatar?: string | ||||||
|         timeoutID = setTimeout(() => { |   }): Promise<Client> { | ||||||
|           this.off(event, eventFunc) |     if (data.username === undefined && data.avatar === undefined) | ||||||
|           resolve([]) |       throw new Error( | ||||||
|         }, timeout) |         'Either username or avatar or both must be specified to edit' | ||||||
|       } |       ) | ||||||
|       const eventFunc = (...args: ClientEvents[K]): void => { | 
 | ||||||
|         if (checkFunction(...args)) { |     if (data.avatar?.startsWith('http') === true) { | ||||||
|           resolve(args) |       data.avatar = await fetchAuto(data.avatar) | ||||||
|           this.off(event, eventFunc) |     } | ||||||
|           if (timeoutID !== undefined) clearTimeout(timeoutID) | 
 | ||||||
|         } |     await this.rest.api.users['@me'].patch({ | ||||||
|       } |       username: data.username, | ||||||
|       this.on(event, eventFunc) |       avatar: data.avatar | ||||||
|     }) |     }) | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Change Username of the Client User */ | ||||||
|  |   async setUsername(username: string): Promise<Client> { | ||||||
|  |     return await this.editUser({ username }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Change Avatar of the Client User */ | ||||||
|  |   async setAvatar(avatar: string): Promise<Client> { | ||||||
|  |     return await this.editUser({ avatar }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Create a DM Channel with a User */ | ||||||
|  |   async createDM(user: User | string): Promise<DMChannel> { | ||||||
|  |     const id = typeof user === 'object' ? user.id : user | ||||||
|  |     const dmPayload = await this.rest.api.users['@me'].channels.post({ | ||||||
|  |       recipient_id: id | ||||||
|  |     }) | ||||||
|  |     await this.channels.set(dmPayload.id, dmPayload) | ||||||
|  |     return (this.channels.get<DMChannel>(dmPayload.id) as unknown) as DMChannel | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Returns a template object for the given code. */ | ||||||
|  |   async fetchTemplate(code: string): Promise<Template> { | ||||||
|  |     const payload = await this.rest.api.guilds.templates[code].get() | ||||||
|  |     return new Template(this, payload) | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  | @ -135,8 +142,8 @@ export class Collector extends EventEmitter { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** Returns a Promise resolved when Collector ends or a timeout occurs */ |   /** Returns a Promise resolved when Collector ends or a timeout occurs */ | ||||||
|   // eslint-disable-next-line
 |   async wait(timeout?: number): Promise<Collector> { | ||||||
|   async wait(timeout: number = this.timeout ?? 0): Promise<Collector> { |     if (timeout === undefined) timeout = this.timeout ?? 0 | ||||||
|     return await new Promise((resolve, reject) => { |     return await new Promise((resolve, reject) => { | ||||||
|       // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
 |       // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
 | ||||||
|       if (!timeout) |       if (!timeout) | ||||||
|  | @ -147,14 +154,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) |  | ||||||
|       throw new Error('Token should be provided when constructing ShardManager') |  | ||||||
|     if (this.__client.intents === undefined || options.intents === undefined) |  | ||||||
|       throw new Error( |  | ||||||
|         'Intents should be provided when constructing ShardManager' |  | ||||||
|       ) |  | ||||||
| 
 |  | ||||||
|     this.token = this.__client.token ?? options.token |  | ||||||
|     this.intents = this.__client.intents ?? options.intents |  | ||||||
|     this.shardCount = options.shards |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // static async init(): Promise<ShardManager> {}
 |   async getShardCount(): Promise<number> { | ||||||
|  |     let shardCount: number | ||||||
|  |     if (this.cachedShardCount !== undefined) shardCount = this.cachedShardCount | ||||||
|  |     else { | ||||||
|  |       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 | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   // async start(): Promise<ShardManager> {
 |   /** Launches a new Shard */ | ||||||
|   //   const info = ((await this.rest.get(
 |   async launch(id: number): Promise<ShardManager> { | ||||||
|   //     GATEWAY_BOT()
 |     if (this.list.has(id.toString()) === true) | ||||||
|   //   )) as unknown) as GatewayBotPayload
 |       throw new Error(`Shard ${id} already launched`) | ||||||
| 
 | 
 | ||||||
|   //   const totalShards = this.__shardCount ?? info.shards
 |     const shardCount = await this.getShardCount() | ||||||
| 
 | 
 | ||||||
|   //   return this
 |     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) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     return gw.waitFor(GatewayEvents.Ready, () => true).then(() => this) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async start(): Promise<ShardManager> { | ||||||
|  |     const shardCount = await this.getShardCount() | ||||||
|  |     for (let i = 0; i <= shardCount; i++) { | ||||||
|  |       await this.launch(i) | ||||||
|  |     } | ||||||
|  |     return this | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import { | ||||||
|   GuildFeatures, |   GuildFeatures, | ||||||
|   GuildIntegrationPayload, |   GuildIntegrationPayload, | ||||||
|   GuildPayload, |   GuildPayload, | ||||||
|  |   GuildWidgetPayload, | ||||||
|   IntegrationAccountPayload, |   IntegrationAccountPayload, | ||||||
|   IntegrationExpireBehavior, |   IntegrationExpireBehavior, | ||||||
|   Verification, |   Verification, | ||||||
|  | @ -38,6 +39,8 @@ import { | ||||||
| import { GuildVoiceStatesManager } from '../managers/guildVoiceStates.ts' | import { GuildVoiceStatesManager } from '../managers/guildVoiceStates.ts' | ||||||
| import { RequestMembersOptions } from '../gateway/index.ts' | import { RequestMembersOptions } from '../gateway/index.ts' | ||||||
| import { GuildPresencesManager } from '../managers/presences.ts' | import { GuildPresencesManager } from '../managers/presences.ts' | ||||||
|  | import { TemplatePayload } from '../types/template.ts' | ||||||
|  | import { Template } from './template.ts' | ||||||
| 
 | 
 | ||||||
| export class GuildBan extends Base { | export class GuildBan extends Base { | ||||||
|   guild: Guild |   guild: Guild | ||||||
|  | @ -328,18 +331,137 @@ export class Guild extends Base { | ||||||
|       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) | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** Attach an integration object from the current user to the guild. */ | ||||||
|  |   async createIntegration(id: string, type: string): Promise<Guild> { | ||||||
|  |     await this.client.rest.api.guilds[this.id].integrations.post({ id, type }) | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Modify the behavior and settings of an integration object for the guild. */ | ||||||
|  |   async editIntegration( | ||||||
|  |     id: string, | ||||||
|  |     data: { | ||||||
|  |       expireBehavior?: number | null | ||||||
|  |       expireGracePeriod?: number | null | ||||||
|  |       enableEmoticons?: boolean | null | ||||||
|  |     } | ||||||
|  |   ): Promise<Guild> { | ||||||
|  |     await this.client.rest.api.guilds[this.id].integrations[id].patch({ | ||||||
|  |       expire_behaviour: data.expireBehavior, | ||||||
|  |       expire_grace_period: data.expireGracePeriod, | ||||||
|  |       enable_emoticons: data.enableEmoticons | ||||||
|  |     }) | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Delete the attached integration object for the guild. Deletes any associated webhooks and kicks the associated bot if there is one. */ | ||||||
|  |   async deleteIntegration(id: string): Promise<Guild> { | ||||||
|  |     await this.client.rest.api.guilds[this.id].integrations[id].delete() | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Sync an integration. */ | ||||||
|  |   async syncIntegration(id: string): Promise<Guild> { | ||||||
|  |     await this.client.rest.api.guilds[this.id].integrations[id].sync.post() | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Returns the widget for the guild. */ | ||||||
|  |   async getWidget(): Promise<GuildWidgetPayload> { | ||||||
|  |     return this.client.rest.api.guilds[this.id]['widget.json'].get() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Modify a guild widget object for the guild. */ | ||||||
|  |   async editWidget(data: { | ||||||
|  |     enabled?: boolean | ||||||
|  |     channel?: string | GuildChannels | ||||||
|  |   }): Promise<Guild> { | ||||||
|  |     await this.client.rest.api.guilds[this.id].widget.patch({ | ||||||
|  |       enabled: data.enabled, | ||||||
|  |       channel_id: | ||||||
|  |         typeof data.channel === 'object' ? data.channel.id : data.channel | ||||||
|  |     }) | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Returns a partial invite object for guilds with that feature enabled. */ | ||||||
|  |   async getVanity(): Promise<{ code: string | null; uses: number }> { | ||||||
|  |     return this.client.rest.api.guilds[this.id]['vanity-url'].get() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Returns a PNG (URL) image widget for the guild. */ | ||||||
|  |   getWidgetImageURL( | ||||||
|  |     style?: 'shield' | 'banner1' | 'banner2' | 'banner3' | 'banner4' | ||||||
|  |   ): string { | ||||||
|  |     return `https://discord.com/api/v${this.client.rest.version ?? 8}/guilds/${ | ||||||
|  |       this.id | ||||||
|  |     }/widget.png${style !== undefined ? `?style=${style}` : ''}` | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Leave a Guild. */ | ||||||
|  |   async leave(): Promise<Client> { | ||||||
|  |     await this.client.rest.api.users['@me'].guilds[this.id].delete() | ||||||
|  |     return this.client | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Returns an array of template objects. */ | ||||||
|  |   async getTemplates(): Promise<Template[]> { | ||||||
|  |     return this.client.rest.api.guilds[this.id].templates | ||||||
|  |       .get() | ||||||
|  |       .then((temps: TemplatePayload[]) => | ||||||
|  |         temps.map((temp) => new Template(this.client, temp)) | ||||||
|  |       ) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Creates a template for the guild. */ | ||||||
|  |   async createTemplate( | ||||||
|  |     name: string, | ||||||
|  |     description?: string | null | ||||||
|  |   ): Promise<Template> { | ||||||
|  |     const payload = await this.client.rest.api.guilds[this.id].templates.post({ | ||||||
|  |       name, | ||||||
|  |       description | ||||||
|  |     }) | ||||||
|  |     return new Template(this.client, payload) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Syncs the template to the guild's current state. */ | ||||||
|  |   async syncTemplate(code: string): Promise<Template> { | ||||||
|  |     const payload = await this.client.rest.api.guilds[this.id].templates[ | ||||||
|  |       code | ||||||
|  |     ].sync.put() | ||||||
|  |     return new Template(this.client, payload) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Modifies the template's metadata. */ | ||||||
|  |   async editTemplate( | ||||||
|  |     code: string, | ||||||
|  |     data: { name?: string; description?: string } | ||||||
|  |   ): Promise<Template> { | ||||||
|  |     const payload = await this.client.rest.api.guilds[this.id].templates[ | ||||||
|  |       code | ||||||
|  |     ].patch({ name: data.name, description: data.description }) | ||||||
|  |     return new Template(this.client, payload) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Deletes the template. Requires the MANAGE_GUILD permission. */ | ||||||
|  |   async deleteTemplate(code: string): Promise<Guild> { | ||||||
|  |     await this.client.rest.api.guilds[this.id].templates[code].delete() | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /** Gets a preview of the guild. Returns GuildPreview. */ |   /** Gets a preview of the guild. Returns GuildPreview. */ | ||||||
|   async preview(): Promise<GuildPreview> { |   async preview(): Promise<GuildPreview> { | ||||||
|     return this.client.guilds.preview(this.id) |     return this.client.guilds.preview(this.id) | ||||||
|  |  | ||||||
|  | @ -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" | ||||||
|  |  | ||||||
|  | @ -162,6 +162,15 @@ export interface GuildBanPayload { | ||||||
|   user: UserPayload |   user: UserPayload | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export interface GuildWidgetPayload { | ||||||
|  |   id: string | ||||||
|  |   name: string | ||||||
|  |   instant_invite: string | ||||||
|  |   channels: Array<{ id: string; name: string; position: number }> | ||||||
|  |   members: MemberPayload[] | ||||||
|  |   presence_count: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export type GuildChannelPayloads = | export type GuildChannelPayloads = | ||||||
|   | GuildTextChannelPayload |   | GuildTextChannelPayload | ||||||
|   | GuildVoiceChannelPayload |   | GuildVoiceChannelPayload | ||||||
|  |  | ||||||
|  | @ -42,3 +42,19 @@ export interface VoiceStatePayload { | ||||||
|   self_video: boolean |   self_video: boolean | ||||||
|   suppress: boolean |   suppress: boolean | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** Voice Region Structure */ | ||||||
|  | export interface VoiceRegion { | ||||||
|  |   /** Unique ID for the region */ | ||||||
|  |   id: string | ||||||
|  |   /** Name of the region */ | ||||||
|  |   name: string | ||||||
|  |   /** True if this is a vip-only server */ | ||||||
|  |   vip: boolean | ||||||
|  |   /** True for a single server that is closest to the current user's client */ | ||||||
|  |   optimal: boolean | ||||||
|  |   /** Whether this is a deprecated voice region (avoid switching to these) */ | ||||||
|  |   deprecated: boolean | ||||||
|  |   /** Whether this is a custom voice region (used for events/etc) */ | ||||||
|  |   custom: boolean | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										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