collectors, rest options and all that
This commit is contained in:
		
							parent
							
								
									e9f461fef4
								
							
						
					
					
						commit
						48976e779b
					
				
					 9 changed files with 381 additions and 39 deletions
				
			
		
							
								
								
									
										4
									
								
								mod.ts
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								mod.ts
									
										
									
									
									
								
							|  | @ -4,7 +4,9 @@ export { Gateway } from './src/gateway/index.ts' | ||||||
| export type { ClientEvents } from './src/gateway/handlers/index.ts' | export type { ClientEvents } from './src/gateway/handlers/index.ts' | ||||||
| export * from './src/models/client.ts' | export * from './src/models/client.ts' | ||||||
| export * from './src/models/slashClient.ts' | export * from './src/models/slashClient.ts' | ||||||
| export { RESTManager } from './src/models/rest.ts' | export { RESTManager, TokenType, HttpResponseCode } from './src/models/rest.ts' | ||||||
|  | export type { RequestHeaders } from './src/models/rest.ts' | ||||||
|  | export type { RESTOptions } from './src/models/rest.ts' | ||||||
| export * from './src/models/cacheAdapter.ts' | export * from './src/models/cacheAdapter.ts' | ||||||
| export { | export { | ||||||
|   Command, |   Command, | ||||||
|  |  | ||||||
|  | @ -348,4 +348,11 @@ export interface ClientEvents { | ||||||
|    * @param message Debug message |    * @param message Debug message | ||||||
|    */ |    */ | ||||||
|   debug: [message: string] |   debug: [message: string] | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Raw event which gives you access to raw events DISPATCH'd from Gateway | ||||||
|  |    * @param evt Event name string | ||||||
|  |    * @param payload Payload JSON of the event | ||||||
|  |    */ | ||||||
|  |   raw: [evt: string, payload: any] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| import { User } from '../structures/user.ts' | import { User } from '../structures/user.ts' | ||||||
| import { GatewayIntents } from '../types/gateway.ts' | import { GatewayIntents } from '../types/gateway.ts' | ||||||
| import { Gateway } from '../gateway/index.ts' | import { Gateway } from '../gateway/index.ts' | ||||||
| import { RESTManager } from './rest.ts' | import { RESTManager, RESTOptions, TokenType } from './rest.ts' | ||||||
| import { EventEmitter } from '../../deps.ts' | import { EventEmitter } from '../../deps.ts' | ||||||
| import { DefaultCacheAdapter, ICacheAdapter } from './cacheAdapter.ts' | import { DefaultCacheAdapter, ICacheAdapter } from './cacheAdapter.ts' | ||||||
| import { UsersManager } from '../managers/users.ts' | import { UsersManager } from '../managers/users.ts' | ||||||
|  | @ -20,6 +20,7 @@ import { Application } from '../structures/application.ts' | ||||||
| import { Invite } from '../structures/invite.ts' | import { Invite } from '../structures/invite.ts' | ||||||
| import { INVITE } from '../types/endpoint.ts' | import { INVITE } from '../types/endpoint.ts' | ||||||
| import { ClientEvents } from '../gateway/handlers/index.ts' | import { ClientEvents } from '../gateway/handlers/index.ts' | ||||||
|  | import type { Collector } from './collectors.ts' | ||||||
| 
 | 
 | ||||||
| /** OS related properties sent with Gateway Identify */ | /** OS related properties sent with Gateway Identify */ | ||||||
| export interface ClientProperties { | export interface ClientProperties { | ||||||
|  | @ -54,6 +55,10 @@ export interface ClientOptions { | ||||||
|   clientProperties?: ClientProperties |   clientProperties?: ClientProperties | ||||||
|   /** Enable/Disable Slash Commands Integration (enabled by default) */ |   /** Enable/Disable Slash Commands Integration (enabled by default) */ | ||||||
|   enableSlash?: boolean |   enableSlash?: boolean | ||||||
|  |   /** Disable taking token from env if not provided (token is taken from env if present by default) */ | ||||||
|  |   disableEnvToken?: boolean | ||||||
|  |   /** Override REST Options */ | ||||||
|  |   restOptions?: RESTOptions | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export declare interface Client { | export declare interface Client { | ||||||
|  | @ -89,7 +94,7 @@ export class Client extends EventEmitter { | ||||||
|   /** Gateway object */ |   /** Gateway object */ | ||||||
|   gateway?: Gateway |   gateway?: Gateway | ||||||
|   /** REST Manager - used to make all requests */ |   /** REST Manager - used to make all requests */ | ||||||
|   rest: RESTManager = new RESTManager(this) |   rest: RESTManager | ||||||
|   /** User which Client logs in to, undefined until logs in */ |   /** User which Client logs in to, undefined until logs in */ | ||||||
|   user?: User |   user?: User | ||||||
|   /** WebSocket ping of Client */ |   /** WebSocket ping of Client */ | ||||||
|  | @ -118,8 +123,6 @@ export class Client extends EventEmitter { | ||||||
|   channels: ChannelsManager = new ChannelsManager(this) |   channels: ChannelsManager = new ChannelsManager(this) | ||||||
|   emojis: EmojisManager = new EmojisManager(this) |   emojis: EmojisManager = new EmojisManager(this) | ||||||
| 
 | 
 | ||||||
|   /** Whether the REST Manager will use Canary API or not */ |  | ||||||
|   canary: boolean = false |  | ||||||
|   /** Client's presence. Startup one if set before connecting */ |   /** Client's presence. Startup one if set before connecting */ | ||||||
|   presence: ClientPresence = new ClientPresence() |   presence: ClientPresence = new ClientPresence() | ||||||
|   _decoratedEvents?: { |   _decoratedEvents?: { | ||||||
|  | @ -140,6 +143,7 @@ export class Client extends EventEmitter { | ||||||
|   shard: number = 0 |   shard: number = 0 | ||||||
|   /** Shard Manager of this Client if Sharded */ |   /** Shard Manager of this Client if Sharded */ | ||||||
|   shardManager?: ShardManager |   shardManager?: ShardManager | ||||||
|  |   collectors: Set<Collector> = new Set() | ||||||
| 
 | 
 | ||||||
|   constructor(options: ClientOptions = {}) { |   constructor(options: ClientOptions = {}) { | ||||||
|     super() |     super() | ||||||
|  | @ -153,7 +157,6 @@ export class Client extends EventEmitter { | ||||||
|         options.presence instanceof ClientPresence |         options.presence instanceof ClientPresence | ||||||
|           ? options.presence |           ? options.presence | ||||||
|           : new ClientPresence(options.presence) |           : new ClientPresence(options.presence) | ||||||
|     if (options.canary === true) this.canary = true |  | ||||||
|     if (options.messageCacheLifetime !== undefined) |     if (options.messageCacheLifetime !== undefined) | ||||||
|       this.messageCacheLifetime = options.messageCacheLifetime |       this.messageCacheLifetime = options.messageCacheLifetime | ||||||
|     if (options.reactionCacheLifetime !== undefined) |     if (options.reactionCacheLifetime !== undefined) | ||||||
|  | @ -185,6 +188,27 @@ export class Client extends EventEmitter { | ||||||
|       client: this, |       client: this, | ||||||
|       enabled: options.enableSlash |       enabled: options.enableSlash | ||||||
|     }) |     }) | ||||||
|  | 
 | ||||||
|  |     if (this.token === undefined) { | ||||||
|  |       try { | ||||||
|  |         const token = Deno.env.get('DISCORD_TOKEN') | ||||||
|  |         if (token !== undefined) { | ||||||
|  |           this.token = token | ||||||
|  |           this.debug('Info', 'Found token in ENV') | ||||||
|  |         } | ||||||
|  |       } catch (e) {} | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const restOptions: RESTOptions = { | ||||||
|  |       token: () => this.token, | ||||||
|  |       tokenType: TokenType.Bot, | ||||||
|  |       canary: options.canary, | ||||||
|  |       client: this | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (options.restOptions !== undefined) | ||||||
|  |       Object.assign(restOptions, options.restOptions) | ||||||
|  |     this.rest = new RESTManager(restOptions) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|  | @ -244,8 +268,8 @@ export class Client extends EventEmitter { | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * This function is used for connecting to discord. |    * This function is used for connecting to discord. | ||||||
|    * @param token Your token. This is required. |    * @param token Your token. This is required if not given in ClientOptions. | ||||||
|    * @param intents Gateway intents in array. This is required. |    * @param intents Gateway intents in array. This is required if not given in ClientOptions. | ||||||
|    */ |    */ | ||||||
|   connect(token?: string, intents?: GatewayIntents[]): void { |   connect(token?: string, intents?: GatewayIntents[]): void { | ||||||
|     if (token === undefined && this.token !== undefined) token = this.token |     if (token === undefined && this.token !== undefined) token = this.token | ||||||
|  | @ -262,9 +286,40 @@ export class Client extends EventEmitter { | ||||||
|     } else if (intents !== undefined && this.intents === undefined) { |     } else if (intents !== undefined && this.intents === undefined) { | ||||||
|       this.intents = intents |       this.intents = intents | ||||||
|     } else throw new Error('No Gateway Intents were provided') |     } else throw new Error('No Gateway Intents were provided') | ||||||
|  | 
 | ||||||
|  |     this.rest.token = token | ||||||
|     this.gateway = new Gateway(this, token, intents) |     this.gateway = new Gateway(this, token, intents) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** Add a new Collector */ | ||||||
|  |   addCollector(collector: Collector): boolean { | ||||||
|  |     if (this.collectors.has(collector)) return false | ||||||
|  |     else { | ||||||
|  |       this.collectors.add(collector) | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Remove a Collector */ | ||||||
|  |   removeCollector(collector: Collector): boolean { | ||||||
|  |     if (!this.collectors.has(collector)) return false | ||||||
|  |     else { | ||||||
|  |       this.collectors.delete(collector) | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   emit(event: keyof ClientEvents, ...args: any[]): boolean { | ||||||
|  |     const collectors: Collector[] = [] | ||||||
|  |     for (const collector of this.collectors.values()) { | ||||||
|  |       if (collector.event === event) collectors.push(collector) | ||||||
|  |     } | ||||||
|  |     if (collectors.length !== 0) { | ||||||
|  |       this.collectors.forEach((collector) => collector._fire(...args)) | ||||||
|  |     } | ||||||
|  |     return super.emit(event, ...args) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /** Wait for an Event (optionally satisfying an event) to occur */ |   /** Wait for an Event (optionally satisfying an event) to occur */ | ||||||
|   async waitFor<K extends keyof ClientEvents>( |   async waitFor<K extends keyof ClientEvents>( | ||||||
|     event: K, |     event: K, | ||||||
|  | @ -293,10 +348,13 @@ export class Client extends EventEmitter { | ||||||
| 
 | 
 | ||||||
| /** Event decorator to create an Event handler from function */ | /** Event decorator to create an Event handler from function */ | ||||||
| export function event(name?: keyof ClientEvents) { | export function event(name?: keyof ClientEvents) { | ||||||
|   return function (client: Client | Extension, prop: keyof ClientEvents) { |   return function ( | ||||||
|  |     client: Client | Extension, | ||||||
|  |     prop: keyof ClientEvents | string | ||||||
|  |   ) { | ||||||
|     const listener = ((client as unknown) as { |     const listener = ((client as unknown) as { | ||||||
|       [name in keyof ClientEvents]: (...args: ClientEvents[name]) => any |       [name in keyof ClientEvents]: (...args: ClientEvents[name]) => any | ||||||
|     })[prop] |     })[name ?? ((prop as unknown) as keyof ClientEvents)] | ||||||
|     if (typeof listener !== 'function') |     if (typeof listener !== 'function') | ||||||
|       throw new Error('@event decorator requires a function') |       throw new Error('@event decorator requires a function') | ||||||
|     if (client._decoratedEvents === undefined) client._decoratedEvents = {} |     if (client._decoratedEvents === undefined) client._decoratedEvents = {} | ||||||
|  |  | ||||||
							
								
								
									
										162
									
								
								src/models/collectors.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								src/models/collectors.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,162 @@ | ||||||
|  | import { Collection } from '../utils/collection.ts' | ||||||
|  | import { EventEmitter } from '../../deps.ts' | ||||||
|  | import type { Client } from './client.ts' | ||||||
|  | 
 | ||||||
|  | export type CollectorFilter = (...args: any[]) => boolean | Promise<boolean> | ||||||
|  | 
 | ||||||
|  | export interface CollectorOptions { | ||||||
|  |   /** Event name to listen for */ | ||||||
|  |   event: string | ||||||
|  |   /** Optionally Client object for deinitOnEnd functionality */ | ||||||
|  |   client?: Client | ||||||
|  |   /** Filter function */ | ||||||
|  |   filter?: CollectorFilter | ||||||
|  |   /** Max entries to collect */ | ||||||
|  |   max?: number | ||||||
|  |   /** Whether or not to de-initialize on end */ | ||||||
|  |   deinitOnEnd?: boolean | ||||||
|  |   /** Timeout to end the Collector if not fulfilled if any filter or max */ | ||||||
|  |   timeout?: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class Collector extends EventEmitter { | ||||||
|  |   client?: Client | ||||||
|  |   private _started: boolean = false | ||||||
|  |   event: string | ||||||
|  |   filter: CollectorFilter = () => true | ||||||
|  |   collected: Collection<string, any[]> = new Collection() | ||||||
|  |   max?: number | ||||||
|  |   deinitOnEnd: boolean = false | ||||||
|  |   timeout?: number | ||||||
|  |   private _timer?: number | ||||||
|  | 
 | ||||||
|  |   get started(): boolean { | ||||||
|  |     return this._started | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   set started(d: boolean) { | ||||||
|  |     if (d !== this._started) { | ||||||
|  |       this._started = d | ||||||
|  |       if (d) this.emit('start') | ||||||
|  |       else { | ||||||
|  |         if (this.deinitOnEnd && this.client !== undefined) | ||||||
|  |           this.deinit(this.client) | ||||||
|  |         this.emit('end') | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   constructor(options: CollectorOptions | string) { | ||||||
|  |     super() | ||||||
|  |     if (typeof options === 'string') this.event = options | ||||||
|  |     else { | ||||||
|  |       this.event = options.event | ||||||
|  |       this.client = options.client | ||||||
|  |       this.filter = options.filter ?? (() => true) | ||||||
|  |       this.max = options.max | ||||||
|  |       this.deinitOnEnd = options.deinitOnEnd ?? false | ||||||
|  |       this.timeout = options.timeout | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Start collecting */ | ||||||
|  |   collect(): Collector { | ||||||
|  |     this.started = true | ||||||
|  |     if (this.client !== undefined) this.init(this.client) | ||||||
|  |     if (this._timer !== undefined) clearTimeout(this._timer) | ||||||
|  |     if (this.timeout !== undefined) { | ||||||
|  |       this._timer = setTimeout(() => { | ||||||
|  |         this.end() | ||||||
|  |       }, this.timeout) | ||||||
|  |     } | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** End collecting */ | ||||||
|  |   end(): Collector { | ||||||
|  |     this.started = false | ||||||
|  |     if (this._timer !== undefined) clearTimeout(this._timer) | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Reset collector and start again */ | ||||||
|  |   reset(): Collector { | ||||||
|  |     this.collected = new Collection() | ||||||
|  |     this.collect() | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Init the Collector on Client */ | ||||||
|  |   init(client: Client): Collector { | ||||||
|  |     this.client = client | ||||||
|  |     client.addCollector(this) | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** De initialize the Collector i.e. remove cleanly */ | ||||||
|  |   deinit(client: Client): Collector { | ||||||
|  |     client.removeCollector(this) | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Checks we may want to perform on an extended version of Collector */ | ||||||
|  |   protected check(..._args: any[]): boolean | Promise<boolean> { | ||||||
|  |     return true | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Fire the Collector */ | ||||||
|  |   async _fire(...args: any[]): Promise<void> { | ||||||
|  |     if (!this.started) return | ||||||
|  |     const check = await this.check(...args) | ||||||
|  |     if (!check) return | ||||||
|  |     const filter = await this.filter(...args) | ||||||
|  |     if (!filter) return | ||||||
|  |     this.collected.set((Number(this.collected.size) + 1).toString(), args) | ||||||
|  |     this.emit('collect', ...args) | ||||||
|  |     if ( | ||||||
|  |       this.max !== undefined && | ||||||
|  |       // linter: send help
 | ||||||
|  |       this.max < Number(this.collected.size) + 1 | ||||||
|  |     ) { | ||||||
|  |       this.end() | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Set filter of the Collector */ | ||||||
|  |   when(filter: CollectorFilter): Collector { | ||||||
|  |     this.filter = filter | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Add a new listener for 'collect' event */ | ||||||
|  |   each(handler: CallableFunction): Collector { | ||||||
|  |     this.on('collect', () => handler()) | ||||||
|  |     return this | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** Returns a Promise resolved when Collector ends or a timeout occurs */ | ||||||
|  |   async wait(timeout: number = this.timeout ?? 0): Promise<Collector> { | ||||||
|  |     return await new Promise((resolve, reject) => { | ||||||
|  |       // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
 | ||||||
|  |       if (!timeout) | ||||||
|  |         throw new Error( | ||||||
|  |           'Timeout is required parameter if not given in CollectorOptions' | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |       let done = false | ||||||
|  |       const onend = (): void => { | ||||||
|  |         done = true | ||||||
|  |         this.removeListener('end', onend) | ||||||
|  |         resolve(this) | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       this.on('end', onend) | ||||||
|  |       setTimeout(() => { | ||||||
|  |         if (!done) { | ||||||
|  |           this.removeListener('end', onend) | ||||||
|  |           reject(new Error('Timeout')) | ||||||
|  |         } | ||||||
|  |       }, timeout) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| import * as baseEndpoints from '../consts/urlsAndVersions.ts' | import * as baseEndpoints from '../consts/urlsAndVersions.ts' | ||||||
| import { Collection } from '../utils/collection.ts' | import { Collection } from '../utils/collection.ts' | ||||||
|  | import { Client } from './client.ts' | ||||||
| 
 | 
 | ||||||
| export type RequestMethods = | export type RequestMethods = | ||||||
|   | 'get' |   | 'get' | ||||||
|  | @ -60,15 +61,23 @@ export type MethodFunction = ( | ||||||
| ) => Promise<any> | ) => Promise<any> | ||||||
| 
 | 
 | ||||||
| export interface APIMap extends MethodFunction { | export interface APIMap extends MethodFunction { | ||||||
|  |   /** Make a GET request to current route */ | ||||||
|   get: APIMap |   get: APIMap | ||||||
|  |   /** Make a POST request to current route */ | ||||||
|   post: APIMap |   post: APIMap | ||||||
|  |   /** Make a PATCH request to current route */ | ||||||
|   patch: APIMap |   patch: APIMap | ||||||
|  |   /** Make a PUT request to current route */ | ||||||
|   put: APIMap |   put: APIMap | ||||||
|  |   /** Make a DELETE request to current route */ | ||||||
|   delete: APIMap |   delete: APIMap | ||||||
|  |   /** Make a HEAD request to current route */ | ||||||
|   head: APIMap |   head: APIMap | ||||||
|  |   /** Continue building API Route */ | ||||||
|   [name: string]: APIMap |   [name: string]: APIMap | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** API Route builder function */ | ||||||
| export const builder = (rest: RESTManager, acum = '/'): APIMap => { | export const builder = (rest: RESTManager, acum = '/'): APIMap => { | ||||||
|   const routes = {} |   const routes = {} | ||||||
|   const proxy = new Proxy(routes, { |   const proxy = new Proxy(routes, { | ||||||
|  | @ -94,25 +103,76 @@ export const builder = (rest: RESTManager, acum = '/'): APIMap => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface RESTOptions { | export interface RESTOptions { | ||||||
|   token?: string |   /** Token to use for authorization */ | ||||||
|  |   token?: string | (() => string | undefined) | ||||||
|  |   /** Headers to patch with if any */ | ||||||
|   headers?: { [name: string]: string | undefined } |   headers?: { [name: string]: string | undefined } | ||||||
|  |   /** Whether to use Canary instance of Discord API or not */ | ||||||
|   canary?: boolean |   canary?: boolean | ||||||
|  |   /** Discord REST API version to use */ | ||||||
|   version?: 6 | 7 | 8 |   version?: 6 | 7 | 8 | ||||||
|  |   /** Token Type to use for Authorization */ | ||||||
|  |   tokenType?: TokenType | ||||||
|  |   /** User Agent to use (Header) */ | ||||||
|  |   userAgent?: string | ||||||
|  |   /** Optional Harmony client */ | ||||||
|  |   client?: Client | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** Token Type for REST API. */ | ||||||
|  | export enum TokenType { | ||||||
|  |   /** Token type for Bot User */ | ||||||
|  |   Bot = 'Bot', | ||||||
|  |   /** Token Type for OAuth2 */ | ||||||
|  |   Bearer = 'Bearer', | ||||||
|  |   /** No Token Type. Can be used for User accounts. */ | ||||||
|  |   None = '' | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** An easier to use interface for interacting with Discord REST API. */ | ||||||
| export class RESTManager { | export class RESTManager { | ||||||
|   client?: RESTOptions |  | ||||||
|   queues: { [key: string]: QueuedItem[] } = {} |   queues: { [key: string]: QueuedItem[] } = {} | ||||||
|   rateLimits = new Collection<string, RateLimit>() |   rateLimits = new Collection<string, RateLimit>() | ||||||
|  |   /** Whether we are globally ratelimited or not */ | ||||||
|   globalRateLimit: boolean = false |   globalRateLimit: boolean = false | ||||||
|  |   /** Whether requests are being processed or not */ | ||||||
|   processing: boolean = false |   processing: boolean = false | ||||||
|  |   /** API Version being used by REST Manager */ | ||||||
|   version: number = 8 |   version: number = 8 | ||||||
|  |   /** | ||||||
|  |    * API Map - easy to use way for interacting with Discord API. | ||||||
|  |    * | ||||||
|  |    * Examples: | ||||||
|  |    * * ```ts
 | ||||||
|  |    *   rest.api.users['123'].get().then(userPayload => doSomething) | ||||||
|  |    *   ``` | ||||||
|  |    * * ```ts
 | ||||||
|  |    *   rest.api.guilds['123'].channels.post({ name: 'my-channel', type: 0 }).then(channelPayload => {}) | ||||||
|  |    *   ``` | ||||||
|  |    */ | ||||||
|   api: APIMap |   api: APIMap | ||||||
|  |   /** Token being used for Authorization */ | ||||||
|  |   token?: string | (() => string | undefined) | ||||||
|  |   /** Token Type of the Token if any */ | ||||||
|  |   tokenType: TokenType = TokenType.Bot | ||||||
|  |   /** Headers object which patch the current ones */ | ||||||
|  |   headers: any = {} | ||||||
|  |   /** Optional custom User Agent (header) */ | ||||||
|  |   userAgent?: string | ||||||
|  |   /** Whether REST Manager is using Canary API */ | ||||||
|  |   canary?: boolean | ||||||
|  |   /** Optional Harmony Client object */ | ||||||
|  |   client?: Client | ||||||
| 
 | 
 | ||||||
|   constructor(client?: RESTOptions) { |   constructor(options?: RESTOptions) { | ||||||
|     this.client = client |  | ||||||
|     this.api = builder(this) |     this.api = builder(this) | ||||||
|     if (client?.version !== undefined) this.version = client.version |     if (options?.token !== undefined) this.token = options.token | ||||||
|  |     if (options?.version !== undefined) this.version = options.version | ||||||
|  |     if (options?.headers !== undefined) this.headers = options.headers | ||||||
|  |     if (options?.tokenType !== undefined) this.tokenType = options.tokenType | ||||||
|  |     if (options?.userAgent !== undefined) this.userAgent = options.userAgent | ||||||
|  |     if (options?.canary !== undefined) this.canary = options.canary | ||||||
|  |     if (options?.client !== undefined) this.client = options.client | ||||||
|     // eslint-disable-next-line @typescript-eslint/no-floating-promises
 |     // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | ||||||
|     this.handleRateLimits() |     this.handleRateLimits() | ||||||
|   } |   } | ||||||
|  | @ -193,13 +253,16 @@ export class RESTManager { | ||||||
| 
 | 
 | ||||||
|   private prepare(body: any, method: RequestMethods): { [key: string]: any } { |   private prepare(body: any, method: RequestMethods): { [key: string]: any } { | ||||||
|     const headers: RequestHeaders = { |     const headers: RequestHeaders = { | ||||||
|       'User-Agent': `DiscordBot (harmony, https://github.com/harmony-org/harmony)` |       'User-Agent': | ||||||
|  |         this.userAgent ?? | ||||||
|  |         `DiscordBot (harmony, https://github.com/harmony-org/harmony)` | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (this.client !== undefined) |     if (this.token !== undefined) { | ||||||
|       headers.Authorization = `Bot ${this.client.token}` |       const token = typeof this.token === 'string' ? this.token : this.token() | ||||||
| 
 |       if (token !== undefined) | ||||||
|     if (this.client?.token === undefined) delete headers.Authorization |         headers.Authorization = `${this.tokenType} ${token}`.trim() | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if (method === 'get' || method === 'head' || method === 'delete') |     if (method === 'get' || method === 'head' || method === 'delete') | ||||||
|       body = undefined |       body = undefined | ||||||
|  | @ -220,9 +283,7 @@ export class RESTManager { | ||||||
|       headers['Content-Type'] = 'application/json' |       headers['Content-Type'] = 'application/json' | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (this.client?.headers !== undefined) |     if (this.headers !== undefined) Object.assign(headers, this.headers) | ||||||
|       Object.assign(headers, this.client.headers) |  | ||||||
| 
 |  | ||||||
|     const data: { [name: string]: any } = { |     const data: { [name: string]: any } = { | ||||||
|       headers, |       headers, | ||||||
|       body: body?.file ?? JSON.stringify(body), |       body: body?.file ?? JSON.stringify(body), | ||||||
|  | @ -305,13 +366,25 @@ export class RESTManager { | ||||||
|     body: any, |     body: any, | ||||||
|     data: { [key: string]: any }, |     data: { [key: string]: any }, | ||||||
|     reject: CallableFunction |     reject: CallableFunction | ||||||
|   ): Promise<undefined> { |   ): Promise<void> { | ||||||
|     const status = response.status |     const status = response.status | ||||||
| 
 | 
 | ||||||
|  |     // We have hit ratelimit - this should not happen
 | ||||||
|  |     if (status === HttpResponseCode.TooManyRequests) { | ||||||
|  |       if (this.client !== undefined) | ||||||
|  |         this.client.emit('rateLimit', { | ||||||
|  |           method: data.method, | ||||||
|  |           url: response.url, | ||||||
|  |           body | ||||||
|  |         }) | ||||||
|  |       reject(new Error('RateLimited')) | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // It's a normal status code... just continue
 | ||||||
|     if ( |     if ( | ||||||
|       (status >= 200 && status < 400) || |       (status >= 200 && status < 400) || | ||||||
|       status === HttpResponseCode.NoContent || |       status === HttpResponseCode.NoContent | ||||||
|       status === HttpResponseCode.TooManyRequests |  | ||||||
|     ) |     ) | ||||||
|       return |       return | ||||||
| 
 | 
 | ||||||
|  | @ -322,9 +395,7 @@ export class RESTManager { | ||||||
| 
 | 
 | ||||||
|     if (status === HttpResponseCode.Unauthorized) |     if (status === HttpResponseCode.Unauthorized) | ||||||
|       reject( |       reject( | ||||||
|         new DiscordAPIError( |         new DiscordAPIError(`Request was Unauthorized. Invalid Token.\n${text}`) | ||||||
|           `Request was not successful (Unauthorized). Invalid Token.\n${text}` |  | ||||||
|         ) |  | ||||||
|       ) |       ) | ||||||
| 
 | 
 | ||||||
|     // At this point we know it is error
 |     // At this point we know it is error
 | ||||||
|  | @ -342,7 +413,7 @@ export class RESTManager { | ||||||
|             } |             } | ||||||
|           }) ?? {} |           }) ?? {} | ||||||
|         ).map((entry) => { |         ).map((entry) => { | ||||||
|           return [entry[0], entry[1]._errors] |           return [entry[0], entry[1]._errors ?? []] | ||||||
|         }) |         }) | ||||||
|       ) |       ) | ||||||
|     } |     } | ||||||
|  | @ -379,7 +450,7 @@ export class RESTManager { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Makes a Request to Discord API |    * Makes a Request to Discord API. | ||||||
|    * @param method HTTP Method to use |    * @param method HTTP Method to use | ||||||
|    * @param url URL of the Request |    * @param url URL of the Request | ||||||
|    * @param body Body to send with Request |    * @param body Body to send with Request | ||||||
|  | @ -422,7 +493,18 @@ export class RESTManager { | ||||||
|           let urlToUse = |           let urlToUse = | ||||||
|             method === 'get' && query !== '' ? `${url}?${query}` : url |             method === 'get' && query !== '' ? `${url}?${query}` : url | ||||||
| 
 | 
 | ||||||
|           if (this.client?.canary === true) { |           // It doesn't start with HTTP, that means it's an incomplete URL
 | ||||||
|  |           if (!urlToUse.startsWith('http')) { | ||||||
|  |             if (!urlToUse.startsWith('/')) urlToUse = `/${urlToUse}` | ||||||
|  |             urlToUse = | ||||||
|  |               // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
 | ||||||
|  |               baseEndpoints.DISCORD_API_URL + | ||||||
|  |               '/v' + | ||||||
|  |               baseEndpoints.DISCORD_API_VERSION + | ||||||
|  |               urlToUse | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           if (this.canary === true && urlToUse.startsWith('http')) { | ||||||
|             const split = urlToUse.split('//') |             const split = urlToUse.split('//') | ||||||
|             urlToUse = split[0] + '//canary.' + split[1] |             urlToUse = split[0] + '//canary.' + split[1] | ||||||
|           } |           } | ||||||
|  |  | ||||||
|  | @ -47,7 +47,9 @@ export class Webhook { | ||||||
|   rest: RESTManager |   rest: RESTManager | ||||||
| 
 | 
 | ||||||
|   get url(): string { |   get url(): string { | ||||||
|     return `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/webhooks/${this.id}/${this.token}` |     return `${DISCORD_API_URL}/v${ | ||||||
|  |       this.rest.version ?? DISCORD_API_VERSION | ||||||
|  |     }/webhooks/${this.id}/${this.token}` | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   constructor(data: WebhookPayload, client?: Client, rest?: RESTManager) { |   constructor(data: WebhookPayload, client?: Client, rest?: RESTManager) { | ||||||
|  | @ -170,7 +172,7 @@ export class Webhook { | ||||||
|    * @param options Options to edit the Webhook. |    * @param options Options to edit the Webhook. | ||||||
|    */ |    */ | ||||||
|   async edit(options: WebhookEditOptions): Promise<Webhook> { |   async edit(options: WebhookEditOptions): Promise<Webhook> { | ||||||
|     if (options.channelID !== undefined && this.rest.client === undefined) |     if (options.channelID !== undefined && this.client === undefined) | ||||||
|       throw new Error('Authentication is required for editing Webhook Channel') |       throw new Error('Authentication is required for editing Webhook Channel') | ||||||
|     if ( |     if ( | ||||||
|       options.avatar !== undefined && |       options.avatar !== undefined && | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import { | ||||||
|   ChannelTypes, |   ChannelTypes, | ||||||
|   GuildTextChannel |   GuildTextChannel | ||||||
| } from '../../mod.ts' | } from '../../mod.ts' | ||||||
|  | import { Collector } from '../models/collectors.ts' | ||||||
| import { TOKEN } from './config.ts' | import { TOKEN } from './config.ts' | ||||||
| 
 | 
 | ||||||
| const client = new Client({ | const client = new Client({ | ||||||
|  | @ -122,6 +123,26 @@ client.on('messageCreate', async (msg: Message) => { | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     msg.channel.send(`Received: ${receivedMsg?.content}`) |     msg.channel.send(`Received: ${receivedMsg?.content}`) | ||||||
|  |   } else if (msg.content.startsWith('!collect') === true) { | ||||||
|  |     let count = parseInt(msg.content.replace(/\D/g, '')) | ||||||
|  |     if (isNaN(count)) count = 5 | ||||||
|  |     await msg.channel.send(`Collecting ${count} messages for 5s`) | ||||||
|  |     const coll = new Collector({ | ||||||
|  |       event: 'messageCreate', | ||||||
|  |       filter: (m) => m.author.id === msg.author.id, | ||||||
|  |       deinitOnEnd: true, | ||||||
|  |       max: count, | ||||||
|  |       timeout: 5000 | ||||||
|  |     }) | ||||||
|  |     coll.init(client) | ||||||
|  |     coll.collect() | ||||||
|  |     coll.on('start', () => msg.channel.send('[COL] Started')) | ||||||
|  |     coll.on('end', () => | ||||||
|  |       msg.channel.send(`[COL] Ended. Collected Size: ${coll.collected.size}`) | ||||||
|  |     ) | ||||||
|  |     coll.on('collect', (msg) => | ||||||
|  |       msg.channel.send(`[COL] Collect: ${msg.content}`) | ||||||
|  |     ) | ||||||
|   } |   } | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,10 +11,7 @@ import { | ||||||
|   GuildTextChannel |   GuildTextChannel | ||||||
| } from '../../mod.ts' | } from '../../mod.ts' | ||||||
| import { LL_IP, LL_PASS, LL_PORT, TOKEN } from './config.ts' | import { LL_IP, LL_PASS, LL_PORT, TOKEN } from './config.ts' | ||||||
| import { | import { Manager, Player } from 'https://deno.land/x/lavadeno/mod.ts' | ||||||
|   Manager, |  | ||||||
|   Player |  | ||||||
| } from '../../deps.ts' |  | ||||||
| import { Interaction } from '../structures/slash.ts' | import { Interaction } from '../structures/slash.ts' | ||||||
| import { slash } from '../models/client.ts' | import { slash } from '../models/client.ts' | ||||||
| // import { SlashCommandOptionType } from '../types/slash.ts'
 | // import { SlashCommandOptionType } from '../types/slash.ts'
 | ||||||
|  |  | ||||||
|  | @ -9,6 +9,11 @@ export class MyClient extends Client { | ||||||
|     console.log(`Logged in as ${this.user?.tag}!`) |     console.log(`Logged in as ${this.user?.tag}!`) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   @event('debug') | ||||||
|  |   debugEvt(txt: string): void { | ||||||
|  |     console.log(txt) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   @slash() |   @slash() | ||||||
|   send(d: Interaction): void { |   send(d: Interaction): void { | ||||||
|     d.respond({ |     d.respond({ | ||||||
|  | @ -92,5 +97,11 @@ export class MyClient extends Client { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const client = new MyClient() | const client = new MyClient({ | ||||||
|  |   presence: { | ||||||
|  |     status: 'dnd', | ||||||
|  |     activity: { name: 'Slash Commands', type: 'LISTENING' } | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  | 
 | ||||||
| client.connect(TOKEN, Intents.None) | client.connect(TOKEN, Intents.None) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue