RESTManager, CacheAdapters, and improvements
This commit is contained in:
		
							parent
							
								
									f319e0df91
								
							
						
					
					
						commit
						935456906d
					
				
					 26 changed files with 770 additions and 57 deletions
				
			
		|  | @ -8,6 +8,7 @@ export const channelCreate: GatewayEventHandler = ( | ||||||
|   const channel = getChannelByType(gateway.client, d) |   const channel = getChannelByType(gateway.client, d) | ||||||
| 
 | 
 | ||||||
|   if (channel !== undefined) { |   if (channel !== undefined) { | ||||||
|  |     gateway.client.channels.set(d.id, d) | ||||||
|     gateway.client.emit('channelCreate', channel) |     gateway.client.emit('channelCreate', channel) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,14 +1,13 @@ | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| import cache from '../../models/cache.ts' |  | ||||||
| import { Channel } from '../../structures/channel.ts' | import { Channel } from '../../structures/channel.ts' | ||||||
| 
 | 
 | ||||||
| export const channelDelete: GatewayEventHandler = ( | export const channelDelete: GatewayEventHandler = ( | ||||||
|   gateway: Gateway, |   gateway: Gateway, | ||||||
|   d: any |   d: any | ||||||
| ) => { | ) => { | ||||||
|   const channel: Channel = cache.get('channel', d.id) |   const channel: Channel = gateway.client.channels.get(d.id) | ||||||
|   if (channel !== undefined) { |   if (channel !== undefined) { | ||||||
|     cache.del('channel', d.id) |     gateway.client.channels.delete(d.id) | ||||||
|     gateway.client.emit('channelDelete', channel) |     gateway.client.emit('channelDelete', channel) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,16 +1,19 @@ | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| import cache from '../../models/cache.ts' | import cache from '../../models/cache.ts' | ||||||
| import { TextChannel } from '../../structures/textChannel.ts' | import { TextChannel } from '../../structures/textChannel.ts' | ||||||
|  | import { ChannelPayload } from "../../types/channelTypes.ts" | ||||||
| 
 | 
 | ||||||
| export const channelPinsUpdate: GatewayEventHandler = ( | export const channelPinsUpdate: GatewayEventHandler = ( | ||||||
|   gateway: Gateway, |   gateway: Gateway, | ||||||
|   d: any |   d: any | ||||||
| ) => { | ) => { | ||||||
|   const after: TextChannel = cache.get('textchannel', d.channel_id) |   const after: TextChannel = gateway.client.channels.get(d.channel_id) | ||||||
|   if (after !== undefined) { |   if (after !== undefined) { | ||||||
|     const before = after.refreshFromData({ |     const before = after.refreshFromData({ | ||||||
|       last_pin_timestamp: d.last_pin_timestamp |       last_pin_timestamp: d.last_pin_timestamp | ||||||
|     }) |     }) | ||||||
|  |     let raw = gateway.client.channels._get(d.channel_id) as ChannelPayload; | ||||||
|  |     gateway.client.channels.set(after.id, Object.assign(raw, { last_pin_timestamp: d.last_pin_timestamp })) | ||||||
|     gateway.client.emit('channelPinsUpdate', before, after) |     gateway.client.emit('channelPinsUpdate', before, after) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,3 @@ | ||||||
| import cache from '../../models/cache.ts' |  | ||||||
| import { Channel } from '../../structures/channel.ts' | import { Channel } from '../../structures/channel.ts' | ||||||
| import getChannelByType from '../../utils/getChannelByType.ts' | import getChannelByType from '../../utils/getChannelByType.ts' | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | @ -7,9 +6,10 @@ export const channelUpdate: GatewayEventHandler = ( | ||||||
|   gateway: Gateway, |   gateway: Gateway, | ||||||
|   d: any |   d: any | ||||||
| ) => { | ) => { | ||||||
|   const oldChannel: Channel = cache.get('channel', d.id) |   const oldChannel: Channel = gateway.client.channels.get(d.id) | ||||||
| 
 | 
 | ||||||
|   if (oldChannel !== undefined) { |   if (oldChannel !== undefined) { | ||||||
|  |     gateway.client.channels.set(d.id, d) | ||||||
|     if (oldChannel.type !== d.type) { |     if (oldChannel.type !== d.type) { | ||||||
|       const channel: Channel = getChannelByType(gateway.client, d) ?? oldChannel |       const channel: Channel = getChannelByType(gateway.client, d) ?? oldChannel | ||||||
|       gateway.client.emit('channelUpdate', oldChannel, channel) |       gateway.client.emit('channelUpdate', oldChannel, channel) | ||||||
|  |  | ||||||
|  | @ -1,14 +1,15 @@ | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| import cache from '../../models/cache.ts' |  | ||||||
| import { Guild } from '../../structures/guild.ts' | import { Guild } from '../../structures/guild.ts' | ||||||
| 
 | 
 | ||||||
| export const guildCreate: GatewayEventHandler = (gateway: Gateway, d: any) => { | export const guildCreate: GatewayEventHandler = (gateway: Gateway, d: any) => { | ||||||
|   let guild: Guild = cache.get('guild', d.id) |   let guild: Guild | void = gateway.client.guilds.get(d.id) | ||||||
|   if (guild !== undefined) { |   if (guild !== undefined) { | ||||||
|  |     // It was just lazy load, so we don't fire the event as its gonna fire for every guild bot is in
 | ||||||
|  |     gateway.client.guilds.set(d.id, d) | ||||||
|     guild.refreshFromData(d) |     guild.refreshFromData(d) | ||||||
|   } else { |   } else { | ||||||
|     guild = new Guild(gateway.client, d) |     gateway.client.guilds.set(d.id, d) | ||||||
|   } |     guild = gateway.client.guilds.get(d.id) | ||||||
| 
 |  | ||||||
|     gateway.client.emit('guildCreate', guild) |     gateway.client.emit('guildCreate', guild) | ||||||
|   } |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -1,13 +1,12 @@ | ||||||
| import cache from '../../models/cache.ts' |  | ||||||
| import { Guild } from '../../structures/guild.ts' | import { Guild } from '../../structures/guild.ts' | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| 
 | 
 | ||||||
| export const guildDelte: GatewayEventHandler = (gateway: Gateway, d: any) => { | export const guildDelte: GatewayEventHandler = (gateway: Gateway, d: any) => { | ||||||
|   const guild: Guild = cache.get('guild', d.id) |   const guild: Guild | void = gateway.client.guilds.get(d.id) | ||||||
| 
 | 
 | ||||||
|   if (guild !== undefined) { |   if (guild !== undefined) { | ||||||
|     guild.refreshFromData(d) |     guild.refreshFromData(d) | ||||||
|     cache.del('guild', d.id) |     gateway.client.guilds.delete(d.id) | ||||||
|     gateway.client.emit('guildDelete', guild) |     gateway.client.emit('guildDelete', guild) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,9 +3,9 @@ import cache from '../../models/cache.ts' | ||||||
| import { Guild } from '../../structures/guild.ts' | import { Guild } from '../../structures/guild.ts' | ||||||
| 
 | 
 | ||||||
| export const guildUpdate: GatewayEventHandler = (gateway: Gateway, d: any) => { | export const guildUpdate: GatewayEventHandler = (gateway: Gateway, d: any) => { | ||||||
|   const after: Guild = cache.get('guild', d.id) |   const before: Guild | void = gateway.client.guilds.get(d.id) | ||||||
|   if (after !== undefined) { |   if(!before) return | ||||||
|     const before: Guild = after.refreshFromData(d) |   gateway.client.guilds.set(d.id, d) | ||||||
|  |   const after: Guild | void = gateway.client.guilds.get(d.id) | ||||||
|   gateway.client.emit('guildUpdate', before, after) |   gateway.client.emit('guildUpdate', before, after) | ||||||
| } | } | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -1,4 +1,3 @@ | ||||||
| import { Guild } from '../../structures/guild.ts' |  | ||||||
| import { User } from '../../structures/user.ts' | import { User } from '../../structures/user.ts' | ||||||
| import { GuildPayload } from '../../types/guildTypes.ts' | import { GuildPayload } from '../../types/guildTypes.ts' | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | @ -6,6 +5,9 @@ import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| export const ready: GatewayEventHandler = (gateway: Gateway, d: any) => { | export const ready: GatewayEventHandler = (gateway: Gateway, d: any) => { | ||||||
|   gateway.client.user = new User(gateway.client, d.user) |   gateway.client.user = new User(gateway.client, d.user) | ||||||
|   gateway.sessionID = d.session_id |   gateway.sessionID = d.session_id | ||||||
|   d.guilds.forEach((guild: GuildPayload) => new Guild(gateway.client, guild)) |   gateway.debug(`Received READY. Session: ${gateway.sessionID}`) | ||||||
|  |   d.guilds.forEach((guild: GuildPayload) => { | ||||||
|  |     gateway.client.guilds.set(guild.id, guild) | ||||||
|  |   }) | ||||||
|   gateway.client.emit('ready') |   gateway.client.emit('ready') | ||||||
| } | } | ||||||
|  | @ -7,6 +7,8 @@ import { | ||||||
| import { GatewayResponse } from '../types/gatewayResponse.ts' | import { GatewayResponse } from '../types/gatewayResponse.ts' | ||||||
| import { GatewayOpcodes, GatewayIntents } from '../types/gatewayTypes.ts' | import { GatewayOpcodes, GatewayIntents } from '../types/gatewayTypes.ts' | ||||||
| import { gatewayHandlers } from './handlers/index.ts' | import { gatewayHandlers } from './handlers/index.ts' | ||||||
|  | import { GATEWAY_BOT } from '../types/endpoint.ts' | ||||||
|  | import { GatewayBotPayload } from "../types/gatewayBot.ts" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handles Discord gateway connection. |  * Handles Discord gateway connection. | ||||||
|  | @ -24,7 +26,7 @@ class Gateway { | ||||||
|   heartbeatIntervalID?: number |   heartbeatIntervalID?: number | ||||||
|   sequenceID?: number |   sequenceID?: number | ||||||
|   sessionID?: string |   sessionID?: string | ||||||
|   lastPingTimestemp = 0 |   lastPingTimestamp = 0 | ||||||
|   private heartbeatServerResponded = false |   private heartbeatServerResponded = false | ||||||
|   client: Client |   client: Client | ||||||
| 
 | 
 | ||||||
|  | @ -46,6 +48,7 @@ class Gateway { | ||||||
| 
 | 
 | ||||||
|   private onopen (): void { |   private onopen (): void { | ||||||
|     this.connected = true |     this.connected = true | ||||||
|  |     this.debug("Connected to Gateway!") | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private onmessage (event: MessageEvent): void { |   private onmessage (event: MessageEvent): void { | ||||||
|  | @ -63,6 +66,7 @@ class Gateway { | ||||||
|     switch (op) { |     switch (op) { | ||||||
|       case GatewayOpcodes.HELLO: |       case GatewayOpcodes.HELLO: | ||||||
|         this.heartbeatInterval = d.heartbeat_interval |         this.heartbeatInterval = d.heartbeat_interval | ||||||
|  |         this.debug(`Received HELLO. Heartbeat Interval: ${this.heartbeatInterval}`) | ||||||
|         this.heartbeatIntervalID = setInterval(() => { |         this.heartbeatIntervalID = setInterval(() => { | ||||||
|           if (this.heartbeatServerResponded) { |           if (this.heartbeatServerResponded) { | ||||||
|             this.heartbeatServerResponded = false |             this.heartbeatServerResponded = false | ||||||
|  | @ -79,7 +83,7 @@ class Gateway { | ||||||
|               d: this.sequenceID ?? null |               d: this.sequenceID ?? null | ||||||
|             }) |             }) | ||||||
|           ) |           ) | ||||||
|           this.lastPingTimestemp = Date.now() |           this.lastPingTimestamp = Date.now() | ||||||
|         }, this.heartbeatInterval) |         }, this.heartbeatInterval) | ||||||
| 
 | 
 | ||||||
|         if (!this.initialized) { |         if (!this.initialized) { | ||||||
|  | @ -92,7 +96,8 @@ class Gateway { | ||||||
| 
 | 
 | ||||||
|       case GatewayOpcodes.HEARTBEAT_ACK: |       case GatewayOpcodes.HEARTBEAT_ACK: | ||||||
|         this.heartbeatServerResponded = true |         this.heartbeatServerResponded = true | ||||||
|         this.client.ping = Date.now() - this.lastPingTimestemp |         this.client.ping = Date.now() - this.lastPingTimestamp | ||||||
|  |         this.debug(`Received Heartbeat Ack. Ping Recognized: ${this.client.ping}ms`) | ||||||
|         break |         break | ||||||
| 
 | 
 | ||||||
|       case GatewayOpcodes.INVALID_SESSION: |       case GatewayOpcodes.INVALID_SESSION: | ||||||
|  | @ -135,7 +140,14 @@ class Gateway { | ||||||
|     console.log(eventError) |     console.log(eventError) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private sendIdentify (): void { |   private async sendIdentify () { | ||||||
|  |     this.debug("Fetching /gateway/bot...") | ||||||
|  |     let info = await this.client.rest.get(GATEWAY_BOT()) as GatewayBotPayload | ||||||
|  |     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(`Reset After: ${info.session_start_limit.reset_after}ms`) | ||||||
|     this.websocket.send( |     this.websocket.send( | ||||||
|       JSON.stringify({ |       JSON.stringify({ | ||||||
|         op: GatewayOpcodes.IDENTIFY, |         op: GatewayOpcodes.IDENTIFY, | ||||||
|  | @ -164,6 +176,7 @@ class Gateway { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private sendResume (): void { |   private sendResume (): void { | ||||||
|  |     this.debug(`Preparing to resume with Session: ${this.sessionID}`) | ||||||
|     this.websocket.send( |     this.websocket.send( | ||||||
|       JSON.stringify({ |       JSON.stringify({ | ||||||
|         op: GatewayOpcodes.RESUME, |         op: GatewayOpcodes.RESUME, | ||||||
|  | @ -176,6 +189,10 @@ class Gateway { | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   debug(msg: string) { | ||||||
|  |     this.client.debug("Gateway", msg) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   initWebsocket (): void { |   initWebsocket (): void { | ||||||
|     this.websocket = new WebSocket( |     this.websocket = new WebSocket( | ||||||
|       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
 |       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
 | ||||||
|  |  | ||||||
							
								
								
									
										32
									
								
								src/managers/BaseManager.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/managers/BaseManager.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | ||||||
|  | import { Client } from "../models/client.ts"; | ||||||
|  | import { Base } from "../structures/base.ts"; | ||||||
|  | 
 | ||||||
|  | export class BaseManager<T, T2> { | ||||||
|  |   client: Client | ||||||
|  |   cacheName: string | ||||||
|  |   dataType: typeof Base | ||||||
|  | 
 | ||||||
|  |   constructor(client: Client, cacheName: string, dataType: typeof Base) { | ||||||
|  |     this.client = client | ||||||
|  |     this.cacheName = cacheName | ||||||
|  |     this.dataType = dataType | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _get(key: string): T { | ||||||
|  |     return this.client.cache.get(this.cacheName, key) as T | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get(key: string): T2 | void { | ||||||
|  |     let raw = this._get(key) | ||||||
|  |     if(!raw) return | ||||||
|  |     return new this.dataType(this.client, raw) as any | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set(key: string, value: T) { | ||||||
|  |     return this.client.cache.set(this.cacheName, key, value) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   delete(key: string) { | ||||||
|  |     return this.client.cache.delete(this.cacheName, key) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								src/managers/ChannelsManager.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/managers/ChannelsManager.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | ||||||
|  | import { Client } from "../models/client.ts"; | ||||||
|  | import { Channel } from "../structures/channel.ts"; | ||||||
|  | import { User } from "../structures/user.ts"; | ||||||
|  | import { ChannelPayload } from "../types/channelTypes.ts"; | ||||||
|  | import { CHANNEL } from "../types/endpoint.ts"; | ||||||
|  | import { BaseManager } from "./BaseManager.ts"; | ||||||
|  | 
 | ||||||
|  | export class ChannelsManager extends BaseManager<ChannelPayload, Channel> { | ||||||
|  |   constructor(client: Client) { | ||||||
|  |     super(client, "channels", User) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Override get method as Generic
 | ||||||
|  |   get<T = Channel>(key: string): T { | ||||||
|  |     return new this.dataType(this.client, this._get(key)) as any | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fetch(id: string) { | ||||||
|  |     return new Promise((res, rej) => { | ||||||
|  |       this.client.rest.get(CHANNEL(id)).then(data => { | ||||||
|  |         this.set(id, data as ChannelPayload) | ||||||
|  |         res(new Channel(this.client, data as ChannelPayload)) | ||||||
|  |       }).catch(e => rej(e)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								src/managers/EmojisManager.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/managers/EmojisManager.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | import { Client } from "../models/client.ts"; | ||||||
|  | import { Emoji } from "../structures/emoji.ts"; | ||||||
|  | import { EmojiPayload } from "../types/emojiTypes.ts"; | ||||||
|  | import { CHANNEL } from "../types/endpoint.ts"; | ||||||
|  | import { BaseManager } from "./BaseManager.ts"; | ||||||
|  | 
 | ||||||
|  | export class EmojisManager extends BaseManager<EmojiPayload, Emoji> { | ||||||
|  |   constructor(client: Client) { | ||||||
|  |     super(client, "emojis", Emoji) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fetch(id: string) { | ||||||
|  |     return new Promise((res, rej) => { | ||||||
|  |       this.client.rest.get(CHANNEL(id)).then(data => { | ||||||
|  |         this.set(id, data as EmojiPayload) | ||||||
|  |         res(new Emoji(this.client, data as EmojiPayload)) | ||||||
|  |       }).catch(e => rej(e)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								src/managers/GuildsManager.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/managers/GuildsManager.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | import { Client } from "../models/client.ts"; | ||||||
|  | import { Guild } from "../structures/guild.ts"; | ||||||
|  | import { GUILD } from "../types/endpoint.ts"; | ||||||
|  | import { GuildPayload } from "../types/guildTypes.ts"; | ||||||
|  | import { BaseManager } from "./BaseManager.ts"; | ||||||
|  | 
 | ||||||
|  | export class GuildManager extends BaseManager<GuildPayload, Guild> { | ||||||
|  |   constructor(client: Client) { | ||||||
|  |     super(client, "guilds", Guild) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fetch(id: string) { | ||||||
|  |     return new Promise((res, rej) => { | ||||||
|  |       this.client.rest.get(GUILD(id)).then(data => { | ||||||
|  |         this.set(id, data as GuildPayload) | ||||||
|  |         res(new Guild(this.client, data as GuildPayload)) | ||||||
|  |       }).catch(e => rej(e)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								src/managers/RolesManager.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/managers/RolesManager.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | ||||||
|  | import { Client } from "../models/client.ts"; | ||||||
|  | import { Guild } from "../structures/guild.ts"; | ||||||
|  | import { Role } from "../structures/role.ts"; | ||||||
|  | import { User } from "../structures/user.ts"; | ||||||
|  | import { GUILD_ROLE } from "../types/endpoint.ts"; | ||||||
|  | import { RolePayload } from "../types/roleTypes.ts"; | ||||||
|  | import { BaseManager } from "./BaseManager.ts"; | ||||||
|  | 
 | ||||||
|  | export class RolesManager extends BaseManager<RolePayload, Role> { | ||||||
|  |   guild: Guild | ||||||
|  | 
 | ||||||
|  |   constructor(client: Client, guild: Guild) { | ||||||
|  |     super(client, "roles:" + guild.id, Role) | ||||||
|  |     this.guild = guild | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fetch(id: string) { | ||||||
|  |     return new Promise((res, rej) => { | ||||||
|  |       this.client.rest.get(GUILD_ROLE(this.guild.id, id)).then(data => { | ||||||
|  |         this.set(id, data as RolePayload) | ||||||
|  |         res(new Role(this.client, data as RolePayload)) | ||||||
|  |       }).catch(e => rej(e)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								src/managers/UsersManager.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/managers/UsersManager.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | import { Client } from "../models/client.ts"; | ||||||
|  | import { User } from "../structures/user.ts"; | ||||||
|  | import { USER } from "../types/endpoint.ts"; | ||||||
|  | import { UserPayload } from "../types/userTypes.ts"; | ||||||
|  | import { BaseManager } from "./BaseManager.ts"; | ||||||
|  | 
 | ||||||
|  | export class UserManager extends BaseManager<UserPayload, User> { | ||||||
|  |   constructor(client: Client) { | ||||||
|  |     super(client, "users", User) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fetch(id: string) { | ||||||
|  |     return new Promise((res, rej) => { | ||||||
|  |       this.client.rest.get(USER(id)).then(data => { | ||||||
|  |         this.set(id, data as UserPayload) | ||||||
|  |         res(new User(this.client, data as UserPayload)) | ||||||
|  |       }).catch(e => rej(e)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										48
									
								
								src/models/CacheAdapter.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/models/CacheAdapter.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | ||||||
|  | import { Collection } from "../utils/collection.ts"; | ||||||
|  | import { Client } from "./client.ts"; | ||||||
|  | 
 | ||||||
|  | export interface ICacheAdapter { | ||||||
|  |   client: Client | ||||||
|  |   get: (cacheName: string, key: string) => any | ||||||
|  |   set: (cacheName: string, key: string, value: any) => any | ||||||
|  |   delete: (cacheName: string, key: string) => boolean | ||||||
|  |   array: (cacheName: string) => void | any[] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class DefaultCacheAdapter implements ICacheAdapter { | ||||||
|  |   client: Client | ||||||
|  |   data: { | ||||||
|  |     [name: string]: Collection<string, any> | ||||||
|  |   } = {} | ||||||
|  | 
 | ||||||
|  |   constructor(client: Client) { | ||||||
|  |     this.client = client | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   get(cacheName: string, key: string) { | ||||||
|  |     let cache = this.data[cacheName] | ||||||
|  |     if (!cache) return; | ||||||
|  |     return cache.get(key) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set(cacheName: string, key: string, value: any) { | ||||||
|  |     let cache = this.data[cacheName] | ||||||
|  |     if (!cache) { | ||||||
|  |       this.data[cacheName] = new Collection() | ||||||
|  |       cache = this.data[cacheName] | ||||||
|  |     } | ||||||
|  |     cache.set(key, value) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   delete(cacheName: string, key: string) { | ||||||
|  |     let cache = this.data[cacheName] | ||||||
|  |     if (!cache) return false | ||||||
|  |     return cache.delete(key) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   array(cacheName: string) { | ||||||
|  |     let cache = this.data[cacheName] | ||||||
|  |     if (!cache) return | ||||||
|  |     return cache.array() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -1,30 +1,64 @@ | ||||||
| import { User } from '../structures/user.ts' | import { User } from '../structures/user.ts' | ||||||
| import { GatewayIntents } from '../types/gatewayTypes.ts' | import { GatewayIntents } from '../types/gatewayTypes.ts' | ||||||
| import { Gateway } from '../gateway/index.ts' | import { Gateway } from '../gateway/index.ts' | ||||||
| import { Rest } from './rest.ts' | import { RESTManager } from './rest.ts' | ||||||
| import EventEmitter from 'https://deno.land/std@0.74.0/node/events.ts' | import EventEmitter from 'https://deno.land/std@0.74.0/node/events.ts' | ||||||
|  | import { DefaultCacheAdapter, ICacheAdapter } from "./CacheAdapter.ts" | ||||||
|  | import { UserManager } from "../managers/UsersManager.ts" | ||||||
|  | import { GuildManager } from "../managers/GuildsManager.ts" | ||||||
|  | import { EmojisManager } from "../managers/EmojisManager.ts" | ||||||
|  | import { ChannelsManager } from "../managers/ChannelsManager.ts" | ||||||
|  | 
 | ||||||
|  | /** Some Client Options to modify behaviour */ | ||||||
|  | export interface ClientOptions { | ||||||
|  |   token?: string | ||||||
|  |   intents?: GatewayIntents[] | ||||||
|  |   cache?: ICacheAdapter | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Discord Client. |  * Discord Client. | ||||||
|  */ |  */ | ||||||
| export class Client extends EventEmitter { | export class Client extends EventEmitter { | ||||||
|   gateway?: Gateway |   gateway?: Gateway | ||||||
|   rest?: Rest |   rest: RESTManager = new RESTManager(this) | ||||||
|   user?: User |   user?: User | ||||||
|   ping = 0 |   ping = 0 | ||||||
|   token?: string |   token?: string | ||||||
|  |   cache: ICacheAdapter = new DefaultCacheAdapter(this) | ||||||
|  |   intents?: GatewayIntents[] | ||||||
|  |   users: UserManager = new UserManager(this) | ||||||
|  |   guilds: GuildManager = new GuildManager(this) | ||||||
|  |   channels: ChannelsManager = new ChannelsManager(this) | ||||||
|  |   emojis: EmojisManager = new EmojisManager(this) | ||||||
| 
 | 
 | ||||||
|   // constructor () {
 |   constructor (options: ClientOptions = {}) { | ||||||
|   //   super()
 |     super() | ||||||
|   // }
 |     this.token = options.token | ||||||
|  |     this.intents = options.intents | ||||||
|  |     if(options.cache) this.cache = options.cache | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   debug(tag: string, msg: string) { | ||||||
|  |     this.emit("debug", `[${tag}] ${msg}`) | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * This function is used for connect to discord. |    * This function is used for connect to discord. | ||||||
|    * @param token Your token. This is required. |    * @param token Your token. This is required. | ||||||
|    * @param intents Gateway intents in array. This is required. |    * @param intents Gateway intents in array. This is required. | ||||||
|    */ |    */ | ||||||
|   connect (token: string, intents: GatewayIntents[]): void { |   connect (token?: string, intents?: GatewayIntents[]): void { | ||||||
|  |     if(!token && this.token) token = this.token | ||||||
|  |     else if(!this.token && token) { | ||||||
|       this.token = token |       this.token = token | ||||||
|  |     } | ||||||
|  |     else throw new Error("No Token Provided") | ||||||
|  |     if(!intents && this.intents) intents = this.intents | ||||||
|  |     else if(intents && !this.intents) { | ||||||
|  |       this.intents = intents | ||||||
|  |     } | ||||||
|  |     else throw new Error("No Gateway Intents were provided") | ||||||
|     this.gateway = new Gateway(this, token, intents) |     this.gateway = new Gateway(this, token, intents) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,11 +1,364 @@ | ||||||
| import { Client } from './client.ts' | import { delay } from "../utils/index.ts"; | ||||||
|  | import * as baseEndpoints from "../consts/urlsAndVersions.ts"; | ||||||
|  | import { Client } from "./client.ts"; | ||||||
|  | 
 | ||||||
|  | export enum HttpResponseCode { | ||||||
|  |   Ok = 200, | ||||||
|  |   Created = 201, | ||||||
|  |   NoContent = 204, | ||||||
|  |   NotModified = 304, | ||||||
|  |   BadRequest = 400, | ||||||
|  |   Unauthorized = 401, | ||||||
|  |   Forbidden = 403, | ||||||
|  |   NotFound = 404, | ||||||
|  |   MethodNotAllowed = 405, | ||||||
|  |   TooManyRequests = 429, | ||||||
|  |   GatewayUnavailable = 502, | ||||||
|  |   // ServerError left untyped because it's 5xx.
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export type RequestMethods = | ||||||
|  |   | "get" | ||||||
|  |   | "post" | ||||||
|  |   | "put" | ||||||
|  |   | "patch" | ||||||
|  |   | "head" | ||||||
|  |   | "delete"; | ||||||
|  | 
 | ||||||
|  | export interface QueuedRequest { | ||||||
|  | 	callback: () => Promise< | ||||||
|  | 		void | { | ||||||
|  | 			rateLimited: any; | ||||||
|  | 			beforeFetch: boolean; | ||||||
|  | 			bucketID?: string | null; | ||||||
|  | 		} | ||||||
|  | 	>; | ||||||
|  | 	bucketID?: string | null; | ||||||
|  | 	url: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface RateLimitedPath { | ||||||
|  | 	url: string; | ||||||
|  | 	resetTimestamp: number; | ||||||
|  | 	bucketID: string | null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class RESTManager { | ||||||
|  | 	client: Client; | ||||||
|  | 	globallyRateLimited: boolean = false; | ||||||
|  | 	queueInProcess: boolean = false; | ||||||
|  | 	pathQueues: { [key: string]: QueuedRequest[] } = {}; | ||||||
|  | 	ratelimitedPaths = new Map<string, RateLimitedPath>(); | ||||||
| 
 | 
 | ||||||
| class Rest { |  | ||||||
|   client: Client |  | ||||||
| 	constructor(client: Client) { | 	constructor(client: Client) { | ||||||
|     this.client = client | 		this.client = client; | ||||||
|   } |  | ||||||
|   // TODO: make endpoints function
 |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| export { Rest } | 	async processRateLimitedPaths() { | ||||||
|  | 		const now = Date.now(); | ||||||
|  | 		this.ratelimitedPaths.forEach((value, key) => { | ||||||
|  | 			if (value.resetTimestamp > now) return; | ||||||
|  | 			this.ratelimitedPaths.delete(key); | ||||||
|  | 			if (key === "global") this.globallyRateLimited = false; | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		await delay(1000); | ||||||
|  | 		this.processRateLimitedPaths(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	addToQueue(request: QueuedRequest) { | ||||||
|  | 		const route = request.url.substring(baseEndpoints.DISCORD_API_URL.length + 1); | ||||||
|  | 		const parts = route.split("/"); | ||||||
|  | 		// Remove the major param
 | ||||||
|  | 		parts.shift(); | ||||||
|  | 		const [id] = parts; | ||||||
|  | 	 | ||||||
|  | 		if (this.pathQueues[id]) { | ||||||
|  | 			this.pathQueues[id].push(request); | ||||||
|  | 		} else { | ||||||
|  | 			this.pathQueues[id] = [request]; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	async cleanupQueues() { | ||||||
|  | 		Object.entries(this.pathQueues).map(([key, value]) => { | ||||||
|  | 			if (!value.length) { | ||||||
|  | 				// Remove it entirely
 | ||||||
|  | 				delete this.pathQueues[key]; | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	async processQueue() { | ||||||
|  | 		if ( | ||||||
|  | 			(Object.keys(this.pathQueues).length) && !this.globallyRateLimited | ||||||
|  | 		) { | ||||||
|  | 			await Promise.allSettled( | ||||||
|  | 				Object.values(this.pathQueues).map(async (pathQueue) => { | ||||||
|  | 					const request = pathQueue.shift(); | ||||||
|  | 					if (!request) return; | ||||||
|  | 	 | ||||||
|  | 					const rateLimitedURLResetIn = await this.checkRatelimits(request.url); | ||||||
|  | 	 | ||||||
|  | 					if (request.bucketID) { | ||||||
|  | 						const rateLimitResetIn = await this.checkRatelimits(request.bucketID); | ||||||
|  | 						if (rateLimitResetIn) { | ||||||
|  | 							// This request is still rate limited readd to queue
 | ||||||
|  | 							this.addToQueue(request); | ||||||
|  | 						} else if (rateLimitedURLResetIn) { | ||||||
|  | 							// This URL is rate limited readd to queue
 | ||||||
|  | 							this.addToQueue(request); | ||||||
|  | 						} else { | ||||||
|  | 							// This request is not rate limited so it should be run
 | ||||||
|  | 							const result = await request.callback(); | ||||||
|  | 							if (result && result.rateLimited) { | ||||||
|  | 								this.addToQueue( | ||||||
|  | 									{ ...request, bucketID: result.bucketID || request.bucketID }, | ||||||
|  | 								); | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						if (rateLimitedURLResetIn) { | ||||||
|  | 							// This URL is rate limited readd to queue
 | ||||||
|  | 							this.addToQueue(request); | ||||||
|  | 						} else { | ||||||
|  | 							// This request has no bucket id so it should be processed
 | ||||||
|  | 							const result = await request.callback(); | ||||||
|  | 							if (request && result && result.rateLimited) { | ||||||
|  | 								this.addToQueue( | ||||||
|  | 									{ ...request, bucketID: result.bucketID || request.bucketID }, | ||||||
|  | 								); | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				}), | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  | 	 | ||||||
|  | 		if (Object.keys(this.pathQueues).length) { | ||||||
|  | 			await delay(1000); | ||||||
|  | 			this.processQueue(); | ||||||
|  | 			this.cleanupQueues(); | ||||||
|  | 		} else this.queueInProcess = false; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	createRequestBody(body: any, method: RequestMethods) { | ||||||
|  | 		const headers: { [key: string]: string } = { | ||||||
|  | 			Authorization: `Bot ${this.client.token}`, | ||||||
|  | 			"User-Agent": | ||||||
|  | 				`DiscordBot (discord.deno)`, | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		if(!this.client.token) delete headers["Authorization"]; | ||||||
|  | 	 | ||||||
|  | 		if (method === "get") body = undefined; | ||||||
|  | 	 | ||||||
|  | 		if (body?.reason) { | ||||||
|  | 			headers["X-Audit-Log-Reason"] = encodeURIComponent(body.reason); | ||||||
|  | 		} | ||||||
|  | 	 | ||||||
|  | 		if (body?.file) { | ||||||
|  | 			const form = new FormData(); | ||||||
|  | 			form.append("file", body.file.blob, body.file.name); | ||||||
|  | 			form.append("payload_json", JSON.stringify({ ...body, file: undefined })); | ||||||
|  | 			body.file = form; | ||||||
|  | 		} else if ( | ||||||
|  | 			body && !["get", "delete"].includes(method) | ||||||
|  | 		) { | ||||||
|  | 			headers["Content-Type"] = "application/json"; | ||||||
|  | 		} | ||||||
|  | 	 | ||||||
|  | 		return { | ||||||
|  | 			headers, | ||||||
|  | 			body: body?.file || JSON.stringify(body), | ||||||
|  | 			method: method.toUpperCase(), | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	async checkRatelimits(url: string) { | ||||||
|  | 		const ratelimited = this.ratelimitedPaths.get(url); | ||||||
|  | 		const global = this.ratelimitedPaths.get("global"); | ||||||
|  | 		const now = Date.now(); | ||||||
|  | 	 | ||||||
|  | 		if (ratelimited && now < ratelimited.resetTimestamp) { | ||||||
|  | 			return ratelimited.resetTimestamp - now; | ||||||
|  | 		} | ||||||
|  | 		if (global && now < global.resetTimestamp) { | ||||||
|  | 			return global.resetTimestamp - now; | ||||||
|  | 		} | ||||||
|  | 	 | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	async runMethod( | ||||||
|  | 		method: RequestMethods, | ||||||
|  | 		url: string, | ||||||
|  | 		body?: unknown, | ||||||
|  | 		retryCount = 0, | ||||||
|  | 		bucketID?: string | null, | ||||||
|  | 	) { | ||||||
|  | 	 | ||||||
|  | 		const errorStack = new Error("Location In Your Files:"); | ||||||
|  | 		Error.captureStackTrace(errorStack); | ||||||
|  | 	 | ||||||
|  | 		return new Promise((resolve, reject) => { | ||||||
|  | 			const callback = async () => { | ||||||
|  | 				try { | ||||||
|  | 					const rateLimitResetIn = await this.checkRatelimits(url); | ||||||
|  | 					if (rateLimitResetIn) { | ||||||
|  | 						return { rateLimited: rateLimitResetIn, beforeFetch: true, bucketID }; | ||||||
|  | 					} | ||||||
|  | 	 | ||||||
|  | 					const query = method === "get" && body | ||||||
|  | 						? Object.entries(body as any).map(([key, value]) => | ||||||
|  | 							`${encodeURIComponent(key)}=${encodeURIComponent(value as any)}` | ||||||
|  | 						) | ||||||
|  | 							.join("&") | ||||||
|  | 						: ""; | ||||||
|  | 					const urlToUse = method === "get" && query ? `${url}?${query}` : url; | ||||||
|  | 
 | ||||||
|  | 					const response = await fetch(urlToUse, this.createRequestBody(body, method)); | ||||||
|  | 					const bucketIDFromHeaders = this.processHeaders(url, response.headers); | ||||||
|  | 					this.handleStatusCode(response, errorStack); | ||||||
|  | 	 | ||||||
|  | 					// Sometimes Discord returns an empty 204 response that can't be made to JSON.
 | ||||||
|  | 					if (response.status === 204) return resolve(); | ||||||
|  | 	 | ||||||
|  | 					const json = await response.json(); | ||||||
|  | 					if ( | ||||||
|  | 						json.retry_after || | ||||||
|  | 						json.message === "You are being rate limited." | ||||||
|  | 					) { | ||||||
|  | 						if (retryCount > 10) { | ||||||
|  | 							throw new Error("Max RateLimit Retries hit"); | ||||||
|  | 						} | ||||||
|  | 	 | ||||||
|  | 						return { | ||||||
|  | 							rateLimited: json.retry_after, | ||||||
|  | 							beforeFetch: false, | ||||||
|  | 							bucketID: bucketIDFromHeaders, | ||||||
|  | 						}; | ||||||
|  | 					} | ||||||
|  | 					return resolve(json); | ||||||
|  | 				} catch (error) { | ||||||
|  | 					return reject(error); | ||||||
|  | 				} | ||||||
|  | 			}; | ||||||
|  | 	 | ||||||
|  | 			this.addToQueue({ | ||||||
|  | 				callback, | ||||||
|  | 				bucketID, | ||||||
|  | 				url, | ||||||
|  | 			}); | ||||||
|  | 			if (!this.queueInProcess) { | ||||||
|  | 				this.queueInProcess = true; | ||||||
|  | 				this.processQueue(); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	async logErrors(response: Response, errorStack?: unknown) { | ||||||
|  | 		try { | ||||||
|  | 			const error = await response.json(); | ||||||
|  | 			console.error(error); | ||||||
|  | 		} catch { | ||||||
|  | 			console.error(response); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	handleStatusCode(response: Response, errorStack?: unknown) { | ||||||
|  | 		const status = response.status; | ||||||
|  | 	 | ||||||
|  | 		if ( | ||||||
|  | 			(status >= 200 && status < 400) || | ||||||
|  | 			status === HttpResponseCode.TooManyRequests | ||||||
|  | 		) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 	 | ||||||
|  | 		this.logErrors(response, errorStack); | ||||||
|  | 	 | ||||||
|  | 		switch (status) { | ||||||
|  | 			case HttpResponseCode.BadRequest: | ||||||
|  | 			case HttpResponseCode.Unauthorized: | ||||||
|  | 			case HttpResponseCode.Forbidden: | ||||||
|  | 			case HttpResponseCode.NotFound: | ||||||
|  | 			case HttpResponseCode.MethodNotAllowed: | ||||||
|  | 				throw new Error("Request Client Error"); | ||||||
|  | 			case HttpResponseCode.GatewayUnavailable: | ||||||
|  | 				throw new Error("Request Server Error"); | ||||||
|  | 		} | ||||||
|  | 	 | ||||||
|  | 		// left are all unknown
 | ||||||
|  | 		throw new Error("Request Unknown Error"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	processHeaders(url: string, headers: Headers) { | ||||||
|  | 		let ratelimited = false; | ||||||
|  | 	 | ||||||
|  | 		// Get all useful headers
 | ||||||
|  | 		const remaining = headers.get("x-ratelimit-remaining"); | ||||||
|  | 		const resetTimestamp = headers.get("x-ratelimit-reset"); | ||||||
|  | 		const retryAfter = headers.get("retry-after"); | ||||||
|  | 		const global = headers.get("x-ratelimit-global"); | ||||||
|  | 		const bucketID = headers.get("x-ratelimit-bucket"); | ||||||
|  | 	 | ||||||
|  | 		// If there is no remaining rate limit for this endpoint, we save it in cache
 | ||||||
|  | 		if (remaining && remaining === "0") { | ||||||
|  | 			ratelimited = true; | ||||||
|  | 	 | ||||||
|  | 			this.ratelimitedPaths.set(url, { | ||||||
|  | 				url, | ||||||
|  | 				resetTimestamp: Number(resetTimestamp) * 1000, | ||||||
|  | 				bucketID, | ||||||
|  | 			}); | ||||||
|  | 	 | ||||||
|  | 			if (bucketID) { | ||||||
|  | 				this.ratelimitedPaths.set(bucketID, { | ||||||
|  | 					url, | ||||||
|  | 					resetTimestamp: Number(resetTimestamp) * 1000, | ||||||
|  | 					bucketID, | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	 | ||||||
|  | 		// If there is no remaining global limit, we save it in cache
 | ||||||
|  | 		if (global) { | ||||||
|  | 			const reset = Date.now() + Number(retryAfter); | ||||||
|  | 			this.globallyRateLimited = true; | ||||||
|  | 			ratelimited = true; | ||||||
|  | 	 | ||||||
|  | 			this.ratelimitedPaths.set("global", { | ||||||
|  | 				url: "global", | ||||||
|  | 				resetTimestamp: reset, | ||||||
|  | 				bucketID, | ||||||
|  | 			}); | ||||||
|  | 	 | ||||||
|  | 			if (bucketID) { | ||||||
|  | 				this.ratelimitedPaths.set(bucketID, { | ||||||
|  | 					url: "global", | ||||||
|  | 					resetTimestamp: reset, | ||||||
|  | 					bucketID, | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	 | ||||||
|  | 		return ratelimited ? bucketID : undefined; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	get(url: string, body?: unknown) { | ||||||
|  |     return this.runMethod("get", url, body); | ||||||
|  |   } | ||||||
|  |   post(url: string, body?: unknown) { | ||||||
|  |     return this.runMethod("post", url, body); | ||||||
|  |   } | ||||||
|  |   delete(url: string, body?: unknown) { | ||||||
|  |     return this.runMethod("delete", url, body); | ||||||
|  |   } | ||||||
|  |   patch(url: string, body?: unknown) { | ||||||
|  |     return this.runMethod("patch", url, body); | ||||||
|  |   } | ||||||
|  |   put(url: string, body?: unknown) { | ||||||
|  |     return this.runMethod("put", url, body); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -5,10 +5,11 @@ import { Base } from './base.ts' | ||||||
| import { Channel } from './channel.ts' | import { Channel } from './channel.ts' | ||||||
| import { Emoji } from './emoji.ts' | import { Emoji } from './emoji.ts' | ||||||
| import { Member } from './member.ts' | import { Member } from './member.ts' | ||||||
| import { Role } from './role.ts' |  | ||||||
| import { VoiceState } from './voiceState.ts' | import { VoiceState } from './voiceState.ts' | ||||||
| import cache from '../models/cache.ts' | import cache from '../models/cache.ts' | ||||||
| import getChannelByType from '../utils/getChannelByType.ts' | import getChannelByType from '../utils/getChannelByType.ts' | ||||||
|  | import { RolesManager } from "../managers/RolesManager.ts" | ||||||
|  | import { Role } from "./role.ts" | ||||||
| 
 | 
 | ||||||
| export class Guild extends Base { | export class Guild extends Base { | ||||||
|   id: string |   id: string | ||||||
|  | @ -28,7 +29,7 @@ export class Guild extends Base { | ||||||
|   verificationLevel?: string |   verificationLevel?: string | ||||||
|   defaultMessageNotifications?: string |   defaultMessageNotifications?: string | ||||||
|   explicitContentFilter?: string |   explicitContentFilter?: string | ||||||
|   roles?: Role[] |   roles: RolesManager = new RolesManager(this.client, this) | ||||||
|   emojis?: Emoji[] |   emojis?: Emoji[] | ||||||
|   features?: GuildFeatures[] |   features?: GuildFeatures[] | ||||||
|   mfaLevel?: string |   mfaLevel?: string | ||||||
|  | @ -79,9 +80,12 @@ export class Guild extends Base { | ||||||
|       this.verificationLevel = data.verification_level |       this.verificationLevel = data.verification_level | ||||||
|       this.defaultMessageNotifications = data.default_message_notifications |       this.defaultMessageNotifications = data.default_message_notifications | ||||||
|       this.explicitContentFilter = data.explicit_content_filter |       this.explicitContentFilter = data.explicit_content_filter | ||||||
|       this.roles = data.roles.map( |       // this.roles = data.roles.map(
 | ||||||
|         v => cache.get('role', v.id) ?? new Role(client, v) |       //   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( |       this.emojis = data.emojis.map( | ||||||
|         v => cache.get('emoji', v.id) ?? new Emoji(client, v) |         v => cache.get('emoji', v.id) ?? new Emoji(client, v) | ||||||
|       ) |       ) | ||||||
|  | @ -120,7 +124,6 @@ export class Guild extends Base { | ||||||
|       this.approximateNumberCount = data.approximate_number_count |       this.approximateNumberCount = data.approximate_number_count | ||||||
|       this.approximatePresenceCount = data.approximate_presence_count |       this.approximatePresenceCount = data.approximate_presence_count | ||||||
|     } |     } | ||||||
|     cache.set('guild', this.id, this) |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: GuildPayload): void { |   protected readFromData (data: GuildPayload): void { | ||||||
|  | @ -147,10 +150,10 @@ export class Guild extends Base { | ||||||
|         data.default_message_notifications ?? this.defaultMessageNotifications |         data.default_message_notifications ?? this.defaultMessageNotifications | ||||||
|       this.explicitContentFilter = |       this.explicitContentFilter = | ||||||
|         data.explicit_content_filter ?? this.explicitContentFilter |         data.explicit_content_filter ?? this.explicitContentFilter | ||||||
|       this.roles = |       // this.roles =
 | ||||||
|         data.roles.map( |       //   data.roles.map(
 | ||||||
|           v => cache.get('role', v.id) ?? new Role(this.client, v) |       //     v => cache.get('role', v.id) ?? new Role(this.client, v)
 | ||||||
|         ) ?? this.roles |       //   ) ?? this.roles
 | ||||||
|       this.emojis = |       this.emojis = | ||||||
|         data.emojis.map( |         data.emojis.map( | ||||||
|           v => cache.get('emoji', v.id) ?? new Emoji(this.client, v) |           v => cache.get('emoji', v.id) ?? new Emoji(this.client, v) | ||||||
|  |  | ||||||
|  | @ -18,6 +18,10 @@ export class User extends Base { | ||||||
|   premiumType?: 0 | 1 | 2 |   premiumType?: 0 | 1 | 2 | ||||||
|   publicFlags?: number |   publicFlags?: number | ||||||
| 
 | 
 | ||||||
|  |   get tag(): string { | ||||||
|  |     return `${this.username}#${this.discriminator}`; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   get nickMention (): string { |   get nickMention (): string { | ||||||
|     return `<@!${this.id}>` |     return `<@!${this.id}>` | ||||||
|   } |   } | ||||||
|  | @ -59,4 +63,8 @@ export class User extends Base { | ||||||
|     this.premiumType = data.premium_type ?? this.premiumType |     this.premiumType = data.premium_type ?? this.premiumType | ||||||
|     this.publicFlags = data.public_flags ?? this.publicFlags |     this.publicFlags = data.public_flags ?? this.publicFlags | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   toString() { | ||||||
|  |     return this.mention; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,2 +1 @@ | ||||||
| const TOKEN = '' | export const TOKEN = '' | ||||||
| export { TOKEN } |  | ||||||
|  | @ -10,9 +10,11 @@ import { User } from '../structures/user.ts' | ||||||
| const bot = new Client() | const bot = new Client() | ||||||
| 
 | 
 | ||||||
| bot.on('ready', () => { | bot.on('ready', () => { | ||||||
|   console.log('READY!') |   console.log(`[Login] Logged in as ${bot.user?.tag}!`) | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  | bot.on('debug', console.log) | ||||||
|  | 
 | ||||||
| bot.on('channelDelete', (channel: Channel) => { | bot.on('channelDelete', (channel: Channel) => { | ||||||
|   console.log('channelDelete', channel.id) |   console.log('channelDelete', channel.id) | ||||||
| }) | }) | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								src/types/gatewayBot.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/types/gatewayBot.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | ||||||
|  | export interface ISessionStartLimit { | ||||||
|  |   total: number | ||||||
|  |   remaining: number | ||||||
|  |   reset_after: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface GatewayBotPayload { | ||||||
|  |   url: string | ||||||
|  |   shards: number | ||||||
|  |   session_start_limit: ISessionStartLimit | ||||||
|  | } | ||||||
							
								
								
									
										87
									
								
								src/utils/collection.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/utils/collection.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,87 @@ | ||||||
|  | export class Collection<K, V> extends Map<K, V> { | ||||||
|  |   maxSize?: number; | ||||||
|  | 
 | ||||||
|  |   set(key: K, value: V) { | ||||||
|  |     if (this.maxSize || this.maxSize === 0) { | ||||||
|  |       if (this.size >= this.maxSize) return this | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return super.set(key, value) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   array() { | ||||||
|  |     return [...this.values()] | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   first(): V { | ||||||
|  |     return this.values().next().value | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   last(): V { | ||||||
|  |     return [...this.values()][this.size - 1] | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   random() { | ||||||
|  |     let arr = [...this.values()] | ||||||
|  |     return arr[Math.floor(Math.random() * arr.length)] | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   find(callback: (value: V, key: K) => boolean) { | ||||||
|  |     for (const key of this.keys()) { | ||||||
|  |       const value = this.get(key)! | ||||||
|  |       if (callback(value, key)) return value | ||||||
|  |     } | ||||||
|  |     // If nothing matched
 | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   filter(callback: (value: V, key: K) => boolean) { | ||||||
|  |     const relevant = new Collection<K, V>() | ||||||
|  |     this.forEach((value, key) => { | ||||||
|  |       if (callback(value, key)) relevant.set(key, value) | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     return relevant; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   map<T>(callback: (value: V, key: K) => T) { | ||||||
|  |     const results = [] | ||||||
|  |     for (const key of this.keys()) { | ||||||
|  |       const value = this.get(key)! | ||||||
|  |       results.push(callback(value, key)) | ||||||
|  |     } | ||||||
|  |     return results | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   some(callback: (value: V, key: K) => boolean) { | ||||||
|  |     for (const key of this.keys()) { | ||||||
|  |       const value = this.get(key)! | ||||||
|  |       if (callback(value, key)) return true | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return false | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   every(callback: (value: V, key: K) => boolean) { | ||||||
|  |     for (const key of this.keys()) { | ||||||
|  |       const value = this.get(key)! | ||||||
|  |       if (!callback(value, key)) return false | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   reduce<T>( | ||||||
|  |     callback: (accumulator: T, value: V, key: K) => T, | ||||||
|  |     initialValue?: T, | ||||||
|  |   ): T { | ||||||
|  |     let accumulator: T = initialValue! | ||||||
|  | 
 | ||||||
|  |     for (const key of this.keys()) { | ||||||
|  |       const value = this.get(key)! | ||||||
|  |       accumulator = callback(accumulator, value, key) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return accumulator | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								src/utils/delay.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/utils/delay.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | export const delay = (ms: number) => new Promise((resolve, reject) => { | ||||||
|  |     setTimeout(() => resolve(true), ms); | ||||||
|  | }); | ||||||
|  | @ -1,3 +1,3 @@ | ||||||
| import getChannelByType from './getChannelByType.ts' | export { default as getChannelByType } from './getChannelByType.ts' | ||||||
| 
 | export type AnyFunction<ReturnType = any> = (...args:any[]) => ReturnType; | ||||||
| export default { getChannelByType } | export { delay } from './delay.ts' | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue