import { Collection } from '../utils/collection.ts'import type { Client } from '../client/mod.ts'import { RequestMethods, METHODS } from './types.ts'import { Constants } from '../types/constants.ts'import { RESTEndpoints } from './endpoints.ts'import { BucketHandler } from './bucket.ts'import { APIRequest, RequestOptions } from './request.ts'
export type MethodFunction = ( body?: unknown, maxRetries?: number, bucket?: string | null, rawResponse?: boolean, options?: RequestOptions) => Promise<any>
export interface APIMap extends MethodFunction { get: APIMap post: APIMap patch: APIMap put: APIMap delete: APIMap head: APIMap [name: string]: APIMap}
export const builder = (rest: RESTManager, acum = '/'): APIMap => { const routes = {} const proxy = new Proxy(routes, { get: (_, p, __) => { if (p === 'toString') return () => acum if (METHODS.includes(String(p)) === true) { const method = ( rest as unknown as { [name: string]: (...args: unknown[]) => Promise<unknown> } )[String(p)] return async (...args: unknown[]) => await method.bind(rest)( `${Constants.DISCORD_API_URL}/v${rest.version}${acum.substring( 0, acum.length - 1 )}`, ...args ) } return builder(rest, acum + String(p) + '/') } }) return proxy as unknown as APIMap}
export interface RESTOptions { token?: string | (() => string | undefined) headers?: Record<string, string> canary?: boolean version?: 6 | 7 | 8 tokenType?: TokenType userAgent?: string client?: Client requestTimeout?: number retryLimit?: number}
export enum TokenType { Bot = 'Bot', Bearer = 'Bearer', None = ''}
export class RESTManager { version: number = Constants.DISCORD_API_VERSION api: APIMap
#token?: string | (() => string | undefined)
get token(): string | (() => string | undefined) | undefined { return this.#token }
set token(val: string | (() => string | undefined) | undefined) { this.#token = val }
tokenType: TokenType = TokenType.Bot headers: Record<string, string> = {} userAgent?: string canary?: boolean readonly client?: Client endpoints: RESTEndpoints requestTimeout = 30000 readonly timers!: Set<number> apiURL = Constants.DISCORD_API_URL
readonly handlers!: Collection<string, BucketHandler> globalLimit = Infinity globalRemaining = this.globalLimit globalReset: number | null = null globalDelay: number | null | Promise<void> = null retryLimit = 1 restTimeOffset = 0
constructor(options?: RESTOptions) { this.api = builder(this) 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?.retryLimit !== undefined) this.retryLimit = options.retryLimit if (options?.requestTimeout !== undefined) this.requestTimeout = options.requestTimeout
if (options?.client !== undefined) { Object.defineProperty(this, 'client', { value: options.client, enumerable: false }) }
this.endpoints = new RESTEndpoints(this)
Object.defineProperty(this, 'timers', { value: new Set(), enumerable: false })
Object.defineProperty(this, 'handlers', { value: new Collection<string, BucketHandler>(), enumerable: false }) }
setTimeout(fn: (...args: unknown[]) => unknown, ms: number): number { const timer = setTimeout(() => { this.timers.delete(timer) fn() }, ms) this.timers.add(timer) return timer }
resolveBucket(url: string): string { if (url.startsWith(this.apiURL)) url = url.slice(this.apiURL.length) if (url.startsWith('/')) url = url.slice(1) const bucket: string[] = [] const route = url.split('/') for (let i = 0; i < route.length; i++) { if (route[i - 1] === 'reactions') break if ( route[i].match(/\d{15,20}/) !== null && route[i - 1].match(/(channels|guilds)/) === null ) bucket.push('minor_id') else bucket.push(route[i]) } return bucket.join('/') }
async request<T = any>( method: RequestMethods, path: string, options: RequestOptions = {} ): Promise<T> { const req = new APIRequest(this, method, path, options) const bucket = this.resolveBucket(path) let handler = this.handlers.get(bucket)
if (handler === undefined) { handler = new BucketHandler(this) this.handlers.set(bucket, handler) }
return handler.push(req) }
async make( method: RequestMethods, url: string, body?: unknown, _maxRetries = 0, bucket?: string | null, rawResponse?: boolean, options: RequestOptions = {} ): Promise<any> { return await this.request( method, url, Object.assign( { data: body, rawResponse, route: bucket ?? undefined }, options ) ) }
async get( url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean, options?: RequestOptions ): Promise<any> { return await this.make( 'get', url, body, maxRetries, bucket, rawResponse, options ) }
async post( url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean, options?: RequestOptions ): Promise<any> { return await this.make( 'post', url, body, maxRetries, bucket, rawResponse, options ) }
async delete( url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean, options?: RequestOptions ): Promise<any> { return await this.make( 'delete', url, body, maxRetries, bucket, rawResponse, options ) }
async patch( url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean, options?: RequestOptions ): Promise<any> { return await this.make( 'patch', url, body, maxRetries, bucket, rawResponse, options ) }
async put( url: string, body?: unknown, maxRetries = 0, bucket?: string | null, rawResponse?: boolean, options?: RequestOptions ): Promise<any> { return await this.make( 'put', url, body, maxRetries, bucket, rawResponse, options ) }}