Merge pull request #29 from DjDeveloperr/main
Fixed Member Caching, Message#member, Member#roles
This commit is contained in:
		
						commit
						d68f168a55
					
				
					 35 changed files with 489 additions and 363 deletions
				
			
		|  | @ -8,7 +8,7 @@ | ||||||
| * Lightweight and easy to use. | * Lightweight and easy to use. | ||||||
| * Built-in Command Framework, | * Built-in Command Framework, | ||||||
|   * Easily build Commands on the fly. |   * Easily build Commands on the fly. | ||||||
|   * Compltely Customizable. |   * Completely Customizable. | ||||||
|   * Complete Object-Oriented approach. |   * Complete Object-Oriented approach. | ||||||
| * 100% Discord API Coverage. | * 100% Discord API Coverage. | ||||||
| * Customizable caching. | * Customizable caching. | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import { Channel } from '../../structures/channel.ts' | import { Channel } from '../../structures/channel.ts' | ||||||
| import { Guild } from "../../structures/guild.ts" | import { Guild } from '../../structures/guild.ts' | ||||||
| import { ChannelPayload, GuildChannelPayload } from '../../types/channel.ts' | import { ChannelPayload, GuildChannelPayload } from '../../types/channel.ts' | ||||||
| import getChannelByType from '../../utils/getChannelByType.ts' | import getChannelByType from '../../utils/getChannelByType.ts' | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
|  |  | ||||||
|  | @ -8,9 +8,7 @@ export const guildBanAdd: GatewayEventHandler = async ( | ||||||
|   d: GuildBanAddPayload |   d: GuildBanAddPayload | ||||||
| ) => { | ) => { | ||||||
|   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) |   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) | ||||||
|   const user: User = |   const user: User = await gateway.client.users.get(d.user.id) ?? new User(gateway.client, d.user) | ||||||
|     (await gateway.client.users.get(d.user.id)) ?? |  | ||||||
|     new User(gateway.client, d.user) |  | ||||||
| 
 | 
 | ||||||
|   if (guild !== undefined) { |   if (guild !== undefined) { | ||||||
|     await guild.members.delete(user.id) |     await guild.members.delete(user.id) | ||||||
|  |  | ||||||
|  | @ -9,10 +9,9 @@ export const guildBanRemove: GatewayEventHandler = async ( | ||||||
| ) => { | ) => { | ||||||
|   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) |   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) | ||||||
|   const user: User = |   const user: User = | ||||||
|     (await gateway.client.users.get(d.user.id)) ?? |     await gateway.client.users.get(d.user.id) ?? new User(gateway.client, d.user) | ||||||
|     new User(gateway.client, d.user) |  | ||||||
| 
 | 
 | ||||||
|   if (guild !== undefined) { |   if (guild !== undefined) { | ||||||
|     gateway.client.emit('guildBanRemove', guild, user) |     gateway.client.emit('guildBanRemove', guild, user) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | @ -10,8 +10,8 @@ import { guildUpdate } from './guildUpdate.ts' | ||||||
| import { guildBanAdd } from './guildBanAdd.ts' | import { guildBanAdd } from './guildBanAdd.ts' | ||||||
| import { ready } from './ready.ts' | import { ready } from './ready.ts' | ||||||
| import { guildBanRemove } from './guildBanRemove.ts' | import { guildBanRemove } from './guildBanRemove.ts' | ||||||
| import { messageCreate } from "./messageCreate.ts" | import { messageCreate } from './messageCreate.ts' | ||||||
| import { resume } from "./resume.ts" | import { resume } from './resume.ts' | ||||||
| import { reconnect } from './reconnect.ts' | import { reconnect } from './reconnect.ts' | ||||||
| 
 | 
 | ||||||
| export const gatewayHandlers: { | export const gatewayHandlers: { | ||||||
|  |  | ||||||
|  | @ -17,11 +17,26 @@ export const messageCreate: GatewayEventHandler = async ( | ||||||
|   const user = new User(gateway.client, d.author) |   const user = new User(gateway.client, d.author) | ||||||
|   await gateway.client.users.set(d.author.id, d.author) |   await gateway.client.users.set(d.author.id, d.author) | ||||||
|   let guild |   let guild | ||||||
|  |   let member | ||||||
|   if (d.guild_id !== undefined) { |   if (d.guild_id !== undefined) { | ||||||
|     guild = await gateway.client.guilds.get(d.guild_id) |     guild = await gateway.client.guilds.get(d.guild_id) | ||||||
|   } |   } | ||||||
|  |   if (guild !== undefined && d.member !== undefined) { | ||||||
|  |     d.member.user = d.author | ||||||
|  |     await guild.members.set(d.author.id, d.member) | ||||||
|  |     member = await guild.members.get(d.author.id) | ||||||
|  |   } | ||||||
|   const mentions = new MessageMentions() |   const mentions = new MessageMentions() | ||||||
|   const message = new Message(gateway.client, d, channel as any, user, mentions) |   const message = new Message(gateway.client, d, channel as any, user, mentions) | ||||||
|  |   message.member = member | ||||||
|   if (guild !== undefined) message.guild = guild |   if (guild !== undefined) message.guild = guild | ||||||
|  |   if (message.member !== undefined) { | ||||||
|  |     if (message.member.user === undefined) { | ||||||
|  |       const user = await gateway.client.users.get(message.member.id) | ||||||
|  |       if (user !== undefined) { | ||||||
|  |         message.member.user = user | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   gateway.client.emit('messageCreate', message) |   gateway.client.emit('messageCreate', message) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { Gateway , GatewayEventHandler } from "../index.ts" | import { Gateway , GatewayEventHandler } from '../index.ts' | ||||||
| 
 | 
 | ||||||
| export const reconnect: GatewayEventHandler = async (gateway: Gateway, d: any) => { | export const reconnect: GatewayEventHandler = async (gateway: Gateway, d: any) => { | ||||||
|     gateway.reconnect() |     gateway.reconnect() | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import { User } from "../../structures/user.ts" | import { User } from '../../structures/user.ts' | ||||||
| import { CLIENT_USER } from "../../types/endpoint.ts" | import { CLIENT_USER } from '../../types/endpoint.ts' | ||||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | import { Gateway, GatewayEventHandler } from '../index.ts' | ||||||
| 
 | 
 | ||||||
| export const resume: GatewayEventHandler = async (gateway: Gateway, d: any) => { | export const resume: GatewayEventHandler = async (gateway: Gateway, d: any) => { | ||||||
|  |  | ||||||
|  | @ -1,9 +1,11 @@ | ||||||
| import { Client } from "../models/client.ts"; | import { Client } from '../models/client.ts' | ||||||
| import { Collection } from "../utils/collection.ts"; | import { Collection } from '../utils/collection.ts' | ||||||
| 
 | 
 | ||||||
| export class BaseManager<T, T2> { | export class BaseManager<T, T2> { | ||||||
|   client: Client |   client: Client | ||||||
|  |   /** Cache Name or Key used to differentiate caches */ | ||||||
|   cacheName: string |   cacheName: string | ||||||
|  |   /** Which data type does this cache have */ | ||||||
|   DataType: any |   DataType: any | ||||||
| 
 | 
 | ||||||
|   constructor (client: Client, cacheName: string, DataType: any) { |   constructor (client: Client, cacheName: string, DataType: any) { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { Client } from "../models/client.ts"; | import { Client } from '../models/client.ts' | ||||||
| import { Collection } from "../utils/collection.ts"; | import { Collection } from '../utils/collection.ts' | ||||||
| import { BaseManager } from "./base.ts"; | import { BaseManager } from './base.ts' | ||||||
| 
 | 
 | ||||||
| export class BaseChildManager<T, T2> { | export class BaseChildManager<T, T2> { | ||||||
|   client: Client |   client: Client | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| import { Client } from "../models/client.ts"; | import { Client } from '../models/client.ts' | ||||||
| import { Channel } from "../structures/channel.ts"; | import { Channel } from '../structures/channel.ts' | ||||||
| import { ChannelPayload } from "../types/channel.ts"; | import { ChannelPayload } from '../types/channel.ts' | ||||||
| import { CHANNEL } from "../types/endpoint.ts"; | import { CHANNEL } from '../types/endpoint.ts' | ||||||
| import getChannelByType from "../utils/getChannelByType.ts"; | import getChannelByType from '../utils/getChannelByType.ts' | ||||||
| import { BaseManager } from "./base.ts"; | import { BaseManager } from './base.ts' | ||||||
| 
 | 
 | ||||||
| export class ChannelsManager extends BaseManager<ChannelPayload, Channel> { | export class ChannelsManager extends BaseManager<ChannelPayload, Channel> { | ||||||
|   constructor(client: Client) { |   constructor(client: Client) { | ||||||
|  |  | ||||||
|  | @ -1,24 +1,32 @@ | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { Emoji } from '../structures/emoji.ts' | import { Emoji } from '../structures/emoji.ts' | ||||||
| import { Guild } from '../structures/guild.ts' |  | ||||||
| import { EmojiPayload } from '../types/emoji.ts' | import { EmojiPayload } from '../types/emoji.ts' | ||||||
| import { GUILD_EMOJI } from '../types/endpoint.ts' | import { GUILD_EMOJI } from '../types/endpoint.ts' | ||||||
| import { BaseManager } from './base.ts' | import { BaseManager } from './base.ts' | ||||||
| 
 | 
 | ||||||
| export class EmojisManager extends BaseManager<EmojiPayload, Emoji> { | export class EmojisManager extends BaseManager<EmojiPayload, Emoji> { | ||||||
|   guild: Guild |  | ||||||
| 
 | 
 | ||||||
|   constructor (client: Client, guild: Guild) { |   constructor (client: Client) { | ||||||
|     super(client, `emojis:${guild.id}`, Emoji) |     super(client, `emojis`, Emoji) | ||||||
|     this.guild = guild |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async fetch (id: string): Promise<Emoji> { |   async get (key: string): Promise<Emoji | undefined> { | ||||||
|  |     const raw = await this._get(key) | ||||||
|  |     if (raw === undefined) return | ||||||
|  |     const emoji = new this.DataType(this.client, raw) | ||||||
|  |     if ((raw as any).guild_id !== undefined) { | ||||||
|  |       const guild = await this.client.guilds.get((raw as any).guild_id) | ||||||
|  |       if (guild !== undefined) emoji.guild = guild | ||||||
|  |     } | ||||||
|  |     return emoji | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async fetch (guildID: string, id: string): Promise<Emoji> { | ||||||
|     return await new Promise((resolve, reject) => { |     return await new Promise((resolve, reject) => { | ||||||
|       this.client.rest |       this.client.rest | ||||||
|         .get(GUILD_EMOJI(this.guild.id, id)) |         .get(GUILD_EMOJI(guildID, id)) | ||||||
|         .then(data => { |         .then(async data => { | ||||||
|           this.set(id, data as EmojiPayload) |           await this.set(id, data as EmojiPayload) | ||||||
|           resolve(new Emoji(this.client, data as EmojiPayload)) |           resolve(new Emoji(this.client, data as EmojiPayload)) | ||||||
|         }) |         }) | ||||||
|         .catch(e => reject(e)) |         .catch(e => reject(e)) | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import { | ||||||
|   GuildTextChannelPayload, |   GuildTextChannelPayload, | ||||||
|   GuildVoiceChannelPayload |   GuildVoiceChannelPayload | ||||||
| } from '../types/channel.ts' | } from '../types/channel.ts' | ||||||
|  | import { CHANNEL } from '../types/endpoint.ts' | ||||||
| import { BaseChildManager } from './baseChild.ts' | import { BaseChildManager } from './baseChild.ts' | ||||||
| import { ChannelsManager } from './channels.ts' | import { ChannelsManager } from './channels.ts' | ||||||
| 
 | 
 | ||||||
|  | @ -34,6 +35,10 @@ export class GuildChannelsManager extends BaseChildManager< | ||||||
|     if (res !== undefined && res.guild.id === this.guild.id) return res |     if (res !== undefined && res.guild.id === this.guild.id) return res | ||||||
|     else return undefined |     else return undefined | ||||||
|   } |   } | ||||||
|  |    | ||||||
|  |   async delete(id: string): Promise<boolean> { | ||||||
|  |     return this.client.rest.delete(CHANNEL(id)) | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   async array (): Promise<GuildChannel[]> { |   async array (): Promise<GuildChannel[]> { | ||||||
|     const arr = (await this.parent.array()) as Channel[] |     const arr = (await this.parent.array()) as Channel[] | ||||||
|  |  | ||||||
							
								
								
									
										91
									
								
								src/managers/guildEmojis.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/managers/guildEmojis.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,91 @@ | ||||||
|  | import { Client } from '../models/client.ts' | ||||||
|  | import { Emoji } from '../structures/emoji.ts' | ||||||
|  | import { Guild } from '../structures/guild.ts' | ||||||
|  | import { Role } from '../structures/role.ts' | ||||||
|  | import { EmojiPayload } from '../types/emoji.ts' | ||||||
|  | import { CHANNEL, GUILD_EMOJI, GUILD_EMOJIS } from '../types/endpoint.ts' | ||||||
|  | import { BaseChildManager } from './baseChild.ts' | ||||||
|  | import { EmojisManager } from './emojis.ts' | ||||||
|  | import { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts' | ||||||
|  | 
 | ||||||
|  | export class GuildEmojisManager extends BaseChildManager< | ||||||
|  |   EmojiPayload, | ||||||
|  |   Emoji | ||||||
|  |   > { | ||||||
|  |   guild: Guild | ||||||
|  | 
 | ||||||
|  |   constructor(client: Client, parent: EmojisManager, guild: Guild) { | ||||||
|  |     super(client, parent as any) | ||||||
|  |     this.guild = guild | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async get(id: string): Promise<Emoji | undefined> { | ||||||
|  |     const res = await this.parent.get(id) | ||||||
|  |     if (res !== undefined && res.guild?.id === this.guild.id) return res | ||||||
|  |     else return undefined | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async delete(id: string): Promise<boolean> { | ||||||
|  |     return this.client.rest.delete(CHANNEL(id)) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async fetch(id: string): Promise<Emoji | undefined> { | ||||||
|  |     return await new Promise((resolve, reject) => { | ||||||
|  |       this.client.rest | ||||||
|  |         .get(GUILD_EMOJI(this.guild.id, id)) | ||||||
|  |         .then(async data => { | ||||||
|  |           const emoji = new Emoji(this.client, data as EmojiPayload) | ||||||
|  |           data.guild_id = this.guild.id | ||||||
|  |           await this.set(id, data as EmojiPayload) | ||||||
|  |           emoji.guild = this.guild | ||||||
|  |           resolve(emoji) | ||||||
|  |         }) | ||||||
|  |         .catch(e => reject(e)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async create(name: string, url: string, roles?: Role[] | string[] | string): Promise<Emoji | undefined> { | ||||||
|  |     let data = url | ||||||
|  |     if (!data.startsWith("data:")) { | ||||||
|  |       data = await fetchAuto(url) | ||||||
|  |     } | ||||||
|  |     return await new Promise((resolve, reject) => { | ||||||
|  |       let roleIDs: string[] = [] | ||||||
|  |       if (roles !== undefined && typeof roles === "string") roleIDs = [roles] | ||||||
|  |       else if (roles !== undefined) { | ||||||
|  |         if (roles?.length === 0) reject(new Error("Empty Roles array was provided")) | ||||||
|  |         if (roles[0] instanceof Role) roleIDs = (roles as any).map((r: Role) => r.id) | ||||||
|  |         else roleIDs = roles as string[] | ||||||
|  |       } else roles = [this.guild.id] | ||||||
|  |       this.client.rest | ||||||
|  |         .post(GUILD_EMOJIS(this.guild.id), { | ||||||
|  |           name, | ||||||
|  |           image: data, | ||||||
|  |           roles: roleIDs | ||||||
|  |         }) | ||||||
|  |         .then(async data => { | ||||||
|  |           const emoji = new Emoji(this.client, data as EmojiPayload) | ||||||
|  |           data.guild_id = this.guild.id | ||||||
|  |           await this.set(data.id, data as EmojiPayload) | ||||||
|  |           emoji.guild = this.guild | ||||||
|  |           resolve(emoji) | ||||||
|  |         }) | ||||||
|  |         .catch(e => reject(e)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async array(): Promise<Emoji[]> { | ||||||
|  |     const arr = (await this.parent.array()) as Emoji[] | ||||||
|  |     return arr.filter( | ||||||
|  |       (c: any) => c.guild !== undefined && c.guild.id === this.guild.id | ||||||
|  |     ) as any | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async flush(): Promise<boolean> { | ||||||
|  |     const arr = await this.array() | ||||||
|  |     for (const elem of arr) { | ||||||
|  |       this.parent.delete(elem.id) | ||||||
|  |     } | ||||||
|  |     return true | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -1,9 +1,10 @@ | ||||||
| import { Client } from "../models/client.ts"; | import { User } from '../structures/user.ts' | ||||||
| import { Guild } from "../structures/guild.ts"; | import { Client } from '../models/client.ts' | ||||||
| import { Member } from "../structures/member.ts"; | import { Guild } from '../structures/guild.ts' | ||||||
| import { GUILD_MEMBER } from "../types/endpoint.ts"; | import { Member } from '../structures/member.ts' | ||||||
| import { MemberPayload } from "../types/guild.ts"; | import { GUILD_MEMBER } from '../types/endpoint.ts' | ||||||
| import { BaseManager } from "./base.ts"; | import { MemberPayload } from '../types/guild.ts' | ||||||
|  | import { BaseManager } from './base.ts' | ||||||
| 
 | 
 | ||||||
| export class MembersManager extends BaseManager<MemberPayload, Member> { | export class MembersManager extends BaseManager<MemberPayload, Member> { | ||||||
|   guild: Guild |   guild: Guild | ||||||
|  | @ -13,11 +14,29 @@ export class MembersManager extends BaseManager<MemberPayload, Member> { | ||||||
|     this.guild = guild |     this.guild = guild | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async get (key: string): Promise<Member | undefined> { | ||||||
|  |     const raw = await this._get(key) | ||||||
|  |     if (raw === undefined) return | ||||||
|  |     const user = new User(this.client, raw.user) | ||||||
|  |     const res = new this.DataType(this.client, raw, user) | ||||||
|  |     for (const roleid of res.roleIDs as string[]) { | ||||||
|  |       const role = await this.guild.roles.get(roleid) | ||||||
|  |       if (role !== undefined) res.roles.push(role) | ||||||
|  |     } | ||||||
|  |     return res | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async fetch(id: string): Promise<Member> { |   async fetch(id: string): Promise<Member> { | ||||||
|     return await new Promise((resolve, reject) => { |     return await new Promise((resolve, reject) => { | ||||||
|       this.client.rest.get(GUILD_MEMBER(this.guild.id, id)).then(data => { |       this.client.rest.get(GUILD_MEMBER(this.guild.id, id)).then(async data => { | ||||||
|         this.set(id, data as MemberPayload) |         await this.set(id, data as MemberPayload) | ||||||
|         resolve(new Member(this.client, data as MemberPayload)) |         const user: User = new User(this.client, data.user) | ||||||
|  |         const res = new Member(this.client, data as MemberPayload, user) | ||||||
|  |         for (const roleid of res.roleIDs as string[]) { | ||||||
|  |           const role = await this.guild.roles.get(roleid) | ||||||
|  |           if (role !== undefined) res.roles.push(role) | ||||||
|  |         } | ||||||
|  |         resolve(res) | ||||||
|       }).catch(e => reject(e)) |       }).catch(e => reject(e)) | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -1,48 +0,0 @@ | ||||||
| let caches: any = {} |  | ||||||
| 
 |  | ||||||
| const get = (cacheName: string, key: string): any => { |  | ||||||
|   const gotCache: Map<string, any> = caches[cacheName] |  | ||||||
|   if (gotCache === undefined || !(gotCache instanceof Map)) { |  | ||||||
|     return undefined |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   const gotMap = gotCache.get(key) |  | ||||||
|   return gotMap |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const set = (cacheName: string, key: string, value: any): any => { |  | ||||||
|   let gotCache: Map<string, any> = caches[cacheName] |  | ||||||
|   if (gotCache === undefined || !(gotCache instanceof Map)) { |  | ||||||
|     gotCache = caches[cacheName] = new Map<string, any>() |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   gotCache.set(key, value) |  | ||||||
| 
 |  | ||||||
|   return value |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const del = (cacheName: string, key: string): boolean | undefined => { |  | ||||||
|   const gotCache: Map<string, any> = caches[cacheName] |  | ||||||
|   if (gotCache === undefined || !(gotCache instanceof Map)) { |  | ||||||
|     return |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   return gotCache.delete(key) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const deleteCache = (cacheName: string): void => { |  | ||||||
|   const gotCache = caches[cacheName] |  | ||||||
|   if (gotCache === undefined) { |  | ||||||
|     return |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
 |  | ||||||
|   delete caches[cacheName] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const resetCaches = (): void => { |  | ||||||
|   caches = {} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export default { get, set, del, deleteCache, resetCaches } |  | ||||||
| export { get, set, del, deleteCache, resetCaches } |  | ||||||
|  | @ -13,15 +13,23 @@ import { | ||||||
|   ClientActivity, |   ClientActivity, | ||||||
|   ClientPresence |   ClientPresence | ||||||
| } from '../structures/presence.ts' | } from '../structures/presence.ts' | ||||||
|  | import { EmojisManager } from '../managers/emojis.ts' | ||||||
| 
 | 
 | ||||||
| /** Some Client Options to modify behaviour */ | /** Some Client Options to modify behaviour */ | ||||||
| export interface ClientOptions { | export interface ClientOptions { | ||||||
|  |   /** Token of the Bot/User */ | ||||||
|   token?: string |   token?: string | ||||||
|  |   /** Gateway Intents */ | ||||||
|   intents?: GatewayIntents[] |   intents?: GatewayIntents[] | ||||||
|   cache?: ICacheAdapter |   /** Cache Adapter to use, defaults to Collections one */ | ||||||
|   forceNewSession?: boolean |   cache?: ICacheAdapter, | ||||||
|  |   /** Force New Session and don't use cached Session (by persistent caching) */ | ||||||
|  |   forceNewSession?: boolean, | ||||||
|  |   /** Startup presence of client */ | ||||||
|   presence?: ClientPresence | ClientActivity | ActivityGame |   presence?: ClientPresence | ClientActivity | ActivityGame | ||||||
|  |   /** Whether it's a bot user or not? Use this if selfbot! */ | ||||||
|   bot?: boolean |   bot?: boolean | ||||||
|  |   /** Force all requests to Canary API */ | ||||||
|   canary?: boolean |   canary?: boolean | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -29,21 +37,34 @@ export interface ClientOptions { | ||||||
|  * Discord Client. |  * Discord Client. | ||||||
|  */ |  */ | ||||||
| export class Client extends EventEmitter { | export class Client extends EventEmitter { | ||||||
|  |   /** Gateway object */ | ||||||
|   gateway?: Gateway |   gateway?: Gateway | ||||||
|  |   /** REST Manager - used to make all requests */ | ||||||
|   rest: RESTManager = new RESTManager(this) |   rest: RESTManager = new RESTManager(this) | ||||||
|  |   /** User which Client logs in to, undefined until logs in */ | ||||||
|   user?: User |   user?: User | ||||||
|  |   /** WebSocket ping of Client */ | ||||||
|   ping = 0 |   ping = 0 | ||||||
|  |   /** Token of the Bot/User */ | ||||||
|   token?: string |   token?: string | ||||||
|  |   /** Cache Adapter */ | ||||||
|   cache: ICacheAdapter = new DefaultCacheAdapter() |   cache: ICacheAdapter = new DefaultCacheAdapter() | ||||||
|  |   /** Gateway Intents */ | ||||||
|   intents?: GatewayIntents[] |   intents?: GatewayIntents[] | ||||||
|  |   /** Whether to force new session or not */ | ||||||
|   forceNewSession?: boolean |   forceNewSession?: boolean | ||||||
|  | 
 | ||||||
|   users: UserManager = new UserManager(this) |   users: UserManager = new UserManager(this) | ||||||
|   guilds: GuildManager = new GuildManager(this) |   guilds: GuildManager = new GuildManager(this) | ||||||
|   channels: ChannelsManager = new ChannelsManager(this) |   channels: ChannelsManager = new ChannelsManager(this) | ||||||
|   messages: MessagesManager = new MessagesManager(this) |   messages: MessagesManager = new MessagesManager(this) | ||||||
|  |   emojis: EmojisManager = new EmojisManager(this) | ||||||
|  |    | ||||||
|  |   /** Whether this client will login as bot user or not */ | ||||||
|   bot: boolean = true |   bot: boolean = true | ||||||
|  |   /** Whether the REST Manager will use Canary API or not */ | ||||||
|   canary: boolean = false |   canary: boolean = false | ||||||
| 
 |   /** Client's presence. Startup one if set before connecting */ | ||||||
|   presence: ClientPresence = new ClientPresence() |   presence: ClientPresence = new ClientPresence() | ||||||
| 
 | 
 | ||||||
|   constructor (options: ClientOptions = {}) { |   constructor (options: ClientOptions = {}) { | ||||||
|  | @ -61,11 +82,13 @@ export class Client extends EventEmitter { | ||||||
|     if (options.canary === true) this.canary = true |     if (options.canary === true) this.canary = true | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** Set Cache Adapter */ | ||||||
|   setAdapter (adapter: ICacheAdapter): Client { |   setAdapter (adapter: ICacheAdapter): Client { | ||||||
|     this.cache = adapter |     this.cache = adapter | ||||||
|     return this |     return this | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** Change Presence of Client */ | ||||||
|   setPresence (presence: ClientPresence | ClientActivity | ActivityGame): void { |   setPresence (presence: ClientPresence | ClientActivity | ActivityGame): void { | ||||||
|     if (presence instanceof ClientPresence) { |     if (presence instanceof ClientPresence) { | ||||||
|       this.presence = presence |       this.presence = presence | ||||||
|  | @ -73,6 +96,7 @@ export class Client extends EventEmitter { | ||||||
|     this.gateway?.sendPresence(this.presence.create()) |     this.gateway?.sendPresence(this.presence.create()) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** Emit debug event */ | ||||||
|   debug (tag: string, msg: string): void { |   debug (tag: string, msg: string): void { | ||||||
|     this.emit('debug', `[${tag}] ${msg}`) |     this.emit('debug', `[${tag}] ${msg}`) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -2,6 +2,9 @@ import { delay } from '../utils/index.ts' | ||||||
| import * as baseEndpoints from '../consts/urlsAndVersions.ts' | import * as baseEndpoints from '../consts/urlsAndVersions.ts' | ||||||
| import { Client } from './client.ts' | import { Client } from './client.ts' | ||||||
| import { getBuildInfo } from '../utils/buildInfo.ts' | import { getBuildInfo } from '../utils/buildInfo.ts' | ||||||
|  | import { Collection } from '../utils/collection.ts' | ||||||
|  | 
 | ||||||
|  | export type RequestMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' | ||||||
| 
 | 
 | ||||||
| export enum HttpResponseCode { | export enum HttpResponseCode { | ||||||
|   Ok = 200, |   Ok = 200, | ||||||
|  | @ -17,122 +20,102 @@ export enum HttpResponseCode { | ||||||
|   GatewayUnavailable = 502 |   GatewayUnavailable = 502 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export type RequestMethods = | export interface RequestHeaders { | ||||||
|   | 'get' |   [name: string]: string | ||||||
|   | 'post' | } | ||||||
|   | 'put' |  | ||||||
|   | 'patch' |  | ||||||
|   | 'head' |  | ||||||
|   | 'delete' |  | ||||||
| 
 | 
 | ||||||
| export interface QueuedRequest { | export interface QueuedItem { | ||||||
|   callback: () => Promise< |   onComplete: () => Promise<{ | ||||||
|     | { |     rateLimited: any | ||||||
|         rateLimited: any |     bucket?: string | null | ||||||
|         beforeFetch: boolean |     before: boolean | ||||||
|         bucketID?: string | null |   } | undefined> | ||||||
|       } |   bucket?: string | null | ||||||
|     | undefined |  | ||||||
|   > |  | ||||||
|   bucketID?: string | null |  | ||||||
|   url: string |   url: string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface RateLimitedPath { | export interface RateLimit { | ||||||
|   url: string |   url: string | ||||||
|   resetTimestamp: number |   resetAt: number | ||||||
|   bucketID: string | null |   bucket: string | null | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class RESTManager { | export class RESTManager { | ||||||
|   client: Client |   client: Client | ||||||
|   globallyRateLimited: boolean = false |   queues: { [key: string]: QueuedItem[] } = {} | ||||||
|   queueInProcess: boolean = false |   rateLimits = new Collection<string, RateLimit>() | ||||||
|   pathQueues: { [key: string]: QueuedRequest[] } = {} |   globalRateLimit: boolean = false | ||||||
|   ratelimitedPaths = new Map<string, RateLimitedPath>() |   processing: boolean = false | ||||||
| 
 | 
 | ||||||
|   constructor (client: Client) { |   constructor(client: Client) { | ||||||
|     this.client = client |     this.client = client | ||||||
|     setTimeout(() => this.processRateLimitedPaths, 1000) |     // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||||
|  |     this.handleRateLimits() | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async processRateLimitedPaths (): Promise<void> { |   async checkQueues(): Promise<void> { | ||||||
|     const now = Date.now() |     Object.entries(this.queues).forEach(([key, value]) => { | ||||||
|     this.ratelimitedPaths.forEach((value, key) => { |       if (value.length === 0) { | ||||||
|       if (value.resetTimestamp > now) return |         // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
 | ||||||
|       this.ratelimitedPaths.delete(key) |         delete this.queues[key] | ||||||
|       if (key === 'global') this.globallyRateLimited = false |       } | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   addToQueue (request: QueuedRequest): void { |   queue(request: QueuedItem): void { | ||||||
|     const route = request.url.substring( |     const route = request.url.substring( | ||||||
|       // eslint seriously?
 |       // eslint seriously?
 | ||||||
|       // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
 |       // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
 | ||||||
|       baseEndpoints.DISCORD_API_URL.length + 1 |       baseEndpoints.DISCORD_API_URL.length + 1 | ||||||
|     ) |     ) | ||||||
|     const parts = route.split('/') |     const parts = route.split('/') | ||||||
|     // Remove the major param
 |  | ||||||
|     parts.shift() |     parts.shift() | ||||||
|     const [id] = parts |     const [id] = parts | ||||||
| 
 | 
 | ||||||
|     if (this.pathQueues[id] !== undefined) { |     if (this.queues[id] !== undefined) { | ||||||
|       this.pathQueues[id].push(request) |       this.queues[id].push(request) | ||||||
|     } else { |     } else { | ||||||
|       this.pathQueues[id] = [request] |       this.queues[id] = [request] | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async cleanupQueues (): Promise<void> { |   async processQueue(): Promise<void> { | ||||||
|     Object.entries(this.pathQueues).forEach(([key, value]) => { |     if (Object.keys(this.queues).length !== 0 && !this.globalRateLimit) { | ||||||
|       if (value.length === 0) { |  | ||||||
|         // Remove it entirely
 |  | ||||||
|         // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
 |  | ||||||
|         delete this.pathQueues[key] |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async processQueue (): Promise<void> { |  | ||||||
|     if ( |  | ||||||
|       Object.keys(this.pathQueues).length !== 0 && |  | ||||||
|       !this.globallyRateLimited |  | ||||||
|     ) { |  | ||||||
|       await Promise.allSettled( |       await Promise.allSettled( | ||||||
|         Object.values(this.pathQueues).map(async pathQueue => { |         Object.values(this.queues).map(async pathQueue => { | ||||||
|           const request = pathQueue.shift() |           const request = pathQueue.shift() | ||||||
|           if (request === undefined) return |           if (request === undefined) return | ||||||
| 
 | 
 | ||||||
|           const rateLimitedURLResetIn = await this.checkRatelimits(request.url) |           const rateLimitedURLResetIn = await this.isRateLimited(request.url) | ||||||
| 
 | 
 | ||||||
|           if (typeof request.bucketID === 'string') { |           if (typeof request.bucket === 'string') { | ||||||
|             const rateLimitResetIn = await this.checkRatelimits( |             const rateLimitResetIn = await this.isRateLimited( | ||||||
|               request.bucketID |               request.bucket | ||||||
|             ) |             ) | ||||||
|             if (rateLimitResetIn !== false) { |             if (rateLimitResetIn !== false) { | ||||||
|               // This request is still rate limited read to queue
 |               // This request is still rate limited read to queue
 | ||||||
|               this.addToQueue(request) |               this.queue(request) | ||||||
|             } else { |             } else { | ||||||
|               // This request is not rate limited so it should be run
 |               // This request is not rate limited so it should be run
 | ||||||
|               const result = await request.callback() |               const result = await request.onComplete() | ||||||
|               if (result?.rateLimited !== undefined) { |               if (result?.rateLimited !== undefined) { | ||||||
|                 this.addToQueue({ |                 this.queue({ | ||||||
|                   ...request, |                   ...request, | ||||||
|                   bucketID: result.bucketID ?? request.bucketID |                   bucket: result.bucket ?? request.bucket | ||||||
|                 }) |                 }) | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|           } else { |           } else { | ||||||
|             if (rateLimitedURLResetIn !== false) { |             if (rateLimitedURLResetIn !== false) { | ||||||
|               // This URL is rate limited readd to queue
 |               // This URL is rate limited readd to queue
 | ||||||
|               this.addToQueue(request) |               this.queue(request) | ||||||
|             } else { |             } else { | ||||||
|               // This request has no bucket id so it should be processed
 |               // This request has no bucket id so it should be processed
 | ||||||
|               const result = await request.callback() |               const result = await request.onComplete() | ||||||
|               if (result?.rateLimited !== undefined) { |               if (result?.rateLimited !== undefined) { | ||||||
|                 this.addToQueue({ |                 this.queue({ | ||||||
|                   ...request, |                   ...request, | ||||||
|                   bucketID: result.bucketID ?? request.bucketID |                   bucket: result.bucket ?? request.bucket | ||||||
|                 }) |                 }) | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|  | @ -141,27 +124,28 @@ export class RESTManager { | ||||||
|       ) |       ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (Object.keys(this.pathQueues).length !== 0) { |     if (Object.keys(this.queues).length !== 0) { | ||||||
|       await delay(1000) |       await delay(1000) | ||||||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 |       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||||
|       this.processQueue() |       this.processQueue() | ||||||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 |       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||||
|       this.cleanupQueues() |       this.checkQueues() | ||||||
|     } else this.queueInProcess = false |     } else this.processing = false | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   createRequestBody ( |   prepare( | ||||||
|     body: any, |     body: any, | ||||||
|     method: RequestMethods |     method: RequestMethods | ||||||
|   ): { [key: string]: any } { |   ): { [key: string]: any } { | ||||||
|     const headers: { [key: string]: string } = { | 
 | ||||||
|       Authorization: `Bot ${this.client.token}`, |     const headers: RequestHeaders = { | ||||||
|       'User-Agent': `DiscordBot (harmony)` |       'Authorization': `Bot ${this.client.token}`, | ||||||
|  |       'User-Agent': `DiscordBot (harmony, https://github.com/harmony-org/harmony)` | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (this.client.token === undefined) delete headers.Authorization |     if (this.client.token === undefined) delete headers.Authorization | ||||||
| 
 | 
 | ||||||
|     if (method === 'get') body = undefined |     if (method === 'get' || method === 'head' || method === 'delete') body = undefined | ||||||
| 
 | 
 | ||||||
|     if (body?.reason !== undefined) { |     if (body?.reason !== undefined) { | ||||||
|       headers['X-Audit-Log-Reason'] = encodeURIComponent(body.reason) |       headers['X-Audit-Log-Reason'] = encodeURIComponent(body.reason) | ||||||
|  | @ -203,53 +187,125 @@ export class RESTManager { | ||||||
|     return data |     return data | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async checkRatelimits (url: string): Promise<number | false> { |   async isRateLimited(url: string): Promise<number | false> { | ||||||
|     const ratelimited = this.ratelimitedPaths.get(url) |     const global = this.rateLimits.get('global') | ||||||
|     const global = this.ratelimitedPaths.get('global') |     const rateLimited = this.rateLimits.get(url) | ||||||
|     const now = Date.now() |     const now = Date.now() | ||||||
| 
 | 
 | ||||||
|     if (ratelimited !== undefined && now < ratelimited.resetTimestamp) { |     if (rateLimited !== undefined && now < rateLimited.resetAt) { | ||||||
|       return ratelimited.resetTimestamp - now |       return rateLimited.resetAt - now | ||||||
|     } |     } | ||||||
|     if (global !== undefined && now < global.resetTimestamp) { |     if (global !== undefined && now < global.resetAt) { | ||||||
|       return global.resetTimestamp - now |       return global.resetAt - now | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return false |     return false | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async runMethod ( |   processHeaders(url: string, headers: Headers): string | null | undefined { | ||||||
|  |     let rateLimited = false | ||||||
|  | 
 | ||||||
|  |     const global = headers.get('x-ratelimit-global') | ||||||
|  |     const bucket = headers.get('x-ratelimit-bucket') | ||||||
|  |     const remaining = headers.get('x-ratelimit-remaining') | ||||||
|  |     const resetAt = headers.get('x-ratelimit-reset') | ||||||
|  |     const retryAfter = headers.get('retry-after') | ||||||
|  | 
 | ||||||
|  |     if (remaining !== null && remaining === '0') { | ||||||
|  |       rateLimited = true | ||||||
|  | 
 | ||||||
|  |       this.rateLimits.set(url, { | ||||||
|  |         url, | ||||||
|  |         resetAt: Number(resetAt) * 1000, | ||||||
|  |         bucket | ||||||
|  |       }) | ||||||
|  | 
 | ||||||
|  |       if (bucket !== null) { | ||||||
|  |         this.rateLimits.set(bucket, { | ||||||
|  |           url, | ||||||
|  |           resetAt: Number(resetAt) * 1000, | ||||||
|  |           bucket | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (global !== null) { | ||||||
|  |       const reset = Date.now() + Number(retryAfter) | ||||||
|  |       this.globalRateLimit = true | ||||||
|  |       rateLimited = true | ||||||
|  | 
 | ||||||
|  |       this.rateLimits.set('global', { | ||||||
|  |         url: 'global', | ||||||
|  |         resetAt: reset, | ||||||
|  |         bucket | ||||||
|  |       }) | ||||||
|  | 
 | ||||||
|  |       if (bucket !== null) { | ||||||
|  |         this.rateLimits.set(bucket, { | ||||||
|  |           url: 'global', | ||||||
|  |           resetAt: reset, | ||||||
|  |           bucket | ||||||
|  |         }) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return rateLimited ? bucket : undefined | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async handleStatusCode( | ||||||
|  |     response: Response | ||||||
|  |   ): Promise<undefined> { | ||||||
|  |     const status = response.status | ||||||
|  | 
 | ||||||
|  |     if ((status >= 200 && status < 400) || status === HttpResponseCode.TooManyRequests) return | ||||||
|  | 
 | ||||||
|  |     const body = await response.json() | ||||||
|  |     const text = Deno.inspect(body.errors) | ||||||
|  | 
 | ||||||
|  |     if (status === HttpResponseCode.Unauthorized) | ||||||
|  |       throw new Error(`Request was not successful (Unauthorized). Invalid Token.\n${text}`) | ||||||
|  | 
 | ||||||
|  |     if ([ | ||||||
|  |       HttpResponseCode.BadRequest, | ||||||
|  |       HttpResponseCode.NotFound, | ||||||
|  |       HttpResponseCode.Forbidden, | ||||||
|  |       HttpResponseCode.MethodNotAllowed | ||||||
|  |     ].includes(status)) { | ||||||
|  |       throw new Error(`Request - Client Error. Code: ${status}\n${text}`) | ||||||
|  |     } else if (status === HttpResponseCode.GatewayUnavailable) { | ||||||
|  |       throw new Error(`Request - Server Error. Code: ${status}\n${text}`) | ||||||
|  |     } else throw new Error('Request - Unknown Error') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async make( | ||||||
|     method: RequestMethods, |     method: RequestMethods, | ||||||
|     url: string, |     url: string, | ||||||
|     body?: unknown, |     body?: unknown, | ||||||
|     retryCount = 0, |     maxRetries = 0, | ||||||
|     bucketID?: string | null |     bucket?: string | null | ||||||
|   ): Promise<any> { |   ): Promise<any> { | ||||||
|     const errorStack = new Error('Location In Your Files:') |  | ||||||
|     Error.captureStackTrace(errorStack) |  | ||||||
| 
 |  | ||||||
|     return await new Promise((resolve, reject) => { |     return await new Promise((resolve, reject) => { | ||||||
|       const callback = async (): Promise<undefined | any> => { |       const onComplete = async (): Promise<undefined | any> => { | ||||||
|         try { |         try { | ||||||
|           const rateLimitResetIn = await this.checkRatelimits(url) |           const rateLimitResetIn = await this.isRateLimited(url) | ||||||
|           if (rateLimitResetIn !== false) { |           if (rateLimitResetIn !== false) { | ||||||
|             return { |             return { | ||||||
|               rateLimited: rateLimitResetIn, |               rateLimited: rateLimitResetIn, | ||||||
|               beforeFetch: true, |               before: true, | ||||||
|               bucketID |               bucket | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           const query = |           const query = | ||||||
|             method === 'get' && body !== undefined |             method === 'get' && body !== undefined | ||||||
|               ? Object.entries(body as any) |               ? Object.entries(body as any) | ||||||
|                   .map( |                 .map( | ||||||
|                     ([key, value]) => |                   ([key, value]) => | ||||||
|                       `${encodeURIComponent(key)}=${encodeURIComponent( |                     `${encodeURIComponent(key)}=${encodeURIComponent( | ||||||
|                         value as any |                       value as any | ||||||
|                       )}` |                     )}` | ||||||
|                   ) |                 ) | ||||||
|                   .join('&') |                 .join('&') | ||||||
|               : '' |               : '' | ||||||
|           let urlToUse = |           let urlToUse = | ||||||
|             method === 'get' && query !== '' ? `${url}?${query}` : url |             method === 'get' && query !== '' ? `${url}?${query}` : url | ||||||
|  | @ -259,13 +315,13 @@ export class RESTManager { | ||||||
|             urlToUse = split[0] + '//canary.' + split[1] |             urlToUse = split[0] + '//canary.' + split[1] | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           const requestData = this.createRequestBody(body, method) |           const requestData = this.prepare(body, method) | ||||||
| 
 | 
 | ||||||
|           const response = await fetch(urlToUse, requestData) |           const response = await fetch(urlToUse, requestData) | ||||||
|           const bucketIDFromHeaders = this.processHeaders(url, response.headers) |           const bucketFromHeaders = this.processHeaders(url, response.headers) | ||||||
|           this.handleStatusCode(response, errorStack) |           // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||||
|  |           this.handleStatusCode(response) | ||||||
| 
 | 
 | ||||||
|           // Sometimes Discord returns an empty 204 response that can't be made to JSON.
 |  | ||||||
|           if (response.status === 204) return resolve(undefined) |           if (response.status === 204) return resolve(undefined) | ||||||
| 
 | 
 | ||||||
|           const json = await response.json() |           const json = await response.json() | ||||||
|  | @ -273,14 +329,14 @@ export class RESTManager { | ||||||
|             json.retry_after !== undefined || |             json.retry_after !== undefined || | ||||||
|             json.message === 'You are being rate limited.' |             json.message === 'You are being rate limited.' | ||||||
|           ) { |           ) { | ||||||
|             if (retryCount > 10) { |             if (maxRetries > 10) { | ||||||
|               throw new Error('Max RateLimit Retries hit') |               throw new Error('Max RateLimit Retries hit') | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return { |             return { | ||||||
|               rateLimited: json.retry_after, |               rateLimited: json.retry_after, | ||||||
|               beforeFetch: false, |               before: false, | ||||||
|               bucketID: bucketIDFromHeaders |               bucket: bucketFromHeaders | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|           return resolve(json) |           return resolve(json) | ||||||
|  | @ -289,132 +345,45 @@ export class RESTManager { | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       this.addToQueue({ |       this.queue({ | ||||||
|         callback, |         onComplete, | ||||||
|         bucketID, |         bucket, | ||||||
|         url |         url | ||||||
|       }) |       }) | ||||||
|       if (!this.queueInProcess) { |       if (!this.processing) { | ||||||
|         this.queueInProcess = true |         this.processing = true | ||||||
|         // eslint-disable-next-line @typescript-eslint/no-floating-promises
 |         // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||||
|         this.processQueue() |         this.processQueue() | ||||||
|       } |       } | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async logErrors (response: Response, errorStack?: unknown): Promise<void> { |   async handleRateLimits(): Promise<void> { | ||||||
|     try { |     const now = Date.now() | ||||||
|       const error = await response.json() |     this.rateLimits.forEach((value, key) => { | ||||||
|       console.error(error) |       if (value.resetAt > now) return | ||||||
|     } catch { |       this.rateLimits.delete(key) | ||||||
|       console.error(response) |       if (key === 'global') this.globalRateLimit = false | ||||||
|     } |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleStatusCode ( |   async get(url: string, body?: unknown): Promise<any> { | ||||||
|     response: Response, |     return await this.make('get', url, body) | ||||||
|     errorStack?: unknown |  | ||||||
|   ): undefined | boolean { |  | ||||||
|     const status = response.status |  | ||||||
| 
 |  | ||||||
|     if ( |  | ||||||
|       (status >= 200 && status < 400) || |  | ||||||
|       status === HttpResponseCode.TooManyRequests |  | ||||||
|     ) { |  | ||||||
|       return true |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // eslint-disable-next-line @typescript-eslint/no-floating-promises
 |  | ||||||
|     this.logErrors(response, errorStack) |  | ||||||
| 
 |  | ||||||
|     if (status === HttpResponseCode.Unauthorized) |  | ||||||
|       throw new Error('Request was not successful. Invalid Token.') |  | ||||||
| 
 |  | ||||||
|     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): string | null | undefined { |   async post(url: string, body?: unknown): Promise<any> { | ||||||
|     let ratelimited = false |     return await this.make('post', url, body) | ||||||
| 
 |  | ||||||
|     // 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 !== null && remaining === '0') { |  | ||||||
|       ratelimited = true |  | ||||||
| 
 |  | ||||||
|       this.ratelimitedPaths.set(url, { |  | ||||||
|         url, |  | ||||||
|         resetTimestamp: Number(resetTimestamp) * 1000, |  | ||||||
|         bucketID |  | ||||||
|       }) |  | ||||||
| 
 |  | ||||||
|       if (bucketID !== null) { |  | ||||||
|         this.ratelimitedPaths.set(bucketID, { |  | ||||||
|           url, |  | ||||||
|           resetTimestamp: Number(resetTimestamp) * 1000, |  | ||||||
|           bucketID |  | ||||||
|         }) |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // If there is no remaining global limit, we save it in cache
 |  | ||||||
|     if (global !== null) { |  | ||||||
|       const reset = Date.now() + Number(retryAfter) |  | ||||||
|       this.globallyRateLimited = true |  | ||||||
|       ratelimited = true |  | ||||||
| 
 |  | ||||||
|       this.ratelimitedPaths.set('global', { |  | ||||||
|         url: 'global', |  | ||||||
|         resetTimestamp: reset, |  | ||||||
|         bucketID |  | ||||||
|       }) |  | ||||||
| 
 |  | ||||||
|       if (bucketID !== null) { |  | ||||||
|         this.ratelimitedPaths.set(bucketID, { |  | ||||||
|           url: 'global', |  | ||||||
|           resetTimestamp: reset, |  | ||||||
|           bucketID |  | ||||||
|         }) |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return ratelimited ? bucketID : undefined |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async get (url: string, body?: unknown): Promise<any> { |   async delete(url: string, body?: unknown): Promise<any> { | ||||||
|     return await this.runMethod('get', url, body) |     return await this.make('delete', url, body) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async post (url: string, body?: unknown): Promise<any> { |   async patch(url: string, body?: unknown): Promise<any> { | ||||||
|     return await this.runMethod('post', url, body) |     return await this.make('patch', url, body) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async delete (url: string, body?: unknown): Promise<any> { |   async put(url: string, body?: unknown): Promise<any> { | ||||||
|     return await this.runMethod('delete', url, body) |     return await this.make('put', url, body) | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async patch (url: string, body?: unknown): Promise<any> { |  | ||||||
|     return await this.runMethod('patch', url, body) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   async put (url: string, body?: unknown): Promise<any> { |  | ||||||
|     return await this.runMethod('put', url, body) |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import * as cache from '../models/cache.ts' |  | ||||||
| 
 | 
 | ||||||
| interface IInit { | interface IInit { | ||||||
|   useCache?: boolean |   useCache?: boolean | ||||||
|  | @ -25,18 +24,13 @@ export class Base { | ||||||
|     this.useCache = useCache |     this.useCache = useCache | ||||||
|     const cacheID = restURLfuncArgs.join(':') |     const cacheID = restURLfuncArgs.join(':') | ||||||
|     if (this.useCache !== undefined) { |     if (this.useCache !== undefined) { | ||||||
|       const cached = cache.get(this.cacheName ?? this.name, cacheID) |       const cached = await client.cache.get(this.cacheName ?? this.name, cacheID) | ||||||
|       if (cached !== undefined) { |       if (cached !== undefined) { | ||||||
|         return cached |         return cached | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const resp = await fetch(endpoint(...restURLfuncArgs), { |     const jsonParsed = await client.rest.get(endpoint(...restURLfuncArgs)) | ||||||
|       headers: { |  | ||||||
|         Authorization: `Bot ${client.token}` |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
|     const jsonParsed = await resp.json() |  | ||||||
| 
 | 
 | ||||||
|     return new this(client, jsonParsed) |     return new this(client, jsonParsed) | ||||||
|   } |   } | ||||||
|  | @ -47,12 +41,7 @@ export class Base { | ||||||
|   ): Promise<this> { |   ): Promise<this> { | ||||||
|     const oldOne = Object.assign(Object.create(this), this) |     const oldOne = Object.assign(Object.create(this), this) | ||||||
| 
 | 
 | ||||||
|     const resp = await fetch(endpoint(...restURLfuncArgs), { |     const jsonParsed = await client.rest.get(endpoint(...restURLfuncArgs)) | ||||||
|       headers: { |  | ||||||
|         Authorization: `Bot ${client.token}` |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
|     const jsonParsed = await resp.json() |  | ||||||
| 
 | 
 | ||||||
|     this.readFromData(jsonParsed) |     this.readFromData(jsonParsed) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ import { Client } from '../models/client.ts' | ||||||
| import { EmojiPayload } from '../types/emoji.ts' | import { EmojiPayload } from '../types/emoji.ts' | ||||||
| import { USER } from '../types/endpoint.ts' | import { USER } from '../types/endpoint.ts' | ||||||
| import { Base } from './base.ts' | import { Base } from './base.ts' | ||||||
|  | import { Guild } from './guild.ts' | ||||||
| import { User } from './user.ts' | import { User } from './user.ts' | ||||||
| 
 | 
 | ||||||
| export class Emoji extends Base { | export class Emoji extends Base { | ||||||
|  | @ -9,6 +10,7 @@ export class Emoji extends Base { | ||||||
|   name: string |   name: string | ||||||
|   roles?: string[] |   roles?: string[] | ||||||
|   user?: User |   user?: User | ||||||
|  |   guild?: Guild | ||||||
|   requireColons?: boolean |   requireColons?: boolean | ||||||
|   managed?: boolean |   managed?: boolean | ||||||
|   animated?: boolean |   animated?: boolean | ||||||
|  | @ -20,17 +22,16 @@ export class Emoji extends Base { | ||||||
|     } else return `<a:${this.name}:${this.id}>` |     } else return `<a:${this.name}:${this.id}>` | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   toString(): string { | ||||||
|  |     return this.getEmojiString | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   constructor (client: Client, data: EmojiPayload) { |   constructor (client: Client, data: EmojiPayload) { | ||||||
|     super(client, data) |     super(client, data) | ||||||
|     this.id = data.id |     this.id = data.id | ||||||
|     this.name = data.name |     this.name = data.name | ||||||
|  |     if (data.user !== undefined) this.user = new User(this.client, data.user) | ||||||
|     this.roles = data.roles |     this.roles = data.roles | ||||||
|     if (data.user !== undefined) { |  | ||||||
|       User.autoInit(this.client, { |  | ||||||
|         endpoint: USER, |  | ||||||
|         restURLfuncArgs: [data.user.id] |  | ||||||
|       }).then(user => (this.user = user)) |  | ||||||
|     } |  | ||||||
|     this.requireColons = data.require_colons |     this.requireColons = data.require_colons | ||||||
|     this.managed = data.managed |     this.managed = data.managed | ||||||
|     this.animated = data.animated |     this.animated = data.animated | ||||||
|  |  | ||||||
|  | @ -6,7 +6,8 @@ import { VoiceState } from './voiceState.ts' | ||||||
| import { RolesManager } from '../managers/roles.ts' | import { RolesManager } from '../managers/roles.ts' | ||||||
| import { GuildChannelsManager } from '../managers/guildChannels.ts' | import { GuildChannelsManager } from '../managers/guildChannels.ts' | ||||||
| import { MembersManager } from '../managers/members.ts' | import { MembersManager } from '../managers/members.ts' | ||||||
| import { EmojisManager } from '../managers/emojis.ts' | import { Role } from './role.ts' | ||||||
|  | import { GuildEmojisManager } from '../managers/guildEmojis.ts' | ||||||
| 
 | 
 | ||||||
| export class Guild extends Base { | export class Guild extends Base { | ||||||
|   id: string |   id: string | ||||||
|  | @ -27,7 +28,7 @@ export class Guild extends Base { | ||||||
|   defaultMessageNotifications?: string |   defaultMessageNotifications?: string | ||||||
|   explicitContentFilter?: string |   explicitContentFilter?: string | ||||||
|   roles: RolesManager |   roles: RolesManager | ||||||
|   emojis: EmojisManager |   emojis: GuildEmojisManager | ||||||
|   features?: GuildFeatures[] |   features?: GuildFeatures[] | ||||||
|   mfaLevel?: string |   mfaLevel?: string | ||||||
|   applicationID?: string |   applicationID?: string | ||||||
|  | @ -61,12 +62,12 @@ export class Guild extends Base { | ||||||
|     this.unavailable = data.unavailable |     this.unavailable = data.unavailable | ||||||
|     this.members = new MembersManager(this.client, this) |     this.members = new MembersManager(this.client, this) | ||||||
|     this.channels = new GuildChannelsManager( |     this.channels = new GuildChannelsManager( | ||||||
|       this.client, |       this.client,  | ||||||
|       this.client.channels, |       this.client.channels,  | ||||||
|       this |       this | ||||||
|     ) |     ) | ||||||
|     this.roles = new RolesManager(this.client, this) |     this.roles = new RolesManager(this.client, this) | ||||||
|     this.emojis = new EmojisManager(this.client, this) |     this.emojis = new GuildEmojisManager(this.client, this.client.emojis, this) | ||||||
| 
 | 
 | ||||||
|     if (!this.unavailable) { |     if (!this.unavailable) { | ||||||
|       this.name = data.name |       this.name = data.name | ||||||
|  | @ -160,6 +161,10 @@ export class Guild extends Base { | ||||||
|       //   data.roles.map(
 |       //   data.roles.map(
 | ||||||
|       //     v => cache.get('role', v.id) ?? new Role(this.client, v)
 |       //     v => cache.get('role', v.id) ?? new Role(this.client, v)
 | ||||||
|       //   ) ?? this.roles
 |       //   ) ?? this.roles
 | ||||||
|  |       // this.emojis =
 | ||||||
|  |       //   data.emojis.map(
 | ||||||
|  |       //     v => cache.get('emoji', v.id) ?? new Emoji(this.client, v)
 | ||||||
|  |       //   ) ?? this.emojis
 | ||||||
|       this.features = data.features ?? this.features |       this.features = data.features ?? this.features | ||||||
|       this.mfaLevel = data.mfa_level ?? this.mfaLevel |       this.mfaLevel = data.mfa_level ?? this.mfaLevel | ||||||
|       this.systemChannelID = data.system_channel_id ?? this.systemChannelID |       this.systemChannelID = data.system_channel_id ?? this.systemChannelID | ||||||
|  | @ -205,4 +210,8 @@ export class Guild extends Base { | ||||||
|         data.approximate_presence_count ?? this.approximatePresenceCount |         data.approximate_presence_count ?? this.approximatePresenceCount | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   async getEveryoneRole (): Promise<Role> { | ||||||
|  |     return (await this.roles.array().then(arr => arr?.sort((b, a) => a.position - b.position)[0]) as any) as Role | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { | ||||||
|   GuildChannelCategoryPayload, |   GuildChannelCategoryPayload, | ||||||
|   Overwrite |   Overwrite | ||||||
| } from '../types/channel.ts' | } from '../types/channel.ts' | ||||||
| import { Guild } from "./guild.ts" | import { Guild } from './guild.ts' | ||||||
| 
 | 
 | ||||||
| export class CategoryChannel extends Channel { | export class CategoryChannel extends Channel { | ||||||
|   guildID: string |   guildID: string | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { GuildNewsChannelPayload, Overwrite } from '../types/channel.ts' | import { GuildNewsChannelPayload, Overwrite } from '../types/channel.ts' | ||||||
| import { Guild } from "./guild.ts" | import { Guild } from './guild.ts' | ||||||
| import { TextChannel } from './textChannel.ts' | import { TextChannel } from './textChannel.ts' | ||||||
| 
 | 
 | ||||||
| export class NewsChannel extends TextChannel { | export class NewsChannel extends TextChannel { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { GuildTextChannelPayload, Overwrite } from '../types/channel.ts' | import { GuildTextChannelPayload, Overwrite } from '../types/channel.ts' | ||||||
| import { TextChannel } from './textChannel.ts' | import { TextChannel } from './textChannel.ts' | ||||||
| import { Guild } from "./guild.ts" | import { Guild } from './guild.ts' | ||||||
| 
 | 
 | ||||||
| export class GuildTextChannel extends TextChannel { | export class GuildTextChannel extends TextChannel { | ||||||
|   guildID: string |   guildID: string | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { GuildVoiceChannelPayload, Overwrite } from '../types/channel.ts' | import { GuildVoiceChannelPayload, Overwrite } from '../types/channel.ts' | ||||||
| import { Channel } from './channel.ts' | import { Channel } from './channel.ts' | ||||||
| import { Guild } from "./guild.ts" | import { Guild } from './guild.ts' | ||||||
| 
 | 
 | ||||||
| export class VoiceChannel extends Channel { | export class VoiceChannel extends Channel { | ||||||
|   bitrate: string |   bitrate: string | ||||||
|  |  | ||||||
|  | @ -1,26 +1,28 @@ | ||||||
| import cache from '../models/cache.ts' |  | ||||||
| import { Client } from '../models/client.ts' | import { Client } from '../models/client.ts' | ||||||
| import { MemberPayload } from '../types/guild.ts' | import { MemberPayload } from '../types/guild.ts' | ||||||
| import { Base } from './base.ts' | import { Base } from './base.ts' | ||||||
|  | import { Role } from './role.ts' | ||||||
| import { User } from './user.ts' | import { User } from './user.ts' | ||||||
| 
 | 
 | ||||||
| export class Member extends Base { | export class Member extends Base { | ||||||
|   id: string |   id: string | ||||||
|   user: User |   user: User | ||||||
|   nick?: string |   nick?: string | ||||||
|   roles: string[] |   roleIDs: string[] | ||||||
|  |   roles: Role[] = [] | ||||||
|   joinedAt: string |   joinedAt: string | ||||||
|   premiumSince?: string |   premiumSince?: string | ||||||
|   deaf: boolean |   deaf: boolean | ||||||
|   mute: boolean |   mute: boolean | ||||||
| 
 | 
 | ||||||
|   constructor (client: Client, data: MemberPayload) { |   constructor (client: Client, data: MemberPayload, user: User) { | ||||||
|     super(client) |     super(client) | ||||||
|     this.id = data.user.id |     this.id = data.user.id | ||||||
|     this.user = |     this.user = user | ||||||
|       cache.get('user', data.user.id) ?? new User(this.client, data.user) |     // this.user =
 | ||||||
|  |     //   cache.get('user', data.user.id) ?? new User(this.client, data.user)
 | ||||||
|     this.nick = data.nick |     this.nick = data.nick | ||||||
|     this.roles = data.roles |     this.roleIDs = data.roles | ||||||
|     this.joinedAt = data.joined_at |     this.joinedAt = data.joined_at | ||||||
|     this.premiumSince = data.premium_since |     this.premiumSince = data.premium_since | ||||||
|     this.deaf = data.deaf |     this.deaf = data.deaf | ||||||
|  | @ -32,7 +34,7 @@ export class Member extends Base { | ||||||
|   protected readFromData (data: MemberPayload): void { |   protected readFromData (data: MemberPayload): void { | ||||||
|     super.readFromData(data.user) |     super.readFromData(data.user) | ||||||
|     this.nick = data.nick ?? this.nick |     this.nick = data.nick ?? this.nick | ||||||
|     this.roles = data.roles ?? this.roles |     this.roleIDs = data.roles ?? this.roles.map(r => r.id) | ||||||
|     this.joinedAt = data.joined_at ?? this.joinedAt |     this.joinedAt = data.joined_at ?? this.joinedAt | ||||||
|     this.premiumSince = data.premium_since ?? this.premiumSince |     this.premiumSince = data.premium_since ?? this.premiumSince | ||||||
|     this.deaf = data.deaf ?? this.deaf |     this.deaf = data.deaf ?? this.deaf | ||||||
|  |  | ||||||
|  | @ -14,10 +14,10 @@ import { User } from './user.ts' | ||||||
| import { Member } from './member.ts' | import { Member } from './member.ts' | ||||||
| import { Embed } from './embed.ts' | import { Embed } from './embed.ts' | ||||||
| import { CHANNEL_MESSAGE } from '../types/endpoint.ts' | import { CHANNEL_MESSAGE } from '../types/endpoint.ts' | ||||||
| import { MessageMentions } from "./messageMentions.ts" | import { MessageMentions } from './messageMentions.ts' | ||||||
| import { TextChannel } from "./textChannel.ts" | import { TextChannel } from './textChannel.ts' | ||||||
| import { DMChannel } from "./dmChannel.ts" | import { DMChannel } from './dmChannel.ts' | ||||||
| import { Guild } from "./guild.ts" | import { Guild } from './guild.ts' | ||||||
| 
 | 
 | ||||||
| export class Message extends Base { | export class Message extends Base { | ||||||
|   // eslint-disable-next-line @typescript-eslint/prefer-readonly
 |   // eslint-disable-next-line @typescript-eslint/prefer-readonly
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { Client } from '../models/client.ts' | ||||||
| import { MessageOption, TextChannelPayload } from '../types/channel.ts' | import { MessageOption, TextChannelPayload } from '../types/channel.ts' | ||||||
| import { CHANNEL_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts' | import { CHANNEL_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts' | ||||||
| import { Channel } from './channel.ts' | import { Channel } from './channel.ts' | ||||||
| import { Embed } from "./embed.ts" | import { Embed } from './embed.ts' | ||||||
| import { Message } from './message.ts' | import { Message } from './message.ts' | ||||||
| import { MessageMentions } from './messageMentions.ts' | import { MessageMentions } from './messageMentions.ts' | ||||||
| 
 | 
 | ||||||
|  | @ -27,14 +27,14 @@ export class TextChannel extends Channel { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async send (text?: string | AllMessageOptions, option?: AllMessageOptions): Promise<Message> { |   async send (text?: string | AllMessageOptions, option?: AllMessageOptions): Promise<Message> { | ||||||
|     if(typeof text === "object") { |     if (typeof text === "object") { | ||||||
|       option = text |       option = text | ||||||
|       text = undefined |       text = undefined | ||||||
|     } |     } | ||||||
|     if (text === undefined && option === undefined) { |     if (text === undefined && option === undefined) { | ||||||
|       throw new Error('Either text or option is necessary.') |       throw new Error('Either text or option is necessary.') | ||||||
|     } |     } | ||||||
|     if(option instanceof Embed) option = { |     if (option instanceof Embed) option = { | ||||||
|       embed: option |       embed: option | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| import { CommandClient, Intents } from '../../mod.ts'; | import { CommandClient, Intents } from '../../mod.ts' | ||||||
| import PingCommand from "./cmds/ping.ts"; | import PingCommand from './cmds/ping.ts' | ||||||
|  | import AddEmojiCommand from './cmds/addemoji.ts' | ||||||
|  | import UserinfoCommand from './cmds/userinfo.ts' | ||||||
| import { TOKEN } from './config.ts' | import { TOKEN } from './config.ts' | ||||||
| 
 | 
 | ||||||
| const client = new CommandClient({ | const client = new CommandClient({ | ||||||
|  | @ -13,6 +15,10 @@ client.on('ready', () => { | ||||||
|   console.log(`[Login] Logged in as ${client.user?.tag}!`) |   console.log(`[Login] Logged in as ${client.user?.tag}!`) | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  | client.on("commandError", console.error) | ||||||
|  | 
 | ||||||
| client.commands.add(PingCommand) | client.commands.add(PingCommand) | ||||||
|  | client.commands.add(UserinfoCommand) | ||||||
|  | client.commands.add(AddEmojiCommand) | ||||||
| 
 | 
 | ||||||
| client.connect(TOKEN, Intents.All) | client.connect(TOKEN, Intents.All) | ||||||
							
								
								
									
										22
									
								
								src/test/cmds/addemoji.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/test/cmds/addemoji.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | import { Command } from '../../../mod.ts' | ||||||
|  | import { CommandContext } from '../../models/command.ts' | ||||||
|  | 
 | ||||||
|  | export default class AddEmojiCommand extends Command { | ||||||
|  |   name = 'addemoji' | ||||||
|  |   aliases = [ 'ae', 'emojiadd' ] | ||||||
|  |   args = 2 | ||||||
|  |   guildOnly = true | ||||||
|  | 
 | ||||||
|  |   execute(ctx: CommandContext): any { | ||||||
|  |     const name = ctx.args[0] | ||||||
|  |     if (name === undefined) return ctx.message.reply('No name was given!') | ||||||
|  |     const url = ctx.argString.slice(name.length).trim() | ||||||
|  |     if (url === '') return ctx.message.reply('No URL was given!') | ||||||
|  |     ctx.message.guild?.emojis.create(name, url).then(emoji => { | ||||||
|  |         if (emoji === undefined) throw new Error('Unknown') | ||||||
|  |         ctx.message.reply(`Successfuly added emoji ${emoji.toString()} ${emoji.name}!`) | ||||||
|  |     }).catch(e => { | ||||||
|  |         ctx.message.reply(`Failed to add emoji. Reason: ${e.message}`) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -1,11 +1,10 @@ | ||||||
| import { Command } from "../../../mod.ts"; | import { Command } from '../../../mod.ts' | ||||||
| import { CommandContext } from "../../models/command.ts"; | import { CommandContext } from '../../models/command.ts' | ||||||
| 
 | 
 | ||||||
| export default class PingCommand extends Command { | export default class PingCommand extends Command { | ||||||
|   name = "ping" |   name = "ping" | ||||||
|   dmOnly = true |  | ||||||
| 
 | 
 | ||||||
|   execute(ctx: CommandContext): void { |   execute(ctx: CommandContext): void { | ||||||
|     ctx.message.reply(`pong! Latency: ${ctx.client.ping}ms`) |     ctx.message.reply(`Pong! Latency: ${ctx.client.ping}ms`) | ||||||
|   } |   } | ||||||
| } | } | ||||||
							
								
								
									
										16
									
								
								src/test/cmds/userinfo.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/test/cmds/userinfo.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | import { Command, Member, CommandContext, Embed } from '../../../mod.ts' | ||||||
|  | 
 | ||||||
|  | export default class UserinfoCommand extends Command { | ||||||
|  |     name = "userinfo" | ||||||
|  |     guildOnly = true | ||||||
|  | 
 | ||||||
|  |     execute(ctx: CommandContext): void { | ||||||
|  |         const member: Member = ctx.message.member as any | ||||||
|  |         const embed = new Embed() | ||||||
|  |         .setTitle(`User Info`) | ||||||
|  |         .setAuthor({ name: member.user.tag }) | ||||||
|  |         .addField("ID", member.id) | ||||||
|  |         .addField("Roles", member.roles.map(r => r.name).join(", ")) | ||||||
|  |         ctx.channel.send(embed) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { Client, Intents, GuildTextChannel, Message, ClientPresence, Member, Role, GuildChannel, Embed, Guild } from '../../mod.ts'; | import { Client, Intents, GuildTextChannel, Message, ClientPresence, Member, Role, GuildChannel, Embed, Guild } from '../../mod.ts' | ||||||
| import { TOKEN } from './config.ts' | import { TOKEN } from './config.ts' | ||||||
| 
 | 
 | ||||||
| const client = new Client({ | const client = new Client({ | ||||||
|  |  | ||||||
|  | @ -14,6 +14,8 @@ const GUILD_WIDGET = (guildID: string): string => | ||||||
|   `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/widget` |   `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/widget` | ||||||
| const GUILD_EMOJI = (guildID: string, emojiID: string): string => | const GUILD_EMOJI = (guildID: string, emojiID: string): string => | ||||||
|   `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis/${emojiID}` |   `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis/${emojiID}` | ||||||
|  | const GUILD_EMOJIS = (guildID: string): string => | ||||||
|  |   `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis` | ||||||
| const GUILD_ROLE = (guildID: string, roleID: string): string => | const GUILD_ROLE = (guildID: string, roleID: string): string => | ||||||
|   `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/roles/${roleID}` |   `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/roles/${roleID}` | ||||||
| const GUILD_ROLES = (guildID: string): string => | const GUILD_ROLES = (guildID: string): string => | ||||||
|  | @ -172,8 +174,6 @@ const TEAM_ICON = (teamID: string, iconID: string): string => | ||||||
| // Emoji Endpoints
 | // Emoji Endpoints
 | ||||||
| const EMOJI = (guildID: string, emojiID: string): string => | const EMOJI = (guildID: string, emojiID: string): string => | ||||||
|   `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis/${emojiID}` |   `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis/${emojiID}` | ||||||
| const EMOJIS = (guildID: string): string => |  | ||||||
|   `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/emojis` |  | ||||||
| 
 | 
 | ||||||
| // Template Endpoint
 | // Template Endpoint
 | ||||||
| const TEMPLATE = (templateCODE: string): string => | const TEMPLATE = (templateCODE: string): string => | ||||||
|  | @ -259,7 +259,7 @@ export default [ | ||||||
|   ACHIEVEMENT_ICON, |   ACHIEVEMENT_ICON, | ||||||
|   TEAM_ICON, |   TEAM_ICON, | ||||||
|   EMOJI, |   EMOJI, | ||||||
|   EMOJIS, |   GUILD_EMOJIS, | ||||||
|   TEMPLATE, |   TEMPLATE, | ||||||
|   INVITE, |   INVITE, | ||||||
|   VOICE_REGIONS |   VOICE_REGIONS | ||||||
|  | @ -305,6 +305,7 @@ export { | ||||||
|   CHANNEL_PIN, |   CHANNEL_PIN, | ||||||
|   CHANNEL_PINS, |   CHANNEL_PINS, | ||||||
|   CHANNEL_PERMISSION, |   CHANNEL_PERMISSION, | ||||||
|  |   GUILD_EMOJIS, | ||||||
|   CHANNEL_TYPING, |   CHANNEL_TYPING, | ||||||
|   GROUP_RECIPIENT, |   GROUP_RECIPIENT, | ||||||
|   CURRENT_USER, |   CURRENT_USER, | ||||||
|  | @ -333,7 +334,6 @@ export { | ||||||
|   ACHIEVEMENT_ICON, |   ACHIEVEMENT_ICON, | ||||||
|   TEAM_ICON, |   TEAM_ICON, | ||||||
|   EMOJI, |   EMOJI, | ||||||
|   EMOJIS, |  | ||||||
|   TEMPLATE, |   TEMPLATE, | ||||||
|   INVITE, |   INVITE, | ||||||
|   VOICE_REGIONS |   VOICE_REGIONS | ||||||
|  |  | ||||||
|  | @ -14,9 +14,9 @@ import { GroupDMChannel } from '../structures/groupChannel.ts' | ||||||
| import { CategoryChannel } from '../structures/guildCategoryChannel.ts' | import { CategoryChannel } from '../structures/guildCategoryChannel.ts' | ||||||
| import { NewsChannel } from '../structures/guildNewsChannel.ts' | import { NewsChannel } from '../structures/guildNewsChannel.ts' | ||||||
| import { VoiceChannel } from '../structures/guildVoiceChannel.ts' | import { VoiceChannel } from '../structures/guildVoiceChannel.ts' | ||||||
| import { Guild } from "../structures/guild.ts" | import { Guild } from '../structures/guild.ts' | ||||||
| import { GuildTextChannel } from "../structures/guildTextChannel.ts" | import { GuildTextChannel } from '../structures/guildTextChannel.ts' | ||||||
| import { TextChannel } from "../structures/textChannel.ts" | import { TextChannel } from '../structures/textChannel.ts' | ||||||
| 
 | 
 | ||||||
| const getChannelByType = ( | const getChannelByType = ( | ||||||
|   client: Client, |   client: Client, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue