Merge pull request #18 from DjDeveloperr/main
feature: Cache Adapters and REST Manager
This commit is contained in:
		
						commit
						98b202fb30
					
				
					 47 changed files with 1518 additions and 192 deletions
				
			
		|  | @ -1,13 +1,13 @@ | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| import getChannelByType from '../../utils/getChannelByType.ts' | import getChannelByType from '../../utils/getChannelByType.ts' | ||||||
| 
 | 
 | ||||||
| export const channelCreate: GatewayEventHandler = ( | export const channelCreate: GatewayEventHandler = async ( | ||||||
|   gateway: Gateway, |   gateway: Gateway, | ||||||
|   d: any |   d: any | ||||||
| ) => { | ) => { | ||||||
|   const channel = getChannelByType(gateway.client, d) |   const channel = getChannelByType(gateway.client, d) | ||||||
| 
 |  | ||||||
|   if (channel !== undefined) { |   if (channel !== undefined) { | ||||||
|  |     await 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 = async( | ||||||
|   gateway: Gateway, |   gateway: Gateway, | ||||||
|   d: any |   d: any | ||||||
| ) => { | ) => { | ||||||
|   const channel: Channel = cache.get('channel', d.id) |   const channel: Channel = await gateway.client.channels.get(d.id) | ||||||
|   if (channel !== undefined) { |   if (channel !== undefined) { | ||||||
|     cache.del('channel', d.id) |     await 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/channel.ts" | ||||||
| 
 | 
 | ||||||
| export const channelPinsUpdate: GatewayEventHandler = ( | export const channelPinsUpdate: GatewayEventHandler = async( | ||||||
|   gateway: Gateway, |   gateway: Gateway, | ||||||
|   d: any |   d: any | ||||||
| ) => { | ) => { | ||||||
|   const after: TextChannel = cache.get('textchannel', d.channel_id) |   const after: TextChannel = await 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 | ||||||
|     }) |     }) | ||||||
|  |     const raw = await gateway.client.channels._get(d.channel_id) ; | ||||||
|  |     await 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,15 +1,15 @@ | ||||||
| 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' | ||||||
| 
 | 
 | ||||||
| export const channelUpdate: GatewayEventHandler = ( | export const channelUpdate: GatewayEventHandler = async ( | ||||||
|   gateway: Gateway, |   gateway: Gateway, | ||||||
|   d: any |   d: any | ||||||
| ) => { | ) => { | ||||||
|   const oldChannel: Channel = cache.get('channel', d.id) |   const oldChannel: Channel = await gateway.client.channels.get(d.id) | ||||||
| 
 | 
 | ||||||
|   if (oldChannel !== undefined) { |   if (oldChannel !== undefined) { | ||||||
|  |     await 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,16 @@ | ||||||
| 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' | ||||||
|  | import { GuildPayload } from "../../types/guild.ts" | ||||||
| 
 | 
 | ||||||
| export const guildCreate: GatewayEventHandler = (gateway: Gateway, d: any) => { | export const guildCreate: GatewayEventHandler = async(gateway: Gateway, d: any) => { | ||||||
|   let guild: Guild = cache.get('guild', d.id) |   let guild: Guild | void = await 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
 | ||||||
|  |     await gateway.client.guilds.set(d.id, d) | ||||||
|     guild.refreshFromData(d) |     guild.refreshFromData(d) | ||||||
|   } else { |   } else { | ||||||
|     guild = new Guild(gateway.client, d) |     await gateway.client.guilds.set(d.id, d) | ||||||
|  |     guild = new Guild(gateway.client, d as GuildPayload) | ||||||
|  |     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 = async (gateway: Gateway, d: any) => { | ||||||
|   const guild: Guild = cache.get('guild', d.id) |   const guild: Guild | void = await gateway.client.guilds.get(d.id) | ||||||
| 
 | 
 | ||||||
|   if (guild !== undefined) { |   if (guild !== undefined) { | ||||||
|     guild.refreshFromData(d) |     guild.refreshFromData(d) | ||||||
|     cache.del('guild', d.id) |     await gateway.client.guilds.delete(d.id) | ||||||
|     gateway.client.emit('guildDelete', guild) |     gateway.client.emit('guildDelete', guild) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,11 +1,10 @@ | ||||||
| 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 guildUpdate: GatewayEventHandler = (gateway: Gateway, d: any) => { | export const guildUpdate: GatewayEventHandler = async(gateway: Gateway, d: any) => { | ||||||
|   const after: Guild = cache.get('guild', d.id) |   const before: Guild | void = await gateway.client.guilds.get(d.id) | ||||||
|   if (after !== undefined) { |   if(!before) return | ||||||
|     const before: Guild = after.refreshFromData(d) |   await gateway.client.guilds.set(d.id, d) | ||||||
|     gateway.client.emit('guildUpdate', before, after) |   const after: Guild | void = await gateway.client.guilds.get(d.id) | ||||||
|   } |   gateway.client.emit('guildUpdate', before, after) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -10,13 +10,15 @@ import { guildUpdate } from './guildUpdate.ts' | ||||||
| import { guildBanAdd } from './guildBanAdd.ts' | import { guildBanAdd } from './guildBanAdd.ts' | ||||||
| import { ready } from './ready.ts' | import { ready } from './ready.ts' | ||||||
| import { guildBanRemove } from './guildBanRemove.ts' | import { guildBanRemove } from './guildBanRemove.ts' | ||||||
|  | import { messageCreate } from "./messageCreate.ts" | ||||||
|  | import { resume } from "./resume.ts" | ||||||
| 
 | 
 | ||||||
| export const gatewayHandlers: { | export const gatewayHandlers: { | ||||||
|   [eventCode in GatewayEvents]: GatewayEventHandler | undefined |   [eventCode in GatewayEvents]: GatewayEventHandler | undefined | ||||||
| } = { | } = { | ||||||
|   READY: ready, |   READY: ready, | ||||||
|   RECONNECT: undefined, |   RECONNECT: undefined, | ||||||
|   RESUMED: undefined, |   RESUMED: resume, | ||||||
|   CHANNEL_CREATE: channelCreate, |   CHANNEL_CREATE: channelCreate, | ||||||
|   CHANNEL_DELETE: channelDelete, |   CHANNEL_DELETE: channelDelete, | ||||||
|   CHANNEL_UPDATE: channelUpdate, |   CHANNEL_UPDATE: channelUpdate, | ||||||
|  | @ -37,7 +39,7 @@ export const gatewayHandlers: { | ||||||
|   GUILD_ROLE_DELETE: undefined, |   GUILD_ROLE_DELETE: undefined, | ||||||
|   INVITE_CREATE: undefined, |   INVITE_CREATE: undefined, | ||||||
|   INVITE_DELETE: undefined, |   INVITE_DELETE: undefined, | ||||||
|   MESSAGE_CREATE: undefined, |   MESSAGE_CREATE: messageCreate, | ||||||
|   MESSAGE_UPDATE: undefined, |   MESSAGE_UPDATE: undefined, | ||||||
|   MESSAGE_DELETE: undefined, |   MESSAGE_DELETE: undefined, | ||||||
|   MESSAGE_DELETE_BULK: undefined, |   MESSAGE_DELETE_BULK: undefined, | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								src/gateway/handlers/messageCreate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/gateway/handlers/messageCreate.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | ||||||
|  | import { Channel } from "../../structures/channel.ts" | ||||||
|  | import { Message } from "../../structures/message.ts" | ||||||
|  | import { MessageMentions } from "../../structures/MessageMentions.ts" | ||||||
|  | import { TextChannel } from "../../structures/textChannel.ts" | ||||||
|  | import { User } from "../../structures/user.ts" | ||||||
|  | import { MessagePayload } from "../../types/channel.ts" | ||||||
|  | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | 
 | ||||||
|  | export const messageCreate: GatewayEventHandler = async( | ||||||
|  |   gateway: Gateway, | ||||||
|  |   d: MessagePayload | ||||||
|  | ) => { | ||||||
|  |   let channel = await gateway.client.channels.get(d.channel_id) | ||||||
|  |   // Fetch the channel if not cached
 | ||||||
|  |   if(!channel) channel = (await gateway.client.channels.fetch(d.channel_id) as any) as TextChannel | ||||||
|  |   let user = new User(gateway.client, d.author) | ||||||
|  |   await gateway.client.users.set(d.author.id, d.author) | ||||||
|  |   let mentions = new MessageMentions() | ||||||
|  |   let message = new Message(gateway.client, d, channel, user, mentions) | ||||||
|  |   gateway.client.emit('messageCreate', message) | ||||||
|  | } | ||||||
|  | @ -1,11 +1,14 @@ | ||||||
| import { Guild } from '../../structures/guild.ts' |  | ||||||
| import { User } from '../../structures/user.ts' | import { User } from '../../structures/user.ts' | ||||||
| import { GuildPayload } from '../../types/guild.ts' | import { GuildPayload } from '../../types/guild.ts' | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| 
 | 
 | ||||||
| export const ready: GatewayEventHandler = (gateway: Gateway, d: any) => { | export const ready: GatewayEventHandler = async (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}`) | ||||||
|  |   await gateway.cache.set("session_id", gateway.sessionID) | ||||||
|  |   d.guilds.forEach((guild: GuildPayload) => { | ||||||
|  |     gateway.client.guilds.set(guild.id, guild) | ||||||
|  |   }) | ||||||
|   gateway.client.emit('ready') |   gateway.client.emit('ready') | ||||||
| } | } | ||||||
							
								
								
									
										7
									
								
								src/gateway/handlers/resume.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/gateway/handlers/resume.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | ||||||
|  | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | 
 | ||||||
|  | export const resume: GatewayEventHandler = (gateway: Gateway, d: any) => { | ||||||
|  |   gateway.debug(`Session Resumed!`) | ||||||
|  |   gateway.client.emit('resume') | ||||||
|  |   gateway.client.emit('ready') | ||||||
|  | } | ||||||
|  | @ -5,8 +5,11 @@ import { | ||||||
|   DISCORD_API_VERSION |   DISCORD_API_VERSION | ||||||
| } from '../consts/urlsAndVersions.ts' | } from '../consts/urlsAndVersions.ts' | ||||||
| import { GatewayResponse } from '../types/gatewayResponse.ts' | import { GatewayResponse } from '../types/gatewayResponse.ts' | ||||||
| import { GatewayOpcodes, GatewayIntents } from '../types/gateway.ts' | import { GatewayOpcodes, GatewayIntents, GatewayCloseCodes } from '../types/gateway.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" | ||||||
|  | import { GatewayCache } from "../managers/GatewayCache.ts" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handles Discord gateway connection. |  * Handles Discord gateway connection. | ||||||
|  | @ -23,15 +26,17 @@ class Gateway { | ||||||
|   heartbeatInterval = 0 |   heartbeatInterval = 0 | ||||||
|   heartbeatIntervalID?: number |   heartbeatIntervalID?: number | ||||||
|   sequenceID?: number |   sequenceID?: number | ||||||
|  |   lastPingTimestamp = 0 | ||||||
|   sessionID?: string |   sessionID?: string | ||||||
|   lastPingTimestemp = 0 |  | ||||||
|   private heartbeatServerResponded = false |   private heartbeatServerResponded = false | ||||||
|   client: Client |   client: Client | ||||||
|  |   cache: GatewayCache | ||||||
| 
 | 
 | ||||||
|   constructor (client: Client, token: string, intents: GatewayIntents[]) { |   constructor (client: Client, token: string, intents: GatewayIntents[]) { | ||||||
|     this.token = token |     this.token = token | ||||||
|     this.intents = intents |     this.intents = intents | ||||||
|     this.client = client |     this.client = client | ||||||
|  |     this.cache = new GatewayCache(client) | ||||||
|     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
 | ||||||
|       `${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`, |       `${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`, | ||||||
|  | @ -46,9 +51,10 @@ 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 async onmessage (event: MessageEvent): Promise<void> { | ||||||
|     let data = event.data |     let data = event.data | ||||||
|     if (data instanceof ArrayBuffer) { |     if (data instanceof ArrayBuffer) { | ||||||
|       data = new Uint8Array(data) |       data = new Uint8Array(data) | ||||||
|  | @ -63,13 +69,13 @@ 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 | ||||||
|           } else { |           } else { | ||||||
|             clearInterval(this.heartbeatIntervalID) |             clearInterval(this.heartbeatIntervalID) | ||||||
|             this.websocket.close() |             this.reconnect() | ||||||
|             this.initWebsocket() |  | ||||||
|             return |             return | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|  | @ -79,36 +85,36 @@ 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) { | ||||||
|           this.sendIdentify() |           this.sendIdentify() | ||||||
|           this.initialized = true |           this.initialized = true | ||||||
|         } else { |         } else { | ||||||
|  |           console.log("Calling Resume") | ||||||
|           this.sendResume() |           this.sendResume() | ||||||
|         } |         } | ||||||
|         break |         break | ||||||
| 
 | 
 | ||||||
|       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: | ||||||
|         // Because we know this gonna be bool
 |         // Because we know this gonna be bool
 | ||||||
|  |         this.debug(`Invalid Session! Identifying with forced new session`) | ||||||
|         // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
 |         // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
 | ||||||
|         if (!d) { |         setTimeout(() => this.sendIdentify(true), 3000) | ||||||
|           setTimeout(this.sendResume, 3000) |  | ||||||
|         } else { |  | ||||||
|           setTimeout(this.sendIdentify, 3000) |  | ||||||
|         } |  | ||||||
|         break |         break | ||||||
| 
 | 
 | ||||||
|       case GatewayOpcodes.DISPATCH: { |       case GatewayOpcodes.DISPATCH: { | ||||||
|         this.heartbeatServerResponded = true |         this.heartbeatServerResponded = true | ||||||
|         if (s !== null) { |         if (s !== null) { | ||||||
|           this.sequenceID = s |           this.sequenceID = s | ||||||
|  |           await this.cache.set("seq", s) | ||||||
|         } |         } | ||||||
|         if (t !== null && t !== undefined) { |         if (t !== null && t !== undefined) { | ||||||
|           const handler = gatewayHandlers[t] |           const handler = gatewayHandlers[t] | ||||||
|  | @ -119,23 +125,83 @@ class Gateway { | ||||||
|         } |         } | ||||||
|         break |         break | ||||||
|       } |       } | ||||||
|  |       case GatewayOpcodes.RESUME: { | ||||||
|  |         // this.token = d.token
 | ||||||
|  |         this.sessionID = d.session_id | ||||||
|  |         this.sequenceID = d.seq | ||||||
|  |         await this.cache.set("seq", d.seq) | ||||||
|  |         await this.cache.set("session_id", this.sessionID) | ||||||
|  |         break | ||||||
|  |       } | ||||||
|  |       case GatewayOpcodes.RECONNECT: { | ||||||
|  |         this.reconnect() | ||||||
|  |         break | ||||||
|  |       } | ||||||
|       default: |       default: | ||||||
|         break |         break | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private onclose (event: CloseEvent): void { |   private onclose (event: CloseEvent): void { | ||||||
|     console.log(event.code) |     this.debug("Connection Closed with code: " + event.code) | ||||||
|     // TODO: Handle close event codes.
 | 
 | ||||||
|  |     if(event.code == GatewayCloseCodes.UNKNOWN_ERROR) { | ||||||
|  |       this.debug("API has encountered Unknown Error. Reconnecting...") | ||||||
|  |       this.reconnect() | ||||||
|  |     } else if(event.code == GatewayCloseCodes.UNKNOWN_OPCODE) { | ||||||
|  |       throw new Error("Unknown OP Code was sent. This shouldn't happen!") | ||||||
|  |     } else if(event.code == GatewayCloseCodes.DECODE_ERROR) { | ||||||
|  |       throw new Error("Invalid Payload was sent. This shouldn't happen!") | ||||||
|  |     } else if(event.code == GatewayCloseCodes.NOT_AUTHENTICATED) { | ||||||
|  |       throw new Error("Not Authorized: Payload was sent before Identifying.") | ||||||
|  |     } else if(event.code == GatewayCloseCodes.AUTHENTICATION_FAILED) { | ||||||
|  |       throw new Error("Invalid Token provided!") | ||||||
|  |     } else if(event.code == GatewayCloseCodes.INVALID_SEQ) { | ||||||
|  |       this.debug("Invalid Seq was sent. Reconnecting.") | ||||||
|  |       this.reconnect() | ||||||
|  |     } else if(event.code == GatewayCloseCodes.RATE_LIMITED) { | ||||||
|  |       throw new Error("You're ratelimited. Calm down.") | ||||||
|  |     } else if(event.code == GatewayCloseCodes.SESSION_TIMED_OUT) { | ||||||
|  |       this.debug("Session Timeout. Reconnecting.") | ||||||
|  |       this.reconnect(true) | ||||||
|  |     } else if(event.code == GatewayCloseCodes.INVALID_SHARD) { | ||||||
|  |       this.debug("Invalid Shard was sent. Reconnecting.") | ||||||
|  |       this.reconnect() | ||||||
|  |     } else if(event.code == GatewayCloseCodes.SHARDING_REQUIRED) { | ||||||
|  |       throw new Error("Couldn't connect. Sharding is requried!") | ||||||
|  |     } else if(event.code == GatewayCloseCodes.INVALID_API_VERSION) { | ||||||
|  |       throw new Error("Invalid API Version was used. This shouldn't happen!") | ||||||
|  |     } else if(event.code == GatewayCloseCodes.INVALID_INTENTS) { | ||||||
|  |       throw new Error("Invalid Intents") | ||||||
|  |     } else if(event.code == GatewayCloseCodes.DISALLOWED_INTENTS) { | ||||||
|  |       throw new Error("Given Intents aren't allowed") | ||||||
|  |     } else { | ||||||
|  |       this.debug("Unknown Close code, probably connection error. Reconnecting.") | ||||||
|  |       this.reconnect() | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private onerror (event: Event | ErrorEvent): void { |   private onerror (event: Event | ErrorEvent): void { | ||||||
|     const eventError = event as ErrorEvent |     const eventError = event as ErrorEvent | ||||||
| 
 |  | ||||||
|     console.log(eventError) |     console.log(eventError) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private sendIdentify (): void { |   private async sendIdentify (forceNewSession?: boolean) { | ||||||
|  |     this.debug("Fetching /gateway/bot...") | ||||||
|  |     const 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`) | ||||||
|  |     if(!forceNewSession) { | ||||||
|  |       let sessionIDCached = await this.cache.get("session_id") | ||||||
|  |       if(sessionIDCached) { | ||||||
|  |         this.debug("Found Cached SessionID: " + sessionIDCached) | ||||||
|  |         this.sessionID = sessionIDCached | ||||||
|  |         return this.sendResume() | ||||||
|  |       } | ||||||
|  |     } | ||||||
|     this.websocket.send( |     this.websocket.send( | ||||||
|       JSON.stringify({ |       JSON.stringify({ | ||||||
|         op: GatewayOpcodes.IDENTIFY, |         op: GatewayOpcodes.IDENTIFY, | ||||||
|  | @ -163,19 +229,36 @@ class Gateway { | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private sendResume (): void { |   private async sendResume (): Promise<void> { | ||||||
|  |     this.debug(`Preparing to resume with Session: ${this.sessionID}`) | ||||||
|  |     if(this.sequenceID === undefined) { | ||||||
|  |       let cached = await this.cache.get("seq") | ||||||
|  |       if(cached) this.sequenceID = typeof cached == "string" ? parseInt(cached) : cached | ||||||
|  |     } | ||||||
|  |     const resumePayload = { | ||||||
|  |       op: GatewayOpcodes.RESUME, | ||||||
|  |       d: { | ||||||
|  |         token: this.token, | ||||||
|  |         session_id: this.sessionID, | ||||||
|  |         seq: this.sequenceID || null | ||||||
|  |       } | ||||||
|  |     } | ||||||
|     this.websocket.send( |     this.websocket.send( | ||||||
|       JSON.stringify({ |       JSON.stringify(resumePayload) | ||||||
|         op: GatewayOpcodes.RESUME, |  | ||||||
|         d: { |  | ||||||
|           token: this.token, |  | ||||||
|           session_id: this.sessionID, |  | ||||||
|           seq: this.sequenceID |  | ||||||
|         } |  | ||||||
|       }) |  | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   debug(msg: string) { | ||||||
|  |     this.client.debug("Gateway", msg) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async reconnect(forceNew?: boolean) { | ||||||
|  |     clearInterval(this.heartbeatIntervalID) | ||||||
|  |     if(forceNew) await this.cache.delete("session_id") | ||||||
|  |     this.close() | ||||||
|  |     this.initWebsocket() | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   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: any | ||||||
|  | 
 | ||||||
|  |   constructor(client: Client, cacheName: string, dataType: any) { | ||||||
|  |     this.client = client | ||||||
|  |     this.cacheName = cacheName | ||||||
|  |     this.dataType = dataType | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   _get(key: string): Promise<T> { | ||||||
|  |     return this.client.cache.get(this.cacheName, key) as Promise<T> | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async get(key: string): Promise<T2 | void> { | ||||||
|  |     const raw = await this._get(key) | ||||||
|  |     if(!raw) return | ||||||
|  |     return new this.dataType(this.client, raw) as any | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async set(key: string, value: T) { | ||||||
|  |     return this.client.cache.set(this.cacheName, key, value) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async 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/channel.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
 | ||||||
|  |   async get<T = Channel>(key: string): Promise<T> { | ||||||
|  |     return new this.dataType(this.client, this._get(key)) as any | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fetch(id: string): Promise<Channel> { | ||||||
|  |     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/emoji.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)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								src/managers/GatewayCache.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/managers/GatewayCache.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | ||||||
|  | import { Client } from "../models/client.ts"; | ||||||
|  | 
 | ||||||
|  | export class GatewayCache { | ||||||
|  |     client: Client | ||||||
|  |     cacheName: string = "discord_gateway_cache" | ||||||
|  |      | ||||||
|  |     constructor(client: Client, cacheName?: string) { | ||||||
|  |         this.client = client | ||||||
|  |         if(cacheName) this.cacheName = cacheName | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     get(key: string) { | ||||||
|  |         return this.client.cache.get(this.cacheName, key) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     set(key: string, value: any) { | ||||||
|  |         return this.client.cache.set(this.cacheName, key, value) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     delete(key: string) { | ||||||
|  |         console.log(`[GatewayCache] DEL ${key}`) | ||||||
|  |         return this.client.cache.delete(this.cacheName, key) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										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/guild.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)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								src/managers/MessagesManager.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/managers/MessagesManager.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | import { Client } from "../models/client.ts"; | ||||||
|  | import { Message } from "../structures/message.ts"; | ||||||
|  | import { MessageMentions } from "../structures/MessageMentions.ts"; | ||||||
|  | import { User } from "../structures/user.ts"; | ||||||
|  | import { MessagePayload } from "../types/channel.ts"; | ||||||
|  | import { CHANNEL_MESSAGE } from "../types/endpoint.ts"; | ||||||
|  | import { UserPayload } from "../types/user.ts"; | ||||||
|  | import { BaseManager } from "./BaseManager.ts"; | ||||||
|  | 
 | ||||||
|  | export class MessagesManager extends BaseManager<MessagePayload, Message> { | ||||||
|  |   constructor(client: Client) { | ||||||
|  |     super(client, "messages", Message) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fetch(channelID: string, id: string) { | ||||||
|  |     return new Promise((res, rej) => { | ||||||
|  |       this.client.rest.get(CHANNEL_MESSAGE(channelID, id)).then(async data => { | ||||||
|  |         this.set(id, data as MessagePayload) | ||||||
|  |         let channel = await this.client.channels.get(channelID) | ||||||
|  |         if(!channel) channel = await this.client.channels.fetch(channelID) | ||||||
|  |         let author = new User(this.client, (data as MessagePayload).author as UserPayload) | ||||||
|  |         await this.client.users.set(author.id, (data as MessagePayload).author) | ||||||
|  |         // TODO: Make this thing work (MessageMentions)
 | ||||||
|  |         let mentions = new MessageMentions() | ||||||
|  |         res(new Message(this.client, data as MessagePayload, channel, author, mentions)) | ||||||
|  |       }).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/role.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/user.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)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										98
									
								
								src/models/CacheAdapter.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/models/CacheAdapter.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,98 @@ | ||||||
|  | import { Collection } from "../utils/collection.ts"; | ||||||
|  | import { Client } from "./client.ts"; | ||||||
|  | import { connect, Redis, RedisConnectOptions } from "https://denopkg.com/keroxp/deno-redis/mod.ts"; | ||||||
|  | 
 | ||||||
|  | export interface ICacheAdapter { | ||||||
|  |   client: Client | ||||||
|  |   get: (cacheName: string, key: string) => Promise<any> | any | ||||||
|  |   set: (cacheName: string, key: string, value: any) => Promise<any> | any | ||||||
|  |   delete: (cacheName: string, key: string) => Promise<boolean> | boolean | ||||||
|  |   array: (cacheName: string) => void | any[] | Promise<any[] | void> | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class DefaultCacheAdapter implements ICacheAdapter { | ||||||
|  |   client: Client | ||||||
|  |   data: { | ||||||
|  |     [name: string]: Collection<string, any> | ||||||
|  |   } = {} | ||||||
|  | 
 | ||||||
|  |   constructor(client: Client) { | ||||||
|  |     this.client = client | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async get(cacheName: string, key: string) { | ||||||
|  |     const cache = this.data[cacheName] | ||||||
|  |     if (!cache) return; | ||||||
|  |     return cache.get(key) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async 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) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async delete(cacheName: string, key: string) { | ||||||
|  |     const cache = this.data[cacheName] | ||||||
|  |     if (!cache) return false | ||||||
|  |     return cache.delete(key) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async array(cacheName: string) { | ||||||
|  |     const cache = this.data[cacheName] | ||||||
|  |     if (!cache) return | ||||||
|  |     return cache.array() | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class RedisCacheAdapter implements ICacheAdapter { | ||||||
|  |   client: Client | ||||||
|  |   _redis: Promise<Redis> | ||||||
|  |   redis?: Redis | ||||||
|  |   ready: boolean = false | ||||||
|  | 
 | ||||||
|  |   constructor(client: Client, options: RedisConnectOptions) { | ||||||
|  |     this.client = client | ||||||
|  |     this._redis = connect(options) | ||||||
|  |     this._redis.then(redis => { | ||||||
|  |       this.redis = redis | ||||||
|  |       this.ready = true | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async _checkReady() { | ||||||
|  |     if(!this.ready) return await this._redis; | ||||||
|  |     else return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async get(cacheName: string, key: string) { | ||||||
|  |     await this._checkReady() | ||||||
|  |     let cache = await this.redis?.hget(cacheName, key) | ||||||
|  |     if(!cache) return | ||||||
|  |     try { | ||||||
|  |       return JSON.parse(cache as string) | ||||||
|  |     } catch(e) { return cache } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async set(cacheName: string, key: string, value: any) { | ||||||
|  |     await this._checkReady() | ||||||
|  |     return await this.redis?.hset(cacheName, key, typeof value === "object" ? JSON.stringify(value) : value) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async delete(cacheName: string, key: string) { | ||||||
|  |     await this._checkReady() | ||||||
|  |     let exists = await this.redis?.hexists(cacheName, key) | ||||||
|  |     if(!exists) return false | ||||||
|  |     await this.redis?.hdel(cacheName, key) | ||||||
|  |     return true | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async array(cacheName: string) { | ||||||
|  |     await this._checkReady() | ||||||
|  |     let data = await this.redis?.hvals(cacheName) | ||||||
|  |     return data?.map((e: string) => JSON.parse(e)) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -1,30 +1,72 @@ | ||||||
| import { User } from '../structures/user.ts' | import { User } from '../structures/user.ts' | ||||||
| import { GatewayIntents } from '../types/gateway.ts' | import { GatewayIntents } from '../types/gateway.ts' | ||||||
| import { Gateway } from '../gateway/index.ts' | import { Gateway } from '../gateway/index.ts' | ||||||
| import { 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" | ||||||
|  | import { MessagesManager } from "../managers/MessagesManager.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) | ||||||
|  |   messages: MessagesManager = new MessagesManager(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 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setAdapter(adapter: ICacheAdapter) { | ||||||
|  |     this.cache = adapter | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   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 { | ||||||
|     this.token = token |     if(!token && this.token) token = this.token | ||||||
|  |     else if(!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,367 @@ | ||||||
| import { Client } from './client.ts' | import { delay } from "../utils/index.ts"; | ||||||
|  | import * as baseEndpoints from "../consts/urlsAndVersions.ts"; | ||||||
|  | import { Client } from "./client.ts"; | ||||||
| 
 | 
 | ||||||
| class Rest { | export enum HttpResponseCode { | ||||||
|   client: Client |   Ok = 200, | ||||||
|   constructor (client: Client) { |   Created = 201, | ||||||
|     this.client = client |   NoContent = 204, | ||||||
|   } |   NotModified = 304, | ||||||
|   // TODO: make endpoints function
 |   BadRequest = 400, | ||||||
|  |   Unauthorized = 401, | ||||||
|  |   Forbidden = 403, | ||||||
|  |   NotFound = 404, | ||||||
|  |   MethodNotAllowed = 405, | ||||||
|  |   TooManyRequests = 429, | ||||||
|  |   GatewayUnavailable = 502 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export { Rest } | 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>(); | ||||||
|  | 
 | ||||||
|  | 	constructor(client: Client) { | ||||||
|  | 		this.client = client; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	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 await 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); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -1 +1 @@ | ||||||
| //TODO: write code
 | // TODO: write code
 | ||||||
							
								
								
									
										3
									
								
								src/structures/MessageMentions.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/structures/MessageMentions.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | export class MessageMentions { | ||||||
|  |     str: string = "str" | ||||||
|  | } | ||||||
|  | @ -15,7 +15,8 @@ export class Channel extends Base { | ||||||
|     super(client, data) |     super(client, data) | ||||||
|     this.type = data.type |     this.type = data.type | ||||||
|     this.id = data.id |     this.id = data.id | ||||||
|     cache.set('channel', this.id, this) |     // TODO: Cache in Gateway Event Code
 | ||||||
|  |     // this.client.channels.set(this.id, data)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: ChannelPayload): void { |   protected readFromData (data: ChannelPayload): void { | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ export class DMChannel extends TextChannel { | ||||||
|   constructor (client: Client, data: DMChannelPayload) { |   constructor (client: Client, data: DMChannelPayload) { | ||||||
|     super(client, data) |     super(client, data) | ||||||
|     this.recipients = data.recipients |     this.recipients = data.recipients | ||||||
|     cache.set('dmchannel', this.id, this) |     // cache.set('dmchannel', this.id, this)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: DMChannelPayload): void { |   protected readFromData (data: DMChannelPayload): void { | ||||||
|  |  | ||||||
|  | @ -14,7 +14,8 @@ export class GroupDMChannel extends Channel { | ||||||
|     this.name = data.name |     this.name = data.name | ||||||
|     this.icon = data.icon |     this.icon = data.icon | ||||||
|     this.ownerID = data.owner_id |     this.ownerID = data.owner_id | ||||||
|     cache.set('groupchannel', this.id, this) |     // TODO: Cache in Gateway Event Code
 | ||||||
|  |     // cache.set('groupchannel', this.id, this)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: GroupDMChannelPayload): void { |   protected readFromData (data: GroupDMChannelPayload): void { | ||||||
|  |  | ||||||
|  | @ -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,12 +80,15 @@ 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)
 | ||||||
|       ) |       // )
 | ||||||
|       this.emojis = data.emojis.map( |       // data.roles.forEach(role => {
 | ||||||
|         v => cache.get('emoji', v.id) ?? new Emoji(client, v) |       //   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.features = data.features | ||||||
|       this.mfaLevel = data.mfa_level |       this.mfaLevel = data.mfa_level | ||||||
|       this.systemChannelID = data.system_channel_id |       this.systemChannelID = data.system_channel_id | ||||||
|  | @ -93,19 +97,20 @@ export class Guild extends Base { | ||||||
|       this.joinedAt = data.joined_at |       this.joinedAt = data.joined_at | ||||||
|       this.large = data.large |       this.large = data.large | ||||||
|       this.memberCount = data.member_count |       this.memberCount = data.member_count | ||||||
|       this.voiceStates = data.voice_states?.map( |       // TODO: Cache in Gateway Event code
 | ||||||
|         v => |       // this.voiceStates = data.voice_states?.map(
 | ||||||
|           cache.get('voiceState', `${v.guild_id}:${v.user_id}`) ?? |       //   v =>
 | ||||||
|           new VoiceState(client, v) |       //     cache.get('voiceState', `${v.guild_id}:${v.user_id}`) ??
 | ||||||
|       ) |       //     new VoiceState(client, v)
 | ||||||
|       this.members = data.members?.map( |       // )
 | ||||||
|         v => |       // this.members = data.members?.map(
 | ||||||
|           cache.get('member', `${this.id}:${v.user.id}`) ?? |       //   v =>
 | ||||||
|           new Member(client, 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.channels = data.channels?.map(
 | ||||||
|       ) |       //   v => cache.get('channel', v.id) ?? getChannelByType(this.client, v)
 | ||||||
|  |       // )
 | ||||||
|       this.presences = data.presences |       this.presences = data.presences | ||||||
|       this.maxPresences = data.max_presences |       this.maxPresences = data.max_presences | ||||||
|       this.maxMembers = data.max_members |       this.maxMembers = data.max_members | ||||||
|  | @ -120,7 +125,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 +151,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) | ||||||
|  |  | ||||||
|  | @ -22,7 +22,8 @@ export class CategoryChannel extends Channel { | ||||||
|     this.permissionOverwrites = data.permission_overwrites |     this.permissionOverwrites = data.permission_overwrites | ||||||
|     this.nsfw = data.nsfw |     this.nsfw = data.nsfw | ||||||
|     this.parentID = data.parent_id |     this.parentID = data.parent_id | ||||||
|     cache.set('guildcategorychannel', this.id, this) |     // TODO: Cache in Gateway Event Code
 | ||||||
|  |     // cache.set('guildcategorychannel', this.id, this)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: GuildChannelCategoryPayload): void { |   protected readFromData (data: GuildChannelCategoryPayload): void { | ||||||
|  |  | ||||||
|  | @ -27,7 +27,8 @@ export class GuildTextChannel extends TextChannel { | ||||||
|     this.parentID = data.parent_id |     this.parentID = data.parent_id | ||||||
|     this.topic = data.topic |     this.topic = data.topic | ||||||
|     this.rateLimit = data.rate_limit_per_user |     this.rateLimit = data.rate_limit_per_user | ||||||
|     cache.set('guildtextchannel', this.id, this) |     // TODO: Cache in Gateway Event Code
 | ||||||
|  |     // cache.set('guildtextchannel', this.id, this)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: GuildTextChannelPayload): void { |   protected readFromData (data: GuildTextChannelPayload): void { | ||||||
|  |  | ||||||
|  | @ -23,7 +23,8 @@ export class VoiceChannel extends Channel { | ||||||
|     this.permissionOverwrites = data.permission_overwrites |     this.permissionOverwrites = data.permission_overwrites | ||||||
|     this.nsfw = data.nsfw |     this.nsfw = data.nsfw | ||||||
|     this.parentID = data.parent_id |     this.parentID = data.parent_id | ||||||
|     cache.set('guildvoicechannel', this.id, this) |     // TODO: Cache in Gateway Event Code
 | ||||||
|  |     // cache.set('guildvoicechannel', this.id, this)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: GuildVoiceChannelPayload): void { |   protected readFromData (data: GuildVoiceChannelPayload): void { | ||||||
|  |  | ||||||
|  | @ -25,7 +25,8 @@ export class Member extends Base { | ||||||
|     this.premiumSince = data.premium_since |     this.premiumSince = data.premium_since | ||||||
|     this.deaf = data.deaf |     this.deaf = data.deaf | ||||||
|     this.mute = data.mute |     this.mute = data.mute | ||||||
|     cache.set('member', this.id, this) |     // TODO: Cache in Gateway Event Code
 | ||||||
|  |     // cache.set('member', this.id, this)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: MemberPayload): void { |   protected readFromData (data: MemberPayload): void { | ||||||
|  |  | ||||||
|  | @ -15,12 +15,16 @@ import { Member } from './member.ts' | ||||||
| import { Embed } from './embed.ts' | import { Embed } from './embed.ts' | ||||||
| import { CHANNEL_MESSAGE } from '../types/endpoint.ts' | import { CHANNEL_MESSAGE } from '../types/endpoint.ts' | ||||||
| import cache from '../models/cache.ts' | import cache from '../models/cache.ts' | ||||||
|  | import { Channel } from "./channel.ts" | ||||||
|  | import { MessageMentions } from "./MessageMentions.ts" | ||||||
|  | import { TextChannel } from "./textChannel.ts" | ||||||
| 
 | 
 | ||||||
| export class Message extends Base { | export class Message extends Base { | ||||||
|   // eslint-disable-next-line @typescript-eslint/prefer-readonly
 |   // eslint-disable-next-line @typescript-eslint/prefer-readonly
 | ||||||
|   private data: MessagePayload |   private data: MessagePayload | ||||||
|   id: string |   id: string | ||||||
|   channelID: string |   channelID: string | ||||||
|  |   channel: Channel | ||||||
|   guildID?: string |   guildID?: string | ||||||
|   author: User |   author: User | ||||||
|   member?: Member |   member?: Member | ||||||
|  | @ -29,7 +33,7 @@ export class Message extends Base { | ||||||
|   editedTimestamp?: string |   editedTimestamp?: string | ||||||
|   tts: boolean |   tts: boolean | ||||||
|   mentionEveryone: boolean |   mentionEveryone: boolean | ||||||
|   mentions: User[] |   mentions: MessageMentions | ||||||
|   mentionRoles: string[] |   mentionRoles: string[] | ||||||
|   mentionChannels?: ChannelMention[] |   mentionChannels?: ChannelMention[] | ||||||
|   attachments: Attachment[] |   attachments: Attachment[] | ||||||
|  | @ -44,22 +48,24 @@ export class Message extends Base { | ||||||
|   messageReference?: MessageReference |   messageReference?: MessageReference | ||||||
|   flags?: number |   flags?: number | ||||||
| 
 | 
 | ||||||
|   constructor (client: Client, data: MessagePayload) { |   constructor (client: Client, data: MessagePayload, channel: Channel, author: User, mentions: MessageMentions) { | ||||||
|     super(client) |     super(client) | ||||||
|     this.data = data |     this.data = data | ||||||
|     this.id = data.id |     this.id = data.id | ||||||
|     this.channelID = data.channel_id |     this.channelID = data.channel_id | ||||||
|     this.guildID = data.guild_id |     this.guildID = data.guild_id | ||||||
|     this.author = |     this.author = author | ||||||
|       cache.get('user', data.author.id) ?? new User(this.client, data.author) |     // this.author =
 | ||||||
|  |     //   this.client.users.get(data.author.id) || new User(this.client, data.author)
 | ||||||
|     this.content = data.content |     this.content = data.content | ||||||
|     this.timestamp = data.timestamp |     this.timestamp = data.timestamp | ||||||
|     this.editedTimestamp = data.edited_timestamp |     this.editedTimestamp = data.edited_timestamp | ||||||
|     this.tts = data.tts |     this.tts = data.tts | ||||||
|     this.mentionEveryone = data.mention_everyone |     this.mentionEveryone = data.mention_everyone | ||||||
|     this.mentions = data.mentions.map( |     this.mentions = mentions | ||||||
|       v => cache.get('user', v.id) ?? new User(client, v) |     // this.mentions = data.mentions.map(
 | ||||||
|     ) |     //   v => this.client.users.get(v.id) || new User(client, v)
 | ||||||
|  |     // )
 | ||||||
|     this.mentionRoles = data.mention_roles |     this.mentionRoles = data.mention_roles | ||||||
|     this.mentionChannels = data.mention_channels |     this.mentionChannels = data.mention_channels | ||||||
|     this.attachments = data.attachments |     this.attachments = data.attachments | ||||||
|  | @ -73,26 +79,28 @@ export class Message extends Base { | ||||||
|     this.application = data.application |     this.application = data.application | ||||||
|     this.messageReference = data.message_reference |     this.messageReference = data.message_reference | ||||||
|     this.flags = data.flags |     this.flags = data.flags | ||||||
|     cache.set('message', this.id, this) |     this.channel = channel | ||||||
|  |     // TODO: Cache in Gateway Event Code
 | ||||||
|  |     // if(!noSave) this.client.messages.set(this.id, data)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: MessagePayload): void { |   protected readFromData (data: MessagePayload): void { | ||||||
|     super.readFromData(data) |     super.readFromData(data) | ||||||
|     this.channelID = data.channel_id ?? this.channelID |     this.channelID = data.channel_id ?? this.channelID | ||||||
|     this.guildID = data.guild_id ?? this.guildID |     this.guildID = data.guild_id ?? this.guildID | ||||||
|     this.author = |     // this.author =
 | ||||||
|       cache.get('user', data.author.id) ?? |     //   this.client.users.get(data.author.id) ||
 | ||||||
|       this.author ?? |     //   this.author ||
 | ||||||
|       new User(this.client, data.author) |     //   new User(this.client, data.author)
 | ||||||
|     this.content = data.content ?? this.content |     this.content = data.content ?? this.content | ||||||
|     this.timestamp = data.timestamp ?? this.timestamp |     this.timestamp = data.timestamp ?? this.timestamp | ||||||
|     this.editedTimestamp = data.edited_timestamp ?? this.editedTimestamp |     this.editedTimestamp = data.edited_timestamp ?? this.editedTimestamp | ||||||
|     this.tts = data.tts ?? this.tts |     this.tts = data.tts ?? this.tts | ||||||
|     this.mentionEveryone = data.mention_everyone ?? this.mentionEveryone |     this.mentionEveryone = data.mention_everyone ?? this.mentionEveryone | ||||||
|     this.mentions = |     // this.mentions =
 | ||||||
|       data.mentions.map( |     //   data.mentions.map(
 | ||||||
|         v => cache.get('user', v.id) ?? new User(this.client, v) |     //     v => this.client.users.get(v.id) || new User(this.client, v)
 | ||||||
|       ) ?? this.mentions |     //   ) ?? this.mentions
 | ||||||
|     this.mentionRoles = data.mention_roles ?? this.mentionRoles |     this.mentionRoles = data.mention_roles ?? this.mentionRoles | ||||||
|     this.mentionChannels = data.mention_channels ?? this.mentionChannels |     this.mentionChannels = data.mention_channels ?? this.mentionChannels | ||||||
|     this.attachments = data.attachments ?? this.attachments |     this.attachments = data.attachments ?? this.attachments | ||||||
|  | @ -108,44 +116,11 @@ export class Message extends Base { | ||||||
|     this.flags = data.flags ?? this.flags |     this.flags = data.flags ?? this.flags | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // TODO: We have to seperate fetch()
 |   edit (text?: string, option?: MessageOption): Promise<Message> { | ||||||
|   async editMessage (text?: string, option?: MessageOption): Promise<Message> { |     return (this.channel as TextChannel).editMessage(this.id, text, option)   | ||||||
|     if (text !== undefined && option !== undefined) { |  | ||||||
|       throw new Error('Either text or option is necessary.') |  | ||||||
|     } |  | ||||||
|     const resp = await fetch(CHANNEL_MESSAGE(this.channelID, this.id), { |  | ||||||
|       headers: { |  | ||||||
|         Authorization: `Bot ${this.client.token}`, |  | ||||||
|         'Content-Type': 'application/json' |  | ||||||
|       }, |  | ||||||
|       method: 'PATCH', |  | ||||||
|       body: JSON.stringify({ |  | ||||||
|         content: text, |  | ||||||
|         embed: option?.embed.toJSON(), |  | ||||||
|         file: option?.file, |  | ||||||
|         tts: option?.tts, |  | ||||||
|         allowed_mentions: option?.allowedMention |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|     return new Message(this.client, await resp.json()) |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // TODO: We have to seperate fetch()
 |   delete (): Promise<void> { | ||||||
|   async delete (): Promise<void> { |     return this.client.rest.delete(CHANNEL_MESSAGE(this.channelID, this.id)) as any | ||||||
|     const resp = await fetch(CHANNEL_MESSAGE(this.channelID, this.id), { |  | ||||||
|       headers: { |  | ||||||
|         Authorization: `Bot ${this.client.token}` |  | ||||||
|       }, |  | ||||||
|       method: 'DELETE' |  | ||||||
|     }) |  | ||||||
| 
 |  | ||||||
|     // TODO: improve Error and Promise
 |  | ||||||
|     return await new Promise((resolve, reject) => { |  | ||||||
|       if (resp.status !== 204) { |  | ||||||
|         reject(new Error()) |  | ||||||
|       } |  | ||||||
|       resolve() |  | ||||||
|     }) |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -27,7 +27,8 @@ export class Role extends Base { | ||||||
|     this.permissions = data.permissions |     this.permissions = data.permissions | ||||||
|     this.managed = data.managed |     this.managed = data.managed | ||||||
|     this.mentionable = data.mentionable |     this.mentionable = data.mentionable | ||||||
|     cache.set('role', this.id, this) |     // TODO: Cache in Gateway Event Code
 | ||||||
|  |     // cache.set('role', this.id, this)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: RolePayload): void { |   protected readFromData (data: RolePayload): void { | ||||||
|  |  | ||||||
|  | @ -1,9 +1,11 @@ | ||||||
| import cache from '../models/cache.ts' | import cache from '../models/cache.ts' | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { MessageOption, TextChannelPayload } from '../types/channel.ts' | import { MessageOption, MessagePayload, TextChannelPayload } from '../types/channel.ts' | ||||||
| import { CHANNEL_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts' | import { CHANNEL_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts' | ||||||
| import { Channel } from './channel.ts' | import { Channel } from './channel.ts' | ||||||
| import { Message } from './message.ts' | import { Message } from './message.ts' | ||||||
|  | import { MessageMentions } from "./MessageMentions.ts" | ||||||
|  | import { User } from "./user.ts" | ||||||
| 
 | 
 | ||||||
| export class TextChannel extends Channel { | export class TextChannel extends Channel { | ||||||
|   lastMessageID?: string |   lastMessageID?: string | ||||||
|  | @ -13,7 +15,8 @@ export class TextChannel extends Channel { | ||||||
|     super(client, data) |     super(client, data) | ||||||
|     this.lastMessageID = data.last_message_id |     this.lastMessageID = data.last_message_id | ||||||
|     this.lastPinTimestamp = data.last_pin_timestamp |     this.lastPinTimestamp = data.last_pin_timestamp | ||||||
|     cache.set('textchannel', this.id, this) |     // TODO: Cache in Gateway Event Code
 | ||||||
|  |     // cache.set('textchannel', this.id, this)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: TextChannelPayload): void { |   protected readFromData (data: TextChannelPayload): void { | ||||||
|  | @ -41,32 +44,29 @@ export class TextChannel extends Channel { | ||||||
|       }) |       }) | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
|     return new Message(this.client, await resp.json()) |     return new Message(this.client, await resp.json(), this, this.client.user as User, new MessageMentions()) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async editMessage ( |   async editMessage ( | ||||||
|     messageID: string, |     message: Message | string, | ||||||
|     text?: string, |     text?: string, | ||||||
|     option?: MessageOption |     option?: MessageOption | ||||||
|   ): Promise<Message> { |   ): Promise<Message> { | ||||||
|     if (text !== undefined && option !== undefined) { |     if (text !== undefined && option !== undefined) { | ||||||
|       throw new Error('Either text or option is necessary.') |       throw new Error('Either text or option is necessary.') | ||||||
|     } |     } | ||||||
|     const resp = await fetch(CHANNEL_MESSAGE(this.id, messageID), { |  | ||||||
|       headers: { |  | ||||||
|         Authorization: `Bot ${this.client.token}`, |  | ||||||
|         'Content-Type': 'application/json' |  | ||||||
|       }, |  | ||||||
|       method: 'PATCH', |  | ||||||
|       body: JSON.stringify({ |  | ||||||
|         content: text, |  | ||||||
|         embed: option?.embed, |  | ||||||
|         file: option?.file, |  | ||||||
|         tts: option?.tts, |  | ||||||
|         allowed_mentions: option?.allowedMention |  | ||||||
|       }) |  | ||||||
|     }) |  | ||||||
| 
 | 
 | ||||||
|     return new Message(this.client, await resp.json()) |     let newMsg = await this.client.rest.patch(CHANNEL_MESSAGE(this.id, typeof message == "string" ? message : message.id), { | ||||||
|  |       content: text, | ||||||
|  |       embed: option?.embed.toJSON(), | ||||||
|  |       file: option?.file, | ||||||
|  |       tts: option?.tts, | ||||||
|  |       allowed_mentions: option?.allowedMention | ||||||
|  |     }) as MessagePayload | ||||||
|  | 
 | ||||||
|  |     // TODO: Actually construct this object
 | ||||||
|  |     let mentions = new MessageMentions() | ||||||
|  | 
 | ||||||
|  |     return new Message(this.client, newMsg, this, this.client.user as User, mentions) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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}>` | ||||||
|   } |   } | ||||||
|  | @ -41,7 +45,8 @@ export class User extends Base { | ||||||
|     this.flags = data.flags |     this.flags = data.flags | ||||||
|     this.premiumType = data.premium_type |     this.premiumType = data.premium_type | ||||||
|     this.publicFlags = data.public_flags |     this.publicFlags = data.public_flags | ||||||
|     cache.set('user', this.id, this) |     // TODO: Cache in Gateway Event Code
 | ||||||
|  |     // cache.set('user', this.id, this)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: UserPayload): void { |   protected readFromData (data: UserPayload): void { | ||||||
|  | @ -59,4 +64,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; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -30,7 +30,8 @@ export class VoiceState extends Base { | ||||||
|     this.selfStream = data.self_stream |     this.selfStream = data.self_stream | ||||||
|     this.selfVideo = data.self_video |     this.selfVideo = data.self_video | ||||||
|     this.suppress = data.suppress |     this.suppress = data.suppress | ||||||
|     cache.set('voiceState', `${this.guildID}:${this.userID}`, this) |     // TODO: Cache in Gateway Event Code
 | ||||||
|  |     // cache.set('voiceState', `${this.guildID}:${this.userID}`, this)
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   protected readFromData (data: VoiceStatePayload): void { |   protected readFromData (data: VoiceStatePayload): void { | ||||||
|  |  | ||||||
|  | @ -1,2 +1 @@ | ||||||
| const TOKEN = '' | export const TOKEN = '' | ||||||
| export { TOKEN } |  | ||||||
|  | @ -1,18 +1,27 @@ | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { GatewayIntents } from '../types/gatewayTypes.ts' | import { GatewayIntents } from '../types/gateway.ts' | ||||||
| import { TOKEN } from './config.ts' | import { TOKEN } from './config.ts' | ||||||
| import { Channel } from '../structures/channel.ts' | import { Channel } from '../structures/channel.ts' | ||||||
| import { GuildTextChannel } from '../structures/guildTextChannel.ts' | import { GuildTextChannel } from '../structures/guildTextChannel.ts' | ||||||
| import { TextChannel } from '../structures/textChannel.ts' | import { TextChannel } from '../structures/textChannel.ts' | ||||||
| import { Guild } from '../structures/guild.ts' | import { Guild } from '../structures/guild.ts' | ||||||
| import { User } from '../structures/user.ts' | import { User } from '../structures/user.ts' | ||||||
|  | import { Message } from "../structures/message.ts" | ||||||
|  | import { RedisCacheAdapter } from "../models/CacheAdapter.ts" | ||||||
| 
 | 
 | ||||||
| const bot = new Client() | const bot = new Client() | ||||||
| 
 | 
 | ||||||
|  | bot.setAdapter(new RedisCacheAdapter(bot, { | ||||||
|  |   hostname: "127.0.0.1", | ||||||
|  |   port: 6379 | ||||||
|  | })) | ||||||
|  | 
 | ||||||
| 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) | ||||||
| }) | }) | ||||||
|  | @ -59,6 +68,10 @@ bot.on('guildUpdate', (before: Guild, after: Guild) => { | ||||||
|   console.log('guildUpdate', before.name, after.name) |   console.log('guildUpdate', before.name, after.name) | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  | bot.on('messageCreate', (msg: Message) => { | ||||||
|  |   console.log(`${msg.author.tag}: ${msg.content}`) | ||||||
|  | }) | ||||||
|  | 
 | ||||||
| bot.connect(TOKEN, [ | bot.connect(TOKEN, [ | ||||||
|   GatewayIntents.GUILD_MEMBERS, |   GatewayIntents.GUILD_MEMBERS, | ||||||
|   GatewayIntents.GUILD_PRESENCES, |   GatewayIntents.GUILD_PRESENCES, | ||||||
|  |  | ||||||
							
								
								
									
										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 | ||||||
|  | } | ||||||
							
								
								
									
										396
									
								
								src/types/gatewayTypes.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										396
									
								
								src/types/gatewayTypes.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,396 @@ | ||||||
|  | // https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway
 | ||||||
|  | // https://discord.com/developers/docs/topics/gateway#commands-and-events-gateway-events
 | ||||||
|  | import { EmojiPayload } from './emoji.ts' | ||||||
|  | import { MemberPayload } from './guild.ts' | ||||||
|  | import { ActivityPayload, PresenceUpdatePayload } from './presence.ts' | ||||||
|  | import { RolePayload } from './role.ts' | ||||||
|  | import { UserPayload } from './user.ts' | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Gateway OPcodes from Discord docs. | ||||||
|  |  */ | ||||||
|  | enum GatewayOpcodes { // 문서를 확인해본 결과 Opcode 5번은 비어있다. - UnderC -
 | ||||||
|  |   DISPATCH = 0, | ||||||
|  |   HEARTBEAT = 1, | ||||||
|  |   IDENTIFY = 2, | ||||||
|  |   PRESENCE_UPDATE = 3, | ||||||
|  |   VOICE_STATE_UPDATE = 4, | ||||||
|  |   RESUME = 6, | ||||||
|  |   RECONNECT = 7, | ||||||
|  |   REQUEST_GUILD_MEMBERS = 8, | ||||||
|  |   INVALID_SESSION = 9, | ||||||
|  |   HELLO = 10, | ||||||
|  |   HEARTBEAT_ACK = 11 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Gateway Close Codes from Discord docs. | ||||||
|  |  */ | ||||||
|  | enum GatewayCloseCodes { | ||||||
|  |   UNKNOWN_ERROR = 4000, | ||||||
|  |   UNKNOWN_OPCODE = 4001, | ||||||
|  |   DECODE_ERROR = 4002, | ||||||
|  |   NOT_AUTHENTICATED = 4003, | ||||||
|  |   AUTHENTICATION_FAILED = 4004, | ||||||
|  |   ALREADY_AUTHENTICATED = 4005, | ||||||
|  |   INVALID_SEQ = 4007, | ||||||
|  |   RATE_LIMITED = 4008, | ||||||
|  |   SESSION_TIMED_OUT = 4009, | ||||||
|  |   INVALID_SHARD = 4010, | ||||||
|  |   SHARDING_REQUIRED = 4011, | ||||||
|  |   INVALID_API_VERSION = 4012, | ||||||
|  |   INVALID_INTENTS = 4013, | ||||||
|  |   DISALLOWED_INTENTS = 4014 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | enum GatewayIntents { | ||||||
|  |   GUILDS = 1 << 0, | ||||||
|  |   GUILD_MEMBERS = 1 << 1, | ||||||
|  |   GUILD_BANS = 1 << 2, | ||||||
|  |   GUILD_EMOJIS = 1 << 3, | ||||||
|  |   GUILD_INTEGRATIONS = 1 << 4, | ||||||
|  |   GUILD_WEBHOOKS = 1 << 5, | ||||||
|  |   GUILD_INVITES = 1 << 6, | ||||||
|  |   GUILD_VOICE_STATES = 1 << 7, | ||||||
|  |   GUILD_PRESENCES = 1 << 8, | ||||||
|  |   GUILD_MESSAGES = 1 << 9, | ||||||
|  |   GUILD_MESSAGE_REACTIONS = 1 << 10, | ||||||
|  |   GUILD_MESSAGE_TYPING = 1 << 11, | ||||||
|  |   DIRECT_MESSAGES = 1 << 12, | ||||||
|  |   DIRECT_MESSAGE_REACTIONS = 1 << 13, | ||||||
|  |   DIRECT_MESSAGE_TYPING = 1 << 13 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | enum GatewayEvents { | ||||||
|  |   Ready = 'READY', | ||||||
|  |   Resumed = 'RESUMED', | ||||||
|  |   Reconnect = 'RECONNECT', | ||||||
|  |   Channel_Create = 'CHANNEL_CREATE', | ||||||
|  |   Channel_Update = 'CHANNEL_UPDATE', | ||||||
|  |   Channel_Delete = 'CHANNEL_DELETE', | ||||||
|  |   Channel_Pins_Update = 'CHANNEL_PINS_UPDATE', | ||||||
|  |   Guild_Create = 'GUILD_CREATE', | ||||||
|  |   Guild_Update = 'GUILD_UPDATE', | ||||||
|  |   Guild_Delete = 'GUILD_DELETE', | ||||||
|  |   Guild_Ban_Add = 'GUILD_BAN_ADD', | ||||||
|  |   Guild_Ban_Remove = 'GUILD_BAN_REMOVE', | ||||||
|  |   Guild_Emojis_Update = 'GUILD_EMOJIS_UPDATE', | ||||||
|  |   Guild_Integrations_Update = 'GUILD_INTEGRATIONS_UPDATE', | ||||||
|  |   Guild_Member_Add = 'GUILD_MEMBER_ADD', | ||||||
|  |   Guild_Member_Remove = 'GUILD_MEMBER_REMOVE', | ||||||
|  |   Guild_Member_Update = 'GUILD_MEMBER_UPDATE', | ||||||
|  |   Guild_Members_Chunk = 'GUILD_MEMBERS_CHUNK', | ||||||
|  |   Guild_Role_Create = 'GUILD_ROLE_CREATE', | ||||||
|  |   Guild_Role_Update = 'GUILD_ROLE_UPDATE', | ||||||
|  |   Guild_Role_Delete = 'GUILD_ROLE_DELETE', | ||||||
|  |   Invite_Create = 'INVITE_CREATE', | ||||||
|  |   Invite_Delete = 'INVITE_DELETE', | ||||||
|  |   Message_Create = 'MESSAGE_CREATE', | ||||||
|  |   Message_Update = 'MESSAGE_UPDATE', | ||||||
|  |   Message_Delete = 'MESSAGE_DELETE', | ||||||
|  |   Message_Delete_Bulk = 'MESSAGE_DELETE_BULK', | ||||||
|  |   Message_Reaction_Add = 'MESSAGE_REACTION_ADD', | ||||||
|  |   Message_Reaction_Remove = 'MESSAGE_REACTION_REMOVE', | ||||||
|  |   Message_Reaction_Remove_All = 'MESSAGE_REACTION_REMOVE_ALL', | ||||||
|  |   Message_Reaction_Remove_Emoji = 'MESSAGE_REACTION_REMOVE_EMOJI', | ||||||
|  |   Presence_Update = 'PRESENCE_UPDATE', | ||||||
|  |   Typing_Start = 'TYPING_START', | ||||||
|  |   User_Update = 'USER_UPDATE', | ||||||
|  |   Voice_Server_Update = 'VOICE_SERVER_UPDATE', | ||||||
|  |   Webhooks_Update = 'WEBHOOKS_UPDATE' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface IdentityPayload { | ||||||
|  |   token: string | ||||||
|  |   properties: IdentityConnection | ||||||
|  |   compress?: boolean | ||||||
|  |   large_threshold?: number | ||||||
|  |   shard?: number[] | ||||||
|  |   presence?: UpdateStatus | ||||||
|  |   guildSubscriptions?: boolean | ||||||
|  |   intents: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | enum UpdateStatus { | ||||||
|  |   online = 'online', | ||||||
|  |   dnd = 'dnd', | ||||||
|  |   afk = 'idle', | ||||||
|  |   invisible = 'invisible', | ||||||
|  |   offline = 'offline' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface IdentityConnection { | ||||||
|  |   $os: 'darwin' | 'windows' | 'linux' | 'custom os' | ||||||
|  |   $browser: 'discord.deno' | ||||||
|  |   $device: 'discord.deno' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface Resume { | ||||||
|  |   token: string | ||||||
|  |   session_id: string | ||||||
|  |   seq: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GuildRequestMembers { | ||||||
|  |   guild_id: string | string[] | ||||||
|  |   query?: string | ||||||
|  |   limit: number | ||||||
|  |   presences?: boolean | ||||||
|  |   user_ids?: string | string[] | ||||||
|  |   nonce?: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GatewayVoiceStateUpdate { | ||||||
|  |   guild_id: string | ||||||
|  |   channel_id: string | ||||||
|  |   self_mute: boolean | ||||||
|  |   self_deaf: boolean | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GatewayStatusUpdate { | ||||||
|  |   since: number | undefined | ||||||
|  |   activities: ActivityPayload[] | ||||||
|  |   status: string | ||||||
|  |   afk: boolean | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface Hello { | ||||||
|  |   heartbeat_interval: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface ReadyEvent { | ||||||
|  |   v: number | ||||||
|  |   user: UserPayload | ||||||
|  |   privateChannels: [] | ||||||
|  |   guilds: [] | ||||||
|  |   session_id: string | ||||||
|  |   shard?: number[] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface ChannelPinsUpdate { | ||||||
|  |   guild_id?: string | ||||||
|  |   channel_id: string | ||||||
|  |   last_pin_timestamp?: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GuildBanAdd { | ||||||
|  |   guild_id: string | ||||||
|  |   user: UserPayload | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GuildBanRemove { | ||||||
|  |   guild_id: string | ||||||
|  |   user: UserPayload | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GuildEmojiUpdate { | ||||||
|  |   guild_id: string | ||||||
|  |   emojis: [] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GuildIntegrationsUpdate { | ||||||
|  |   guild_id: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GuildMemberAddExtra { | ||||||
|  |   guild_id: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GuildMemberRemove { | ||||||
|  |   guild_id: string | ||||||
|  |   user: UserPayload | ||||||
|  | } | ||||||
|  | interface GuildMemberUpdate { | ||||||
|  |   guild_id: string | ||||||
|  |   roles: string[] | ||||||
|  |   user: UserPayload | ||||||
|  |   nick?: string | undefined | ||||||
|  |   joined_at: string | ||||||
|  |   premium_since?: string | undefined | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GuildMemberChunk { | ||||||
|  |   guild_id: string | ||||||
|  |   members: MemberPayload[] | ||||||
|  |   chunk_index: number | ||||||
|  |   chunk_count: number | ||||||
|  |   not_found?: [] | ||||||
|  |   presences?: PresenceUpdatePayload[] | ||||||
|  |   nonce?: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GuildRoleCreate { | ||||||
|  |   guild_id: string | ||||||
|  |   role: RolePayload | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GuildRoleUpdate { | ||||||
|  |   guild_id: string | ||||||
|  |   role: RolePayload | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface GuildRoleDelete { | ||||||
|  |   guild_id: string | ||||||
|  |   role_id: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface InviteCreate { | ||||||
|  |   channel_id: string | ||||||
|  |   code: string | ||||||
|  |   created_at: string | ||||||
|  |   guild_id?: string | ||||||
|  |   inviter?: UserPayload | ||||||
|  |   max_age: number | ||||||
|  |   max_uses: number | ||||||
|  |   target_user?: UserPayload | ||||||
|  |   target_user_type?: number | ||||||
|  |   temporary: boolean | ||||||
|  |   uses: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface InviteDelete { | ||||||
|  |   channel_id: string | ||||||
|  |   guild_id?: string | ||||||
|  |   code: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface MessageDelete { | ||||||
|  |   id: string | ||||||
|  |   channel_id: string | ||||||
|  |   guild_id?: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface MessageDeleteBulk { | ||||||
|  |   ids: string[] | ||||||
|  |   channel_id: string | ||||||
|  |   guild_id: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface MessageReactionAdd { | ||||||
|  |   user_id: string | ||||||
|  |   channel_id: string | ||||||
|  |   message_id: string | ||||||
|  |   guild_id?: string | ||||||
|  |   emoji: EmojiPayload | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface MessageReactionRemove { | ||||||
|  |   user_id: string | ||||||
|  |   channel_id: string | ||||||
|  |   message_id: string | ||||||
|  |   guild_id?: string | ||||||
|  |   emoji: EmojiPayload | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface MessageReactionRemoveAll { | ||||||
|  |   channel_id: string | ||||||
|  |   guild_id?: string | ||||||
|  |   message_id: string | ||||||
|  |   emoji: EmojiPayload | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface MessageReactionRemove { | ||||||
|  |   channel_id: string | ||||||
|  |   guild_id?: string | ||||||
|  |   message_id: string | ||||||
|  |   emoji: EmojiPayload | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface PresenceUpdate { | ||||||
|  |   user: UserPayload | ||||||
|  |   guild_id: string | ||||||
|  |   status: string | ||||||
|  |   activities: ActivityPayload[] | ||||||
|  |   client_status: UpdateStatus[] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface CilentStatus { | ||||||
|  |   desktop?: string | ||||||
|  |   moblie?: string | ||||||
|  |   web?: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface Activity { | ||||||
|  |   name: string | ||||||
|  |   type: number | ||||||
|  |   url?: string | undefined | ||||||
|  |   created_at: number | ||||||
|  |   timestamps?: string | ||||||
|  |   application_id: string | ||||||
|  |   details?: string | undefined | ||||||
|  |   state?: string | undefined | ||||||
|  |   emoji?: EmojiPayload | undefined | ||||||
|  |   party?: ActivityParty | ||||||
|  |   assets?: ActivityAssets | ||||||
|  |   secrets?: ActivitySecrets | ||||||
|  |   instance?: boolean | ||||||
|  |   flags?: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | enum ActivityTypes { | ||||||
|  |   GAME = 0, | ||||||
|  |   STREAMING = 1, | ||||||
|  |   LISTENING = 2, | ||||||
|  |   CUSTOM = 4, | ||||||
|  |   COMPETING = 5 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface ActivityTimestamps { | ||||||
|  |   start?: number | ||||||
|  |   end?: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface ActivityEmoji { | ||||||
|  |   name: string | ||||||
|  |   id?: string | ||||||
|  |   animated?: boolean | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface ActivityParty { | ||||||
|  |   id?: string | ||||||
|  |   size?: number[] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface ActivityAssets { | ||||||
|  |   large_image?: string | ||||||
|  |   large_text?: string | ||||||
|  |   small_image?: string | ||||||
|  |   small_text?: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface ActivitySecrets { | ||||||
|  |   join?: string | ||||||
|  |   spectate?: string | ||||||
|  |   match?: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | enum ActivityFlags { | ||||||
|  |   INSTANCE = 1 << 0, | ||||||
|  |   JOIN = 1 << 1, | ||||||
|  |   SPECTATE = 1 << 2, | ||||||
|  |   JOIN_REQUEST = 1 << 3, | ||||||
|  |   SYNC = 1 << 4, | ||||||
|  |   PLAY = 1 << 5 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface TypeStart { | ||||||
|  |   channel_id: string | ||||||
|  |   guild_id?: string | ||||||
|  |   user_id: string | ||||||
|  |   timestamp: number | ||||||
|  |   member?: MemberPayload | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface VoiceServerUpdate { | ||||||
|  |   token: string | ||||||
|  |   guild_id: string | ||||||
|  |   endpoint: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface WebhooksUpdate { | ||||||
|  |   guild_id: string | ||||||
|  |   channel_id: string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // https://discord.com/developers/docs/topics/gateway#typing-start-typing-start-event-fields
 | ||||||
|  | export { GatewayCloseCodes, GatewayOpcodes, GatewayIntents, GatewayEvents } | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| // https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice
 | // https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice
 | ||||||
| import { MemberPayload } from './guildTypes.ts' | import { MemberPayload } from './guild.ts' | ||||||
| 
 | 
 | ||||||
| enum VoiceOpcodes { // VoiceOpcodes 추가 - UnderC -
 | enum VoiceOpcodes { // VoiceOpcodes 추가 - UnderC -
 | ||||||
|   IDENTIFY = 0, |   IDENTIFY = 0, | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { UserPayload } from './userTypes.ts' | import { UserPayload } from './user.ts' | ||||||
| 
 | 
 | ||||||
| export interface WebhookPayload { | export interface WebhookPayload { | ||||||
|   id: string |   id: string | ||||||
|  |  | ||||||
							
								
								
									
										95
									
								
								src/utils/collection.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/utils/collection.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,95 @@ | ||||||
|  | export class Collection<K = string, V = any> 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() { | ||||||
|  |     const 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
 | ||||||
|  |      | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   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 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static fromObject<V>(object: { [key: string]: V }) { | ||||||
|  |     return new Collection<string, V>(Object.entries(object)) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toObject() { | ||||||
|  |     return Object.entries(this) | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										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