Merge branch 'main' into main
This commit is contained in:
		
						commit
						a427c8887d
					
				
					 51 changed files with 327 additions and 241 deletions
				
			
		|  | @ -6,18 +6,20 @@ export const channelPinsUpdate: GatewayEventHandler = async ( | |||
|   gateway: Gateway, | ||||
|   d: ChannelPinsUpdatePayload | ||||
| ) => { | ||||
|   const after: | ||||
|   const before: | ||||
|     | TextChannel | ||||
|     | undefined = await gateway.client.channels.get<TextChannel>(d.channel_id) | ||||
|   if (after !== undefined) { | ||||
|     const before = after.refreshFromData({ | ||||
|       last_pin_timestamp: d.last_pin_timestamp | ||||
|     }) | ||||
| 
 | ||||
|   if (before !== undefined) { | ||||
|     const raw = await gateway.client.channels._get(d.channel_id) | ||||
|     if (raw === undefined) return | ||||
|     await gateway.client.channels.set( | ||||
|       after.id, | ||||
|       raw.id, | ||||
|       Object.assign(raw, { last_pin_timestamp: d.last_pin_timestamp }) | ||||
|     ) | ||||
|     const after = ((await gateway.client.channels.get( | ||||
|       d.channel_id | ||||
|     )) as unknown) as TextChannel | ||||
|     gateway.client.emit('channelPinsUpdate', before, after) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -2,15 +2,13 @@ import { Guild } from '../../structures/guild.ts' | |||
| import { GuildPayload } from '../../types/guild.ts' | ||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| 
 | ||||
| export const guildDelte: GatewayEventHandler = async ( | ||||
| export const guildDelete: GatewayEventHandler = async ( | ||||
|   gateway: Gateway, | ||||
|   d: GuildPayload | ||||
| ) => { | ||||
|   const guild: Guild | undefined = await gateway.client.guilds.get(d.id) | ||||
| 
 | ||||
|   if (guild !== undefined) { | ||||
|     guild.refreshFromData(d) | ||||
| 
 | ||||
|     await guild.members.flush() | ||||
|     await guild.channels.flush() | ||||
|     await guild.roles.flush() | ||||
|  |  | |||
|  | @ -6,9 +6,9 @@ export const guildUpdate: GatewayEventHandler = async ( | |||
|   gateway: Gateway, | ||||
|   d: GuildPayload | ||||
| ) => { | ||||
|   const after: Guild | undefined = await gateway.client.guilds.get(d.id) | ||||
|   if (after === undefined) return | ||||
|   const before = after.refreshFromData(d) | ||||
|   const before: Guild | undefined = await gateway.client.guilds.get(d.id) | ||||
|   if (before === undefined) return | ||||
|   await gateway.client.guilds.set(d.id, d) | ||||
|   const after = ((await gateway.client.guilds.get(d.id)) as unknown) as Guild | ||||
|   gateway.client.emit('guildUpdate', before, after) | ||||
| } | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import { channelDelete } from './channelDelete.ts' | |||
| import { channelUpdate } from './channelUpdate.ts' | ||||
| import { channelPinsUpdate } from './channelPinsUpdate.ts' | ||||
| import { guildCreate } from './guildCreate.ts' | ||||
| import { guildDelte as guildDelete } from './guildDelete.ts' | ||||
| import { guildDelete } from './guildDelete.ts' | ||||
| import { guildUpdate } from './guildUpdate.ts' | ||||
| import { guildBanAdd } from './guildBanAdd.ts' | ||||
| import { ready } from './ready.ts' | ||||
|  |  | |||
|  | @ -24,8 +24,11 @@ export interface RequestMembersOptions { | |||
|   users?: string[] | ||||
| } | ||||
| 
 | ||||
| export const RECONNECT_REASON = 'harmony-reconnect' | ||||
| 
 | ||||
| /** | ||||
|  * Handles Discord gateway connection. | ||||
|  * | ||||
|  * You should not use this and rather use Client class. | ||||
|  */ | ||||
| class Gateway { | ||||
|  | @ -42,6 +45,7 @@ class Gateway { | |||
|   private heartbeatServerResponded = false | ||||
|   client: Client | ||||
|   cache: GatewayCache | ||||
|   private timedIdentify: number | null = null | ||||
| 
 | ||||
|   constructor(client: Client, token: string, intents: GatewayIntents[]) { | ||||
|     this.token = token | ||||
|  | @ -108,10 +112,20 @@ class Gateway { | |||
| 
 | ||||
|       case GatewayOpcodes.INVALID_SESSION: | ||||
|         // Because we know this gonna be bool
 | ||||
|         this.debug(`Invalid Session! Identifying with forced new session`) | ||||
|         // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
 | ||||
|         // eslint-disable-next-line @typescript-eslint/promise-function-async
 | ||||
|         setTimeout(() => this.sendIdentify(true), 3000) | ||||
|         this.debug( | ||||
|           `Invalid Session received! Resumeable? ${d === true ? 'Yes' : 'No'}` | ||||
|         ) | ||||
|         if (d !== true) { | ||||
|           this.debug(`Session was invalidated, deleting from cache`) | ||||
|           await this.cache.delete('session_id') | ||||
|           await this.cache.delete('seq') | ||||
|           this.sessionID = undefined | ||||
|           this.sequenceID = undefined | ||||
|         } | ||||
|         this.timedIdentify = setTimeout(async () => { | ||||
|           this.timedIdentify = null | ||||
|           await this.sendIdentify(!(d as boolean)) | ||||
|         }, 5000) | ||||
|         break | ||||
| 
 | ||||
|       case GatewayOpcodes.DISPATCH: { | ||||
|  | @ -150,6 +164,7 @@ class Gateway { | |||
|   } | ||||
| 
 | ||||
|   private async onclose(event: CloseEvent): Promise<void> { | ||||
|     if (event.reason === RECONNECT_REASON) return | ||||
|     this.debug(`Connection Closed with code: ${event.code}`) | ||||
| 
 | ||||
|     if (event.code === GatewayCloseCodes.UNKNOWN_ERROR) { | ||||
|  | @ -179,7 +194,7 @@ class Gateway { | |||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|       this.reconnect() | ||||
|     } else if (event.code === GatewayCloseCodes.SHARDING_REQUIRED) { | ||||
|       throw new Error("Couldn't connect. Sharding is requried!") | ||||
|       throw new Error("Couldn't connect. Sharding is required!") | ||||
|     } else if (event.code === GatewayCloseCodes.INVALID_API_VERSION) { | ||||
|       throw new Error("Invalid API Version was used. This shouldn't happen!") | ||||
|     } else if (event.code === GatewayCloseCodes.INVALID_INTENTS) { | ||||
|  | @ -190,9 +205,12 @@ class Gateway { | |||
|       this.debug( | ||||
|         'Unknown Close code, probably connection error. Reconnecting in 5s.' | ||||
|       ) | ||||
|       if (this.timedIdentify !== null) { | ||||
|         clearTimeout(this.timedIdentify) | ||||
|         this.debug('Timed Identify found. Cleared timeout.') | ||||
|       } | ||||
|       await delay(5000) | ||||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|       this.reconnect() | ||||
|       await this.reconnect(true) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | @ -215,13 +233,14 @@ class Gateway { | |||
|         `Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}` | ||||
|       ) | ||||
|       this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`) | ||||
|       if (forceNewSession === undefined || !forceNewSession) { | ||||
|         const sessionIDCached = await this.cache.get('session_id') | ||||
|         if (sessionIDCached !== undefined) { | ||||
|           this.debug(`Found Cached SessionID: ${sessionIDCached}`) | ||||
|           this.sessionID = sessionIDCached | ||||
|           return await this.sendResume() | ||||
|         } | ||||
|     } else this.debug('Skipping /gateway/bot because bot: false') | ||||
| 
 | ||||
|     if (forceNewSession === undefined || !forceNewSession) { | ||||
|       const sessionIDCached = await this.cache.get('session_id') | ||||
|       if (sessionIDCached !== undefined) { | ||||
|         this.debug(`Found Cached SessionID: ${sessionIDCached}`) | ||||
|         this.sessionID = sessionIDCached | ||||
|         return await this.sendResume() | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | @ -254,6 +273,7 @@ class Gateway { | |||
|       } | ||||
|     } | ||||
| 
 | ||||
|     this.debug('Sending Identify payload...') | ||||
|     this.send({ | ||||
|       op: GatewayOpcodes.IDENTIFY, | ||||
|       d: payload | ||||
|  | @ -261,6 +281,10 @@ class Gateway { | |||
|   } | ||||
| 
 | ||||
|   private async sendResume(): Promise<void> { | ||||
|     if (this.sessionID === undefined) { | ||||
|       this.sessionID = await this.cache.get('session_id') | ||||
|       if (this.sessionID === undefined) return await this.sendIdentify() | ||||
|     } | ||||
|     this.debug(`Preparing to resume with Session: ${this.sessionID}`) | ||||
|     if (this.sequenceID === undefined) { | ||||
|       const cached = await this.cache.get('seq') | ||||
|  | @ -275,6 +299,7 @@ class Gateway { | |||
|         seq: this.sequenceID ?? null | ||||
|       } | ||||
|     } | ||||
|     this.debug('Sending Resume payload...') | ||||
|     this.send(resumePayload) | ||||
|   } | ||||
| 
 | ||||
|  | @ -304,13 +329,16 @@ class Gateway { | |||
| 
 | ||||
|   async reconnect(forceNew?: boolean): Promise<void> { | ||||
|     clearInterval(this.heartbeatIntervalID) | ||||
|     if (forceNew === undefined || !forceNew) | ||||
|     if (forceNew === true) { | ||||
|       await this.cache.delete('session_id') | ||||
|     this.close() | ||||
|       await this.cache.delete('seq') | ||||
|     } | ||||
|     this.close(1000, RECONNECT_REASON) | ||||
|     this.initWebsocket() | ||||
|   } | ||||
| 
 | ||||
|   initWebsocket(): void { | ||||
|     this.debug('Initializing WebSocket...') | ||||
|     this.websocket = new WebSocket( | ||||
|       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
 | ||||
|       `${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`, | ||||
|  | @ -323,8 +351,8 @@ class Gateway { | |||
|     this.websocket.onerror = this.onerror.bind(this) | ||||
|   } | ||||
| 
 | ||||
|   close(): void { | ||||
|     this.websocket.close(1000) | ||||
|   close(code: number = 1000, reason?: string): void { | ||||
|     return this.websocket.close(code, reason) | ||||
|   } | ||||
| 
 | ||||
|   send(data: GatewayResponse): boolean { | ||||
|  | @ -361,6 +389,7 @@ class Gateway { | |||
|     if (this.heartbeatServerResponded) { | ||||
|       this.heartbeatServerResponded = false | ||||
|     } else { | ||||
|       this.debug('Found dead connection, reconnecting...') | ||||
|       clearInterval(this.heartbeatIntervalID) | ||||
|       // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||
|       this.reconnect() | ||||
|  |  | |||
|  | @ -1,6 +1,11 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { Collection } from '../utils/collection.ts' | ||||
| 
 | ||||
| /** | ||||
|  * Managers handle caching data. And also some REST Methods as required. | ||||
|  * | ||||
|  * You should not be making Managers yourself. | ||||
|  */ | ||||
| export class BaseManager<T, T2> { | ||||
|   client: Client | ||||
|   /** Cache Name or Key used to differentiate caches */ | ||||
|  | @ -14,30 +19,36 @@ export class BaseManager<T, T2> { | |||
|     this.DataType = DataType | ||||
|   } | ||||
| 
 | ||||
|   /** Get raw value from a cache (payload) */ | ||||
|   async _get(key: string): Promise<T | undefined> { | ||||
|     return this.client.cache.get(this.cacheName, key) | ||||
|   } | ||||
| 
 | ||||
|   /** Get a value from Cache */ | ||||
|   async get(key: string): Promise<T2 | undefined> { | ||||
|     const raw = await this._get(key) | ||||
|     if (raw === undefined) return | ||||
|     return new this.DataType(this.client, raw) | ||||
|   } | ||||
| 
 | ||||
|   /** Set a value to Cache */ | ||||
|   async set(key: string, value: T): Promise<any> { | ||||
|     return this.client.cache.set(this.cacheName, key, value) | ||||
|   } | ||||
| 
 | ||||
|   /** Delete a key from Cache */ | ||||
|   async delete(key: string): Promise<boolean> { | ||||
|     return this.client.cache.delete(this.cacheName, key) | ||||
|   } | ||||
| 
 | ||||
|   /** Get an Array of values from Cache */ | ||||
|   async array(): Promise<undefined | T2[]> { | ||||
|     let arr = await (this.client.cache.array(this.cacheName) as T[]) | ||||
|     if (arr === undefined) arr = [] | ||||
|     return arr.map((e) => new this.DataType(this.client, e)) as any | ||||
|   } | ||||
| 
 | ||||
|   /** Get a Collection of values from Cache */ | ||||
|   async collection(): Promise<Collection<string, T2>> { | ||||
|     const arr = await this.array() | ||||
|     if (arr === undefined) return new Collection() | ||||
|  | @ -49,6 +60,7 @@ export class BaseManager<T, T2> { | |||
|     return collection | ||||
|   } | ||||
| 
 | ||||
|   /** Delete everything from Cache */ | ||||
|   flush(): any { | ||||
|     return this.client.cache.deleteCache(this.cacheName) | ||||
|   } | ||||
|  |  | |||
|  | @ -2,8 +2,10 @@ import { Client } from '../models/client.ts' | |||
| import { Collection } from '../utils/collection.ts' | ||||
| import { BaseManager } from './base.ts' | ||||
| 
 | ||||
| /** Child Managers validate data from their parents i.e. from Managers */ | ||||
| export class BaseChildManager<T, T2> { | ||||
|   client: Client | ||||
|   /** Parent Manager */ | ||||
|   parent: BaseManager<T, T2> | ||||
| 
 | ||||
|   constructor(client: Client, parent: BaseManager<T, T2>) { | ||||
|  | @ -32,8 +34,8 @@ export class BaseChildManager<T, T2> { | |||
|     if (arr === undefined) return new Collection() | ||||
|     const collection = new Collection() | ||||
|     for (const elem of arr) { | ||||
|       // @ts-expect-error
 | ||||
|       collection.set(elem.id, elem) | ||||
|       // any is required here. Else you would need ts-ignore or expect-error.
 | ||||
|       collection.set((elem as any).id, elem) | ||||
|     } | ||||
|     return collection | ||||
|   } | ||||
|  |  | |||
|  | @ -43,6 +43,7 @@ export class ChannelsManager extends BaseManager<ChannelPayload, Channel> { | |||
|     return result | ||||
|   } | ||||
| 
 | ||||
|   /** Fetch a Channel by ID, cache it, resolve it */ | ||||
|   async fetch<T = Channel>(id: string): Promise<T> { | ||||
|     return await new Promise((resolve, reject) => { | ||||
|       this.client.rest | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ export class EmojisManager extends BaseManager<EmojiPayload, Emoji> { | |||
|     return emoji | ||||
|   } | ||||
| 
 | ||||
|   /** Fetch an Emoji by Guild ID and Emoji ID, cache it and resolve it */ | ||||
|   async fetch(guildID: string, id: string): Promise<Emoji> { | ||||
|     return await new Promise((resolve, reject) => { | ||||
|       this.client.rest | ||||
|  |  | |||
|  | @ -1,5 +1,10 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| 
 | ||||
| /** | ||||
|  * Cache Manager used for Caching values related to Gateway connection | ||||
|  * | ||||
|  * In case of Redis, this will persistently cache session ID and seq for fast resumes. | ||||
|  */ | ||||
| export class GatewayCache { | ||||
|   client: Client | ||||
|   cacheName: string = 'discord_gateway_cache' | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { MessageReaction } from '../structures/messageReaction.ts' | ||||
| import { UserManager } from './users.ts' | ||||
| import { UsersManager } from './users.ts' | ||||
| 
 | ||||
| export class ReactionUsersManager extends UserManager { | ||||
| export class ReactionUsersManager extends UsersManager { | ||||
|   reaction: MessageReaction | ||||
| 
 | ||||
|   constructor(client: Client, reaction: MessageReaction) { | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import { USER } from '../types/endpoint.ts' | |||
| import { UserPayload } from '../types/user.ts' | ||||
| import { BaseManager } from './base.ts' | ||||
| 
 | ||||
| export class UserManager extends BaseManager<UserPayload, User> { | ||||
| export class UsersManager extends BaseManager<UserPayload, User> { | ||||
|   constructor(client: Client) { | ||||
|     super(client, 'users', User) | ||||
|   } | ||||
|  |  | |||
|  | @ -5,19 +5,30 @@ import { | |||
|   RedisConnectOptions | ||||
| } from 'https://denopkg.com/keroxp/deno-redis/mod.ts' | ||||
| 
 | ||||
| /** | ||||
|  * ICacheAdapter is the interface to be implemented by Cache Adapters for them to be usable with Harmony. | ||||
|  * | ||||
|  * Methods can return Promises too. | ||||
|  */ | ||||
| export interface ICacheAdapter { | ||||
|   /** Get a key from a Cache */ | ||||
|   get: (cacheName: string, key: string) => Promise<any> | any | ||||
|   /** Set a key to value in a Cache Name with optional expire value in MS */ | ||||
|   set: ( | ||||
|     cacheName: string, | ||||
|     key: string, | ||||
|     value: any, | ||||
|     expire?: number | ||||
|   ) => Promise<any> | any | ||||
|   /** Delete a key from a Cache */ | ||||
|   delete: (cacheName: string, key: string) => Promise<boolean> | boolean | ||||
|   /** Get array of all values in a Cache */ | ||||
|   array: (cacheName: string) => undefined | any[] | Promise<any[] | undefined> | ||||
|   /** Entirely delete a Cache */ | ||||
|   deleteCache: (cacheName: string) => any | ||||
| } | ||||
| 
 | ||||
| /** Default Cache Adapter for in-memory caching. */ | ||||
| export class DefaultCacheAdapter implements ICacheAdapter { | ||||
|   data: { | ||||
|     [name: string]: Collection<string, any> | ||||
|  | @ -65,6 +76,7 @@ export class DefaultCacheAdapter implements ICacheAdapter { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| /** Redis Cache Adatper for using Redis as a cache-provider. */ | ||||
| export class RedisCacheAdapter implements ICacheAdapter { | ||||
|   _redis: Promise<Redis> | ||||
|   redis?: Redis | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import { Gateway } from '../gateway/index.ts' | |||
| import { RESTManager } from './rest.ts' | ||||
| import EventEmitter from 'https://deno.land/std@0.74.0/node/events.ts' | ||||
| import { DefaultCacheAdapter, ICacheAdapter } from './cacheAdapter.ts' | ||||
| import { UserManager } from '../managers/users.ts' | ||||
| import { UsersManager } from '../managers/users.ts' | ||||
| import { GuildManager } from '../managers/guilds.ts' | ||||
| import { ChannelsManager } from '../managers/channels.ts' | ||||
| import { ClientPresence } from '../structures/presence.ts' | ||||
|  | @ -36,15 +36,6 @@ export interface ClientOptions { | |||
|   fetchUncachedReactions?: boolean | ||||
| } | ||||
| 
 | ||||
| export declare interface Client { | ||||
|   on: <U extends string>(event: U, listener: ClientEvents[U]) => this | ||||
| 
 | ||||
|   emit: <U extends string>( | ||||
|     event: U, | ||||
|     ...args: Parameters<ClientEvents[U]> | ||||
|   ) => boolean | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Discord Client. | ||||
|  */ | ||||
|  | @ -72,7 +63,7 @@ export class Client extends EventEmitter { | |||
|   /** Whether to fetch Uncached Message of Reaction or not? */ | ||||
|   fetchUncachedReactions: boolean = false | ||||
| 
 | ||||
|   users: UserManager = new UserManager(this) | ||||
|   users: UsersManager = new UsersManager(this) | ||||
|   guilds: GuildManager = new GuildManager(this) | ||||
|   channels: ChannelsManager = new ChannelsManager(this) | ||||
|   emojis: EmojisManager = new EmojisManager(this) | ||||
|  | @ -84,6 +75,18 @@ export class Client extends EventEmitter { | |||
|   /** Client's presence. Startup one if set before connecting */ | ||||
|   presence: ClientPresence = new ClientPresence() | ||||
| 
 | ||||
|   private readonly _untypedOn = this.on | ||||
| 
 | ||||
|   private readonly _untypedEmit = this.emit | ||||
| 
 | ||||
|   public on = <K extends string>(event: K, listener: ClientEvents[K]): this => | ||||
|     this._untypedOn(event, listener) | ||||
| 
 | ||||
|   public emit = <K extends string>( | ||||
|     event: K, | ||||
|     ...args: Parameters<ClientEvents[K]> | ||||
|   ): boolean => this._untypedEmit(event, ...args) | ||||
| 
 | ||||
|   constructor(options: ClientOptions = {}) { | ||||
|     super() | ||||
|     this.token = options.token | ||||
|  | @ -105,7 +108,12 @@ export class Client extends EventEmitter { | |||
|       this.fetchUncachedReactions = true | ||||
|   } | ||||
| 
 | ||||
|   /** Set Cache Adapter */ | ||||
|   /** | ||||
|    * Set Cache Adapter | ||||
|    * | ||||
|    * Should NOT set after bot is already logged in or using current cache. | ||||
|    * Please look into using `cache` option. | ||||
|    */ | ||||
|   setAdapter(adapter: ICacheAdapter): Client { | ||||
|     this.cache = adapter | ||||
|     return this | ||||
|  |  | |||
|  | @ -403,12 +403,14 @@ export class CategoriesManager { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| /** Parsed Command object */ | ||||
| export interface ParsedCommand { | ||||
|   name: string | ||||
|   args: string[] | ||||
|   argString: string | ||||
| } | ||||
| 
 | ||||
| /** Parses a Command to later look for. */ | ||||
| export const parseCommand = ( | ||||
|   client: CommandClient, | ||||
|   msg: Message, | ||||
|  |  | |||
|  | @ -11,19 +11,33 @@ import { ExtensionsManager } from './extensions.ts' | |||
| 
 | ||||
| type PrefixReturnType = string | string[] | Promise<string | string[]> | ||||
| 
 | ||||
| /** Command Client options extending Client Options to provide a lot of Commands-related customizations */ | ||||
| export interface CommandClientOptions extends ClientOptions { | ||||
|   /** Global prefix(s) of the bot. */ | ||||
|   prefix: string | string[] | ||||
|   /** Whether to enable mention prefix or not. */ | ||||
|   mentionPrefix?: boolean | ||||
|   /** Method to get a Guild's custom prefix(s). */ | ||||
|   getGuildPrefix?: (guildID: string) => PrefixReturnType | ||||
|   /** Method to get a User's custom prefix(s). */ | ||||
|   getUserPrefix?: (userID: string) => PrefixReturnType | ||||
|   /** Method to check if certain Guild is blacklisted from using Commands. */ | ||||
|   isGuildBlacklisted?: (guildID: string) => boolean | Promise<boolean> | ||||
|   /** Method to check if certain User is blacklisted from using Commands. */ | ||||
|   isUserBlacklisted?: (guildID: string) => boolean | Promise<boolean> | ||||
|   /** Method to check if certain Channel is blacklisted from using Commands. */ | ||||
|   isChannelBlacklisted?: (guildID: string) => boolean | Promise<boolean> | ||||
|   /** Allow spaces after prefix? Recommended with Mention Prefix ON. */ | ||||
|   spacesAfterPrefix?: boolean | ||||
|   /** Better Arguments regex to split at every whitespace. */ | ||||
|   betterArgs?: boolean | ||||
|   /** List of Bot's Owner IDs whom can access `ownerOnly` commands. */ | ||||
|   owners?: string[] | ||||
|   /** Whether to allow Bots to use Commands or not, not allowed by default. */ | ||||
|   allowBots?: boolean | ||||
|   /** Whether to allow Commands in DMs or not, allowed by default. */ | ||||
|   allowDMs?: boolean | ||||
|   /** Whether Commands should be case-sensitive or not, not by default. */ | ||||
|   caseSensitive?: boolean | ||||
| } | ||||
| 
 | ||||
|  | @ -88,6 +102,7 @@ export class CommandClient extends Client implements CommandClientOptions { | |||
|     ) | ||||
|   } | ||||
| 
 | ||||
|   /** Process a Message to Execute Command. */ | ||||
|   async processMessage(msg: Message): Promise<any> { | ||||
|     if (!this.allowBots && msg.author.bot === true) return | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { CommandClient } from './commandClient.ts' | |||
| 
 | ||||
| export type ExtensionEventCallback = (ext: Extension, ...args: any[]) => any | ||||
| 
 | ||||
| /** Extension Commands Manager */ | ||||
| export class ExtensionCommands { | ||||
|   extension: Extension | ||||
| 
 | ||||
|  | @ -11,12 +12,14 @@ export class ExtensionCommands { | |||
|     this.extension = ext | ||||
|   } | ||||
| 
 | ||||
|   /** Get a list of Extension's Commands */ | ||||
|   get list(): Collection<string, Command> { | ||||
|     return this.extension.client.commands.list.filter( | ||||
|       (c) => c.extension?.name === this.extension.name | ||||
|     ) | ||||
|   } | ||||
| 
 | ||||
|   /** Get an Extension Command */ | ||||
|   get(cmd: string): Command | undefined { | ||||
|     const find = this.extension.client.commands.find(cmd) | ||||
|     // linter sucks
 | ||||
|  | @ -26,12 +29,14 @@ export class ExtensionCommands { | |||
|     else return find | ||||
|   } | ||||
| 
 | ||||
|   /** Add an Extension Command */ | ||||
|   add(Cmd: Command | typeof Command): boolean { | ||||
|     const cmd = Cmd instanceof Command ? Cmd : new Cmd() | ||||
|     cmd.extension = this.extension | ||||
|     return this.extension.client.commands.add(cmd) | ||||
|   } | ||||
| 
 | ||||
|   /** Delete an Extension Command */ | ||||
|   delete(cmd: Command | string): boolean { | ||||
|     const find = this.extension.client.commands.find( | ||||
|       typeof cmd === 'string' ? cmd : cmd.name | ||||
|  | @ -45,6 +50,7 @@ export class ExtensionCommands { | |||
|     else return this.extension.client.commands.delete(find) | ||||
|   } | ||||
| 
 | ||||
|   /** Delete all Commands of an Extension */ | ||||
|   deleteAll(): void { | ||||
|     for (const [cmd] of this.list) { | ||||
|       this.delete(cmd) | ||||
|  | @ -52,17 +58,23 @@ export class ExtensionCommands { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| /** Customizable, isolated and pluggable Extensions are a great way of writing certain Modules independent of others */ | ||||
| export class Extension { | ||||
|   client: CommandClient | ||||
|   /** Name of the Extension */ | ||||
|   name: string = '' | ||||
|   /** Description of the Extension */ | ||||
|   description?: string | ||||
|   /** Extensions's Commands Manager */ | ||||
|   commands: ExtensionCommands = new ExtensionCommands(this) | ||||
|   /** Events registered by this Extension */ | ||||
|   events: { [name: string]: (...args: any[]) => {} } = {} | ||||
| 
 | ||||
|   constructor(client: CommandClient) { | ||||
|     this.client = client | ||||
|   } | ||||
| 
 | ||||
|   /** Listen for an Event through Extension. */ | ||||
|   listen(event: string, cb: ExtensionEventCallback): boolean { | ||||
|     if (this.events[event] !== undefined) return false | ||||
|     else { | ||||
|  | @ -76,10 +88,13 @@ export class Extension { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** Method called upon loading of an Extension */ | ||||
|   load(): any {} | ||||
|   /** Method called upon unloading of an Extension */ | ||||
|   unload(): any {} | ||||
| } | ||||
| 
 | ||||
| /** Extensions Manager for CommandClient */ | ||||
| export class ExtensionsManager { | ||||
|   client: CommandClient | ||||
|   list: Collection<string, Extension> = new Collection() | ||||
|  | @ -88,14 +103,17 @@ export class ExtensionsManager { | |||
|     this.client = client | ||||
|   } | ||||
| 
 | ||||
|   /** Get an Extension by name */ | ||||
|   get(ext: string): Extension | undefined { | ||||
|     return this.list.get(ext) | ||||
|   } | ||||
| 
 | ||||
|   /** Check whether an Extension exists or not */ | ||||
|   exists(ext: string): boolean { | ||||
|     return this.get(ext) !== undefined | ||||
|   } | ||||
| 
 | ||||
|   /** Load an Extension onto Command Client */ | ||||
|   load(ext: Extension | typeof Extension): void { | ||||
|     // eslint-disable-next-line new-cap
 | ||||
|     if (!(ext instanceof Extension)) ext = new ext(this.client) | ||||
|  | @ -105,6 +123,7 @@ export class ExtensionsManager { | |||
|     ext.load() | ||||
|   } | ||||
| 
 | ||||
|   /** Unload an Extension from Command Client */ | ||||
|   unload(ext: Extension | string): boolean { | ||||
|     const name = typeof ext === 'string' ? ext : ext.name | ||||
|     const extension = this.get(name) | ||||
|  |  | |||
|  | @ -65,7 +65,7 @@ export class RESTManager { | |||
|     this.handleRateLimits() | ||||
|   } | ||||
| 
 | ||||
|   async checkQueues(): Promise<void> { | ||||
|   private async checkQueues(): Promise<void> { | ||||
|     Object.entries(this.queues).forEach(([key, value]) => { | ||||
|       if (value.length === 0) { | ||||
|         // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
 | ||||
|  | @ -74,7 +74,7 @@ export class RESTManager { | |||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   queue(request: QueuedItem): void { | ||||
|   private queue(request: QueuedItem): void { | ||||
|     const route = request.url.substring( | ||||
|       // eslint seriously?
 | ||||
|       // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
 | ||||
|  | @ -91,7 +91,7 @@ export class RESTManager { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async processQueue(): Promise<void> { | ||||
|   private async processQueue(): Promise<void> { | ||||
|     if (Object.keys(this.queues).length !== 0 && !this.globalRateLimit) { | ||||
|       await Promise.allSettled( | ||||
|         Object.values(this.queues).map(async (pathQueue) => { | ||||
|  | @ -139,7 +139,7 @@ export class RESTManager { | |||
|     } else this.processing = false | ||||
|   } | ||||
| 
 | ||||
|   prepare(body: any, method: RequestMethods): { [key: string]: any } { | ||||
|   private prepare(body: any, method: RequestMethods): { [key: string]: any } { | ||||
|     const headers: RequestHeaders = { | ||||
|       'User-Agent': `DiscordBot (harmony, https://github.com/harmony-org/harmony)` | ||||
|     } | ||||
|  | @ -192,7 +192,7 @@ export class RESTManager { | |||
|     return data | ||||
|   } | ||||
| 
 | ||||
|   async isRateLimited(url: string): Promise<number | false> { | ||||
|   private async isRateLimited(url: string): Promise<number | false> { | ||||
|     const global = this.rateLimits.get('global') | ||||
|     const rateLimited = this.rateLimits.get(url) | ||||
|     const now = Date.now() | ||||
|  | @ -207,7 +207,10 @@ export class RESTManager { | |||
|     return false | ||||
|   } | ||||
| 
 | ||||
|   processHeaders(url: string, headers: Headers): string | null | undefined { | ||||
|   private processHeaders( | ||||
|     url: string, | ||||
|     headers: Headers | ||||
|   ): string | null | undefined { | ||||
|     let rateLimited = false | ||||
| 
 | ||||
|     const global = headers.get('x-ratelimit-global') | ||||
|  | @ -257,7 +260,7 @@ export class RESTManager { | |||
|     return rateLimited ? bucket : undefined | ||||
|   } | ||||
| 
 | ||||
|   async handleStatusCode( | ||||
|   private async handleStatusCode( | ||||
|     response: Response, | ||||
|     body: any, | ||||
|     data: { [key: string]: any } | ||||
|  | @ -304,6 +307,15 @@ export class RESTManager { | |||
|     } else throw new DiscordAPIError('Request - Unknown Error') | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Make a Request to Discord API | ||||
|    * @param method HTTP Method to use | ||||
|    * @param url URL of the Request | ||||
|    * @param body Body to send with Request | ||||
|    * @param maxRetries Number of Max Retries to perform | ||||
|    * @param bucket BucketID of the Request | ||||
|    * @param rawResponse Whether to get Raw Response or body itself | ||||
|    */ | ||||
|   async make( | ||||
|     method: RequestMethods, | ||||
|     url: string, | ||||
|  | @ -389,7 +401,7 @@ export class RESTManager { | |||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   async handleRateLimits(): Promise<void> { | ||||
|   private async handleRateLimits(): Promise<void> { | ||||
|     const now = Date.now() | ||||
|     this.rateLimits.forEach((value, key) => { | ||||
|       if (value.resetAt > now) return | ||||
|  | @ -398,6 +410,7 @@ export class RESTManager { | |||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   /** Make a GET Request to API */ | ||||
|   async get( | ||||
|     url: string, | ||||
|     body?: unknown, | ||||
|  | @ -408,6 +421,7 @@ export class RESTManager { | |||
|     return await this.make('get', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| 
 | ||||
|   /** Make a POST Request to API */ | ||||
|   async post( | ||||
|     url: string, | ||||
|     body?: unknown, | ||||
|  | @ -418,6 +432,7 @@ export class RESTManager { | |||
|     return await this.make('post', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| 
 | ||||
|   /** Make a DELETE Request to API */ | ||||
|   async delete( | ||||
|     url: string, | ||||
|     body?: unknown, | ||||
|  | @ -428,6 +443,7 @@ export class RESTManager { | |||
|     return await this.make('delete', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| 
 | ||||
|   /** Make a PATCH Request to API */ | ||||
|   async patch( | ||||
|     url: string, | ||||
|     body?: unknown, | ||||
|  | @ -438,6 +454,7 @@ export class RESTManager { | |||
|     return await this.make('patch', url, body, maxRetries, bucket, rawResponse) | ||||
|   } | ||||
| 
 | ||||
|   /** Make a PUT Request to API */ | ||||
|   async put( | ||||
|     url: string, | ||||
|     body?: unknown, | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ export class VoiceClient { | |||
|   } | ||||
| 
 | ||||
|   async connect(): Promise<VoiceClient> { | ||||
|     // TODO(DjDeveloperr): Actually understand what the hell docs say
 | ||||
|     // TODO(DjDeveloperr): understand docs
 | ||||
|     return this | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,63 +1,9 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| 
 | ||||
| interface IInit { | ||||
|   useCache?: boolean | ||||
|   endpoint: (...restURLfuncArgs: string[]) => string | ||||
|   restURLfuncArgs: string[] | ||||
| } | ||||
| 
 | ||||
| export class Base { | ||||
|   client: Client | ||||
|   static cacheName?: string | ||||
|   propertyConverterOverride: { [k: string]: string } = {} | ||||
|   static useCache?: boolean = true | ||||
|   static restFunc?: (...restURLfuncArgs: string[]) => string | ||||
| 
 | ||||
|   constructor(client: Client, _data?: any) { | ||||
|     this.client = client | ||||
|   } | ||||
| 
 | ||||
|   static async autoInit( | ||||
|     client: Client, | ||||
|     { useCache, endpoint, restURLfuncArgs }: IInit | ||||
|   ): Promise<any> { | ||||
|     this.useCache = useCache | ||||
|     const cacheID = restURLfuncArgs.join(':') | ||||
|     if (this.useCache !== undefined) { | ||||
|       const cached = await client.cache.get( | ||||
|         this.cacheName ?? this.name, | ||||
|         cacheID | ||||
|       ) | ||||
|       if (cached !== undefined) { | ||||
|         return cached | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const jsonParsed = await client.rest.get(endpoint(...restURLfuncArgs)) | ||||
| 
 | ||||
|     return new this(client, jsonParsed) | ||||
|   } | ||||
| 
 | ||||
|   async refreshFromAPI( | ||||
|     client: Client, | ||||
|     { endpoint, restURLfuncArgs }: IInit | ||||
|   ): Promise<this> { | ||||
|     const oldOne = Object.assign(Object.create(this), this) | ||||
| 
 | ||||
|     const jsonParsed = await client.rest.get(endpoint(...restURLfuncArgs)) | ||||
| 
 | ||||
|     this.readFromData(jsonParsed) | ||||
| 
 | ||||
|     return oldOne | ||||
|   } | ||||
| 
 | ||||
|   refreshFromData(data: { [k: string]: any }): this { | ||||
|     const oldOne = Object.assign(Object.create(this), this) | ||||
|     this.readFromData(data) | ||||
|     return oldOne | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: { [k: string]: any }): void {} | ||||
| 
 | ||||
|   // toJSON() {}
 | ||||
| } | ||||
|  |  | |||
|  | @ -14,12 +14,9 @@ export class Channel extends Base { | |||
|     super(client, data) | ||||
|     this.type = data.type | ||||
|     this.id = data.id | ||||
|     // TODO: Cache in Gateway Event Code
 | ||||
|     // this.client.channels.set(this.id, data)
 | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: ChannelPayload): void { | ||||
|     super.readFromData(data) | ||||
|   readFromData(data: ChannelPayload): void { | ||||
|     this.type = data.type ?? this.type | ||||
|     this.id = data.id ?? this.id | ||||
|   } | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ export class DMChannel extends TextChannel { | |||
|     this.recipients = data.recipients | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: DMChannelPayload): void { | ||||
|   readFromData(data: DMChannelPayload): void { | ||||
|     super.readFromData(data) | ||||
|     this.recipients = data.recipients ?? this.recipients | ||||
|   } | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| 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' | ||||
|  | @ -38,8 +37,7 @@ export class Emoji extends Base { | |||
|     this.available = data.available | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: EmojiPayload): void { | ||||
|     super.readFromData(data) | ||||
|   readFromData(data: EmojiPayload): void { | ||||
|     this.id = data.id ?? this.id | ||||
|     this.name = data.name ?? this.name | ||||
|     this.roles = data.roles ?? this.roles | ||||
|  | @ -47,11 +45,6 @@ export class Emoji extends Base { | |||
|     this.managed = data.managed ?? this.managed | ||||
|     this.animated = data.animated ?? this.animated | ||||
|     this.available = data.available ?? this.available | ||||
|     if (data.user !== undefined && data.user.id !== this.user?.id) { | ||||
|       User.autoInit(this.client, { | ||||
|         endpoint: USER, | ||||
|         restURLfuncArgs: [data.user.id] | ||||
|       }).then((user) => (this.user = user)) | ||||
|     } | ||||
|     if (data.user !== undefined) this.user = new User(this.client, data.user) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ export class GroupDMChannel extends Channel { | |||
|     // cache.set('groupchannel', this.id, this)
 | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: GroupDMChannelPayload): void { | ||||
|   readFromData(data: GroupDMChannelPayload): void { | ||||
|     super.readFromData(data) | ||||
|     this.name = data.name ?? this.name | ||||
|     this.icon = data.icon ?? this.icon | ||||
|  |  | |||
|  | @ -91,8 +91,7 @@ export class GuildBans { | |||
|       null, | ||||
|       true | ||||
|     ) | ||||
| 
 | ||||
|     if (res.status !== 204) throw new Error('Failed to Add Guild Ban') | ||||
|     if (res.response.status !== 204) throw new Error('Failed to Add Guild Ban') | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -108,7 +107,7 @@ export class GuildBans { | |||
|       true | ||||
|     ) | ||||
| 
 | ||||
|     if (res.status !== 204) return false | ||||
|     if (res.response.status !== 204) return false | ||||
|     else return true | ||||
|   } | ||||
| } | ||||
|  | @ -219,8 +218,7 @@ export class Guild extends Base { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: GuildPayload): void { | ||||
|     super.readFromData(data) | ||||
|   readFromData(data: GuildPayload): void { | ||||
|     this.id = data.id ?? this.id | ||||
|     this.unavailable = data.unavailable ?? this.unavailable | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ export class CategoryChannel extends Channel { | |||
|     // cache.set('guildcategorychannel', this.id, this)
 | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: GuildChannelCategoryPayload): void { | ||||
|   readFromData(data: GuildChannelCategoryPayload): void { | ||||
|     super.readFromData(data) | ||||
|     this.guildID = data.guild_id ?? this.guildID | ||||
|     this.name = data.name ?? this.name | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ export class NewsChannel extends TextChannel { | |||
|     this.topic = data.topic | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: GuildNewsChannelPayload): void { | ||||
|   readFromData(data: GuildNewsChannelPayload): void { | ||||
|     super.readFromData(data) | ||||
|     this.guildID = data.guild_id ?? this.guildID | ||||
|     this.name = data.name ?? this.name | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ export class VoiceChannel extends Channel { | |||
|     // cache.set('guildvoicechannel', this.id, this)
 | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: GuildVoiceChannelPayload): void { | ||||
|   readFromData(data: GuildVoiceChannelPayload): void { | ||||
|     super.readFromData(data) | ||||
|     this.bitrate = data.bitrate ?? this.bitrate | ||||
|     this.userLimit = data.user_limit ?? this.userLimit | ||||
|  |  | |||
|  | @ -31,8 +31,7 @@ export class Invite extends Base { | |||
|     this.approximatePresenceCount = data.approximate_presence_count | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: InvitePayload): void { | ||||
|     super.readFromData(data) | ||||
|   readFromData(data: InvitePayload): void { | ||||
|     this.code = data.code ?? this.code | ||||
|     this.guild = data.guild ?? this.guild | ||||
|     this.channel = data.channel ?? this.channel | ||||
|  |  | |||
|  | @ -56,8 +56,7 @@ export class Member extends Base { | |||
|     return this.user.nickMention | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: MemberPayload): void { | ||||
|     super.readFromData(data.user) | ||||
|   readFromData(data: MemberPayload): void { | ||||
|     this.nick = data.nick ?? this.nick | ||||
|     this.joinedAt = data.joined_at ?? this.joinedAt | ||||
|     this.premiumSince = data.premium_since ?? this.premiumSince | ||||
|  |  | |||
|  | @ -81,8 +81,7 @@ export class Message extends Base { | |||
|     this.channel = channel | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: MessagePayload): void { | ||||
|     super.readFromData(data) | ||||
|   readFromData(data: MessagePayload): void { | ||||
|     this.channelID = data.channel_id ?? this.channelID | ||||
|     this.guildID = data.guild_id ?? this.guildID | ||||
|     this.content = data.content ?? this.content | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ import { Guild } from './guild.ts' | |||
| import { User } from './user.ts' | ||||
| import { Client } from '../models/client.ts' | ||||
| 
 | ||||
| enum ActivityTypes { | ||||
| export enum ActivityTypes { | ||||
|   PLAYING = 0, | ||||
|   STREAMING = 1, | ||||
|   LISTENING = 2, | ||||
|  |  | |||
|  | @ -33,8 +33,7 @@ export class Role extends Base { | |||
|     this.mentionable = data.mentionable | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: RolePayload): void { | ||||
|     super.readFromData(data) | ||||
|   readFromData(data: RolePayload): void { | ||||
|     this.name = data.name ?? this.name | ||||
|     this.color = data.color ?? this.color | ||||
|     this.hoist = data.hoist ?? this.hoist | ||||
|  |  | |||
|  | @ -1,7 +1,12 @@ | |||
| export class Snowflake { | ||||
|   snowflake: bigint | ||||
|   id: string | ||||
| 
 | ||||
|   constructor(id: string) { | ||||
|     this.snowflake = BigInt.asUintN(64, BigInt(id)) | ||||
|     this.id = id | ||||
|   } | ||||
| 
 | ||||
|   get snowflake(): bigint { | ||||
|     return BigInt.asUintN(64, BigInt(this.id)) | ||||
|   } | ||||
| 
 | ||||
|   get timestamp(): string { | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ export class TextChannel extends Channel { | |||
|     this.lastPinTimestamp = data.last_pin_timestamp | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: TextChannelPayload): void { | ||||
|   readFromData(data: TextChannelPayload): void { | ||||
|     super.readFromData(data) | ||||
|     this.lastMessageID = data.last_message_id ?? this.lastMessageID | ||||
|     this.lastPinTimestamp = data.last_pin_timestamp ?? this.lastPinTimestamp | ||||
|  | @ -152,7 +152,7 @@ export class GuildTextChannel extends TextChannel { | |||
|     this.rateLimit = data.rate_limit_per_user | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: GuildTextChannelPayload): void { | ||||
|   readFromData(data: GuildTextChannelPayload): void { | ||||
|     super.readFromData(data) | ||||
|     this.guildID = data.guild_id ?? this.guildID | ||||
|     this.name = data.name ?? this.name | ||||
|  |  | |||
|  | @ -67,8 +67,7 @@ export class User extends Base { | |||
|     this.publicFlags = new UserFlagsManager(data.public_flags) | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: UserPayload): void { | ||||
|     super.readFromData(data) | ||||
|   readFromData(data: UserPayload): void { | ||||
|     this.username = data.username ?? this.username | ||||
|     this.discriminator = data.discriminator ?? this.discriminator | ||||
|     this.avatar = data.avatar ?? this.avatar | ||||
|  |  | |||
|  | @ -43,8 +43,7 @@ export class VoiceState extends Base { | |||
|     this.suppress = data.suppress | ||||
|   } | ||||
| 
 | ||||
|   protected readFromData(data: VoiceStatePayload): void { | ||||
|     super.readFromData(data) | ||||
|   readFromData(data: VoiceStatePayload): void { | ||||
|     this.sessionID = data.session_id ?? this.sessionID | ||||
|     this.deaf = data.deaf ?? this.deaf | ||||
|     this.mute = data.mute ?? this.mute | ||||
|  |  | |||
|  | @ -2,9 +2,9 @@ import { | |||
|   Command, | ||||
|   CommandClient, | ||||
|   Intents, | ||||
|   GuildChannel, | ||||
|   CommandContext, | ||||
|   Extension | ||||
|   Extension, | ||||
|   GuildChannel | ||||
| } from '../../mod.ts' | ||||
| import { Invite } from '../structures/invite.ts' | ||||
| import { TOKEN } from './config.ts' | ||||
|  |  | |||
|  | @ -1 +1,2 @@ | |||
| export const TOKEN = '' | ||||
| export const TOKEN = '' | ||||
| export const WEBHOOK = '' | ||||
|  | @ -92,4 +92,10 @@ client.on('messageCreate', async (msg: Message) => { | |||
|   } | ||||
| }) | ||||
| 
 | ||||
| client.connect(TOKEN, Intents.None) | ||||
| client.connect(TOKEN, Intents.All) | ||||
| 
 | ||||
| // OLD: Was a way to reproduce reconnect infinite loop
 | ||||
| // setTimeout(() => {
 | ||||
| //   console.log('[DEBUG] Reconnect')
 | ||||
| //   client.gateway?.reconnect()
 | ||||
| // }, 1000 * 4)
 | ||||
|  |  | |||
|  | @ -103,9 +103,9 @@ export interface MessageOption { | |||
|   embed?: Embed | ||||
|   file?: Attachment | ||||
|   allowedMentions?: { | ||||
|     parse: 'everyone' | 'users' | 'roles' | ||||
|     roles: string[] | ||||
|     users: string[] | ||||
|     parse?: 'everyone' | 'users' | 'roles' | ||||
|     roles?: string[] | ||||
|     users?: string[] | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| // Ported from https://github.com/discordjs/discord.js/blob/master/src/util/BitField.js
 | ||||
| export type BitFieldResolvable = number | BitField | string | BitField[] | ||||
| 
 | ||||
| /** Bit Field utility to work with Bits and Flags */ | ||||
| export class BitField { | ||||
|   flags: { [name: string]: number } = {} | ||||
|   bitfield: number | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| /* eslint-disable @typescript-eslint/naming-convention */ | ||||
| import { Client } from '../models/client.ts' | ||||
| 
 | ||||
| /** Gets Discord Build info for self-bot support */ | ||||
| export const getBuildInfo = ( | ||||
|   client: Client | ||||
| ): { | ||||
|  |  | |||
|  | @ -1,25 +1,32 @@ | |||
| /** Enhanced Map with various utility functions */ | ||||
| export class Collection<K = string, V = any> extends Map<K, V> { | ||||
|   /** Set a key to value in Collection */ | ||||
|   set(key: K, value: V): this { | ||||
|     return super.set(key, value) | ||||
|   } | ||||
| 
 | ||||
|   /** Get Array of values in Collection */ | ||||
|   array(): V[] { | ||||
|     return [...this.values()] | ||||
|   } | ||||
| 
 | ||||
|   /** Get first value in Collection */ | ||||
|   first(): V { | ||||
|     return this.values().next().value | ||||
|   } | ||||
| 
 | ||||
|   /** Get last value in Collection */ | ||||
|   last(): V { | ||||
|     return [...this.values()][this.size - 1] | ||||
|   } | ||||
| 
 | ||||
|   /** Get a random value from Collection */ | ||||
|   random(): V { | ||||
|     const arr = [...this.values()] | ||||
|     return arr[Math.floor(Math.random() * arr.length)] | ||||
|   } | ||||
| 
 | ||||
|   /** Find a value from Collection using callback */ | ||||
|   find(callback: (value: V, key: K) => boolean): V | undefined { | ||||
|     for (const key of this.keys()) { | ||||
|       const value = this.get(key) as V | ||||
|  | @ -28,6 +35,7 @@ export class Collection<K = string, V = any> extends Map<K, V> { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** Filter out the Collection using callback */ | ||||
|   filter(callback: (value: V, key: K) => boolean): Collection<K, V> { | ||||
|     const relevant = new Collection<K, V>() | ||||
|     this.forEach((value, key) => { | ||||
|  | @ -36,6 +44,7 @@ export class Collection<K = string, V = any> extends Map<K, V> { | |||
|     return relevant | ||||
|   } | ||||
| 
 | ||||
|   /** Map the collection */ | ||||
|   map<T>(callback: (value: V, key: K) => T): T[] { | ||||
|     const results = [] | ||||
|     for (const key of this.keys()) { | ||||
|  | @ -45,6 +54,7 @@ export class Collection<K = string, V = any> extends Map<K, V> { | |||
|     return results | ||||
|   } | ||||
| 
 | ||||
|   /** Check if any of the values/keys in Collection satisfy callback */ | ||||
|   some(callback: (value: V, key: K) => boolean): boolean { | ||||
|     for (const key of this.keys()) { | ||||
|       const value = this.get(key) as V | ||||
|  | @ -53,6 +63,7 @@ export class Collection<K = string, V = any> extends Map<K, V> { | |||
|     return false | ||||
|   } | ||||
| 
 | ||||
|   /** Check if every value/key in Collection satisfy callback */ | ||||
|   every(callback: (value: V, key: K) => boolean): boolean { | ||||
|     for (const key of this.keys()) { | ||||
|       const value = this.get(key) as V | ||||
|  | @ -61,6 +72,7 @@ export class Collection<K = string, V = any> extends Map<K, V> { | |||
|     return true | ||||
|   } | ||||
| 
 | ||||
|   /** Reduce the Collection to a single value */ | ||||
|   reduce<T>( | ||||
|     callback: (accumulator: T, value: V, key: K) => T, | ||||
|     initialValue?: T | ||||
|  | @ -75,10 +87,12 @@ export class Collection<K = string, V = any> extends Map<K, V> { | |||
|     return accumulator | ||||
|   } | ||||
| 
 | ||||
|   /** Create a Collection from an Object */ | ||||
|   static fromObject<V>(object: { [key: string]: V }): Collection<string, V> { | ||||
|     return new Collection<string, V>(Object.entries(object)) | ||||
|   } | ||||
| 
 | ||||
|   /** Convert Collection to an object */ | ||||
|   toObject(): { [name: string]: V } { | ||||
|     return Object.fromEntries(this) | ||||
|   } | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| /** Delay by `ms` miliseconds */ | ||||
| export const delay = async (ms: number): Promise<true> => | ||||
|   await new Promise((resolve, reject) => { | ||||
|     setTimeout(() => resolve(true), ms) | ||||
|  |  | |||
|  | @ -45,6 +45,7 @@ export type EveryChannelPayloadTypes = | |||
|   | GuildVoiceChannelPayload | ||||
|   | EveryTextChannelPayloadTypes | ||||
| 
 | ||||
| /** Get appropriate Channel structure by its type */ | ||||
| const getChannelByType = ( | ||||
|   client: Client, | ||||
|   data: EveryChannelPayloadTypes, | ||||
|  |  | |||
|  | @ -2,7 +2,8 @@ import { GatewayIntents } from '../types/gateway.ts' | |||
| 
 | ||||
| export type PriviligedIntents = 'GUILD_MEMBERS' | 'GUILD_PRESENCES' | ||||
| 
 | ||||
| // eslint-disable-next-line @typescript-eslint/no-extraneous-class
 | ||||
| /* eslint-disable @typescript-eslint/no-extraneous-class */ | ||||
| /** Utility class for handling Gateway Intents */ | ||||
| export class Intents { | ||||
|   static NonPriviliged: number[] = [ | ||||
|     GatewayIntents.GUILD_MESSAGES, | ||||
|  | @ -38,6 +39,7 @@ export class Intents { | |||
| 
 | ||||
|   static None: number[] = [...Intents.NonPriviliged] | ||||
| 
 | ||||
|   /** Create an Array of Intents easily passing Intents you're priviliged for and disable the ones you don't need */ | ||||
|   static create( | ||||
|     priviliged?: PriviligedIntents[], | ||||
|     disable?: number[] | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ export type PermissionResolvable = | |||
|   | Permissions | ||||
|   | PermissionResolvable[] | ||||
| 
 | ||||
| /** Manages Discord's Bit-based Permissions */ | ||||
| export class Permissions extends BitField { | ||||
|   static DEFAULT = 104324673 | ||||
|   static ALL = Object.values(PermissionFlags).reduce((all, p) => all | p, 0) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue