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. | ||||
| * Built-in Command Framework, | ||||
|   * Easily build Commands on the fly. | ||||
|   * Compltely Customizable. | ||||
|   * Completely Customizable. | ||||
|   * Complete Object-Oriented approach. | ||||
| * 100% Discord API Coverage. | ||||
| * Customizable caching. | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| 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 getChannelByType from '../../utils/getChannelByType.ts' | ||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
|  |  | |||
|  | @ -8,9 +8,7 @@ export const guildBanAdd: GatewayEventHandler = async ( | |||
|   d: GuildBanAddPayload | ||||
| ) => { | ||||
|   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) | ||||
|   const user: User = | ||||
|     (await gateway.client.users.get(d.user.id)) ?? | ||||
|     new User(gateway.client, d.user) | ||||
|   const user: User = await gateway.client.users.get(d.user.id) ?? new User(gateway.client, d.user) | ||||
| 
 | ||||
|   if (guild !== undefined) { | ||||
|     await guild.members.delete(user.id) | ||||
|  |  | |||
|  | @ -9,8 +9,7 @@ export const guildBanRemove: GatewayEventHandler = async ( | |||
| ) => { | ||||
|   const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id) | ||||
|   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) { | ||||
|     gateway.client.emit('guildBanRemove', guild, user) | ||||
|  |  | |||
|  | @ -10,8 +10,8 @@ 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" | ||||
| import { messageCreate } from './messageCreate.ts' | ||||
| import { resume } from './resume.ts' | ||||
| import { reconnect } from './reconnect.ts' | ||||
| 
 | ||||
| export const gatewayHandlers: { | ||||
|  |  | |||
|  | @ -17,11 +17,26 @@ export const messageCreate: GatewayEventHandler = async ( | |||
|   const user = new User(gateway.client, d.author) | ||||
|   await gateway.client.users.set(d.author.id, d.author) | ||||
|   let guild | ||||
|   let member | ||||
|   if (d.guild_id !== undefined) { | ||||
|     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 message = new Message(gateway.client, d, channel as any, user, mentions) | ||||
|   message.member = member | ||||
|   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) | ||||
| } | ||||
|  |  | |||
|  | @ -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) => { | ||||
|     gateway.reconnect() | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { User } from "../../structures/user.ts" | ||||
| import { CLIENT_USER } from "../../types/endpoint.ts" | ||||
| import { User } from '../../structures/user.ts' | ||||
| import { CLIENT_USER } from '../../types/endpoint.ts' | ||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| 
 | ||||
| export const resume: GatewayEventHandler = async (gateway: Gateway, d: any) => { | ||||
|  |  | |||
|  | @ -1,9 +1,11 @@ | |||
| import { Client } from "../models/client.ts"; | ||||
| import { Collection } from "../utils/collection.ts"; | ||||
| import { Client } from '../models/client.ts' | ||||
| import { Collection } from '../utils/collection.ts' | ||||
| 
 | ||||
| export class BaseManager<T, T2> { | ||||
|   client: Client | ||||
|   /** Cache Name or Key used to differentiate caches */ | ||||
|   cacheName: string | ||||
|   /** Which data type does this cache have */ | ||||
|   DataType: any | ||||
| 
 | ||||
|   constructor (client: Client, cacheName: string, DataType: any) { | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { Client } from "../models/client.ts"; | ||||
| import { Collection } from "../utils/collection.ts"; | ||||
| import { BaseManager } from "./base.ts"; | ||||
| import { Client } from '../models/client.ts' | ||||
| import { Collection } from '../utils/collection.ts' | ||||
| import { BaseManager } from './base.ts' | ||||
| 
 | ||||
| export class BaseChildManager<T, T2> { | ||||
|   client: Client | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| import { Client } from "../models/client.ts"; | ||||
| import { Channel } from "../structures/channel.ts"; | ||||
| import { ChannelPayload } from "../types/channel.ts"; | ||||
| import { CHANNEL } from "../types/endpoint.ts"; | ||||
| import getChannelByType from "../utils/getChannelByType.ts"; | ||||
| import { BaseManager } from "./base.ts"; | ||||
| import { Client } from '../models/client.ts' | ||||
| import { Channel } from '../structures/channel.ts' | ||||
| import { ChannelPayload } from '../types/channel.ts' | ||||
| import { CHANNEL } from '../types/endpoint.ts' | ||||
| import getChannelByType from '../utils/getChannelByType.ts' | ||||
| import { BaseManager } from './base.ts' | ||||
| 
 | ||||
| export class ChannelsManager extends BaseManager<ChannelPayload, Channel> { | ||||
|   constructor(client: Client) { | ||||
|  |  | |||
|  | @ -1,24 +1,32 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { Emoji } from '../structures/emoji.ts' | ||||
| import { Guild } from '../structures/guild.ts' | ||||
| import { EmojiPayload } from '../types/emoji.ts' | ||||
| import { GUILD_EMOJI } from '../types/endpoint.ts' | ||||
| import { BaseManager } from './base.ts' | ||||
| 
 | ||||
| export class EmojisManager extends BaseManager<EmojiPayload, Emoji> { | ||||
|   guild: Guild | ||||
| 
 | ||||
|   constructor (client: Client, guild: Guild) { | ||||
|     super(client, `emojis:${guild.id}`, Emoji) | ||||
|     this.guild = guild | ||||
|   constructor (client: Client) { | ||||
|     super(client, `emojis`, Emoji) | ||||
|   } | ||||
| 
 | ||||
|   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) => { | ||||
|       this.client.rest | ||||
|         .get(GUILD_EMOJI(this.guild.id, id)) | ||||
|         .then(data => { | ||||
|           this.set(id, data as EmojiPayload) | ||||
|         .get(GUILD_EMOJI(guildID, id)) | ||||
|         .then(async data => { | ||||
|           await this.set(id, data as EmojiPayload) | ||||
|           resolve(new Emoji(this.client, data as EmojiPayload)) | ||||
|         }) | ||||
|         .catch(e => reject(e)) | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import { | |||
|   GuildTextChannelPayload, | ||||
|   GuildVoiceChannelPayload | ||||
| } from '../types/channel.ts' | ||||
| import { CHANNEL } from '../types/endpoint.ts' | ||||
| import { BaseChildManager } from './baseChild.ts' | ||||
| import { ChannelsManager } from './channels.ts' | ||||
| 
 | ||||
|  | @ -35,6 +36,10 @@ export class GuildChannelsManager extends BaseChildManager< | |||
|     else return undefined | ||||
|   } | ||||
|    | ||||
|   async delete(id: string): Promise<boolean> { | ||||
|     return this.client.rest.delete(CHANNEL(id)) | ||||
|   } | ||||
| 
 | ||||
|   async array (): Promise<GuildChannel[]> { | ||||
|     const arr = (await this.parent.array()) as Channel[] | ||||
|     return arr.filter( | ||||
|  |  | |||
							
								
								
									
										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 { Guild } from "../structures/guild.ts"; | ||||
| import { Member } from "../structures/member.ts"; | ||||
| import { GUILD_MEMBER } from "../types/endpoint.ts"; | ||||
| import { MemberPayload } from "../types/guild.ts"; | ||||
| import { BaseManager } from "./base.ts"; | ||||
| import { User } from '../structures/user.ts' | ||||
| import { Client } from '../models/client.ts' | ||||
| import { Guild } from '../structures/guild.ts' | ||||
| import { Member } from '../structures/member.ts' | ||||
| import { GUILD_MEMBER } from '../types/endpoint.ts' | ||||
| import { MemberPayload } from '../types/guild.ts' | ||||
| import { BaseManager } from './base.ts' | ||||
| 
 | ||||
| export class MembersManager extends BaseManager<MemberPayload, Member> { | ||||
|   guild: Guild | ||||
|  | @ -13,11 +14,29 @@ export class MembersManager extends BaseManager<MemberPayload, Member> { | |||
|     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> { | ||||
|     return await new Promise((resolve, reject) => { | ||||
|       this.client.rest.get(GUILD_MEMBER(this.guild.id, id)).then(data => { | ||||
|         this.set(id, data as MemberPayload) | ||||
|         resolve(new Member(this.client, data as MemberPayload)) | ||||
|       this.client.rest.get(GUILD_MEMBER(this.guild.id, id)).then(async data => { | ||||
|         await this.set(id, 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)) | ||||
|     }) | ||||
|   } | ||||
|  |  | |||
|  | @ -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, | ||||
|   ClientPresence | ||||
| } from '../structures/presence.ts' | ||||
| import { EmojisManager } from '../managers/emojis.ts' | ||||
| 
 | ||||
| /** Some Client Options to modify behaviour */ | ||||
| export interface ClientOptions { | ||||
|   /** Token of the Bot/User */ | ||||
|   token?: string | ||||
|   /** Gateway Intents */ | ||||
|   intents?: GatewayIntents[] | ||||
|   cache?: ICacheAdapter | ||||
|   forceNewSession?: boolean | ||||
|   /** Cache Adapter to use, defaults to Collections one */ | ||||
|   cache?: ICacheAdapter, | ||||
|   /** Force New Session and don't use cached Session (by persistent caching) */ | ||||
|   forceNewSession?: boolean, | ||||
|   /** Startup presence of client */ | ||||
|   presence?: ClientPresence | ClientActivity | ActivityGame | ||||
|   /** Whether it's a bot user or not? Use this if selfbot! */ | ||||
|   bot?: boolean | ||||
|   /** Force all requests to Canary API */ | ||||
|   canary?: boolean | ||||
| } | ||||
| 
 | ||||
|  | @ -29,21 +37,34 @@ export interface ClientOptions { | |||
|  * Discord Client. | ||||
|  */ | ||||
| export class Client extends EventEmitter { | ||||
|   /** Gateway object */ | ||||
|   gateway?: Gateway | ||||
|   /** REST Manager - used to make all requests */ | ||||
|   rest: RESTManager = new RESTManager(this) | ||||
|   /** User which Client logs in to, undefined until logs in */ | ||||
|   user?: User | ||||
|   /** WebSocket ping of Client */ | ||||
|   ping = 0 | ||||
|   /** Token of the Bot/User */ | ||||
|   token?: string | ||||
|   /** Cache Adapter */ | ||||
|   cache: ICacheAdapter = new DefaultCacheAdapter() | ||||
|   /** Gateway Intents */ | ||||
|   intents?: GatewayIntents[] | ||||
|   /** Whether to force new session or not */ | ||||
|   forceNewSession?: boolean | ||||
| 
 | ||||
|   users: UserManager = new UserManager(this) | ||||
|   guilds: GuildManager = new GuildManager(this) | ||||
|   channels: ChannelsManager = new ChannelsManager(this) | ||||
|   messages: MessagesManager = new MessagesManager(this) | ||||
|   bot: boolean = true | ||||
|   canary: boolean = false | ||||
|   emojis: EmojisManager = new EmojisManager(this) | ||||
|    | ||||
|   /** Whether this client will login as bot user or not */ | ||||
|   bot: boolean = true | ||||
|   /** Whether the REST Manager will use Canary API or not */ | ||||
|   canary: boolean = false | ||||
|   /** Client's presence. Startup one if set before connecting */ | ||||
|   presence: ClientPresence = new ClientPresence() | ||||
| 
 | ||||
|   constructor (options: ClientOptions = {}) { | ||||
|  | @ -61,11 +82,13 @@ export class Client extends EventEmitter { | |||
|     if (options.canary === true) this.canary = true | ||||
|   } | ||||
| 
 | ||||
|   /** Set Cache Adapter */ | ||||
|   setAdapter (adapter: ICacheAdapter): Client { | ||||
|     this.cache = adapter | ||||
|     return this | ||||
|   } | ||||
| 
 | ||||
|   /** Change Presence of Client */ | ||||
|   setPresence (presence: ClientPresence | ClientActivity | ActivityGame): void { | ||||
|     if (presence instanceof ClientPresence) { | ||||
|       this.presence = presence | ||||
|  | @ -73,6 +96,7 @@ export class Client extends EventEmitter { | |||
|     this.gateway?.sendPresence(this.presence.create()) | ||||
|   } | ||||
| 
 | ||||
|   /** Emit debug event */ | ||||
|   debug (tag: string, msg: string): void { | ||||
|     this.emit('debug', `[${tag}] ${msg}`) | ||||
|   } | ||||
|  |  | |||
|  | @ -2,6 +2,9 @@ import { delay } from '../utils/index.ts' | |||
| import * as baseEndpoints from '../consts/urlsAndVersions.ts' | ||||
| import { Client } from './client.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 { | ||||
|   Ok = 200, | ||||
|  | @ -17,122 +20,102 @@ export enum HttpResponseCode { | |||
|   GatewayUnavailable = 502 | ||||
| } | ||||
| 
 | ||||
| export type RequestMethods = | ||||
|   | 'get' | ||||
|   | 'post' | ||||
|   | 'put' | ||||
|   | 'patch' | ||||
|   | 'head' | ||||
|   | 'delete' | ||||
| export interface RequestHeaders { | ||||
|   [name: string]: string | ||||
| } | ||||
| 
 | ||||
| export interface QueuedRequest { | ||||
|   callback: () => Promise< | ||||
|     | { | ||||
| export interface QueuedItem { | ||||
|   onComplete: () => Promise<{ | ||||
|     rateLimited: any | ||||
|         beforeFetch: boolean | ||||
|         bucketID?: string | null | ||||
|       } | ||||
|     | undefined | ||||
|   > | ||||
|   bucketID?: string | null | ||||
|     bucket?: string | null | ||||
|     before: boolean | ||||
|   } | undefined> | ||||
|   bucket?: string | null | ||||
|   url: string | ||||
| } | ||||
| 
 | ||||
| export interface RateLimitedPath { | ||||
| export interface RateLimit { | ||||
|   url: string | ||||
|   resetTimestamp: number | ||||
|   bucketID: string | null | ||||
|   resetAt: number | ||||
|   bucket: string | null | ||||
| } | ||||
| 
 | ||||
| export class RESTManager { | ||||
|   client: Client | ||||
|   globallyRateLimited: boolean = false | ||||
|   queueInProcess: boolean = false | ||||
|   pathQueues: { [key: string]: QueuedRequest[] } = {} | ||||
|   ratelimitedPaths = new Map<string, RateLimitedPath>() | ||||
|   queues: { [key: string]: QueuedItem[] } = {} | ||||
|   rateLimits = new Collection<string, RateLimit>() | ||||
|   globalRateLimit: boolean = false | ||||
|   processing: boolean = false | ||||
| 
 | ||||
|   constructor(client: Client) { | ||||
|     this.client = client | ||||
|     setTimeout(() => this.processRateLimitedPaths, 1000) | ||||
|     // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|     this.handleRateLimits() | ||||
|   } | ||||
| 
 | ||||
|   async processRateLimitedPaths (): Promise<void> { | ||||
|     const now = Date.now() | ||||
|     this.ratelimitedPaths.forEach((value, key) => { | ||||
|       if (value.resetTimestamp > now) return | ||||
|       this.ratelimitedPaths.delete(key) | ||||
|       if (key === 'global') this.globallyRateLimited = false | ||||
|   async checkQueues(): Promise<void> { | ||||
|     Object.entries(this.queues).forEach(([key, value]) => { | ||||
|       if (value.length === 0) { | ||||
|         // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
 | ||||
|         delete this.queues[key] | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   addToQueue (request: QueuedRequest): void { | ||||
|   queue(request: QueuedItem): void { | ||||
|     const route = request.url.substring( | ||||
|       // eslint seriously?
 | ||||
|       // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
 | ||||
|       baseEndpoints.DISCORD_API_URL.length + 1 | ||||
|     ) | ||||
|     const parts = route.split('/') | ||||
|     // Remove the major param
 | ||||
|     parts.shift() | ||||
|     const [id] = parts | ||||
| 
 | ||||
|     if (this.pathQueues[id] !== undefined) { | ||||
|       this.pathQueues[id].push(request) | ||||
|     if (this.queues[id] !== undefined) { | ||||
|       this.queues[id].push(request) | ||||
|     } else { | ||||
|       this.pathQueues[id] = [request] | ||||
|       this.queues[id] = [request] | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async cleanupQueues (): Promise<void> { | ||||
|     Object.entries(this.pathQueues).forEach(([key, value]) => { | ||||
|       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 | ||||
|     ) { | ||||
|     if (Object.keys(this.queues).length !== 0 && !this.globalRateLimit) { | ||||
|       await Promise.allSettled( | ||||
|         Object.values(this.pathQueues).map(async pathQueue => { | ||||
|         Object.values(this.queues).map(async pathQueue => { | ||||
|           const request = pathQueue.shift() | ||||
|           if (request === undefined) return | ||||
| 
 | ||||
|           const rateLimitedURLResetIn = await this.checkRatelimits(request.url) | ||||
|           const rateLimitedURLResetIn = await this.isRateLimited(request.url) | ||||
| 
 | ||||
|           if (typeof request.bucketID === 'string') { | ||||
|             const rateLimitResetIn = await this.checkRatelimits( | ||||
|               request.bucketID | ||||
|           if (typeof request.bucket === 'string') { | ||||
|             const rateLimitResetIn = await this.isRateLimited( | ||||
|               request.bucket | ||||
|             ) | ||||
|             if (rateLimitResetIn !== false) { | ||||
|               // This request is still rate limited read to queue
 | ||||
|               this.addToQueue(request) | ||||
|               this.queue(request) | ||||
|             } else { | ||||
|               // 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) { | ||||
|                 this.addToQueue({ | ||||
|                 this.queue({ | ||||
|                   ...request, | ||||
|                   bucketID: result.bucketID ?? request.bucketID | ||||
|                   bucket: result.bucket ?? request.bucket | ||||
|                 }) | ||||
|               } | ||||
|             } | ||||
|           } else { | ||||
|             if (rateLimitedURLResetIn !== false) { | ||||
|               // This URL is rate limited readd to queue
 | ||||
|               this.addToQueue(request) | ||||
|               this.queue(request) | ||||
|             } else { | ||||
|               // 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) { | ||||
|                 this.addToQueue({ | ||||
|                 this.queue({ | ||||
|                   ...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) | ||||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|       this.processQueue() | ||||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|       this.cleanupQueues() | ||||
|     } else this.queueInProcess = false | ||||
|       this.checkQueues() | ||||
|     } else this.processing = false | ||||
|   } | ||||
| 
 | ||||
|   createRequestBody ( | ||||
|   prepare( | ||||
|     body: any, | ||||
|     method: RequestMethods | ||||
|   ): { [key: string]: any } { | ||||
|     const headers: { [key: string]: string } = { | ||||
|       Authorization: `Bot ${this.client.token}`, | ||||
|       'User-Agent': `DiscordBot (harmony)` | ||||
| 
 | ||||
|     const headers: RequestHeaders = { | ||||
|       'Authorization': `Bot ${this.client.token}`, | ||||
|       'User-Agent': `DiscordBot (harmony, https://github.com/harmony-org/harmony)` | ||||
|     } | ||||
| 
 | ||||
|     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) { | ||||
|       headers['X-Audit-Log-Reason'] = encodeURIComponent(body.reason) | ||||
|  | @ -203,40 +187,112 @@ export class RESTManager { | |||
|     return data | ||||
|   } | ||||
| 
 | ||||
|   async checkRatelimits (url: string): Promise<number | false> { | ||||
|     const ratelimited = this.ratelimitedPaths.get(url) | ||||
|     const global = this.ratelimitedPaths.get('global') | ||||
|   async isRateLimited(url: string): Promise<number | false> { | ||||
|     const global = this.rateLimits.get('global') | ||||
|     const rateLimited = this.rateLimits.get(url) | ||||
|     const now = Date.now() | ||||
| 
 | ||||
|     if (ratelimited !== undefined && now < ratelimited.resetTimestamp) { | ||||
|       return ratelimited.resetTimestamp - now | ||||
|     if (rateLimited !== undefined && now < rateLimited.resetAt) { | ||||
|       return rateLimited.resetAt - now | ||||
|     } | ||||
|     if (global !== undefined && now < global.resetTimestamp) { | ||||
|       return global.resetTimestamp - now | ||||
|     if (global !== undefined && now < global.resetAt) { | ||||
|       return global.resetAt - now | ||||
|     } | ||||
| 
 | ||||
|     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, | ||||
|     url: string, | ||||
|     body?: unknown, | ||||
|     retryCount = 0, | ||||
|     bucketID?: string | null | ||||
|     maxRetries = 0, | ||||
|     bucket?: string | null | ||||
|   ): Promise<any> { | ||||
|     const errorStack = new Error('Location In Your Files:') | ||||
|     Error.captureStackTrace(errorStack) | ||||
| 
 | ||||
|     return await new Promise((resolve, reject) => { | ||||
|       const callback = async (): Promise<undefined | any> => { | ||||
|       const onComplete = async (): Promise<undefined | any> => { | ||||
|         try { | ||||
|           const rateLimitResetIn = await this.checkRatelimits(url) | ||||
|           const rateLimitResetIn = await this.isRateLimited(url) | ||||
|           if (rateLimitResetIn !== false) { | ||||
|             return { | ||||
|               rateLimited: rateLimitResetIn, | ||||
|               beforeFetch: true, | ||||
|               bucketID | ||||
|               before: true, | ||||
|               bucket | ||||
|             } | ||||
|           } | ||||
| 
 | ||||
|  | @ -259,13 +315,13 @@ export class RESTManager { | |||
|             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 bucketIDFromHeaders = this.processHeaders(url, response.headers) | ||||
|           this.handleStatusCode(response, errorStack) | ||||
|           const bucketFromHeaders = this.processHeaders(url, response.headers) | ||||
|           // 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) | ||||
| 
 | ||||
|           const json = await response.json() | ||||
|  | @ -273,14 +329,14 @@ export class RESTManager { | |||
|             json.retry_after !== undefined || | ||||
|             json.message === 'You are being rate limited.' | ||||
|           ) { | ||||
|             if (retryCount > 10) { | ||||
|             if (maxRetries > 10) { | ||||
|               throw new Error('Max RateLimit Retries hit') | ||||
|             } | ||||
| 
 | ||||
|             return { | ||||
|               rateLimited: json.retry_after, | ||||
|               beforeFetch: false, | ||||
|               bucketID: bucketIDFromHeaders | ||||
|               before: false, | ||||
|               bucket: bucketFromHeaders | ||||
|             } | ||||
|           } | ||||
|           return resolve(json) | ||||
|  | @ -289,132 +345,45 @@ export class RESTManager { | |||
|         } | ||||
|       } | ||||
| 
 | ||||
|       this.addToQueue({ | ||||
|         callback, | ||||
|         bucketID, | ||||
|       this.queue({ | ||||
|         onComplete, | ||||
|         bucket, | ||||
|         url | ||||
|       }) | ||||
|       if (!this.queueInProcess) { | ||||
|         this.queueInProcess = true | ||||
|       if (!this.processing) { | ||||
|         this.processing = true | ||||
|         // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|         this.processQueue() | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   async logErrors (response: Response, errorStack?: unknown): Promise<void> { | ||||
|     try { | ||||
|       const error = await response.json() | ||||
|       console.error(error) | ||||
|     } catch { | ||||
|       console.error(response) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   handleStatusCode ( | ||||
|     response: Response, | ||||
|     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 { | ||||
|     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 !== null && remaining === '0') { | ||||
|       ratelimited = true | ||||
| 
 | ||||
|       this.ratelimitedPaths.set(url, { | ||||
|         url, | ||||
|         resetTimestamp: Number(resetTimestamp) * 1000, | ||||
|         bucketID | ||||
|   async handleRateLimits(): Promise<void> { | ||||
|     const now = Date.now() | ||||
|     this.rateLimits.forEach((value, key) => { | ||||
|       if (value.resetAt > now) return | ||||
|       this.rateLimits.delete(key) | ||||
|       if (key === 'global') this.globalRateLimit = false | ||||
|     }) | ||||
| 
 | ||||
|       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> { | ||||
|     return await this.runMethod('get', url, body) | ||||
|     return await this.make('get', url, body) | ||||
|   } | ||||
| 
 | ||||
|   async post(url: string, body?: unknown): Promise<any> { | ||||
|     return await this.runMethod('post', url, body) | ||||
|     return await this.make('post', url, body) | ||||
|   } | ||||
| 
 | ||||
|   async delete(url: string, body?: unknown): Promise<any> { | ||||
|     return await this.runMethod('delete', url, body) | ||||
|     return await this.make('delete', url, body) | ||||
|   } | ||||
| 
 | ||||
|   async patch(url: string, body?: unknown): Promise<any> { | ||||
|     return await this.runMethod('patch', url, body) | ||||
|     return await this.make('patch', url, body) | ||||
|   } | ||||
| 
 | ||||
|   async put(url: string, body?: unknown): Promise<any> { | ||||
|     return await this.runMethod('put', url, body) | ||||
|     return await this.make('put', url, body) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import * as cache from '../models/cache.ts' | ||||
| 
 | ||||
| interface IInit { | ||||
|   useCache?: boolean | ||||
|  | @ -25,18 +24,13 @@ export class Base { | |||
|     this.useCache = useCache | ||||
|     const cacheID = restURLfuncArgs.join(':') | ||||
|     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) { | ||||
|         return cached | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const resp = await fetch(endpoint(...restURLfuncArgs), { | ||||
|       headers: { | ||||
|         Authorization: `Bot ${client.token}` | ||||
|       } | ||||
|     }) | ||||
|     const jsonParsed = await resp.json() | ||||
|     const jsonParsed = await client.rest.get(endpoint(...restURLfuncArgs)) | ||||
| 
 | ||||
|     return new this(client, jsonParsed) | ||||
|   } | ||||
|  | @ -47,12 +41,7 @@ export class Base { | |||
|   ): Promise<this> { | ||||
|     const oldOne = Object.assign(Object.create(this), this) | ||||
| 
 | ||||
|     const resp = await fetch(endpoint(...restURLfuncArgs), { | ||||
|       headers: { | ||||
|         Authorization: `Bot ${client.token}` | ||||
|       } | ||||
|     }) | ||||
|     const jsonParsed = await resp.json() | ||||
|     const jsonParsed = await client.rest.get(endpoint(...restURLfuncArgs)) | ||||
| 
 | ||||
|     this.readFromData(jsonParsed) | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ import { Client } from '../models/client.ts' | |||
| import { EmojiPayload } from '../types/emoji.ts' | ||||
| import { USER } from '../types/endpoint.ts' | ||||
| import { Base } from './base.ts' | ||||
| import { Guild } from './guild.ts' | ||||
| import { User } from './user.ts' | ||||
| 
 | ||||
| export class Emoji extends Base { | ||||
|  | @ -9,6 +10,7 @@ export class Emoji extends Base { | |||
|   name: string | ||||
|   roles?: string[] | ||||
|   user?: User | ||||
|   guild?: Guild | ||||
|   requireColons?: boolean | ||||
|   managed?: boolean | ||||
|   animated?: boolean | ||||
|  | @ -20,17 +22,16 @@ export class Emoji extends Base { | |||
|     } else return `<a:${this.name}:${this.id}>` | ||||
|   } | ||||
| 
 | ||||
|   toString(): string { | ||||
|     return this.getEmojiString | ||||
|   } | ||||
| 
 | ||||
|   constructor (client: Client, data: EmojiPayload) { | ||||
|     super(client, data) | ||||
|     this.id = data.id | ||||
|     this.name = data.name | ||||
|     if (data.user !== undefined) this.user = new User(this.client, data.user) | ||||
|     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.managed = data.managed | ||||
|     this.animated = data.animated | ||||
|  |  | |||
|  | @ -6,7 +6,8 @@ import { VoiceState } from './voiceState.ts' | |||
| import { RolesManager } from '../managers/roles.ts' | ||||
| import { GuildChannelsManager } from '../managers/guildChannels.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 { | ||||
|   id: string | ||||
|  | @ -27,7 +28,7 @@ export class Guild extends Base { | |||
|   defaultMessageNotifications?: string | ||||
|   explicitContentFilter?: string | ||||
|   roles: RolesManager | ||||
|   emojis: EmojisManager | ||||
|   emojis: GuildEmojisManager | ||||
|   features?: GuildFeatures[] | ||||
|   mfaLevel?: string | ||||
|   applicationID?: string | ||||
|  | @ -66,7 +67,7 @@ export class Guild extends Base { | |||
|       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) { | ||||
|       this.name = data.name | ||||
|  | @ -160,6 +161,10 @@ export class Guild extends Base { | |||
|       //   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)
 | ||||
|       //   ) ?? this.emojis
 | ||||
|       this.features = data.features ?? this.features | ||||
|       this.mfaLevel = data.mfa_level ?? this.mfaLevel | ||||
|       this.systemChannelID = data.system_channel_id ?? this.systemChannelID | ||||
|  | @ -205,4 +210,8 @@ export class Guild extends Base { | |||
|         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, | ||||
|   Overwrite | ||||
| } from '../types/channel.ts' | ||||
| import { Guild } from "./guild.ts" | ||||
| import { Guild } from './guild.ts' | ||||
| 
 | ||||
| export class CategoryChannel extends Channel { | ||||
|   guildID: string | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { GuildNewsChannelPayload, Overwrite } from '../types/channel.ts' | ||||
| import { Guild } from "./guild.ts" | ||||
| import { Guild } from './guild.ts' | ||||
| import { TextChannel } from './textChannel.ts' | ||||
| 
 | ||||
| export class NewsChannel extends TextChannel { | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { GuildTextChannelPayload, Overwrite } from '../types/channel.ts' | ||||
| import { TextChannel } from './textChannel.ts' | ||||
| import { Guild } from "./guild.ts" | ||||
| import { Guild } from './guild.ts' | ||||
| 
 | ||||
| export class GuildTextChannel extends TextChannel { | ||||
|   guildID: string | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { GuildVoiceChannelPayload, Overwrite } from '../types/channel.ts' | ||||
| import { Channel } from './channel.ts' | ||||
| import { Guild } from "./guild.ts" | ||||
| import { Guild } from './guild.ts' | ||||
| 
 | ||||
| export class VoiceChannel extends Channel { | ||||
|   bitrate: string | ||||
|  |  | |||
|  | @ -1,26 +1,28 @@ | |||
| import cache from '../models/cache.ts' | ||||
| import { Client } from '../models/client.ts' | ||||
| import { MemberPayload } from '../types/guild.ts' | ||||
| import { Base } from './base.ts' | ||||
| import { Role } from './role.ts' | ||||
| import { User } from './user.ts' | ||||
| 
 | ||||
| export class Member extends Base { | ||||
|   id: string | ||||
|   user: User | ||||
|   nick?: string | ||||
|   roles: string[] | ||||
|   roleIDs: string[] | ||||
|   roles: Role[] = [] | ||||
|   joinedAt: string | ||||
|   premiumSince?: string | ||||
|   deaf: boolean | ||||
|   mute: boolean | ||||
| 
 | ||||
|   constructor (client: Client, data: MemberPayload) { | ||||
|   constructor (client: Client, data: MemberPayload, user: User) { | ||||
|     super(client) | ||||
|     this.id = data.user.id | ||||
|     this.user = | ||||
|       cache.get('user', data.user.id) ?? new User(this.client, data.user) | ||||
|     this.user = user | ||||
|     // this.user =
 | ||||
|     //   cache.get('user', data.user.id) ?? new User(this.client, data.user)
 | ||||
|     this.nick = data.nick | ||||
|     this.roles = data.roles | ||||
|     this.roleIDs = data.roles | ||||
|     this.joinedAt = data.joined_at | ||||
|     this.premiumSince = data.premium_since | ||||
|     this.deaf = data.deaf | ||||
|  | @ -32,7 +34,7 @@ export class Member extends Base { | |||
|   protected readFromData (data: MemberPayload): void { | ||||
|     super.readFromData(data.user) | ||||
|     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.premiumSince = data.premium_since ?? this.premiumSince | ||||
|     this.deaf = data.deaf ?? this.deaf | ||||
|  |  | |||
|  | @ -14,10 +14,10 @@ import { User } from './user.ts' | |||
| import { Member } from './member.ts' | ||||
| import { Embed } from './embed.ts' | ||||
| import { CHANNEL_MESSAGE } from '../types/endpoint.ts' | ||||
| import { MessageMentions } from "./messageMentions.ts" | ||||
| import { TextChannel } from "./textChannel.ts" | ||||
| import { DMChannel } from "./dmChannel.ts" | ||||
| import { Guild } from "./guild.ts" | ||||
| import { MessageMentions } from './messageMentions.ts' | ||||
| import { TextChannel } from './textChannel.ts' | ||||
| import { DMChannel } from './dmChannel.ts' | ||||
| import { Guild } from './guild.ts' | ||||
| 
 | ||||
| export class Message extends Base { | ||||
|   // 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 { CHANNEL_MESSAGE, CHANNEL_MESSAGES } from '../types/endpoint.ts' | ||||
| import { Channel } from './channel.ts' | ||||
| import { Embed } from "./embed.ts" | ||||
| import { Embed } from './embed.ts' | ||||
| import { Message } from './message.ts' | ||||
| import { MessageMentions } from './messageMentions.ts' | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| import { CommandClient, Intents } from '../../mod.ts'; | ||||
| import PingCommand from "./cmds/ping.ts"; | ||||
| import { CommandClient, Intents } from '../../mod.ts' | ||||
| import PingCommand from './cmds/ping.ts' | ||||
| import AddEmojiCommand from './cmds/addemoji.ts' | ||||
| import UserinfoCommand from './cmds/userinfo.ts' | ||||
| import { TOKEN } from './config.ts' | ||||
| 
 | ||||
| const client = new CommandClient({ | ||||
|  | @ -13,6 +15,10 @@ client.on('ready', () => { | |||
|   console.log(`[Login] Logged in as ${client.user?.tag}!`) | ||||
| }) | ||||
| 
 | ||||
| client.on("commandError", console.error) | ||||
| 
 | ||||
| client.commands.add(PingCommand) | ||||
| client.commands.add(UserinfoCommand) | ||||
| client.commands.add(AddEmojiCommand) | ||||
| 
 | ||||
| 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 { CommandContext } from "../../models/command.ts"; | ||||
| import { Command } from '../../../mod.ts' | ||||
| import { CommandContext } from '../../models/command.ts' | ||||
| 
 | ||||
| export default class PingCommand extends Command { | ||||
|   name = "ping" | ||||
|   dmOnly = true | ||||
| 
 | ||||
|   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' | ||||
| 
 | ||||
| const client = new Client({ | ||||
|  |  | |||
|  | @ -14,6 +14,8 @@ const GUILD_WIDGET = (guildID: string): string => | |||
|   `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/widget` | ||||
| const GUILD_EMOJI = (guildID: string, emojiID: string): string => | ||||
|   `${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 => | ||||
|   `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/guilds/${guildID}/roles/${roleID}` | ||||
| const GUILD_ROLES = (guildID: string): string => | ||||
|  | @ -172,8 +174,6 @@ const TEAM_ICON = (teamID: string, iconID: string): string => | |||
| // Emoji Endpoints
 | ||||
| const EMOJI = (guildID: string, emojiID: string): string => | ||||
|   `${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
 | ||||
| const TEMPLATE = (templateCODE: string): string => | ||||
|  | @ -259,7 +259,7 @@ export default [ | |||
|   ACHIEVEMENT_ICON, | ||||
|   TEAM_ICON, | ||||
|   EMOJI, | ||||
|   EMOJIS, | ||||
|   GUILD_EMOJIS, | ||||
|   TEMPLATE, | ||||
|   INVITE, | ||||
|   VOICE_REGIONS | ||||
|  | @ -305,6 +305,7 @@ export { | |||
|   CHANNEL_PIN, | ||||
|   CHANNEL_PINS, | ||||
|   CHANNEL_PERMISSION, | ||||
|   GUILD_EMOJIS, | ||||
|   CHANNEL_TYPING, | ||||
|   GROUP_RECIPIENT, | ||||
|   CURRENT_USER, | ||||
|  | @ -333,7 +334,6 @@ export { | |||
|   ACHIEVEMENT_ICON, | ||||
|   TEAM_ICON, | ||||
|   EMOJI, | ||||
|   EMOJIS, | ||||
|   TEMPLATE, | ||||
|   INVITE, | ||||
|   VOICE_REGIONS | ||||
|  |  | |||
|  | @ -14,9 +14,9 @@ import { GroupDMChannel } from '../structures/groupChannel.ts' | |||
| import { CategoryChannel } from '../structures/guildCategoryChannel.ts' | ||||
| import { NewsChannel } from '../structures/guildNewsChannel.ts' | ||||
| import { VoiceChannel } from '../structures/guildVoiceChannel.ts' | ||||
| import { Guild } from "../structures/guild.ts" | ||||
| import { GuildTextChannel } from "../structures/guildTextChannel.ts" | ||||
| import { TextChannel } from "../structures/textChannel.ts" | ||||
| import { Guild } from '../structures/guild.ts' | ||||
| import { GuildTextChannel } from '../structures/guildTextChannel.ts' | ||||
| import { TextChannel } from '../structures/textChannel.ts' | ||||
| 
 | ||||
| const getChannelByType = ( | ||||
|   client: Client, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue