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 getChannelByType from '../../utils/getChannelByType.ts' | ||||
| 
 | ||||
| export const channelCreate: GatewayEventHandler = ( | ||||
| export const channelCreate: GatewayEventHandler = async ( | ||||
|   gateway: Gateway, | ||||
|   d: any | ||||
| ) => { | ||||
|   const channel = getChannelByType(gateway.client, d) | ||||
| 
 | ||||
|   if (channel !== undefined) { | ||||
|     await gateway.client.channels.set(d.id, d) | ||||
|     gateway.client.emit('channelCreate', channel) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,14 +1,13 @@ | |||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| import cache from '../../models/cache.ts' | ||||
| import { Channel } from '../../structures/channel.ts' | ||||
| 
 | ||||
| export const channelDelete: GatewayEventHandler = ( | ||||
| export const channelDelete: GatewayEventHandler = async( | ||||
|   gateway: Gateway, | ||||
|   d: any | ||||
| ) => { | ||||
|   const channel: Channel = cache.get('channel', d.id) | ||||
|   const channel: Channel = await gateway.client.channels.get(d.id) | ||||
|   if (channel !== undefined) { | ||||
|     cache.del('channel', d.id) | ||||
|     await gateway.client.channels.delete(d.id) | ||||
|     gateway.client.emit('channelDelete', channel) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,16 +1,19 @@ | |||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| import cache from '../../models/cache.ts' | ||||
| import { TextChannel } from '../../structures/textChannel.ts' | ||||
| import { ChannelPayload } from "../../types/channel.ts" | ||||
| 
 | ||||
| export const channelPinsUpdate: GatewayEventHandler = ( | ||||
| export const channelPinsUpdate: GatewayEventHandler = async( | ||||
|   gateway: Gateway, | ||||
|   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) { | ||||
|     const before = after.refreshFromData({ | ||||
|       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) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,15 +1,15 @@ | |||
| import cache from '../../models/cache.ts' | ||||
| import { Channel } from '../../structures/channel.ts' | ||||
| import getChannelByType from '../../utils/getChannelByType.ts' | ||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| 
 | ||||
| export const channelUpdate: GatewayEventHandler = ( | ||||
| export const channelUpdate: GatewayEventHandler = async ( | ||||
|   gateway: Gateway, | ||||
|   d: any | ||||
| ) => { | ||||
|   const oldChannel: Channel = cache.get('channel', d.id) | ||||
|   const oldChannel: Channel = await gateway.client.channels.get(d.id) | ||||
| 
 | ||||
|   if (oldChannel !== undefined) { | ||||
|     await gateway.client.channels.set(d.id, d) | ||||
|     if (oldChannel.type !== d.type) { | ||||
|       const channel: Channel = getChannelByType(gateway.client, d) ?? oldChannel | ||||
|       gateway.client.emit('channelUpdate', oldChannel, channel) | ||||
|  |  | |||
|  | @ -1,14 +1,16 @@ | |||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| import cache from '../../models/cache.ts' | ||||
| import { Guild } from '../../structures/guild.ts' | ||||
| import { GuildPayload } from "../../types/guild.ts" | ||||
| 
 | ||||
| export const guildCreate: GatewayEventHandler = (gateway: Gateway, d: any) => { | ||||
|   let guild: Guild = cache.get('guild', d.id) | ||||
| export const guildCreate: GatewayEventHandler = async(gateway: Gateway, d: any) => { | ||||
|   let guild: Guild | void = await gateway.client.guilds.get(d.id) | ||||
|   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) | ||||
|   } 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) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,13 +1,12 @@ | |||
| import cache from '../../models/cache.ts' | ||||
| import { Guild } from '../../structures/guild.ts' | ||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| 
 | ||||
| export const guildDelte: GatewayEventHandler = (gateway: Gateway, d: any) => { | ||||
|   const guild: Guild = cache.get('guild', d.id) | ||||
| export const guildDelte: GatewayEventHandler = async (gateway: Gateway, d: any) => { | ||||
|   const guild: Guild | void = await gateway.client.guilds.get(d.id) | ||||
| 
 | ||||
|   if (guild !== undefined) { | ||||
|     guild.refreshFromData(d) | ||||
|     cache.del('guild', d.id) | ||||
|     await gateway.client.guilds.delete(d.id) | ||||
|     gateway.client.emit('guildDelete', guild) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,11 +1,10 @@ | |||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| import cache from '../../models/cache.ts' | ||||
| import { Guild } from '../../structures/guild.ts' | ||||
| 
 | ||||
| export const guildUpdate: GatewayEventHandler = (gateway: Gateway, d: any) => { | ||||
|   const after: Guild = cache.get('guild', d.id) | ||||
|   if (after !== undefined) { | ||||
|     const before: Guild = after.refreshFromData(d) | ||||
| export const guildUpdate: GatewayEventHandler = async(gateway: Gateway, d: any) => { | ||||
|   const before: Guild | void = await gateway.client.guilds.get(d.id) | ||||
|   if(!before) return | ||||
|   await gateway.client.guilds.set(d.id, d) | ||||
|   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 { ready } from './ready.ts' | ||||
| import { guildBanRemove } from './guildBanRemove.ts' | ||||
| import { messageCreate } from "./messageCreate.ts" | ||||
| import { resume } from "./resume.ts" | ||||
| 
 | ||||
| export const gatewayHandlers: { | ||||
|   [eventCode in GatewayEvents]: GatewayEventHandler | undefined | ||||
| } = { | ||||
|   READY: ready, | ||||
|   RECONNECT: undefined, | ||||
|   RESUMED: undefined, | ||||
|   RESUMED: resume, | ||||
|   CHANNEL_CREATE: channelCreate, | ||||
|   CHANNEL_DELETE: channelDelete, | ||||
|   CHANNEL_UPDATE: channelUpdate, | ||||
|  | @ -37,7 +39,7 @@ export const gatewayHandlers: { | |||
|   GUILD_ROLE_DELETE: undefined, | ||||
|   INVITE_CREATE: undefined, | ||||
|   INVITE_DELETE: undefined, | ||||
|   MESSAGE_CREATE: undefined, | ||||
|   MESSAGE_CREATE: messageCreate, | ||||
|   MESSAGE_UPDATE: undefined, | ||||
|   MESSAGE_DELETE: 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 { GuildPayload } from '../../types/guild.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.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') | ||||
| } | ||||
							
								
								
									
										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 | ||||
| } from '../consts/urlsAndVersions.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 { GATEWAY_BOT } from '../types/endpoint.ts' | ||||
| import { GatewayBotPayload } from "../types/gatewayBot.ts" | ||||
| import { GatewayCache } from "../managers/GatewayCache.ts" | ||||
| 
 | ||||
| /** | ||||
|  * Handles Discord gateway connection. | ||||
|  | @ -23,15 +26,17 @@ class Gateway { | |||
|   heartbeatInterval = 0 | ||||
|   heartbeatIntervalID?: number | ||||
|   sequenceID?: number | ||||
|   lastPingTimestamp = 0 | ||||
|   sessionID?: string | ||||
|   lastPingTimestemp = 0 | ||||
|   private heartbeatServerResponded = false | ||||
|   client: Client | ||||
|   cache: GatewayCache | ||||
| 
 | ||||
|   constructor (client: Client, token: string, intents: GatewayIntents[]) { | ||||
|     this.token = token | ||||
|     this.intents = intents | ||||
|     this.client = client | ||||
|     this.cache = new GatewayCache(client) | ||||
|     this.websocket = new WebSocket( | ||||
|       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
 | ||||
|       `${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`, | ||||
|  | @ -46,9 +51,10 @@ class Gateway { | |||
| 
 | ||||
|   private onopen (): void { | ||||
|     this.connected = true | ||||
|     this.debug("Connected to Gateway!") | ||||
|   } | ||||
| 
 | ||||
|   private onmessage (event: MessageEvent): void { | ||||
|   private async onmessage (event: MessageEvent): Promise<void> { | ||||
|     let data = event.data | ||||
|     if (data instanceof ArrayBuffer) { | ||||
|       data = new Uint8Array(data) | ||||
|  | @ -63,13 +69,13 @@ class Gateway { | |||
|     switch (op) { | ||||
|       case GatewayOpcodes.HELLO: | ||||
|         this.heartbeatInterval = d.heartbeat_interval | ||||
|         this.debug(`Received HELLO. Heartbeat Interval: ${this.heartbeatInterval}`) | ||||
|         this.heartbeatIntervalID = setInterval(() => { | ||||
|           if (this.heartbeatServerResponded) { | ||||
|             this.heartbeatServerResponded = false | ||||
|           } else { | ||||
|             clearInterval(this.heartbeatIntervalID) | ||||
|             this.websocket.close() | ||||
|             this.initWebsocket() | ||||
|             this.reconnect() | ||||
|             return | ||||
|           } | ||||
| 
 | ||||
|  | @ -79,36 +85,36 @@ class Gateway { | |||
|               d: this.sequenceID ?? null | ||||
|             }) | ||||
|           ) | ||||
|           this.lastPingTimestemp = Date.now() | ||||
|           this.lastPingTimestamp = Date.now() | ||||
|         }, this.heartbeatInterval) | ||||
| 
 | ||||
|         if (!this.initialized) { | ||||
|           this.sendIdentify() | ||||
|           this.initialized = true | ||||
|         } else { | ||||
|           console.log("Calling Resume") | ||||
|           this.sendResume() | ||||
|         } | ||||
|         break | ||||
| 
 | ||||
|       case GatewayOpcodes.HEARTBEAT_ACK: | ||||
|         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 | ||||
| 
 | ||||
|       case GatewayOpcodes.INVALID_SESSION: | ||||
|         // 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
 | ||||
|         if (!d) { | ||||
|           setTimeout(this.sendResume, 3000) | ||||
|         } else { | ||||
|           setTimeout(this.sendIdentify, 3000) | ||||
|         } | ||||
|         setTimeout(() => this.sendIdentify(true), 3000) | ||||
|         break | ||||
| 
 | ||||
|       case GatewayOpcodes.DISPATCH: { | ||||
|         this.heartbeatServerResponded = true | ||||
|         if (s !== null) { | ||||
|           this.sequenceID = s | ||||
|           await this.cache.set("seq", s) | ||||
|         } | ||||
|         if (t !== null && t !== undefined) { | ||||
|           const handler = gatewayHandlers[t] | ||||
|  | @ -119,23 +125,83 @@ class Gateway { | |||
|         } | ||||
|         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: | ||||
|         break | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private onclose (event: CloseEvent): void { | ||||
|     console.log(event.code) | ||||
|     // TODO: Handle close event codes.
 | ||||
|     this.debug("Connection Closed with code: " + event.code) | ||||
| 
 | ||||
|     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 { | ||||
|     const eventError = event as ErrorEvent | ||||
| 
 | ||||
|     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( | ||||
|       JSON.stringify({ | ||||
|         op: GatewayOpcodes.IDENTIFY, | ||||
|  | @ -163,19 +229,36 @@ class Gateway { | |||
|     ) | ||||
|   } | ||||
| 
 | ||||
|   private sendResume (): void { | ||||
|     this.websocket.send( | ||||
|       JSON.stringify({ | ||||
|   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 | ||||
|         seq: this.sequenceID || null | ||||
|       } | ||||
|       }) | ||||
|     } | ||||
|     this.websocket.send( | ||||
|       JSON.stringify(resumePayload) | ||||
|     ) | ||||
|   } | ||||
| 
 | ||||
|   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 { | ||||
|     this.websocket = new WebSocket( | ||||
|       // 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 { GatewayIntents } from '../types/gateway.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 { 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. | ||||
|  */ | ||||
| export class Client extends EventEmitter { | ||||
|   gateway?: Gateway | ||||
|   rest?: Rest | ||||
|   rest: RESTManager = new RESTManager(this) | ||||
|   user?: User | ||||
|   ping = 0 | ||||
|   token?: string | ||||
|   cache: ICacheAdapter = new DefaultCacheAdapter(this) | ||||
|   intents?: GatewayIntents[] | ||||
|    | ||||
|   // constructor () {
 | ||||
|   //   super()
 | ||||
|   // }
 | ||||
|   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 (options: ClientOptions = {}) { | ||||
|     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. | ||||
|    * @param token Your token. This is required. | ||||
|    * @param intents Gateway intents in array. This is required. | ||||
|    */ | ||||
|   connect (token: string, intents: GatewayIntents[]): void { | ||||
|   connect (token?: string, intents?: GatewayIntents[]): void { | ||||
|     if(!token && this.token) token = this.token | ||||
|     else if(!this.token && token) { | ||||
|       this.token = token | ||||
|     } | ||||
|     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) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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 { | ||||
|   client: Client | ||||
|   constructor (client: Client) { | ||||
|     this.client = client | ||||
|   } | ||||
|   // TODO: make endpoints function
 | ||||
| export enum HttpResponseCode { | ||||
|   Ok = 200, | ||||
|   Created = 201, | ||||
|   NoContent = 204, | ||||
|   NotModified = 304, | ||||
|   BadRequest = 400, | ||||
|   Unauthorized = 401, | ||||
|   Forbidden = 403, | ||||
|   NotFound = 404, | ||||
|   MethodNotAllowed = 405, | ||||
|   TooManyRequests = 429, | ||||
|   GatewayUnavailable = 502 | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
|     this.type = data.type | ||||
|     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 { | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ export class DMChannel extends TextChannel { | |||
|   constructor (client: Client, data: DMChannelPayload) { | ||||
|     super(client, data) | ||||
|     this.recipients = data.recipients | ||||
|     cache.set('dmchannel', this.id, this) | ||||
|     // cache.set('dmchannel', this.id, this)
 | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData (data: DMChannelPayload): void { | ||||
|  |  | |||
|  | @ -14,7 +14,8 @@ export class GroupDMChannel extends Channel { | |||
|     this.name = data.name | ||||
|     this.icon = data.icon | ||||
|     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 { | ||||
|  |  | |||
|  | @ -5,10 +5,11 @@ import { Base } from './base.ts' | |||
| import { Channel } from './channel.ts' | ||||
| import { Emoji } from './emoji.ts' | ||||
| import { Member } from './member.ts' | ||||
| import { Role } from './role.ts' | ||||
| import { VoiceState } from './voiceState.ts' | ||||
| import cache from '../models/cache.ts' | ||||
| import getChannelByType from '../utils/getChannelByType.ts' | ||||
| import { RolesManager } from "../managers/RolesManager.ts" | ||||
| import { Role } from "./role.ts" | ||||
| 
 | ||||
| export class Guild extends Base { | ||||
|   id: string | ||||
|  | @ -28,7 +29,7 @@ export class Guild extends Base { | |||
|   verificationLevel?: string | ||||
|   defaultMessageNotifications?: string | ||||
|   explicitContentFilter?: string | ||||
|   roles?: Role[] | ||||
|   roles: RolesManager = new RolesManager(this.client, this) | ||||
|   emojis?: Emoji[] | ||||
|   features?: GuildFeatures[] | ||||
|   mfaLevel?: string | ||||
|  | @ -79,12 +80,15 @@ export class Guild extends Base { | |||
|       this.verificationLevel = data.verification_level | ||||
|       this.defaultMessageNotifications = data.default_message_notifications | ||||
|       this.explicitContentFilter = data.explicit_content_filter | ||||
|       this.roles = data.roles.map( | ||||
|         v => cache.get('role', v.id) ?? new Role(client, v) | ||||
|       ) | ||||
|       this.emojis = data.emojis.map( | ||||
|         v => cache.get('emoji', v.id) ?? new Emoji(client, v) | ||||
|       ) | ||||
|       // this.roles = data.roles.map(
 | ||||
|       //   v => cache.get('role', v.id) ?? new Role(client, v)
 | ||||
|       // )
 | ||||
|       // data.roles.forEach(role => {
 | ||||
|       //   this.roles.set(role.id, new Role(client, role))
 | ||||
|       // })
 | ||||
|       // this.emojis = data.emojis.map(
 | ||||
|       //   v => cache.get('emoji', v.id) ?? new Emoji(client, v)
 | ||||
|       // )
 | ||||
|       this.features = data.features | ||||
|       this.mfaLevel = data.mfa_level | ||||
|       this.systemChannelID = data.system_channel_id | ||||
|  | @ -93,19 +97,20 @@ export class Guild extends Base { | |||
|       this.joinedAt = data.joined_at | ||||
|       this.large = data.large | ||||
|       this.memberCount = data.member_count | ||||
|       this.voiceStates = data.voice_states?.map( | ||||
|         v => | ||||
|           cache.get('voiceState', `${v.guild_id}:${v.user_id}`) ?? | ||||
|           new VoiceState(client, v) | ||||
|       ) | ||||
|       this.members = data.members?.map( | ||||
|         v => | ||||
|           cache.get('member', `${this.id}:${v.user.id}`) ?? | ||||
|           new Member(client, v) | ||||
|       ) | ||||
|       this.channels = data.channels?.map( | ||||
|         v => cache.get('channel', v.id) ?? getChannelByType(this.client, v) | ||||
|       ) | ||||
|       // TODO: Cache in Gateway Event code
 | ||||
|       // this.voiceStates = data.voice_states?.map(
 | ||||
|       //   v =>
 | ||||
|       //     cache.get('voiceState', `${v.guild_id}:${v.user_id}`) ??
 | ||||
|       //     new VoiceState(client, v)
 | ||||
|       // )
 | ||||
|       // this.members = data.members?.map(
 | ||||
|       //   v =>
 | ||||
|       //     cache.get('member', `${this.id}:${v.user.id}`) ??
 | ||||
|       //     new Member(client, v)
 | ||||
|       // )
 | ||||
|       // this.channels = data.channels?.map(
 | ||||
|       //   v => cache.get('channel', v.id) ?? getChannelByType(this.client, v)
 | ||||
|       // )
 | ||||
|       this.presences = data.presences | ||||
|       this.maxPresences = data.max_presences | ||||
|       this.maxMembers = data.max_members | ||||
|  | @ -120,7 +125,6 @@ export class Guild extends Base { | |||
|       this.approximateNumberCount = data.approximate_number_count | ||||
|       this.approximatePresenceCount = data.approximate_presence_count | ||||
|     } | ||||
|     cache.set('guild', this.id, this) | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData (data: GuildPayload): void { | ||||
|  | @ -147,10 +151,10 @@ export class Guild extends Base { | |||
|         data.default_message_notifications ?? this.defaultMessageNotifications | ||||
|       this.explicitContentFilter = | ||||
|         data.explicit_content_filter ?? this.explicitContentFilter | ||||
|       this.roles = | ||||
|         data.roles.map( | ||||
|           v => cache.get('role', v.id) ?? new Role(this.client, v) | ||||
|         ) ?? this.roles | ||||
|       // this.roles =
 | ||||
|       //   data.roles.map(
 | ||||
|       //     v => cache.get('role', v.id) ?? new Role(this.client, v)
 | ||||
|       //   ) ?? this.roles
 | ||||
|       this.emojis = | ||||
|         data.emojis.map( | ||||
|           v => cache.get('emoji', v.id) ?? new Emoji(this.client, v) | ||||
|  |  | |||
|  | @ -22,7 +22,8 @@ export class CategoryChannel extends Channel { | |||
|     this.permissionOverwrites = data.permission_overwrites | ||||
|     this.nsfw = data.nsfw | ||||
|     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 { | ||||
|  |  | |||
|  | @ -27,7 +27,8 @@ export class GuildTextChannel extends TextChannel { | |||
|     this.parentID = data.parent_id | ||||
|     this.topic = data.topic | ||||
|     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 { | ||||
|  |  | |||
|  | @ -23,7 +23,8 @@ export class VoiceChannel extends Channel { | |||
|     this.permissionOverwrites = data.permission_overwrites | ||||
|     this.nsfw = data.nsfw | ||||
|     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 { | ||||
|  |  | |||
|  | @ -25,7 +25,8 @@ export class Member extends Base { | |||
|     this.premiumSince = data.premium_since | ||||
|     this.deaf = data.deaf | ||||
|     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 { | ||||
|  |  | |||
|  | @ -15,12 +15,16 @@ import { Member } from './member.ts' | |||
| import { Embed } from './embed.ts' | ||||
| import { CHANNEL_MESSAGE } from '../types/endpoint.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 { | ||||
|   // eslint-disable-next-line @typescript-eslint/prefer-readonly
 | ||||
|   private data: MessagePayload | ||||
|   id: string | ||||
|   channelID: string | ||||
|   channel: Channel | ||||
|   guildID?: string | ||||
|   author: User | ||||
|   member?: Member | ||||
|  | @ -29,7 +33,7 @@ export class Message extends Base { | |||
|   editedTimestamp?: string | ||||
|   tts: boolean | ||||
|   mentionEveryone: boolean | ||||
|   mentions: User[] | ||||
|   mentions: MessageMentions | ||||
|   mentionRoles: string[] | ||||
|   mentionChannels?: ChannelMention[] | ||||
|   attachments: Attachment[] | ||||
|  | @ -44,22 +48,24 @@ export class Message extends Base { | |||
|   messageReference?: MessageReference | ||||
|   flags?: number | ||||
| 
 | ||||
|   constructor (client: Client, data: MessagePayload) { | ||||
|   constructor (client: Client, data: MessagePayload, channel: Channel, author: User, mentions: MessageMentions) { | ||||
|     super(client) | ||||
|     this.data = data | ||||
|     this.id = data.id | ||||
|     this.channelID = data.channel_id | ||||
|     this.guildID = data.guild_id | ||||
|     this.author = | ||||
|       cache.get('user', data.author.id) ?? new User(this.client, data.author) | ||||
|     this.author = author | ||||
|     // this.author =
 | ||||
|     //   this.client.users.get(data.author.id) || new User(this.client, data.author)
 | ||||
|     this.content = data.content | ||||
|     this.timestamp = data.timestamp | ||||
|     this.editedTimestamp = data.edited_timestamp | ||||
|     this.tts = data.tts | ||||
|     this.mentionEveryone = data.mention_everyone | ||||
|     this.mentions = data.mentions.map( | ||||
|       v => cache.get('user', v.id) ?? new User(client, v) | ||||
|     ) | ||||
|     this.mentions = mentions | ||||
|     // this.mentions = data.mentions.map(
 | ||||
|     //   v => this.client.users.get(v.id) || new User(client, v)
 | ||||
|     // )
 | ||||
|     this.mentionRoles = data.mention_roles | ||||
|     this.mentionChannels = data.mention_channels | ||||
|     this.attachments = data.attachments | ||||
|  | @ -73,26 +79,28 @@ export class Message extends Base { | |||
|     this.application = data.application | ||||
|     this.messageReference = data.message_reference | ||||
|     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 { | ||||
|     super.readFromData(data) | ||||
|     this.channelID = data.channel_id ?? this.channelID | ||||
|     this.guildID = data.guild_id ?? this.guildID | ||||
|     this.author = | ||||
|       cache.get('user', data.author.id) ?? | ||||
|       this.author ?? | ||||
|       new User(this.client, data.author) | ||||
|     // this.author =
 | ||||
|     //   this.client.users.get(data.author.id) ||
 | ||||
|     //   this.author ||
 | ||||
|     //   new User(this.client, data.author)
 | ||||
|     this.content = data.content ?? this.content | ||||
|     this.timestamp = data.timestamp ?? this.timestamp | ||||
|     this.editedTimestamp = data.edited_timestamp ?? this.editedTimestamp | ||||
|     this.tts = data.tts ?? this.tts | ||||
|     this.mentionEveryone = data.mention_everyone ?? this.mentionEveryone | ||||
|     this.mentions = | ||||
|       data.mentions.map( | ||||
|         v => cache.get('user', v.id) ?? new User(this.client, v) | ||||
|       ) ?? this.mentions | ||||
|     // this.mentions =
 | ||||
|     //   data.mentions.map(
 | ||||
|     //     v => this.client.users.get(v.id) || new User(this.client, v)
 | ||||
|     //   ) ?? this.mentions
 | ||||
|     this.mentionRoles = data.mention_roles ?? this.mentionRoles | ||||
|     this.mentionChannels = data.mention_channels ?? this.mentionChannels | ||||
|     this.attachments = data.attachments ?? this.attachments | ||||
|  | @ -108,44 +116,11 @@ export class Message extends Base { | |||
|     this.flags = data.flags ?? this.flags | ||||
|   } | ||||
| 
 | ||||
|   // TODO: We have to seperate fetch()
 | ||||
|   async editMessage (text?: string, option?: MessageOption): Promise<Message> { | ||||
|     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()) | ||||
|   edit (text?: string, option?: MessageOption): Promise<Message> { | ||||
|     return (this.channel as TextChannel).editMessage(this.id, text, option)   | ||||
|   } | ||||
| 
 | ||||
|   // TODO: We have to seperate fetch()
 | ||||
|   async delete (): Promise<void> { | ||||
|     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() | ||||
|     }) | ||||
|   delete (): Promise<void> { | ||||
|     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.managed = data.managed | ||||
|     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 { | ||||
|  |  | |||
|  | @ -1,9 +1,11 @@ | |||
| import cache from '../models/cache.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 } from './channel.ts' | ||||
| import { Message } from './message.ts' | ||||
| import { MessageMentions } from "./MessageMentions.ts" | ||||
| import { User } from "./user.ts" | ||||
| 
 | ||||
| export class TextChannel extends Channel { | ||||
|   lastMessageID?: string | ||||
|  | @ -13,7 +15,8 @@ export class TextChannel extends Channel { | |||
|     super(client, data) | ||||
|     this.lastMessageID = data.last_message_id | ||||
|     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 { | ||||
|  | @ -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 ( | ||||
|     messageID: string, | ||||
|     message: Message | string, | ||||
|     text?: string, | ||||
|     option?: MessageOption | ||||
|   ): Promise<Message> { | ||||
|     if (text !== undefined && option !== undefined) { | ||||
|       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({ | ||||
| 
 | ||||
|     let newMsg = await this.client.rest.patch(CHANNEL_MESSAGE(this.id, typeof message == "string" ? message : message.id), { | ||||
|       content: text, | ||||
|         embed: option?.embed, | ||||
|       embed: option?.embed.toJSON(), | ||||
|       file: option?.file, | ||||
|       tts: option?.tts, | ||||
|       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) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -18,6 +18,10 @@ export class User extends Base { | |||
|   premiumType?: 0 | 1 | 2 | ||||
|   publicFlags?: number | ||||
| 
 | ||||
|   get tag(): string { | ||||
|     return `${this.username}#${this.discriminator}`; | ||||
|   } | ||||
| 
 | ||||
|   get nickMention (): string { | ||||
|     return `<@!${this.id}>` | ||||
|   } | ||||
|  | @ -41,7 +45,8 @@ export class User extends Base { | |||
|     this.flags = data.flags | ||||
|     this.premiumType = data.premium_type | ||||
|     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 { | ||||
|  | @ -59,4 +64,8 @@ export class User extends Base { | |||
|     this.premiumType = data.premium_type ?? this.premiumType | ||||
|     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.selfVideo = data.self_video | ||||
|     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 { | ||||
|  |  | |||
|  | @ -1,2 +1 @@ | |||
| const TOKEN = '' | ||||
| export { TOKEN } | ||||
| export const TOKEN = '' | ||||
|  | @ -1,18 +1,27 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { GatewayIntents } from '../types/gatewayTypes.ts' | ||||
| import { GatewayIntents } from '../types/gateway.ts' | ||||
| import { TOKEN } from './config.ts' | ||||
| import { Channel } from '../structures/channel.ts' | ||||
| import { GuildTextChannel } from '../structures/guildTextChannel.ts' | ||||
| import { TextChannel } from '../structures/textChannel.ts' | ||||
| import { Guild } from '../structures/guild.ts' | ||||
| import { User } from '../structures/user.ts' | ||||
| import { Message } from "../structures/message.ts" | ||||
| import { RedisCacheAdapter } from "../models/CacheAdapter.ts" | ||||
| 
 | ||||
| const bot = new Client() | ||||
| 
 | ||||
| bot.setAdapter(new RedisCacheAdapter(bot, { | ||||
|   hostname: "127.0.0.1", | ||||
|   port: 6379 | ||||
| })) | ||||
| 
 | ||||
| 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) => { | ||||
|   console.log('channelDelete', channel.id) | ||||
| }) | ||||
|  | @ -59,6 +68,10 @@ bot.on('guildUpdate', (before: Guild, after: Guild) => { | |||
|   console.log('guildUpdate', before.name, after.name) | ||||
| }) | ||||
| 
 | ||||
| bot.on('messageCreate', (msg: Message) => { | ||||
|   console.log(`${msg.author.tag}: ${msg.content}`) | ||||
| }) | ||||
| 
 | ||||
| bot.connect(TOKEN, [ | ||||
|   GatewayIntents.GUILD_MEMBERS, | ||||
|   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
 | ||||
| import { MemberPayload } from './guildTypes.ts' | ||||
| import { MemberPayload } from './guild.ts' | ||||
| 
 | ||||
| enum VoiceOpcodes { // VoiceOpcodes 추가 - UnderC -
 | ||||
|   IDENTIFY = 0, | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { UserPayload } from './userTypes.ts' | ||||
| import { UserPayload } from './user.ts' | ||||
| 
 | ||||
| export interface WebhookPayload { | ||||
|   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 { getChannelByType } | ||||
| export { default as getChannelByType } from './getChannelByType.ts' | ||||
| export type AnyFunction<ReturnType = any> = (...args:any[]) => ReturnType; | ||||
| export { delay } from './delay.ts' | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue