Merge branch 'main' into master
This commit is contained in:
		
						commit
						7c8e235bb4
					
				
					 23 changed files with 1133 additions and 644 deletions
				
			
		
							
								
								
									
										10
									
								
								.prettierrc
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								.prettierrc
									
										
									
									
									
								
							|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|     "tabWidth": 2, | ||||
|     "useTabs": false, | ||||
|     "semi": false, | ||||
|     "singleQuote": true | ||||
|   } | ||||
|   "tabWidth": 2, | ||||
|   "useTabs": false, | ||||
|   "semi": false, | ||||
|   "singleQuote": true | ||||
| } | ||||
|  |  | |||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
										
									
									
										vendored
									
									
								
							|  | @ -6,5 +6,6 @@ | |||
|   "deno.import_intellisense_origins": { | ||||
|     "https://deno.land": true | ||||
|   }, | ||||
|   "editor.tabSize": 2 | ||||
|   "editor.tabSize": 2, | ||||
|   "editor.formatOnSave": true | ||||
| } | ||||
							
								
								
									
										2
									
								
								LICENSE
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
										
									
									
									
								
							|  | @ -1,6 +1,6 @@ | |||
| MIT License | ||||
| 
 | ||||
| Copyright (c) 2020 Helloyunho | ||||
| Copyright (c) 2020 Harmony Org | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
| 
 | ||||
|  | ||||
| 
 | ||||
| [](https://github.com/RichardLitt/standard-readme) | ||||
| [](https://github.com/RichardLitt/standard-readme) [](https://discord.gg/WVN2JF2FRv) | ||||
| 
 | ||||
| **An easy to use Discord API Library for Deno.** | ||||
| * Lightweight and easy to use. | ||||
|  | @ -22,6 +22,7 @@ Note: Library is yet under development and not completely usable. You're still a | |||
| 
 | ||||
| - [Usage](#usage) | ||||
| - [Docs](#docs) | ||||
| - [Discord](#discord) | ||||
| - [Maintainer](#maintainer) | ||||
| - [Contributing](#contributing) | ||||
| - [License](#license) | ||||
|  | @ -92,6 +93,10 @@ client.connect('super secret token comes here', Intents.All) | |||
| 
 | ||||
| Not made yet. | ||||
| 
 | ||||
| ## Discord | ||||
| 
 | ||||
| [](https://discord.gg/WVN2JF2FRv) | ||||
| 
 | ||||
| ## Maintainer | ||||
| 
 | ||||
| [@Helloyunho](https://github.com/Helloyunho) | ||||
|  | @ -106,4 +111,4 @@ Small note: If editing the README, please conform to the [standard-readme](https | |||
| 
 | ||||
| ## License | ||||
| 
 | ||||
| [MIT © 2020 Helloyunho](LICENSE) | ||||
| [MIT © 2020 Harmony Org](LICENSE) | ||||
|  |  | |||
|  | @ -5,3 +5,5 @@ export const DISCORD_GATEWAY_URL: string = 'wss://gateway.discord.gg' | |||
| export const DISCORD_CDN_URL: string = 'https://cdn.discordapp.com' | ||||
| 
 | ||||
| export const DISCORD_API_VERSION: number = 8 | ||||
| 
 | ||||
| export const DISCORD_VOICE_VERSION: number = 4 | ||||
							
								
								
									
										31
									
								
								src/gateway/handlers/guildMembersChunk.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/gateway/handlers/guildMembersChunk.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| import { Guild } from '../../structures/guild.ts' | ||||
| import { GuildMemberChunkPayload } from '../../types/gateway.ts' | ||||
| 
 | ||||
| export const guildMembersChunk: GatewayEventHandler = async ( | ||||
|   gateway: Gateway, | ||||
|   d: GuildMemberChunkPayload | ||||
| ) => { | ||||
|   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) | ||||
|   // Weird case, shouldn't happen
 | ||||
|   if (guild === undefined) return | ||||
| 
 | ||||
|   for (const member of d.members) { | ||||
|     await guild.members.set(member.user.id, member) | ||||
|   } | ||||
| 
 | ||||
|   // TODO: Cache Presences
 | ||||
| 
 | ||||
|   gateway.client.emit('guildMembersChunk', guild, { | ||||
|     members: d.members.map((m) => m.user.id), | ||||
|     presences: | ||||
|       d.presences === undefined ? undefined : d.presences.map((p) => p.user.id), | ||||
|     chunkIndex: d.chunk_index, | ||||
|     chunkCount: d.chunk_count, | ||||
|   }) | ||||
| 
 | ||||
|   // Guild is now completely chunked. Emit an event for that.
 | ||||
|   if (d.chunk_index >= d.chunk_count - 1) { | ||||
|     gateway.client.emit('guildMembersChunked', guild, d.chunk_count) | ||||
|   } | ||||
| } | ||||
|  | @ -36,6 +36,9 @@ import { Member } from '../../structures/member.ts' | |||
| import { Role } from '../../structures/role.ts' | ||||
| import { Message } from '../../structures/message.ts' | ||||
| import { Collection } from '../../utils/collection.ts' | ||||
| import { voiceServerUpdate } from './voiceServerUpdate.ts' | ||||
| import { voiceStateUpdate } from './voiceStateUpdate.ts' | ||||
| import { VoiceState } from '../../structures/voiceState.ts' | ||||
| import { inviteCreate } from './inviteCreate.ts' | ||||
| import { inviteDelete } from './inviteDelete.ts' | ||||
| 
 | ||||
|  | @ -75,52 +78,54 @@ export const gatewayHandlers: { | |||
|   MESSAGE_REACTION_REMOVE_EMOJI: undefined, | ||||
|   PRESENCE_UPDATE: undefined, | ||||
|   TYPING_START: typingStart, | ||||
|   USER_UPDATE: userUpdate, | ||||
|   VOICE_SERVER_UPDATE: undefined, | ||||
|   WEBHOOKS_UPDATE: webhooksUpdate, | ||||
|   USER_UPDATE: userUpdate | ||||
|   VOICE_STATE_UPDATE: voiceStateUpdate, | ||||
|   VOICE_SERVER_UPDATE: voiceServerUpdate, | ||||
|   WEBHOOKS_UPDATE: webhooksUpdate | ||||
| } | ||||
| 
 | ||||
| export interface EventTypes { | ||||
|   [name: string]: (...args: any[]) => void | ||||
| } | ||||
| 
 | ||||
| export interface ClientEvents extends EventTypes { | ||||
|   ready: () => void | ||||
|   reconnect: () => void | ||||
|   resumed: () => void | ||||
|   channelCreate: (channel: Channel) => void | ||||
|   channelDelete: (channel: Channel) => void | ||||
|   channelPinsUpdate: (before: TextChannel, after: TextChannel) => void | ||||
|   channelUpdate: (before: Channel, after: Channel) => void | ||||
|   guildBanAdd: (guild: Guild, user: User) => void | ||||
|   guildBanRemove: (guild: Guild, user: User) => void | ||||
|   guildCreate: (guild: Guild) => void | ||||
|   guildDelete: (guild: Guild) => void | ||||
|   guildEmojiAdd: (guild: Guild, emoji: Emoji) => void | ||||
|   guildEmojiDelete: (guild: Guild, emoji: Emoji) => void | ||||
|   guildEmojiUpdate: (guild: Guild, before: Emoji, after: Emoji) => void | ||||
|   guildIntegrationsUpdate: (guild: Guild) => void | ||||
|   guildMemberAdd: (member: Member) => void | ||||
|   guildMemberRemove: (member: Member) => void | ||||
|   guildMemberUpdate: (before: Member, after: Member) => void | ||||
|   guildRoleCreate: (role: Role) => void | ||||
|   guildRoleDelete: (role: Role) => void | ||||
|   guildRoleUpdate: (before: Role, after: Role) => void | ||||
|   guildUpdate: (before: Guild, after: Guild) => void | ||||
|   messageCreate: (message: Message) => void | ||||
|   messageDelete: (message: Message) => void | ||||
|   messageDeleteBulk: ( | ||||
|     channel: GuildTextChannel, | ||||
|     messages: Collection<string, Message>, | ||||
|     uncached: Set<string> | ||||
|   ) => void | ||||
|   messageUpdate: (before: Message, after: Message) => void | ||||
|   typingStart: ( | ||||
|     user: User, | ||||
|     channel: TextChannel, | ||||
|     at: Date, | ||||
|     guildData?: TypingStartGuildData | ||||
|   ) => void | ||||
|   userUpdate: (before: User, after: User) => void | ||||
|   webhooksUpdate: (guild: Guild, channel: GuildTextChannel) => void | ||||
| export interface VoiceServerUpdateData { | ||||
|   token: string | ||||
|   endpoint: string | ||||
|   guild: Guild | ||||
| } | ||||
| 
 | ||||
| export interface ClientEvents extends EventTypes { | ||||
|   'ready': () => void | ||||
|   'reconnect': () => void | ||||
|   'resumed': () => void | ||||
|   'channelCreate': (channel: Channel) => void | ||||
|   'channelDelete': (channel: Channel) => void | ||||
|   'channelPinsUpdate': (before: TextChannel, after: TextChannel) => void | ||||
|   'channelUpdate': (before: Channel, after: Channel) => void | ||||
|   'guildBanAdd': (guild: Guild, user: User) => void | ||||
|   'guildBanRemove': (guild: Guild, user: User) => void | ||||
|   'guildCreate': (guild: Guild) => void | ||||
|   'guildDelete': (guild: Guild) => void | ||||
|   'guildEmojiAdd': (guild: Guild, emoji: Emoji) => void | ||||
|   'guildEmojiDelete': (guild: Guild, emoji: Emoji) => void | ||||
|   'guildEmojiUpdate': (guild: Guild, before: Emoji, after: Emoji) => void | ||||
|   'guildIntegrationsUpdate': (guild: Guild) => void | ||||
|   'guildMemberAdd': (member: Member) => void | ||||
|   'guildMemberRemove': (member: Member) => void | ||||
|   'guildMemberUpdate': (before: Member, after: Member) => void | ||||
|   'guildRoleCreate': (role: Role) => void | ||||
|   'guildRoleDelete': (role: Role) => void | ||||
|   'guildRoleUpdate': (before: Role, after: Role) => void | ||||
|   'guildUpdate': (before: Guild, after: Guild) => void | ||||
|   'messageCreate': (message: Message) => void | ||||
|   'messageDelete': (message: Message) => void | ||||
|   'messageDeleteBulk': (channel: GuildTextChannel, messages: Collection<string, Message>, uncached: Set<string>) => void | ||||
|   'messageUpdate': (before: Message, after: Message) => void | ||||
|   'typingStart': (user: User, channel: TextChannel, at: Date, guildData?: TypingStartGuildData) => void | ||||
|   'userUpdate': (before: User, after: User) => void | ||||
|   'voiceServerUpdate': (data: VoiceServerUpdateData) => void | ||||
|   'voiceStateAdd': (state: VoiceState) => void | ||||
|   'voiceStateRemove': (state: VoiceState) => void | ||||
|   'voiceStateUpdate': (state: VoiceState, after: VoiceState) => void | ||||
|   'webhooksUpdate': (guild: Guild, channel: GuildTextChannel) => void | ||||
| } | ||||
|  |  | |||
							
								
								
									
										14
									
								
								src/gateway/handlers/voiceServerUpdate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/gateway/handlers/voiceServerUpdate.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | |||
| import { Guild } from "../../structures/guild.ts" | ||||
| import { VoiceServerUpdatePayload } from "../../types/gateway.ts" | ||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| 
 | ||||
| export const voiceServerUpdate: GatewayEventHandler = async ( | ||||
|   gateway: Gateway, | ||||
|   d: VoiceServerUpdatePayload | ||||
| ) => { | ||||
|   gateway.client.emit('voiceServerUpdate', { | ||||
|     token: d.token, | ||||
|     endpoint: d.endpoint, | ||||
|     guild: (await gateway.client.guilds.get(d.guild_id) as unknown) as Guild | ||||
|   }) | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/gateway/handlers/voiceStateUpdate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/gateway/handlers/voiceStateUpdate.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| import { Guild } from "../../structures/guild.ts" | ||||
| import { VoiceState } from "../../structures/voiceState.ts" | ||||
| import { VoiceStatePayload } from "../../types/voice.ts" | ||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| 
 | ||||
| export const voiceStateUpdate: GatewayEventHandler = async ( | ||||
|   gateway: Gateway, | ||||
|   d: VoiceStatePayload | ||||
| ) => { | ||||
|   // TODO(DjDeveloperr): Support self-bot here; they can be in DMs (Call)
 | ||||
|   if (d.guild_id === undefined) return | ||||
|   const guild = (await gateway.client.guilds.get(d.guild_id) as unknown) as Guild | ||||
| 
 | ||||
|   const voiceState = await guild.voiceStates.get(d.user_id) | ||||
| 
 | ||||
|   if (d.channel_id === null) { | ||||
|     // No longer in the channel, so delete
 | ||||
|     await guild.voiceStates.delete(d.user_id) | ||||
|     gateway.client.emit('voiceStateRemove', (voiceState as unknown) as VoiceState) | ||||
|     return | ||||
|   } | ||||
| 
 | ||||
|   await guild.voiceStates.set(d.user_id, d) | ||||
|   const newVoiceState = await guild.voiceStates.get(d.user_id) | ||||
|   if (voiceState === undefined) { | ||||
|     gateway.client.emit('voiceStateAdd', (newVoiceState as unknown) as VoiceState) | ||||
|   } else { | ||||
|     gateway.client.emit('voiceStateUpdate', voiceState, (newVoiceState as unknown) as VoiceState) | ||||
|   } | ||||
| } | ||||
|  | @ -2,7 +2,7 @@ import { unzlib } from 'https://deno.land/x/denoflate@1.1/mod.ts' | |||
| import { Client } from '../models/client.ts' | ||||
| import { | ||||
|   DISCORD_GATEWAY_URL, | ||||
|   DISCORD_API_VERSION | ||||
|   DISCORD_API_VERSION, | ||||
| } from '../consts/urlsAndVersions.ts' | ||||
| import { GatewayResponse } from '../types/gatewayResponse.ts' | ||||
| import { | ||||
|  | @ -10,18 +10,23 @@ import { | |||
|   GatewayIntents, | ||||
|   GatewayCloseCodes, | ||||
|   IdentityPayload, | ||||
|   StatusUpdatePayload | ||||
|   StatusUpdatePayload, | ||||
| } from '../types/gateway.ts' | ||||
| import { gatewayHandlers } from './handlers/index.ts' | ||||
| import { GATEWAY_BOT } from '../types/endpoint.ts' | ||||
| import { GatewayCache } from '../managers/gatewayCache.ts' | ||||
| import { delay } from '../utils/delay.ts' | ||||
| 
 | ||||
| export interface RequestMembersOptions { | ||||
|   limit?: number | ||||
|   presences?: boolean | ||||
|   query?: string | ||||
|   users?: string[] | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Handles Discord gateway connection. | ||||
|  * You should not use this and rather use Client class. | ||||
|  * | ||||
|  * @beta | ||||
|  */ | ||||
| class Gateway { | ||||
|   websocket: WebSocket | ||||
|  | @ -38,7 +43,7 @@ class Gateway { | |||
|   client: Client | ||||
|   cache: GatewayCache | ||||
| 
 | ||||
|   constructor (client: Client, token: string, intents: GatewayIntents[]) { | ||||
|   constructor(client: Client, token: string, intents: GatewayIntents[]) { | ||||
|     this.token = token | ||||
|     this.intents = intents | ||||
|     this.client = client | ||||
|  | @ -55,12 +60,12 @@ class Gateway { | |||
|     this.websocket.onerror = this.onerror.bind(this) | ||||
|   } | ||||
| 
 | ||||
|   private onopen (): void { | ||||
|   private onopen(): void { | ||||
|     this.connected = true | ||||
|     this.debug('Connected to Gateway!') | ||||
|   } | ||||
| 
 | ||||
|   private async onmessage (event: MessageEvent): Promise<void> { | ||||
|   private async onmessage(event: MessageEvent): Promise<void> { | ||||
|     let data = event.data | ||||
|     if (data instanceof ArrayBuffer) { | ||||
|       data = new Uint8Array(data) | ||||
|  | @ -144,7 +149,7 @@ class Gateway { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private async onclose (event: CloseEvent): Promise<void> { | ||||
|   private async onclose(event: CloseEvent): Promise<void> { | ||||
|     this.debug(`Connection Closed with code: ${event.code}`) | ||||
| 
 | ||||
|     if (event.code === GatewayCloseCodes.UNKNOWN_ERROR) { | ||||
|  | @ -191,12 +196,12 @@ class Gateway { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private onerror (event: Event | ErrorEvent): void { | ||||
|   private onerror(event: Event | ErrorEvent): void { | ||||
|     const eventError = event as ErrorEvent | ||||
|     console.log(eventError) | ||||
|   } | ||||
| 
 | ||||
|   private async sendIdentify (forceNewSession?: boolean): Promise<void> { | ||||
|   private async sendIdentify(forceNewSession?: boolean): Promise<void> { | ||||
|     if (this.client.bot === true) { | ||||
|       this.debug('Fetching /gateway/bot...') | ||||
|       const info = await this.client.rest.get(GATEWAY_BOT()) | ||||
|  | @ -225,7 +230,7 @@ class Gateway { | |||
|       properties: { | ||||
|         $os: Deno.build.os, | ||||
|         $browser: 'harmony', | ||||
|         $device: 'harmony' | ||||
|         $device: 'harmony', | ||||
|       }, | ||||
|       compress: true, | ||||
|       shard: [0, 1], // TODO: Make sharding possible
 | ||||
|  | @ -233,7 +238,7 @@ class Gateway { | |||
|         (previous, current) => previous | current, | ||||
|         0 | ||||
|       ), | ||||
|       presence: this.client.presence.create() | ||||
|       presence: this.client.presence.create(), | ||||
|     } | ||||
| 
 | ||||
|     if (this.client.bot === false) { | ||||
|  | @ -245,17 +250,17 @@ class Gateway { | |||
|         $browser: 'Firefox', | ||||
|         $device: '', | ||||
|         $referrer: '', | ||||
|         $referring_domain: '' | ||||
|         $referring_domain: '', | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     this.send({ | ||||
|       op: GatewayOpcodes.IDENTIFY, | ||||
|       d: payload | ||||
|       d: payload, | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   private async sendResume (): Promise<void> { | ||||
|   private async sendResume(): Promise<void> { | ||||
|     this.debug(`Preparing to resume with Session: ${this.sessionID}`) | ||||
|     if (this.sequenceID === undefined) { | ||||
|       const cached = await this.cache.get('seq') | ||||
|  | @ -267,17 +272,37 @@ class Gateway { | |||
|       d: { | ||||
|         token: this.token, | ||||
|         session_id: this.sessionID, | ||||
|         seq: this.sequenceID ?? null | ||||
|       } | ||||
|         seq: this.sequenceID ?? null, | ||||
|       }, | ||||
|     } | ||||
|     this.send(resumePayload) | ||||
|   } | ||||
| 
 | ||||
|   debug (msg: string): void { | ||||
|   requestMembers(guild: string, options: RequestMembersOptions = {}): string { | ||||
|     if (options.query !== undefined && options.limit === undefined) | ||||
|       throw new Error( | ||||
|         'Missing limit property when specifying query for Requesting Members!' | ||||
|       ) | ||||
|     const nonce = `${guild}_${new Date().getTime()}` | ||||
|     this.send({ | ||||
|       op: GatewayOpcodes.REQUEST_GUILD_MEMBERS, | ||||
|       d: { | ||||
|         guild_id: guild, | ||||
|         query: options.query, | ||||
|         limit: options.limit, | ||||
|         presences: options.presences, | ||||
|         user_ids: options.users, | ||||
|         nonce, | ||||
|       }, | ||||
|     }) | ||||
|     return nonce | ||||
|   } | ||||
| 
 | ||||
|   debug(msg: string): void { | ||||
|     this.client.debug('Gateway', msg) | ||||
|   } | ||||
| 
 | ||||
|   async reconnect (forceNew?: boolean): Promise<void> { | ||||
|   async reconnect(forceNew?: boolean): Promise<void> { | ||||
|     clearInterval(this.heartbeatIntervalID) | ||||
|     if (forceNew === undefined || !forceNew) | ||||
|       await this.cache.delete('session_id') | ||||
|  | @ -285,7 +310,7 @@ class Gateway { | |||
|     this.initWebsocket() | ||||
|   } | ||||
| 
 | ||||
|   initWebsocket (): void { | ||||
|   initWebsocket(): void { | ||||
|     this.websocket = new WebSocket( | ||||
|       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
 | ||||
|       `${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`, | ||||
|  | @ -298,41 +323,41 @@ class Gateway { | |||
|     this.websocket.onerror = this.onerror.bind(this) | ||||
|   } | ||||
| 
 | ||||
|   close (): void { | ||||
|   close(): void { | ||||
|     this.websocket.close(1000) | ||||
|   } | ||||
| 
 | ||||
|   send (data: GatewayResponse): boolean { | ||||
|   send(data: GatewayResponse): boolean { | ||||
|     if (this.websocket.readyState !== this.websocket.OPEN) return false | ||||
|     this.websocket.send( | ||||
|       JSON.stringify({ | ||||
|         op: data.op, | ||||
|         d: data.d, | ||||
|         s: typeof data.s === 'number' ? data.s : null, | ||||
|         t: data.t === undefined ? null : data.t | ||||
|         t: data.t === undefined ? null : data.t, | ||||
|       }) | ||||
|     ) | ||||
|     return true | ||||
|   } | ||||
| 
 | ||||
|   sendPresence (data: StatusUpdatePayload): void { | ||||
|   sendPresence(data: StatusUpdatePayload): void { | ||||
|     this.send({ | ||||
|       op: GatewayOpcodes.PRESENCE_UPDATE, | ||||
|       d: data | ||||
|       d: data, | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   sendHeartbeat (): void { | ||||
|   sendHeartbeat(): void { | ||||
|     const payload = { | ||||
|       op: GatewayOpcodes.HEARTBEAT, | ||||
|       d: this.sequenceID ?? null | ||||
|       d: this.sequenceID ?? null, | ||||
|     } | ||||
| 
 | ||||
|     this.send(payload) | ||||
|     this.lastPingTimestamp = Date.now() | ||||
|   } | ||||
| 
 | ||||
|   heartbeat (): void { | ||||
|   heartbeat(): void { | ||||
|     if (this.heartbeatServerResponded) { | ||||
|       this.heartbeatServerResponded = false | ||||
|     } else { | ||||
|  |  | |||
							
								
								
									
										30
									
								
								src/managers/guildVoiceStates.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/managers/guildVoiceStates.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { Guild } from "../structures/guild.ts" | ||||
| import { VoiceChannel } from "../structures/guildVoiceChannel.ts" | ||||
| import { User } from "../structures/user.ts" | ||||
| import { VoiceState } from "../structures/voiceState.ts" | ||||
| import { VoiceStatePayload } from "../types/voice.ts" | ||||
| import { BaseManager } from './base.ts' | ||||
| 
 | ||||
| export class GuildVoiceStatesManager extends BaseManager<VoiceStatePayload, VoiceState> { | ||||
|   guild: Guild | ||||
| 
 | ||||
|   async get (key: string): Promise<VoiceState | undefined> { | ||||
|     const raw = await this._get(key) | ||||
|     if (raw === undefined) return | ||||
| 
 | ||||
|     const guild = raw.guild_id === undefined ? undefined : await this.client.guilds.get(raw.guild_id) | ||||
| 
 | ||||
|     return new VoiceState(this.client, raw, { | ||||
|       user: (await this.client.users.get(raw.user_id) as unknown) as User, | ||||
|       channel: raw.channel_id == null ? null : (await this.client.channels.get<VoiceChannel>(raw.channel_id) as unknown) as VoiceChannel, | ||||
|       guild, | ||||
|       member: guild === undefined ? undefined : await guild.members.get(raw.user_id)  | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   constructor (client: Client, guild: Guild) { | ||||
|     super(client, `vs:${guild.id}`, VoiceState) | ||||
|     this.guild = guild | ||||
|   } | ||||
| } | ||||
|  | @ -1,42 +1,81 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { BaseChildManager } from './baseChild.ts' | ||||
| import { RolePayload } from "../types/role.ts" | ||||
| import { Role } from "../structures/role.ts" | ||||
| import { Member } from "../structures/member.ts" | ||||
| import { RolesManager } from "./roles.ts" | ||||
| import { MemberPayload } from "../types/guild.ts" | ||||
| import { RolePayload } from '../types/role.ts' | ||||
| import { Role } from '../structures/role.ts' | ||||
| import { Member } from '../structures/member.ts' | ||||
| import { RolesManager } from './roles.ts' | ||||
| import { MemberPayload } from '../types/guild.ts' | ||||
| import { GUILD_MEMBER_ROLE } from '../types/endpoint.ts' | ||||
| 
 | ||||
| export class MemberRolesManager extends BaseChildManager< | ||||
|   RolePayload, | ||||
|   Role | ||||
| > { | ||||
| export class MemberRolesManager extends BaseChildManager<RolePayload, Role> { | ||||
|   member: Member | ||||
| 
 | ||||
|   constructor (client: Client, parent: RolesManager, member: Member) { | ||||
|   constructor(client: Client, parent: RolesManager, member: Member) { | ||||
|     super(client, parent as any) | ||||
|     this.member = member | ||||
|   } | ||||
| 
 | ||||
|   async get (id: string): Promise<Role | undefined> { | ||||
|   async get(id: string): Promise<Role | undefined> { | ||||
|     const res = await this.parent.get(id) | ||||
|     const mem = await (this.parent as any).guild.members._get(this.member.id) as MemberPayload | ||||
|     if (res !== undefined && (mem.roles.includes(res.id) === true || res.id === this.member.guild.id)) return res | ||||
|     const mem = (await (this.parent as any).guild.members._get( | ||||
|       this.member.id | ||||
|     )) as MemberPayload | ||||
|     if ( | ||||
|       res !== undefined && | ||||
|       (mem.roles.includes(res.id) === true || res.id === this.member.guild.id) | ||||
|     ) | ||||
|       return res | ||||
|     else return undefined | ||||
|   } | ||||
| 
 | ||||
|   async array (): Promise<Role[]> { | ||||
|   async array(): Promise<Role[]> { | ||||
|     const arr = (await this.parent.array()) as Role[] | ||||
|     const mem = await (this.parent as any).guild.members._get(this.member.id) as MemberPayload | ||||
|     const mem = (await (this.parent as any).guild.members._get( | ||||
|       this.member.id | ||||
|     )) as MemberPayload | ||||
|     return arr.filter( | ||||
|       (c: any) => mem.roles.includes(c.id) as boolean || c.id === this.member.guild.id | ||||
|       (c: any) => | ||||
|         (mem.roles.includes(c.id) as boolean) || c.id === this.member.guild.id | ||||
|     ) as any | ||||
|   } | ||||
| 
 | ||||
|   async flush (): Promise<boolean> { | ||||
|   async flush(): Promise<boolean> { | ||||
|     const arr = await this.array() | ||||
|     for (const elem of arr) { | ||||
|       this.parent.delete(elem.id) | ||||
|     } | ||||
|     return true | ||||
|   } | ||||
| 
 | ||||
|   async add(role: string | Role): Promise<boolean> { | ||||
|     const res = await this.client.rest.put( | ||||
|       GUILD_MEMBER_ROLE( | ||||
|         this.member.guild.id, | ||||
|         this.member.id, | ||||
|         typeof role === 'string' ? role : role.id | ||||
|       ), | ||||
|       undefined, | ||||
|       undefined, | ||||
|       undefined, | ||||
|       true | ||||
|     ) | ||||
| 
 | ||||
|     return res.status === 204 | ||||
|   } | ||||
| 
 | ||||
|   async remove(role: string | Role): Promise<boolean> { | ||||
|     const res = await this.client.rest.delete( | ||||
|       GUILD_MEMBER_ROLE( | ||||
|         this.member.guild.id, | ||||
|         this.member.id, | ||||
|         typeof role === 'string' ? role : role.id | ||||
|       ), | ||||
|       undefined, | ||||
|       undefined, | ||||
|       undefined, | ||||
|       true | ||||
|     ) | ||||
| 
 | ||||
|     return res.status === 204 | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -3,7 +3,13 @@ import { Client } from './client.ts' | |||
| import { getBuildInfo } from '../utils/buildInfo.ts' | ||||
| import { Collection } from '../utils/collection.ts' | ||||
| 
 | ||||
| export type RequestMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' | ||||
| export type RequestMethods = | ||||
|   | 'get' | ||||
|   | 'post' | ||||
|   | 'put' | ||||
|   | 'patch' | ||||
|   | 'head' | ||||
|   | 'delete' | ||||
| 
 | ||||
| export enum HttpResponseCode { | ||||
|   Ok = 200, | ||||
|  | @ -16,7 +22,7 @@ export enum HttpResponseCode { | |||
|   NotFound = 404, | ||||
|   MethodNotAllowed = 405, | ||||
|   TooManyRequests = 429, | ||||
|   GatewayUnavailable = 502 | ||||
|   GatewayUnavailable = 502, | ||||
| } | ||||
| 
 | ||||
| export interface RequestHeaders { | ||||
|  | @ -30,11 +36,14 @@ export class DiscordAPIError extends Error { | |||
| export interface QueuedItem { | ||||
|   bucket?: string | null | ||||
|   url: string | ||||
|   onComplete: () => Promise<{ | ||||
|     rateLimited: any | ||||
|     bucket?: string | null | ||||
|     before: boolean | ||||
|   } | undefined> | ||||
|   onComplete: () => Promise< | ||||
|     | { | ||||
|         rateLimited: any | ||||
|         bucket?: string | null | ||||
|         before: boolean | ||||
|       } | ||||
|     | undefined | ||||
|   > | ||||
| } | ||||
| 
 | ||||
| export interface RateLimit { | ||||
|  | @ -85,16 +94,14 @@ export class RESTManager { | |||
|   async processQueue(): Promise<void> { | ||||
|     if (Object.keys(this.queues).length !== 0 && !this.globalRateLimit) { | ||||
|       await Promise.allSettled( | ||||
|         Object.values(this.queues).map(async pathQueue => { | ||||
|         Object.values(this.queues).map(async (pathQueue) => { | ||||
|           const request = pathQueue.shift() | ||||
|           if (request === undefined) return | ||||
| 
 | ||||
|           const rateLimitedURLResetIn = await this.isRateLimited(request.url) | ||||
| 
 | ||||
|           if (typeof request.bucket === 'string') { | ||||
|             const rateLimitResetIn = await this.isRateLimited( | ||||
|               request.bucket | ||||
|             ) | ||||
|             const rateLimitResetIn = await this.isRateLimited(request.bucket) | ||||
|             if (rateLimitResetIn !== false) { | ||||
|               this.queue(request) | ||||
|             } else { | ||||
|  | @ -102,7 +109,7 @@ export class RESTManager { | |||
|               if (result?.rateLimited !== undefined) { | ||||
|                 this.queue({ | ||||
|                   ...request, | ||||
|                   bucket: result.bucket ?? request.bucket | ||||
|                   bucket: result.bucket ?? request.bucket, | ||||
|                 }) | ||||
|               } | ||||
|             } | ||||
|  | @ -114,7 +121,7 @@ export class RESTManager { | |||
|               if (result?.rateLimited !== undefined) { | ||||
|                 this.queue({ | ||||
|                   ...request, | ||||
|                   bucket: result.bucket ?? request.bucket | ||||
|                   bucket: result.bucket ?? request.bucket, | ||||
|                 }) | ||||
|               } | ||||
|             } | ||||
|  | @ -132,20 +139,18 @@ export class RESTManager { | |||
|     } else this.processing = false | ||||
|   } | ||||
| 
 | ||||
|   prepare( | ||||
|     body: any, | ||||
|     method: RequestMethods | ||||
|   ): { [key: string]: any } { | ||||
| 
 | ||||
|   prepare(body: any, method: RequestMethods): { [key: string]: any } { | ||||
|     const headers: RequestHeaders = { | ||||
|       'User-Agent': `DiscordBot (harmony, https://github.com/harmony-org/harmony)` | ||||
|       'User-Agent': `DiscordBot (harmony, https://github.com/harmony-org/harmony)`, | ||||
|     } | ||||
| 
 | ||||
|     if (this.client !== undefined) headers.Authorization = `Bot ${this.client.token}` | ||||
|     if (this.client !== undefined) | ||||
|       headers.Authorization = `Bot ${this.client.token}` | ||||
| 
 | ||||
|     if (this.client?.token === undefined) delete headers.Authorization | ||||
| 
 | ||||
|     if (method === 'get' || method === 'head' || method === 'delete') body = undefined | ||||
|     if (method === 'get' || method === 'head' || method === 'delete') | ||||
|       body = undefined | ||||
| 
 | ||||
|     if (body?.reason !== undefined) { | ||||
|       headers['X-Audit-Log-Reason'] = encodeURIComponent(body.reason) | ||||
|  | @ -163,7 +168,7 @@ export class RESTManager { | |||
|     const data: { [name: string]: any } = { | ||||
|       headers, | ||||
|       body: body?.file ?? JSON.stringify(body), | ||||
|       method: method.toUpperCase() | ||||
|       method: method.toUpperCase(), | ||||
|     } | ||||
| 
 | ||||
|     if (this.client?.bot === false) { | ||||
|  | @ -217,14 +222,14 @@ export class RESTManager { | |||
|       this.rateLimits.set(url, { | ||||
|         url, | ||||
|         resetAt: Number(resetAt) * 1000, | ||||
|         bucket | ||||
|         bucket, | ||||
|       }) | ||||
| 
 | ||||
|       if (bucket !== null) { | ||||
|         this.rateLimits.set(bucket, { | ||||
|           url, | ||||
|           resetAt: Number(resetAt) * 1000, | ||||
|           bucket | ||||
|           bucket, | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
|  | @ -237,14 +242,14 @@ export class RESTManager { | |||
|       this.rateLimits.set('global', { | ||||
|         url: 'global', | ||||
|         resetAt: reset, | ||||
|         bucket | ||||
|         bucket, | ||||
|       }) | ||||
| 
 | ||||
|       if (bucket !== null) { | ||||
|         this.rateLimits.set(bucket, { | ||||
|           url: 'global', | ||||
|           resetAt: reset, | ||||
|           bucket | ||||
|           bucket, | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
|  | @ -253,32 +258,46 @@ export class RESTManager { | |||
|   } | ||||
| 
 | ||||
|   async handleStatusCode( | ||||
|     response: Response, body: any, data: { [key: string]: any } | ||||
|     response: Response, | ||||
|     body: any, | ||||
|     data: { [key: string]: any } | ||||
|   ): Promise<undefined> { | ||||
|     const status = response.status | ||||
| 
 | ||||
|     if ( | ||||
|       (status >= 200 && status < 400) | ||||
|       || status === HttpResponseCode.NoContent | ||||
|       || status === HttpResponseCode.TooManyRequests | ||||
|     ) return | ||||
|       (status >= 200 && status < 400) || | ||||
|       status === HttpResponseCode.NoContent || | ||||
|       status === HttpResponseCode.TooManyRequests | ||||
|     ) | ||||
|       return | ||||
| 
 | ||||
|     let text: undefined | string = Deno.inspect(body.errors === undefined ? body : body.errors) | ||||
|     let text: undefined | string = Deno.inspect( | ||||
|       body.errors === undefined ? body : body.errors | ||||
|     ) | ||||
|     if (text === 'undefined') text = undefined | ||||
| 
 | ||||
|     if (status === HttpResponseCode.Unauthorized) | ||||
|       throw new DiscordAPIError(`Request was not successful (Unauthorized). Invalid Token.\n${text}`) | ||||
|       throw new DiscordAPIError( | ||||
|         `Request was not successful (Unauthorized). Invalid Token.\n${text}` | ||||
|       ) | ||||
| 
 | ||||
|     // At this point we know it is error
 | ||||
|     let error = { url: response.url, status, method: data.method, body: data.body } | ||||
|     let error = { | ||||
|       url: response.url, | ||||
|       status, | ||||
|       method: data.method, | ||||
|       body: data.body, | ||||
|     } | ||||
|     if (body !== undefined) error = Object.assign(error, body) | ||||
| 
 | ||||
|     if ([ | ||||
|       HttpResponseCode.BadRequest, | ||||
|       HttpResponseCode.NotFound, | ||||
|       HttpResponseCode.Forbidden, | ||||
|       HttpResponseCode.MethodNotAllowed | ||||
|     ].includes(status)) { | ||||
|     if ( | ||||
|       [ | ||||
|         HttpResponseCode.BadRequest, | ||||
|         HttpResponseCode.NotFound, | ||||
|         HttpResponseCode.Forbidden, | ||||
|         HttpResponseCode.MethodNotAllowed, | ||||
|       ].includes(status) | ||||
|     ) { | ||||
|       throw new DiscordAPIError(Deno.inspect(error)) | ||||
|     } else if (status === HttpResponseCode.GatewayUnavailable) { | ||||
|       throw new DiscordAPIError(Deno.inspect(error)) | ||||
|  | @ -291,7 +310,7 @@ export class RESTManager { | |||
|     body?: unknown, | ||||
|     maxRetries = 0, | ||||
|     bucket?: string | null, | ||||
|     rawResponse?: boolean, | ||||
|     rawResponse?: boolean | ||||
|   ): Promise<any> { | ||||
|     return await new Promise((resolve, reject) => { | ||||
|       const onComplete = async (): Promise<undefined | any> => { | ||||
|  | @ -301,20 +320,20 @@ export class RESTManager { | |||
|             return { | ||||
|               rateLimited: rateLimitResetIn, | ||||
|               before: true, | ||||
|               bucket | ||||
|               bucket, | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|           const query = | ||||
|             method === 'get' && body !== undefined | ||||
|               ? Object.entries(body as any) | ||||
|                 .map( | ||||
|                   ([key, value]) => | ||||
|                     `${encodeURIComponent(key)}=${encodeURIComponent( | ||||
|                       value as any | ||||
|                     )}` | ||||
|                 ) | ||||
|                 .join('&') | ||||
|                   .map( | ||||
|                     ([key, value]) => | ||||
|                       `${encodeURIComponent(key)}=${encodeURIComponent( | ||||
|                         value as any | ||||
|                       )}` | ||||
|                   ) | ||||
|                   .join('&') | ||||
|               : '' | ||||
|           let urlToUse = | ||||
|             method === 'get' && query !== '' ? `${url}?${query}` : url | ||||
|  | @ -329,7 +348,10 @@ export class RESTManager { | |||
|           const response = await fetch(urlToUse, requestData) | ||||
|           const bucketFromHeaders = this.processHeaders(url, response.headers) | ||||
| 
 | ||||
|           if (response.status === 204) return resolve(rawResponse === true ? { response, body: null } : undefined) | ||||
|           if (response.status === 204) | ||||
|             return resolve( | ||||
|               rawResponse === true ? { response, body: null } : undefined | ||||
|             ) | ||||
| 
 | ||||
|           const json: any = await response.json() | ||||
|           await this.handleStatusCode(response, json, requestData) | ||||
|  | @ -345,7 +367,7 @@ export class RESTManager { | |||
|             return { | ||||
|               rateLimited: json.retry_after, | ||||
|               before: false, | ||||
|               bucket: bucketFromHeaders | ||||
|               bucket: bucketFromHeaders, | ||||
|             } | ||||
|           } | ||||
|           return resolve(rawResponse === true ? { response, body: json } : json) | ||||
|  | @ -357,7 +379,7 @@ export class RESTManager { | |||
|       this.queue({ | ||||
|         onComplete, | ||||
|         bucket, | ||||
|         url | ||||
|         url, | ||||
|       }) | ||||
|       if (!this.processing) { | ||||
|         this.processing = true | ||||
|  | @ -376,23 +398,53 @@ export class RESTManager { | |||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   async get(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise<any> { | ||||
|   async get( | ||||
|     url: string, | ||||
|     body?: unknown, | ||||
|     maxRetries = 0, | ||||
|     bucket?: string | null, | ||||
|     rawResponse?: boolean | ||||
|   ): Promise<any> { | ||||
|     return await this.make('get', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| 
 | ||||
|   async post(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise<any> { | ||||
|   async post( | ||||
|     url: string, | ||||
|     body?: unknown, | ||||
|     maxRetries = 0, | ||||
|     bucket?: string | null, | ||||
|     rawResponse?: boolean | ||||
|   ): Promise<any> { | ||||
|     return await this.make('post', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| 
 | ||||
|   async delete(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise<any> { | ||||
|   async delete( | ||||
|     url: string, | ||||
|     body?: unknown, | ||||
|     maxRetries = 0, | ||||
|     bucket?: string | null, | ||||
|     rawResponse?: boolean | ||||
|   ): Promise<any> { | ||||
|     return await this.make('delete', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| 
 | ||||
|   async patch(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise<any> { | ||||
|   async patch( | ||||
|     url: string, | ||||
|     body?: unknown, | ||||
|     maxRetries = 0, | ||||
|     bucket?: string | null, | ||||
|     rawResponse?: boolean | ||||
|   ): Promise<any> { | ||||
|     return await this.make('patch', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| 
 | ||||
|   async put(url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean): Promise<any> { | ||||
|   async put( | ||||
|     url: string, | ||||
|     body?: unknown, | ||||
|     maxRetries = 0, | ||||
|     bucket?: string | null, | ||||
|     rawResponse?: boolean | ||||
|   ): Promise<any> { | ||||
|     return await this.make('put', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,10 +1,26 @@ | |||
| import { Guild } from "../structures/guild.ts" | ||||
| import { VoiceChannel } from "../structures/guildVoiceChannel.ts" | ||||
| import { Client } from './client.ts' | ||||
| 
 | ||||
| export interface VoiceOptions { | ||||
|   guild: Guild, | ||||
|   channel: VoiceChannel | ||||
| } | ||||
| 
 | ||||
| export class VoiceClient { | ||||
|   client: Client | ||||
|   ws?: WebSocket | ||||
|   guild: Guild | ||||
|   channel: VoiceChannel | ||||
| 
 | ||||
|   constructor(client: Client) { | ||||
|   constructor(client: Client, options: VoiceOptions) { | ||||
|     this.client = client | ||||
|     this.guild = options.guild | ||||
|     this.channel = options.channel | ||||
|   } | ||||
| 
 | ||||
|   async connect(): Promise<VoiceClient> { | ||||
|     // TODO(DjDeveloperr): Actually understand what the hell docs say
 | ||||
|     return this | ||||
|   } | ||||
| } | ||||
|  | @ -1,5 +1,6 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { | ||||
|   GuildBanPayload, | ||||
|   GuildFeatures, | ||||
|   GuildIntegrationPayload, | ||||
|   GuildPayload, | ||||
|  | @ -8,7 +9,6 @@ import { | |||
| } from '../types/guild.ts' | ||||
| import { PresenceUpdatePayload } from '../types/gateway.ts' | ||||
| import { Base } from './base.ts' | ||||
| import { VoiceState } from './voiceState.ts' | ||||
| import { RolesManager } from '../managers/roles.ts' | ||||
| import { InviteManager } from '../managers/invites.ts' | ||||
| import { GuildChannelsManager } from '../managers/guildChannels.ts' | ||||
|  | @ -18,7 +18,100 @@ import { GuildEmojisManager } from '../managers/guildEmojis.ts' | |||
| import { Member } from './member.ts' | ||||
| import { User } from './user.ts' | ||||
| import { Application } from './application.ts' | ||||
| import { GUILD_INTEGRATIONS } from '../types/endpoint.ts' | ||||
| import { GUILD_BAN, GUILD_BANS, GUILD_INTEGRATIONS } from '../types/endpoint.ts' | ||||
| import { GuildVoiceStatesManager } from '../managers/guildVoiceStates.ts' | ||||
| import { RequestMembersOptions } from '../gateway/index.ts' | ||||
| 
 | ||||
| export class GuildBan extends Base { | ||||
|   guild: Guild | ||||
|   reason?: string | ||||
|   user: User | ||||
| 
 | ||||
|   constructor(client: Client, data: GuildBanPayload, guild: Guild) { | ||||
|     super(client, data) | ||||
|     this.guild = guild | ||||
|     this.reason = data.reason === null ? undefined : data.reason | ||||
|     this.user = new User(client, data.user) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class GuildBans { | ||||
|   client: Client | ||||
|   guild: Guild | ||||
| 
 | ||||
|   constructor(client: Client, guild: Guild) { | ||||
|     this.client = client | ||||
|     this.guild = guild | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Get all bans in the Guild. | ||||
|    */ | ||||
|   async all(): Promise<GuildBan[]> { | ||||
|     const res = await this.client.rest.get(GUILD_BANS(this.guild.id)) | ||||
|     if (typeof res !== 'object' || !Array.isArray(res)) | ||||
|       throw new Error('Failed to fetch Guild Bans') | ||||
| 
 | ||||
|     const bans = (res as GuildBanPayload[]).map( | ||||
|       (ban) => new GuildBan(this.client, ban, this.guild) | ||||
|     ) | ||||
|     return bans | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Get ban details of a User if any. | ||||
|    * @param user User to get ban of, ID or User object. | ||||
|    */ | ||||
|   async get(user: string | User): Promise<GuildBan> { | ||||
|     const res = await this.client.rest.get( | ||||
|       GUILD_BAN(this.guild.id, typeof user === 'string' ? user : user.id) | ||||
|     ) | ||||
|     if (typeof res !== 'object') throw new Error('Failed to fetch Guild Ban') | ||||
|     return new GuildBan(this.client, res, this.guild) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Ban a User. | ||||
|    * @param user User to ban, ID or User object. | ||||
|    * @param reason Reason for the Ban. | ||||
|    * @param deleteMessagesDays Delete Old Messages? If yes, how much days. | ||||
|    */ | ||||
|   async add( | ||||
|     user: string | User, | ||||
|     reason?: string, | ||||
|     deleteMessagesDays?: number | ||||
|   ): Promise<void> { | ||||
|     const res = await this.client.rest.put( | ||||
|       GUILD_BAN(this.guild.id, typeof user === 'string' ? user : user.id), | ||||
|       { | ||||
|         reason, | ||||
|         delete_message_days: deleteMessagesDays, | ||||
|       }, | ||||
|       undefined, | ||||
|       null, | ||||
|       true | ||||
|     ) | ||||
| 
 | ||||
|     if (res.status !== 204) throw new Error('Failed to Add Guild Ban') | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Unban (remove ban from) a User. | ||||
|    * @param user User to unban, ID or User object. | ||||
|    */ | ||||
|   async remove(user: string | User): Promise<boolean> { | ||||
|     const res = await this.client.rest.delete( | ||||
|       GUILD_BAN(this.guild.id, typeof user === 'string' ? user : user.id), | ||||
|       undefined, | ||||
|       undefined, | ||||
|       null, | ||||
|       true | ||||
|     ) | ||||
| 
 | ||||
|     if (res.status !== 204) return false | ||||
|     else return true | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class Guild extends Base { | ||||
|   id: string | ||||
|  | @ -51,7 +144,7 @@ export class Guild extends Base { | |||
|   large?: boolean | ||||
|   unavailable: boolean | ||||
|   memberCount?: number | ||||
|   voiceStates?: VoiceState[] | ||||
|   voiceStates: GuildVoiceStatesManager | ||||
|   members: MembersManager | ||||
|   channels: GuildChannelsManager | ||||
|   presences?: PresenceUpdatePayload[] | ||||
|  | @ -67,12 +160,15 @@ export class Guild extends Base { | |||
|   maxVideoChannelUsers?: number | ||||
|   approximateNumberCount?: number | ||||
|   approximatePresenceCount?: number | ||||
|   bans: GuildBans | ||||
| 
 | ||||
|   constructor(client: Client, data: GuildPayload) { | ||||
|     super(client, data) | ||||
|     this.id = data.id | ||||
|     this.bans = new GuildBans(client, this) | ||||
|     this.unavailable = data.unavailable | ||||
|     this.members = new MembersManager(this.client, this) | ||||
|     this.voiceStates = new GuildVoiceStatesManager(client, this) | ||||
|     this.channels = new GuildChannelsManager( | ||||
|       this.client, | ||||
|       this.client.channels, | ||||
|  | @ -99,15 +195,6 @@ export class Guild extends Base { | |||
|       this.verificationLevel = data.verification_level | ||||
|       this.defaultMessageNotifications = data.default_message_notifications | ||||
|       this.explicitContentFilter = data.explicit_content_filter | ||||
|       // this.roles = data.roles.map(
 | ||||
|       //   v => cache.get('role', v.id) ?? new Role(client, v)
 | ||||
|       // )
 | ||||
|       // data.roles.forEach(role => {
 | ||||
|       //   this.roles.set(role.id, new Role(client, role))
 | ||||
|       // })
 | ||||
|       // this.emojis = data.emojis.map(
 | ||||
|       //   v => cache.get('emoji', v.id) ?? new Emoji(client, v)
 | ||||
|       // )
 | ||||
|       this.features = data.features | ||||
|       this.mfaLevel = data.mfa_level | ||||
|       this.systemChannelID = data.system_channel_id | ||||
|  | @ -116,20 +203,6 @@ export class Guild extends Base { | |||
|       this.joinedAt = data.joined_at | ||||
|       this.large = data.large | ||||
|       this.memberCount = data.member_count | ||||
|       // TODO: Cache in Gateway Event code
 | ||||
|       // this.voiceStates = data.voice_states?.map(
 | ||||
|       //   v =>
 | ||||
|       //     cache.get('voiceState', `${v.guild_id}:${v.user_id}`) ??
 | ||||
|       //     new VoiceState(client, v)
 | ||||
|       // )
 | ||||
|       // this.members = data.members?.map(
 | ||||
|       //   v =>
 | ||||
|       //     cache.get('member', `${this.id}:${v.user.id}`) ??
 | ||||
|       //     new Member(client, v)
 | ||||
|       // )
 | ||||
|       // this.channels = data.channels?.map(
 | ||||
|       //   v => cache.get('channel', v.id) ?? getChannelByType(this.client, v)
 | ||||
|       // )
 | ||||
|       this.presences = data.presences | ||||
|       this.maxPresences = data.max_presences | ||||
|       this.maxMembers = data.max_members | ||||
|  | @ -170,14 +243,6 @@ export class Guild extends Base { | |||
|         data.default_message_notifications ?? this.defaultMessageNotifications | ||||
|       this.explicitContentFilter = | ||||
|         data.explicit_content_filter ?? this.explicitContentFilter | ||||
|       // this.roles =
 | ||||
|       //   data.roles.map(
 | ||||
|       //     v => cache.get('role', v.id) ?? new Role(this.client, v)
 | ||||
|       //   ) ?? this.roles
 | ||||
|       // this.emojis =
 | ||||
|       //   data.emojis.map(
 | ||||
|       //     v => cache.get('emoji', v.id) ?? new Emoji(this.client, v)
 | ||||
|       //   ) ?? this.emojis
 | ||||
|       this.features = data.features ?? this.features | ||||
|       this.mfaLevel = data.mfa_level ?? this.mfaLevel | ||||
|       this.systemChannelID = data.system_channel_id ?? this.systemChannelID | ||||
|  | @ -187,22 +252,6 @@ export class Guild extends Base { | |||
|       this.joinedAt = data.joined_at ?? this.joinedAt | ||||
|       this.large = data.large ?? this.large | ||||
|       this.memberCount = data.member_count ?? this.memberCount | ||||
|       // this.voiceStates =
 | ||||
|       //   data.voice_states?.map(
 | ||||
|       //     v =>
 | ||||
|       //       cache.get('voiceState', `${v.guild_id}:${v.user_id}`) ??
 | ||||
|       //       new VoiceState(this.client, v)
 | ||||
|       //   ) ?? this.voiceStates
 | ||||
|       // this.members =
 | ||||
|       //   data.members?.map(
 | ||||
|       //     v =>
 | ||||
|       //       cache.get('member', `${this.id}:${v.user.id}`) ??
 | ||||
|       //       new Member(this.client, v)
 | ||||
|       //   ) ?? this.members
 | ||||
|       // this.channels =
 | ||||
|       //   data.channels?.map(
 | ||||
|       //     v => cache.get('channel', v.id) ?? getChannelByType(this.client, v, this)
 | ||||
|       //   ) ?? this.members
 | ||||
|       this.presences = data.presences ?? this.presences | ||||
|       this.maxPresences = data.max_presences ?? this.maxPresences | ||||
|       this.maxMembers = data.max_members ?? this.maxMembers | ||||
|  | @ -224,23 +273,66 @@ export class Guild extends Base { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Get Everyone role of the Guild | ||||
|    */ | ||||
|   async getEveryoneRole(): Promise<Role> { | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
 | ||||
|     return (await this.roles.get(this.id)) as Role | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Get current client's member in the Guild | ||||
|    */ | ||||
|   async me(): Promise<Member> { | ||||
|     const get = await this.members.get(this.client.user?.id as string) | ||||
|     if (get === undefined) throw new Error('Guild#me is not cached') | ||||
|     return get | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Fetch Guild's Integrations (Webhooks, Bots, etc.) | ||||
|    */ | ||||
|   async fetchIntegrations(): Promise<GuildIntegration[]> { | ||||
|     const raw = (await this.client.rest.get( | ||||
|       GUILD_INTEGRATIONS(this.id) | ||||
|     )) as GuildIntegrationPayload[] | ||||
|     return raw.map((e) => new GuildIntegration(this.client, e)) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Chunk the Guild Members, i.e. cache them. | ||||
|    * @param options Options regarding the Members Request | ||||
|    * @param wait Whether to wait for all Members to come before resolving Promise or not. | ||||
|    * @param timeout Configurable timeout to cancel the wait to safely remove listener. | ||||
|    */ | ||||
|   async chunk( | ||||
|     options: RequestMembersOptions, | ||||
|     wait: boolean = false, | ||||
|     timeout: number = 60000 | ||||
|   ): Promise<Guild> { | ||||
|     return await new Promise((resolve, reject) => { | ||||
|       this.client.gateway?.requestMembers(this.id, options) | ||||
|       if (!wait) return resolve(this) | ||||
|       else { | ||||
|         let chunked = false | ||||
|         const listener = (guild: Guild): void => { | ||||
|           if (guild.id === this.id) { | ||||
|             chunked = true | ||||
|             this.client.removeListener('guildMembersChunked', listener) | ||||
|             resolve(this) | ||||
|           } | ||||
|         } | ||||
|         this.client.on('guildMembersChunked', listener) | ||||
|         setTimeout(() => { | ||||
|           if (!chunked) { | ||||
|             this.client.removeListener('guildMembersChunked', listener) | ||||
|           } | ||||
|         }, timeout) | ||||
|       } | ||||
|       resolve(this) | ||||
|     }) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class GuildIntegration extends Base { | ||||
|  |  | |||
|  | @ -1,11 +1,20 @@ | |||
| import { MemberRolesManager } from "../managers/memberRoles.ts" | ||||
| import { MemberRolesManager } from '../managers/memberRoles.ts' | ||||
| import { Client } from '../models/client.ts' | ||||
| import { GUILD_MEMBER } from '../types/endpoint.ts' | ||||
| import { MemberPayload } from '../types/guild.ts' | ||||
| import { Permissions } from "../utils/permissions.ts" | ||||
| import { Permissions } from '../utils/permissions.ts' | ||||
| import { Base } from './base.ts' | ||||
| import { Guild } from "./guild.ts" | ||||
| import { Guild } from './guild.ts' | ||||
| import { Role } from './role.ts' | ||||
| import { User } from './user.ts' | ||||
| 
 | ||||
| export interface MemberData { | ||||
|   nick?: string | null | ||||
|   roles?: Array<Role | string> | ||||
|   deaf?: boolean | ||||
|   mute?: boolean | ||||
| } | ||||
| 
 | ||||
| export class Member extends Base { | ||||
|   id: string | ||||
|   user: User | ||||
|  | @ -18,7 +27,13 @@ export class Member extends Base { | |||
|   guild: Guild | ||||
|   permissions: Permissions | ||||
| 
 | ||||
|   constructor (client: Client, data: MemberPayload, user: User, guild: Guild, perms?: Permissions) { | ||||
|   constructor( | ||||
|     client: Client, | ||||
|     data: MemberPayload, | ||||
|     user: User, | ||||
|     guild: Guild, | ||||
|     perms?: Permissions | ||||
|   ) { | ||||
|     super(client) | ||||
|     this.id = data.user.id | ||||
|     this.user = user | ||||
|  | @ -33,7 +48,15 @@ export class Member extends Base { | |||
|     else this.permissions = new Permissions(Permissions.DEFAULT) | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData (data: MemberPayload): void { | ||||
|   get displayName(): string { | ||||
|     return this.nick !== undefined ? this.nick : this.user.username | ||||
|   } | ||||
| 
 | ||||
|   toString(): string { | ||||
|     return this.user.nickMention | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: MemberPayload): void { | ||||
|     super.readFromData(data.user) | ||||
|     this.nick = data.nick ?? this.nick | ||||
|     this.joinedAt = data.joined_at ?? this.joinedAt | ||||
|  | @ -41,4 +64,110 @@ export class Member extends Base { | |||
|     this.deaf = data.deaf ?? this.deaf | ||||
|     this.mute = data.mute ?? this.mute | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Update the Member data in cache (and this object). | ||||
|    */ | ||||
|   async fetch(): Promise<Member> { | ||||
|     const raw = await this.client.rest.get(this.id) | ||||
|     if (typeof raw !== 'object') throw new Error('Member not found') | ||||
|     await this.guild.members.set(this.id, raw) | ||||
|     this.readFromData(raw) | ||||
|     return this | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Edit the Member | ||||
|    * @param data Data to apply | ||||
|    */ | ||||
|   async edit(data: MemberData): Promise<Member> { | ||||
|     const payload = { | ||||
|       nick: data.nick, | ||||
|       roles: data.roles?.map((e) => (typeof e === 'string' ? e : e.id)), | ||||
|       deaf: data.deaf, | ||||
|       mute: data.mute, | ||||
|     } | ||||
|     const res = await this.client.rest.patch( | ||||
|       GUILD_MEMBER(this.guild.id, this.id), | ||||
|       payload, | ||||
|       undefined, | ||||
|       null, | ||||
|       true | ||||
|     ) | ||||
|     if (res.ok === true) { | ||||
|       if (data.nick !== undefined) | ||||
|         this.nick = data.nick === null ? undefined : data.nick | ||||
|       if (data.deaf !== undefined) this.deaf = data.deaf | ||||
|       if (data.mute !== undefined) this.mute = data.mute | ||||
|     } | ||||
|     return this | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * New nickname to set. If empty, nick is reset | ||||
|    * @param nick New nickname | ||||
|    */ | ||||
|   async setNickname(nick?: string): Promise<Member> { | ||||
|     return await this.edit({ | ||||
|       nick: nick === undefined ? null : nick, | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Reset nickname of the Member | ||||
|    */ | ||||
|   async resetNickname(): Promise<Member> { | ||||
|     return await this.setNickname() | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Set mute of a Member in VC | ||||
|    * @param mute Value to set | ||||
|    */ | ||||
|   async setMute(mute?: boolean): Promise<Member> { | ||||
|     return await this.edit({ | ||||
|       mute: mute === undefined ? false : mute, | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Set deaf of a Member in VC | ||||
|    * @param deaf Value to set | ||||
|    */ | ||||
|   async setDeaf(deaf?: boolean): Promise<Member> { | ||||
|     return await this.edit({ | ||||
|       deaf: deaf === undefined ? false : deaf, | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Unmute the Member from VC. | ||||
|    */ | ||||
|   async unmute(): Promise<Member> { | ||||
|     return await this.setMute(false) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Kick the member. | ||||
|    */ | ||||
|   async kick(): Promise<boolean> { | ||||
|     const resp = await this.client.rest.delete( | ||||
|       GUILD_MEMBER(this.guild.id, this.id), | ||||
|       undefined, | ||||
|       undefined, | ||||
|       null, | ||||
|       true | ||||
|     ) | ||||
|     if (resp.ok !== true) return false | ||||
|     else return true | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Ban the Member. | ||||
|    * @param reason Reason for the Ban. | ||||
|    * @param deleteMessagesDays Delete Old Messages? If yes, how much days. | ||||
|    */ | ||||
|   async ban(reason?: string, deleteOldMessages?: number): Promise<void> { | ||||
|     return this.guild.bans.add(this.id, reason, deleteOldMessages) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,49 +1,53 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { MemberPayload } from '../types/guild.ts' | ||||
| import { VoiceStatePayload } from '../types/voice.ts' | ||||
| import { Base } from './base.ts' | ||||
| import { Guild } from "./guild.ts" | ||||
| import { VoiceChannel } from "./guildVoiceChannel.ts" | ||||
| import { Member } from "./member.ts" | ||||
| import { User } from "./user.ts" | ||||
| 
 | ||||
| export class VoiceState extends Base { | ||||
|   guildID?: string | ||||
|   channelID?: string | ||||
|   userID: string | ||||
|   member?: MemberPayload | ||||
|   guild?: Guild | ||||
|   channel: VoiceChannel | null | ||||
|   user: User | ||||
|   member?: Member | ||||
|   sessionID: string | ||||
|   deaf: boolean | ||||
|   mute: boolean | ||||
|   selfDeaf: boolean | ||||
|   selfMute: boolean | ||||
|   selfStream?: boolean | ||||
|   selfVideo: boolean | ||||
|   stream?: boolean | ||||
|   video: boolean | ||||
|   suppress: boolean | ||||
| 
 | ||||
|   constructor (client: Client, data: VoiceStatePayload) { | ||||
|   constructor (client: Client, data: VoiceStatePayload, _data: { | ||||
|     user: User, | ||||
|     channel: VoiceChannel | null, | ||||
|     member?: Member, | ||||
|     guild?: Guild | ||||
|   }) { | ||||
|     super(client, data) | ||||
|     this.channelID = data.channel_id | ||||
|     this.channel = _data.channel | ||||
|     this.sessionID = data.session_id | ||||
|     this.userID = data.user_id | ||||
|     this.user = _data.user | ||||
|     this.member = _data.member | ||||
|     this.guild = _data.guild | ||||
|     this.deaf = data.deaf | ||||
|     this.mute = data.mute | ||||
|     this.selfDeaf = data.self_deaf | ||||
|     this.selfMute = data.self_mute | ||||
|     this.selfStream = data.self_stream | ||||
|     this.selfVideo = data.self_video | ||||
|     this.deaf = data.self_deaf | ||||
|     this.mute = data.self_mute | ||||
|     this.stream = data.self_stream | ||||
|     this.video = data.self_video | ||||
|     this.suppress = data.suppress | ||||
|     // TODO: Cache in Gateway Event Code
 | ||||
|     // cache.set('voiceState', `${this.guildID}:${this.userID}`, this)
 | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData (data: VoiceStatePayload): void { | ||||
|     super.readFromData(data) | ||||
|     this.channelID = data.channel_id ?? this.channelID | ||||
|     this.sessionID = data.session_id ?? this.sessionID | ||||
|     this.userID = data.user_id ?? this.userID | ||||
|     this.deaf = data.deaf ?? this.deaf | ||||
|     this.mute = data.mute ?? this.mute | ||||
|     this.selfDeaf = data.self_deaf ?? this.selfDeaf | ||||
|     this.selfMute = data.self_mute ?? this.selfMute | ||||
|     this.selfStream = data.self_stream ?? this.selfStream | ||||
|     this.selfVideo = data.self_video ?? this.selfVideo | ||||
|     this.deaf = data.self_deaf ?? this.deaf | ||||
|     this.mute = data.self_mute ?? this.mute | ||||
|     this.stream = data.self_stream ?? this.stream | ||||
|     this.video = data.self_video ?? this.video | ||||
|     this.suppress = data.suppress ?? this.suppress | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -123,6 +123,14 @@ client.on('typingStart', (user, channel, at, guildData) => { | |||
|   ) | ||||
| }) | ||||
| 
 | ||||
| client.on('voiceStateAdd', (state) => { | ||||
|   console.log('VC Join', state) | ||||
| }) | ||||
| 
 | ||||
| client.on('voiceStateRemove', (state) => { | ||||
|   console.log('VC Leave', state) | ||||
| }) | ||||
| 
 | ||||
| // client.on('raw', (evt: string) => console.log(`EVENT: ${evt}`))
 | ||||
| 
 | ||||
| const files = Deno.readDirSync('./src/test/cmds') | ||||
|  |  | |||
|  | @ -104,6 +104,7 @@ export enum GatewayEvents { | |||
|   Typing_Start = 'TYPING_START', | ||||
|   User_Update = 'USER_UPDATE', | ||||
|   Voice_Server_Update = 'VOICE_SERVER_UPDATE', | ||||
|   Voice_State_Update = 'VOICE_STATE_UPDATE', | ||||
|   Webhooks_Update = 'WEBHOOKS_UPDATE' | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { ApplicationPayload } from "./application.ts" | ||||
| import { ApplicationPayload } from './application.ts' | ||||
| import { ChannelPayload } from './channel.ts' | ||||
| import { EmojiPayload } from './emoji.ts' | ||||
| import { PresenceUpdatePayload } from './gateway.ts' | ||||
|  | @ -66,18 +66,18 @@ export interface MemberPayload { | |||
| 
 | ||||
| export enum MessageNotification { | ||||
|   ALL_MESSAGES = 0, | ||||
|   ONLY_MENTIONS = 1 | ||||
|   ONLY_MENTIONS = 1, | ||||
| } | ||||
| 
 | ||||
| export enum ContentFilter { | ||||
|   DISABLED = 0, | ||||
|   MEMBERS_WITHOUT_ROLES = 1, | ||||
|   ALL_MEMBERS = 3 | ||||
|   ALL_MEMBERS = 3, | ||||
| } | ||||
| 
 | ||||
| export enum MFA { | ||||
|   NONE = 0, | ||||
|   ELEVATED = 1 | ||||
|   ELEVATED = 1, | ||||
| } | ||||
| 
 | ||||
| export enum Verification { | ||||
|  | @ -85,19 +85,19 @@ export enum Verification { | |||
|   LOW = 1, | ||||
|   MEDIUM = 2, | ||||
|   HIGH = 3, | ||||
|   VERY_HIGH = 4 | ||||
|   VERY_HIGH = 4, | ||||
| } | ||||
| 
 | ||||
| export enum PremiumTier { | ||||
|   NONE = 0, | ||||
|   TIER_1 = 1, | ||||
|   TIER_2 = 2, | ||||
|   TIER_3 = 3 | ||||
|   TIER_3 = 3, | ||||
| } | ||||
| 
 | ||||
| export enum SystemChannelFlags { | ||||
|   SUPPRESS_JOIN_NOTIFICATIONS = 1 << 0, | ||||
|   SUPPRESS_PREMIUM_SUBSCRIPTIONS = 1 << 1 | ||||
|   SUPPRESS_PREMIUM_SUBSCRIPTIONS = 1 << 1, | ||||
| } | ||||
| 
 | ||||
| export type GuildFeatures = | ||||
|  | @ -116,7 +116,7 @@ export type GuildFeatures = | |||
| 
 | ||||
| export enum IntegrationExpireBehavior { | ||||
|   REMOVE_ROLE = 0, | ||||
|   KICK = 1 | ||||
|   KICK = 1, | ||||
| } | ||||
| 
 | ||||
| export interface IntegrationAccountPayload { | ||||
|  | @ -141,3 +141,8 @@ export interface GuildIntegrationPayload { | |||
|   revoked?: boolean | ||||
|   application?: ApplicationPayload | ||||
| } | ||||
| 
 | ||||
| export interface GuildBanPayload { | ||||
|   reason: string | null | ||||
|   user: UserPayload | ||||
| } | ||||
|  |  | |||
|  | @ -31,7 +31,7 @@ export enum VoiceCloseCodes { | |||
| 
 | ||||
| export interface VoiceStatePayload { | ||||
|   guild_id?: string | ||||
|   channel_id?: string | ||||
|   channel_id: string | null | ||||
|   user_id: string | ||||
|   member?: MemberPayload | ||||
|   session_id: string | ||||
|  |  | |||
|  | @ -60,7 +60,7 @@ | |||
|     // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */ | ||||
|     // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ | ||||
|     /* Experimental Options */ | ||||
|     // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */ | ||||
|     "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */ | ||||
|     "emitDecoratorMetadata": false /* Enables experimental support for emitting type metadata for decorators. */, | ||||
|     /* Advanced Options */ | ||||
|     "skipLibCheck": true, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue