RedisCacheAdapter (!), fixed resuming, error code handling, resume event added. Some caching missing yet though
This commit is contained in:
		
							parent
							
								
									be3bec017c
								
							
						
					
					
						commit
						1c02edb015
					
				
					 35 changed files with 340 additions and 158 deletions
				
			
		|  | @ -1,14 +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) { | ||||||
|     gateway.client.channels.set(d.id, d) |     await gateway.client.channels.set(d.id, d) | ||||||
|     gateway.client.emit('channelCreate', channel) |     gateway.client.emit('channelCreate', channel) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,13 +1,13 @@ | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.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 = gateway.client.channels.get(d.id) |   const channel: Channel = await gateway.client.channels.get(d.id) | ||||||
|   if (channel !== undefined) { |   if (channel !== undefined) { | ||||||
|     gateway.client.channels.delete(d.id) |     await gateway.client.channels.delete(d.id) | ||||||
|     gateway.client.emit('channelDelete', channel) |     gateway.client.emit('channelDelete', channel) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,17 +3,17 @@ import cache from '../../models/cache.ts' | ||||||
| import { TextChannel } from '../../structures/textChannel.ts' | import { TextChannel } from '../../structures/textChannel.ts' | ||||||
| import { ChannelPayload } from "../../types/channelTypes.ts" | import { ChannelPayload } from "../../types/channelTypes.ts" | ||||||
| 
 | 
 | ||||||
| export const channelPinsUpdate: GatewayEventHandler = ( | export const channelPinsUpdate: GatewayEventHandler = async( | ||||||
|   gateway: Gateway, |   gateway: Gateway, | ||||||
|   d: any |   d: any | ||||||
| ) => { | ) => { | ||||||
|   const after: TextChannel = gateway.client.channels.get(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 = gateway.client.channels._get(d.channel_id) ; |     const raw = await gateway.client.channels._get(d.channel_id) ; | ||||||
|     gateway.client.channels.set(after.id, Object.assign(raw, { last_pin_timestamp: d.last_pin_timestamp })) |     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) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,14 +2,14 @@ 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 = gateway.client.channels.get(d.id) |   const oldChannel: Channel = await gateway.client.channels.get(d.id) | ||||||
| 
 | 
 | ||||||
|   if (oldChannel !== undefined) { |   if (oldChannel !== undefined) { | ||||||
|     gateway.client.channels.set(d.id, d) |     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,15 +1,16 @@ | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| import { Guild } from '../../structures/guild.ts' | import { Guild } from '../../structures/guild.ts' | ||||||
|  | import { GuildPayload } from "../../types/guildTypes.ts" | ||||||
| 
 | 
 | ||||||
| export const guildCreate: GatewayEventHandler = (gateway: Gateway, d: any) => { | export const guildCreate: GatewayEventHandler = async(gateway: Gateway, d: any) => { | ||||||
|   let guild: Guild | void = gateway.client.guilds.get(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
 |     // It was just lazy load, so we don't fire the event as its gonna fire for every guild bot is in
 | ||||||
|     gateway.client.guilds.set(d.id, d) |     await gateway.client.guilds.set(d.id, d) | ||||||
|     guild.refreshFromData(d) |     guild.refreshFromData(d) | ||||||
|   } else { |   } else { | ||||||
|     gateway.client.guilds.set(d.id, d) |     await gateway.client.guilds.set(d.id, d) | ||||||
|     guild = gateway.client.guilds.get(d.id) |     guild = new Guild(gateway.client, d as GuildPayload) | ||||||
|     gateway.client.emit('guildCreate', guild) |     gateway.client.emit('guildCreate', guild) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| 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 | void = gateway.client.guilds.get(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) | ||||||
|     gateway.client.guilds.delete(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 before: Guild | void = gateway.client.guilds.get(d.id) |   const before: Guild | void = await gateway.client.guilds.get(d.id) | ||||||
|   if(!before) return |   if(!before) return | ||||||
|   gateway.client.guilds.set(d.id, d) |   await gateway.client.guilds.set(d.id, d) | ||||||
|   const after: Guild | void = gateway.client.guilds.get(d.id) |   const after: Guild | void = await gateway.client.guilds.get(d.id) | ||||||
|   gateway.client.emit('guildUpdate', before, after) |   gateway.client.emit('guildUpdate', before, after) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -11,13 +11,14 @@ 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 { 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, | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| import { Channel } from "../../structures/channel.ts" | import { Channel } from "../../structures/channel.ts" | ||||||
| import { Message } from "../../structures/message.ts" | import { Message } from "../../structures/message.ts" | ||||||
|  | import { MessageMentions } from "../../structures/MessageMentions.ts" | ||||||
|  | import { User } from "../../structures/user.ts" | ||||||
| import { MessagePayload } from "../../types/channelTypes.ts" | import { MessagePayload } from "../../types/channelTypes.ts" | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| 
 | 
 | ||||||
|  | @ -7,9 +9,12 @@ export const messageCreate: GatewayEventHandler = async( | ||||||
|   gateway: Gateway, |   gateway: Gateway, | ||||||
|   d: MessagePayload |   d: MessagePayload | ||||||
| ) => { | ) => { | ||||||
|   let channel = gateway.client.channels.get(d.channel_id) |   let channel = await gateway.client.channels.get(d.channel_id) | ||||||
|   // Fetch the channel if not cached
 |   // Fetch the channel if not cached
 | ||||||
|   if(!channel) channel = (await gateway.client.channels.fetch(d.channel_id) as any) as Channel |   if(!channel) channel = (await gateway.client.channels.fetch(d.channel_id) as any) as Channel | ||||||
|   let message = new Message(gateway.client, d, channel) |   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) |   gateway.client.emit('messageCreate', message) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,10 +2,11 @@ import { User } from '../../structures/user.ts' | ||||||
| import { GuildPayload } from '../../types/guildTypes.ts' | import { GuildPayload } from '../../types/guildTypes.ts' | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| 
 | 
 | ||||||
| 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 | ||||||
|   gateway.debug(`Received READY. Session: ${gateway.sessionID}`) |   gateway.debug(`Received READY. Session: ${gateway.sessionID}`) | ||||||
|  |   await gateway.cache.set("session_id", gateway.sessionID) | ||||||
|   d.guilds.forEach((guild: GuildPayload) => { |   d.guilds.forEach((guild: GuildPayload) => { | ||||||
|     gateway.client.guilds.set(guild.id, guild) |     gateway.client.guilds.set(guild.id, guild) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								src/gateway/handlers/resume.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/gateway/handlers/resume.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | ||||||
|  | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  | 
 | ||||||
|  | export const resume: GatewayEventHandler = (gateway: Gateway, d: any) => { | ||||||
|  |   gateway.debug(`Session Resumed!`) | ||||||
|  |   gateway.client.emit('resume') | ||||||
|  | } | ||||||
|  | @ -5,10 +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/gatewayTypes.ts' | import { GatewayOpcodes, GatewayIntents, GatewayCloseCodes } from '../types/gatewayTypes.ts' | ||||||
| import { gatewayHandlers } from './handlers/index.ts' | import { gatewayHandlers } from './handlers/index.ts' | ||||||
| import { GATEWAY_BOT } from '../types/endpoint.ts' | import { GATEWAY_BOT } from '../types/endpoint.ts' | ||||||
| import { GatewayBotPayload } from "../types/gatewayBot.ts" | import { GatewayBotPayload } from "../types/gatewayBot.ts" | ||||||
|  | import { GatewayCache } from "../managers/GatewayCache.ts" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handles Discord gateway connection. |  * Handles Discord gateway connection. | ||||||
|  | @ -25,15 +26,17 @@ class Gateway { | ||||||
|   heartbeatInterval = 0 |   heartbeatInterval = 0 | ||||||
|   heartbeatIntervalID?: number |   heartbeatIntervalID?: number | ||||||
|   sequenceID?: number |   sequenceID?: number | ||||||
|   sessionID?: string |  | ||||||
|   lastPingTimestamp = 0 |   lastPingTimestamp = 0 | ||||||
|  |   sessionID?: string | ||||||
|   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`, | ||||||
|  | @ -51,7 +54,7 @@ class Gateway { | ||||||
|     this.debug("Connected to Gateway!") |     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) | ||||||
|  | @ -72,8 +75,7 @@ class Gateway { | ||||||
|             this.heartbeatServerResponded = false |             this.heartbeatServerResponded = false | ||||||
|           } else { |           } else { | ||||||
|             clearInterval(this.heartbeatIntervalID) |             clearInterval(this.heartbeatIntervalID) | ||||||
|             this.websocket.close() |             this.reconnect() | ||||||
|             this.initWebsocket() |  | ||||||
|             return |             return | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|  | @ -90,6 +92,7 @@ class Gateway { | ||||||
|           this.sendIdentify() |           this.sendIdentify() | ||||||
|           this.initialized = true |           this.initialized = true | ||||||
|         } else { |         } else { | ||||||
|  |           console.log("Calling Resume") | ||||||
|           this.sendResume() |           this.sendResume() | ||||||
|         } |         } | ||||||
|         break |         break | ||||||
|  | @ -102,18 +105,16 @@ class Gateway { | ||||||
| 
 | 
 | ||||||
|       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] | ||||||
|  | @ -124,23 +125,68 @@ 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 async sendIdentify () { |   private async sendIdentify (forceNewSession?: boolean) { | ||||||
|     this.debug("Fetching /gateway/bot...") |     this.debug("Fetching /gateway/bot...") | ||||||
|     const info = await this.client.rest.get(GATEWAY_BOT()) as GatewayBotPayload |     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") |     if(info.session_start_limit.remaining == 0) throw new Error("Session Limit Reached. Retry After " + info.session_start_limit.reset_after + "ms") | ||||||
|  | @ -148,6 +194,14 @@ class Gateway { | ||||||
|     this.debug("=== Session Limit Info ===") |     this.debug("=== Session Limit Info ===") | ||||||
|     this.debug(`Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}`) |     this.debug(`Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}`) | ||||||
|     this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`) |     this.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, | ||||||
|  | @ -175,17 +229,22 @@ class Gateway { | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private sendResume (): void { |   private async sendResume (): Promise<void> { | ||||||
|     this.debug(`Preparing to resume with Session: ${this.sessionID}`) |     this.debug(`Preparing to resume with Session: ${this.sessionID}`) | ||||||
|     this.websocket.send( |     if(this.sequenceID === undefined) { | ||||||
|       JSON.stringify({ |       let cached = await this.cache.get("seq") | ||||||
|  |       if(cached) this.sequenceID = typeof cached == "string" ? parseInt(cached) : cached | ||||||
|  |     } | ||||||
|  |     const resumePayload = { | ||||||
|       op: GatewayOpcodes.RESUME, |       op: GatewayOpcodes.RESUME, | ||||||
|       d: { |       d: { | ||||||
|         token: this.token, |         token: this.token, | ||||||
|         session_id: this.sessionID, |         session_id: this.sessionID, | ||||||
|           seq: this.sequenceID |         seq: this.sequenceID || null | ||||||
|       } |       } | ||||||
|       }) |     } | ||||||
|  |     this.websocket.send( | ||||||
|  |       JSON.stringify(resumePayload) | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -193,6 +252,13 @@ class Gateway { | ||||||
|     this.client.debug("Gateway", msg) |     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
 | ||||||
|  |  | ||||||
|  | @ -4,29 +4,29 @@ import { Base } from "../structures/base.ts"; | ||||||
| export class BaseManager<T, T2> { | export class BaseManager<T, T2> { | ||||||
|   client: Client |   client: Client | ||||||
|   cacheName: string |   cacheName: string | ||||||
|   dataType: typeof Base |   dataType: any | ||||||
| 
 | 
 | ||||||
|   constructor(client: Client, cacheName: string, dataType: typeof Base) { |   constructor(client: Client, cacheName: string, dataType: any) { | ||||||
|     this.client = client |     this.client = client | ||||||
|     this.cacheName = cacheName |     this.cacheName = cacheName | ||||||
|     this.dataType = dataType |     this.dataType = dataType | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _get(key: string): T { |   _get(key: string): Promise<T> { | ||||||
|     return this.client.cache.get(this.cacheName, key) as T |     return this.client.cache.get(this.cacheName, key) as Promise<T> | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   get(key: string): T2 | void { |   async get(key: string): Promise<T2 | void> { | ||||||
|     const raw = this._get(key) |     const raw = await this._get(key) | ||||||
|     if(!raw) return |     if(!raw) return | ||||||
|     return new this.dataType(this.client, raw) as any |     return new this.dataType(this.client, raw) as any | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   set(key: string, value: T) { |   async set(key: string, value: T) { | ||||||
|     return this.client.cache.set(this.cacheName, key, value) |     return this.client.cache.set(this.cacheName, key, value) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   delete(key: string) { |   async delete(key: string) { | ||||||
|     return this.client.cache.delete(this.cacheName, key) |     return this.client.cache.delete(this.cacheName, key) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | @ -11,11 +11,11 @@ export class ChannelsManager extends BaseManager<ChannelPayload, Channel> { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // Override get method as Generic
 |   // Override get method as Generic
 | ||||||
|   get<T = Channel>(key: string): T { |   async get<T = Channel>(key: string): Promise<T> { | ||||||
|     return new this.dataType(this.client, this._get(key)) as any |     return new this.dataType(this.client, this._get(key)) as any | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   fetch(id: string) { |   fetch(id: string): Promise<Channel> { | ||||||
|     return new Promise((res, rej) => { |     return new Promise((res, rej) => { | ||||||
|       this.client.rest.get(CHANNEL(id)).then(data => { |       this.client.rest.get(CHANNEL(id)).then(data => { | ||||||
|         this.set(id, data as ChannelPayload) |         this.set(id, data as ChannelPayload) | ||||||
|  |  | ||||||
							
								
								
									
										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) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,7 +1,10 @@ | ||||||
| import { Client } from "../models/client.ts"; | import { Client } from "../models/client.ts"; | ||||||
| import { Message } from "../structures/message.ts"; | import { Message } from "../structures/message.ts"; | ||||||
|  | import { MessageMentions } from "../structures/MessageMentions.ts"; | ||||||
|  | import { User } from "../structures/user.ts"; | ||||||
| import { MessagePayload } from "../types/channelTypes.ts"; | import { MessagePayload } from "../types/channelTypes.ts"; | ||||||
| import { CHANNEL_MESSAGE } from "../types/endpoint.ts"; | import { CHANNEL_MESSAGE } from "../types/endpoint.ts"; | ||||||
|  | import { UserPayload } from "../types/userTypes.ts"; | ||||||
| import { BaseManager } from "./BaseManager.ts"; | import { BaseManager } from "./BaseManager.ts"; | ||||||
| 
 | 
 | ||||||
| export class MessagesManager extends BaseManager<MessagePayload, Message> { | export class MessagesManager extends BaseManager<MessagePayload, Message> { | ||||||
|  | @ -11,9 +14,15 @@ export class MessagesManager extends BaseManager<MessagePayload, Message> { | ||||||
| 
 | 
 | ||||||
|   fetch(channelID: string, id: string) { |   fetch(channelID: string, id: string) { | ||||||
|     return new Promise((res, rej) => { |     return new Promise((res, rej) => { | ||||||
|       this.client.rest.get(CHANNEL_MESSAGE(channelID, id)).then(data => { |       this.client.rest.get(CHANNEL_MESSAGE(channelID, id)).then(async data => { | ||||||
|         this.set(id, data as MessagePayload) |         this.set(id, data as MessagePayload) | ||||||
|         res(new Message(this.client, 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)) |       }).catch(e => rej(e)) | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -1,12 +1,13 @@ | ||||||
| import { Collection } from "../utils/collection.ts"; | import { Collection } from "../utils/collection.ts"; | ||||||
| import { Client } from "./client.ts"; | import { Client } from "./client.ts"; | ||||||
|  | import { connect, Redis, RedisConnectOptions } from "https://denopkg.com/keroxp/deno-redis/mod.ts"; | ||||||
| 
 | 
 | ||||||
| export interface ICacheAdapter { | export interface ICacheAdapter { | ||||||
|   client: Client |   client: Client | ||||||
|   get: (cacheName: string, key: string) => any |   get: (cacheName: string, key: string) => Promise<any> | any | ||||||
|   set: (cacheName: string, key: string, value: any) => any |   set: (cacheName: string, key: string, value: any) => Promise<any> | any | ||||||
|   delete: (cacheName: string, key: string) => boolean |   delete: (cacheName: string, key: string) => Promise<boolean> | boolean | ||||||
|   array: (cacheName: string) => void | any[] |   array: (cacheName: string) => void | any[] | Promise<any[] | void> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class DefaultCacheAdapter implements ICacheAdapter { | export class DefaultCacheAdapter implements ICacheAdapter { | ||||||
|  | @ -19,13 +20,13 @@ export class DefaultCacheAdapter implements ICacheAdapter { | ||||||
|     this.client = client |     this.client = client | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   get(cacheName: string, key: string) { |   async get(cacheName: string, key: string) { | ||||||
|     const cache = this.data[cacheName] |     const cache = this.data[cacheName] | ||||||
|     if (!cache) return; |     if (!cache) return; | ||||||
|     return cache.get(key) |     return cache.get(key) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   set(cacheName: string, key: string, value: any) { |   async set(cacheName: string, key: string, value: any) { | ||||||
|     let cache = this.data[cacheName] |     let cache = this.data[cacheName] | ||||||
|     if (!cache) { |     if (!cache) { | ||||||
|       this.data[cacheName] = new Collection() |       this.data[cacheName] = new Collection() | ||||||
|  | @ -34,15 +35,64 @@ export class DefaultCacheAdapter implements ICacheAdapter { | ||||||
|     cache.set(key, value) |     cache.set(key, value) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   delete(cacheName: string, key: string) { |   async delete(cacheName: string, key: string) { | ||||||
|     const cache = this.data[cacheName] |     const cache = this.data[cacheName] | ||||||
|     if (!cache) return false |     if (!cache) return false | ||||||
|     return cache.delete(key) |     return cache.delete(key) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   array(cacheName: string) { |   async array(cacheName: string) { | ||||||
|     const cache = this.data[cacheName] |     const cache = this.data[cacheName] | ||||||
|     if (!cache) return |     if (!cache) return | ||||||
|     return cache.array() |     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)) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -42,6 +42,11 @@ export class Client extends EventEmitter { | ||||||
|     if(options.cache) this.cache = options.cache |     if(options.cache) this.cache = options.cache | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   setAdapter(adapter: ICacheAdapter) { | ||||||
|  |     this.cache = adapter | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   debug(tag: string, msg: string) { |   debug(tag: string, msg: string) { | ||||||
|     this.emit("debug", `[${tag}] ${msg}`) |     this.emit("debug", `[${tag}] ${msg}`) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -13,8 +13,7 @@ export enum HttpResponseCode { | ||||||
|   NotFound = 404, |   NotFound = 404, | ||||||
|   MethodNotAllowed = 405, |   MethodNotAllowed = 405, | ||||||
|   TooManyRequests = 429, |   TooManyRequests = 429, | ||||||
|   GatewayUnavailable = 502, |   GatewayUnavailable = 502 | ||||||
|   // ServerError left untyped because it's 5xx.
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export type RequestMethods = | export type RequestMethods = | ||||||
|  |  | ||||||
							
								
								
									
										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 | ||||||
|     this.client.channels.set(this.id, data) |     // 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 { | ||||||
|  |  | ||||||
|  | @ -83,12 +83,12 @@ export class Guild extends Base { | ||||||
|       // this.roles = data.roles.map(
 |       // this.roles = data.roles.map(
 | ||||||
|       //   v => cache.get('role', v.id) ?? new Role(client, v)
 |       //   v => cache.get('role', v.id) ?? new Role(client, v)
 | ||||||
|       // )
 |       // )
 | ||||||
|       data.roles.forEach(role => { |       // data.roles.forEach(role => {
 | ||||||
|         this.roles.set(role.id, new Role(client, role)) |       //   this.roles.set(role.id, new Role(client, role))
 | ||||||
|       }) |       // })
 | ||||||
|       this.emojis = data.emojis.map( |       // this.emojis = data.emojis.map(
 | ||||||
|         v => cache.get('emoji', v.id) ?? new Emoji(client, v) |       //   v => cache.get('emoji', v.id) ?? new Emoji(client, v)
 | ||||||
|       ) |       // )
 | ||||||
|       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 | ||||||
|  | @ -97,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 | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -16,6 +16,8 @@ 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 { 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
 | ||||||
|  | @ -31,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[] | ||||||
|  | @ -46,22 +48,24 @@ export class Message extends Base { | ||||||
|   messageReference?: MessageReference |   messageReference?: MessageReference | ||||||
|   flags?: number |   flags?: number | ||||||
| 
 | 
 | ||||||
|   constructor (client: Client, data: MessagePayload, channel?: Channel, noSave?: boolean) { |   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 | ||||||
|       this.client.users.get(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 => this.client.users.get(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 | ||||||
|  | @ -75,28 +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 | ||||||
|     if(channel) this.channel = channel || this.client.channels.get(this.channelID) |     this.channel = channel | ||||||
|     else throw new Error("Message received without Channel (neither in cache)") // unlikely to happen
 |     // TODO: Cache in Gateway Event Code
 | ||||||
|     if(!noSave) this.client.messages.set(this.id, data) |     // 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 =
 | ||||||
|       this.client.users.get(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 => this.client.users.get(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 | ||||||
|  | @ -112,24 +116,10 @@ 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 edit (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.') |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|     let newMsg = await this.client.rest.patch(CHANNEL_MESSAGE(this.channelID, this.id), { |  | ||||||
|       content: text, |  | ||||||
|       embed: option?.embed.toJSON(), |  | ||||||
|       file: option?.file, |  | ||||||
|       tts: option?.tts, |  | ||||||
|       allowed_mentions: option?.allowedMention |  | ||||||
|     }) as MessagePayload |  | ||||||
| 
 |  | ||||||
|     return new Message(this.client, newMsg) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // TODO: We have to seperate fetch()
 |  | ||||||
|   delete (): Promise<void> { |   delete (): Promise<void> { | ||||||
|     return this.client.rest.delete(CHANNEL_MESSAGE(this.channelID, this.id)) as any |     return this.client.rest.delete(CHANNEL_MESSAGE(this.channelID, this.id)) as any | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -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/channelTypes.ts' | import { MessageOption, MessagePayload, TextChannelPayload } from '../types/channelTypes.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: { |     let newMsg = await this.client.rest.patch(CHANNEL_MESSAGE(this.id, typeof message == "string" ? message : message.id), { | ||||||
|         Authorization: `Bot ${this.client.token}`, |  | ||||||
|         'Content-Type': 'application/json' |  | ||||||
|       }, |  | ||||||
|       method: 'PATCH', |  | ||||||
|       body: JSON.stringify({ |  | ||||||
|       content: text, |       content: text, | ||||||
|         embed: option?.embed, |       embed: option?.embed.toJSON(), | ||||||
|       file: option?.file, |       file: option?.file, | ||||||
|       tts: option?.tts, |       tts: option?.tts, | ||||||
|       allowed_mentions: option?.allowedMention |       allowed_mentions: option?.allowedMention | ||||||
|       }) |     }) as MessagePayload | ||||||
|     }) |  | ||||||
| 
 | 
 | ||||||
|     return new Message(this.client, await resp.json()) |     // TODO: Actually construct this object
 | ||||||
|  |     let mentions = new MessageMentions() | ||||||
|  | 
 | ||||||
|  |     return new Message(this.client, newMsg, this, this.client.user as User, mentions) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -45,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 { | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -7,9 +7,15 @@ 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 { 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(`[Login] Logged in as ${bot.user?.tag}!`) |   console.log(`[Login] Logged in as ${bot.user?.tag}!`) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| export class Collection<K, V> extends Map<K, V> { | export class Collection<K = string, V = any> extends Map<K, V> { | ||||||
|   maxSize?: number; |   maxSize?: number; | ||||||
| 
 | 
 | ||||||
|   set(key: K, value: V) { |   set(key: K, value: V) { | ||||||
|  | @ -84,4 +84,12 @@ export class Collection<K, V> extends Map<K, V> { | ||||||
| 
 | 
 | ||||||
|     return accumulator |     return accumulator | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   static fromObject<V>(object: { [key: string]: V }) { | ||||||
|  |     return new Collection<string, V>(Object.entries(object)) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toObject() { | ||||||
|  |     return Object.entries(this) | ||||||
|  |   } | ||||||
| } | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue