index.mjs.map 71.9 KB
{"version":3,"sources":["../src/lib/utils/constants.ts","../src/lib/CDN.ts","../src/lib/errors/DiscordAPIError.ts","../src/lib/errors/HTTPError.ts","../src/lib/errors/RateLimitError.ts","../src/lib/RequestManager.ts","../src/lib/handlers/SequentialHandler.ts","../src/lib/utils/utils.ts","../src/lib/REST.ts"],"sourcesContent":["import { APIVersion } from 'discord-api-types/v9';\nimport type { RESTOptions } from '../REST';\n// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment\nconst Package = require('../../../package.json');\n\n// eslint-disable-next-line @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-member-access\nexport const DefaultUserAgent = `DiscordBot (${Package.homepage}, ${Package.version})`;\n\nexport const DefaultRestOptions: Required<RESTOptions> = {\n\tagent: {},\n\tapi: 'https://discord.com/api',\n\tcdn: 'https://cdn.discordapp.com',\n\theaders: {},\n\tinvalidRequestWarningInterval: 0,\n\tglobalRequestsPerSecond: 50,\n\toffset: 50,\n\trejectOnRateLimit: null,\n\tretries: 3,\n\ttimeout: 15_000,\n\tuserAgentAppendix: `Node.js ${process.version}`,\n\tversion: APIVersion,\n\thashSweepInterval: 14_400_000, // 4 Hours\n\thashLifetime: 86_400_000, // 24 Hours\n\thandlerSweepInterval: 3_600_000, // 1 Hour\n};\n\n/**\n * The events that the REST manager emits\n */\nexport const enum RESTEvents {\n\tDebug = 'restDebug',\n\tInvalidRequestWarning = 'invalidRequestWarning',\n\tRateLimited = 'rateLimited',\n\tRequest = 'request',\n\tResponse = 'response',\n\tHashSweep = 'hashSweep',\n\tHandlerSweep = 'handlerSweep',\n}\n\nexport const ALLOWED_EXTENSIONS = ['webp', 'png', 'jpg', 'jpeg', 'gif'] as const;\nexport const ALLOWED_STICKER_EXTENSIONS = ['png', 'json'] as const;\nexport const ALLOWED_SIZES = [16, 32, 64, 128, 256, 512, 1024, 2048, 4096] as const;\n\nexport type ImageExtension = typeof ALLOWED_EXTENSIONS[number];\nexport type StickerExtension = typeof ALLOWED_STICKER_EXTENSIONS[number];\nexport type ImageSize = typeof ALLOWED_SIZES[number];\n","import {\n\tALLOWED_EXTENSIONS,\n\tALLOWED_SIZES,\n\tALLOWED_STICKER_EXTENSIONS,\n\tDefaultRestOptions,\n\tImageExtension,\n\tImageSize,\n\tStickerExtension,\n} from './utils/constants';\n\n/**\n * The options used for image URLs\n */\nexport interface BaseImageURLOptions {\n\t/**\n\t * The extension to use for the image URL\n\t * @default 'webp'\n\t */\n\textension?: ImageExtension;\n\t/**\n\t * The size specified in the image URL\n\t */\n\tsize?: ImageSize;\n}\n\n/**\n * The options used for image URLs with animated content\n */\nexport interface ImageURLOptions extends BaseImageURLOptions {\n\t/**\n\t * Whether or not to prefer the static version of an image asset.\n\t */\n\tforceStatic?: boolean;\n}\n\n/**\n * The options to use when making a CDN URL\n */\nexport interface MakeURLOptions {\n\t/**\n\t * The extension to use for the image URL\n\t * @default 'webp'\n\t */\n\textension?: string | undefined;\n\t/**\n\t * The size specified in the image URL\n\t */\n\tsize?: ImageSize;\n\t/**\n\t * The allowed extensions that can be used\n\t */\n\tallowedExtensions?: ReadonlyArray<string>;\n}\n\n/**\n * The CDN link builder\n */\nexport class CDN {\n\tpublic constructor(private readonly base: string = DefaultRestOptions.cdn) {}\n\n\t/**\n\t * Generates an app asset URL for a client's asset.\n\t * @param clientId The client id that has the asset\n\t * @param assetHash The hash provided by Discord for this asset\n\t * @param options Optional options for the asset\n\t */\n\tpublic appAsset(clientId: string, assetHash: string, options?: Readonly<BaseImageURLOptions>): string {\n\t\treturn this.makeURL(`/app-assets/${clientId}/${assetHash}`, options);\n\t}\n\n\t/**\n\t * Generates an app icon URL for a client's icon.\n\t * @param clientId The client id that has the icon\n\t * @param iconHash The hash provided by Discord for this icon\n\t * @param options Optional options for the icon\n\t */\n\tpublic appIcon(clientId: string, iconHash: string, options?: Readonly<BaseImageURLOptions>): string {\n\t\treturn this.makeURL(`/app-icons/${clientId}/${iconHash}`, options);\n\t}\n\n\t/**\n\t * Generates an avatar URL, e.g. for a user or a webhook.\n\t * @param id The id that has the icon\n\t * @param avatarHash The hash provided by Discord for this avatar\n\t * @param options Optional options for the avatar\n\t */\n\tpublic avatar(id: string, avatarHash: string, options?: Readonly<ImageURLOptions>): string {\n\t\treturn this.dynamicMakeURL(`/avatars/${id}/${avatarHash}`, avatarHash, options);\n\t}\n\n\t/**\n\t * Generates a banner URL, e.g. for a user or a guild.\n\t * @param id The id that has the banner splash\n\t * @param bannerHash The hash provided by Discord for this banner\n\t * @param options Optional options for the banner\n\t */\n\tpublic banner(id: string, bannerHash: string, options?: Readonly<ImageURLOptions>): string {\n\t\treturn this.dynamicMakeURL(`/banners/${id}/${bannerHash}`, bannerHash, options);\n\t}\n\n\t/**\n\t * Generates an icon URL for a channel, e.g. a group DM.\n\t * @param channelId The channel id that has the icon\n\t * @param iconHash The hash provided by Discord for this channel\n\t * @param options Optional options for the icon\n\t */\n\tpublic channelIcon(channelId: string, iconHash: string, options?: Readonly<BaseImageURLOptions>): string {\n\t\treturn this.makeURL(`/channel-icons/${channelId}/${iconHash}`, options);\n\t}\n\n\t/**\n\t * Generates the default avatar URL for a discriminator.\n\t * @param discriminator The discriminator modulo 5\n\t */\n\tpublic defaultAvatar(discriminator: number): string {\n\t\treturn this.makeURL(`/embed/avatars/${discriminator}`);\n\t}\n\n\t/**\n\t * Generates a discovery splash URL for a guild's discovery splash.\n\t * @param guildId The guild id that has the discovery splash\n\t * @param splashHash The hash provided by Discord for this splash\n\t * @param options Optional options for the splash\n\t */\n\tpublic discoverySplash(guildId: string, splashHash: string, options?: Readonly<BaseImageURLOptions>): string {\n\t\treturn this.makeURL(`/discovery-splashes/${guildId}/${splashHash}`, options);\n\t}\n\n\t/**\n\t * Generates an emoji's URL for an emoji.\n\t * @param emojiId The emoji id\n\t * @param extension The extension of the emoji\n\t */\n\tpublic emoji(emojiId: string, extension?: ImageExtension): string {\n\t\treturn this.makeURL(`/emojis/${emojiId}`, { extension });\n\t}\n\n\t/**\n\t * Generates a guild member avatar URL.\n\t * @param guildId The id of the guild\n\t * @param userId The id of the user\n\t * @param avatarHash The hash provided by Discord for this avatar\n\t * @param options Optional options for the avatar\n\t */\n\tpublic guildMemberAvatar(\n\t\tguildId: string,\n\t\tuserId: string,\n\t\tavatarHash: string,\n\t\toptions?: Readonly<ImageURLOptions>,\n\t): string {\n\t\treturn this.dynamicMakeURL(`/guilds/${guildId}/users/${userId}/avatars/${avatarHash}`, avatarHash, options);\n\t}\n\n\t/**\n\t * Generates an icon URL, e.g. for a guild.\n\t * @param id The id that has the icon splash\n\t * @param iconHash The hash provided by Discord for this icon\n\t * @param options Optional options for the icon\n\t */\n\tpublic icon(id: string, iconHash: string, options?: Readonly<ImageURLOptions>): string {\n\t\treturn this.dynamicMakeURL(`/icons/${id}/${iconHash}`, iconHash, options);\n\t}\n\n\t/**\n\t * Generates a URL for the icon of a role\n\t * @param roleId The id of the role that has the icon\n\t * @param roleIconHash The hash provided by Discord for this role icon\n\t * @param options Optional options for the role icon\n\t */\n\tpublic roleIcon(roleId: string, roleIconHash: string, options?: Readonly<BaseImageURLOptions>): string {\n\t\treturn this.makeURL(`/role-icons/${roleId}/${roleIconHash}`, options);\n\t}\n\n\t/**\n\t * Generates a guild invite splash URL for a guild's invite splash.\n\t * @param guildId The guild id that has the invite splash\n\t * @param splashHash The hash provided by Discord for this splash\n\t * @param options Optional options for the splash\n\t */\n\tpublic splash(guildId: string, splashHash: string, options?: Readonly<BaseImageURLOptions>): string {\n\t\treturn this.makeURL(`/splashes/${guildId}/${splashHash}`, options);\n\t}\n\n\t/**\n\t * Generates a sticker URL.\n\t * @param stickerId The sticker id\n\t * @param extension The extension of the sticker\n\t */\n\tpublic sticker(stickerId: string, extension?: StickerExtension): string {\n\t\treturn this.makeURL(`/stickers/${stickerId}`, {\n\t\t\tallowedExtensions: ALLOWED_STICKER_EXTENSIONS,\n\t\t\textension: extension ?? 'png', // Stickers cannot have a `.webp` extension, so we default to a `.png`\n\t\t});\n\t}\n\n\t/**\n\t * Generates a sticker pack banner URL.\n\t * @param bannerId The banner id\n\t * @param options Optional options for the banner\n\t */\n\tpublic stickerPackBanner(bannerId: string, options?: Readonly<BaseImageURLOptions>): string {\n\t\treturn this.makeURL(`/app-assets/710982414301790216/store/${bannerId}`, options);\n\t}\n\n\t/**\n\t * Generates a team icon URL for a team's icon.\n\t * @param teamId The team id that has the icon\n\t * @param iconHash The hash provided by Discord for this icon\n\t * @param options Optional options for the icon\n\t */\n\tpublic teamIcon(teamId: string, iconHash: string, options?: Readonly<BaseImageURLOptions>): string {\n\t\treturn this.makeURL(`/team-icons/${teamId}/${iconHash}`, options);\n\t}\n\n\t/**\n\t * Constructs the URL for the resource, checking whether or not `hash` starts with `a_` if `dynamic` is set to `true`.\n\t * @param route The base cdn route\n\t * @param hash The hash provided by Discord for this icon\n\t * @param options Optional options for the link\n\t */\n\tprivate dynamicMakeURL(\n\t\troute: string,\n\t\thash: string,\n\t\t{ forceStatic = false, ...options }: Readonly<ImageURLOptions> = {},\n\t): string {\n\t\treturn this.makeURL(route, !forceStatic && hash.startsWith('a_') ? { ...options, extension: 'gif' } : options);\n\t}\n\n\t/**\n\t * Constructs the URL for the resource\n\t * @param route The base cdn route\n\t * @param options The extension/size options for the link\n\t */\n\tprivate makeURL(\n\t\troute: string,\n\t\t{ allowedExtensions = ALLOWED_EXTENSIONS, extension = 'webp', size }: Readonly<MakeURLOptions> = {},\n\t): string {\n\t\textension = String(extension).toLowerCase();\n\n\t\tif (!allowedExtensions.includes(extension)) {\n\t\t\tthrow new RangeError(`Invalid extension provided: ${extension}\\nMust be one of: ${allowedExtensions.join(', ')}`);\n\t\t}\n\n\t\tif (size && !ALLOWED_SIZES.includes(size)) {\n\t\t\tthrow new RangeError(`Invalid size provided: ${size}\\nMust be one of: ${ALLOWED_SIZES.join(', ')}`);\n\t\t}\n\n\t\tconst url = new URL(`${this.base}${route}.${extension}`);\n\n\t\tif (size) {\n\t\t\turl.searchParams.set('size', String(size));\n\t\t}\n\n\t\treturn url.toString();\n\t}\n}\n","import type { InternalRequest, RawFile } from '../RequestManager';\n\ninterface DiscordErrorFieldInformation {\n\tcode: string;\n\tmessage: string;\n}\n\ninterface DiscordErrorGroupWrapper {\n\t_errors: DiscordError[];\n}\n\ntype DiscordError = DiscordErrorGroupWrapper | DiscordErrorFieldInformation | { [k: string]: DiscordError } | string;\n\nexport interface DiscordErrorData {\n\tcode: number;\n\tmessage: string;\n\terrors?: DiscordError;\n}\n\nexport interface OAuthErrorData {\n\terror: string;\n\terror_description?: string;\n}\n\nexport interface RequestBody {\n\tfiles: RawFile[] | undefined;\n\tjson: unknown | undefined;\n}\n\nfunction isErrorGroupWrapper(error: DiscordError): error is DiscordErrorGroupWrapper {\n\treturn Reflect.has(error as Record<string, unknown>, '_errors');\n}\n\nfunction isErrorResponse(error: DiscordError): error is DiscordErrorFieldInformation {\n\treturn typeof Reflect.get(error as Record<string, unknown>, 'message') === 'string';\n}\n\n/**\n * Represents an API error returned by Discord\n * @extends Error\n */\nexport class DiscordAPIError extends Error {\n\tpublic requestBody: RequestBody;\n\n\t/**\n\t * @param rawError The error reported by Discord\n\t * @param code The error code reported by Discord\n\t * @param status The status code of the response\n\t * @param method The method of the request that erred\n\t * @param url The url of the request that erred\n\t * @param bodyData The unparsed data for the request that errored\n\t */\n\tpublic constructor(\n\t\tpublic rawError: DiscordErrorData | OAuthErrorData,\n\t\tpublic code: number | string,\n\t\tpublic status: number,\n\t\tpublic method: string,\n\t\tpublic url: string,\n\t\tbodyData: Pick<InternalRequest, 'files' | 'body'>,\n\t) {\n\t\tsuper(DiscordAPIError.getMessage(rawError));\n\n\t\tthis.requestBody = { files: bodyData.files, json: bodyData.body };\n\t}\n\n\t/**\n\t * The name of the error\n\t */\n\tpublic override get name(): string {\n\t\treturn `${DiscordAPIError.name}[${this.code}]`;\n\t}\n\n\tprivate static getMessage(error: DiscordErrorData | OAuthErrorData) {\n\t\tlet flattened = '';\n\t\tif ('code' in error) {\n\t\t\tif (error.errors) {\n\t\t\t\tflattened = [...this.flattenDiscordError(error.errors)].join('\\n');\n\t\t\t}\n\t\t\treturn error.message && flattened\n\t\t\t\t? `${error.message}\\n${flattened}`\n\t\t\t\t: error.message || flattened || 'Unknown Error';\n\t\t}\n\t\treturn error.error_description ?? 'No Description';\n\t}\n\n\tprivate static *flattenDiscordError(obj: DiscordError, key = ''): IterableIterator<string> {\n\t\tif (isErrorResponse(obj)) {\n\t\t\treturn yield `${key.length ? `${key}[${obj.code}]` : `${obj.code}`}: ${obj.message}`.trim();\n\t\t}\n\n\t\tfor (const [k, v] of Object.entries(obj)) {\n\t\t\tconst nextKey = k.startsWith('_') ? key : key ? (Number.isNaN(Number(k)) ? `${key}.${k}` : `${key}[${k}]`) : k;\n\n\t\t\tif (typeof v === 'string') {\n\t\t\t\tyield v;\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n\t\t\t} else if (isErrorGroupWrapper(v)) {\n\t\t\t\tfor (const error of v._errors) {\n\t\t\t\t\tyield* this.flattenDiscordError(error, nextKey);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n\t\t\t\tyield* this.flattenDiscordError(v, nextKey);\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { InternalRequest } from '../RequestManager';\nimport type { RequestBody } from './DiscordAPIError';\n\n/**\n * Represents a HTTP error\n */\nexport class HTTPError extends Error {\n\tpublic requestBody: RequestBody;\n\n\t/**\n\t * @param message The error message\n\t * @param name The name of the error\n\t * @param status The status code of the response\n\t * @param method The method of the request that erred\n\t * @param url The url of the request that erred\n\t * @param bodyData The unparsed data for the request that errored\n\t */\n\tpublic constructor(\n\t\tmessage: string,\n\t\tpublic override name: string,\n\t\tpublic status: number,\n\t\tpublic method: string,\n\t\tpublic url: string,\n\t\tbodyData: Pick<InternalRequest, 'files' | 'body'>,\n\t) {\n\t\tsuper(message);\n\n\t\tthis.requestBody = { files: bodyData.files, json: bodyData.body };\n\t}\n}\n","import type { RateLimitData } from '../REST';\n\nexport class RateLimitError extends Error implements RateLimitData {\n\tpublic timeToReset: number;\n\tpublic limit: number;\n\tpublic method: string;\n\tpublic hash: string;\n\tpublic url: string;\n\tpublic route: string;\n\tpublic majorParameter: string;\n\tpublic global: boolean;\n\tpublic constructor({ timeToReset, limit, method, hash, url, route, majorParameter, global }: RateLimitData) {\n\t\tsuper();\n\t\tthis.timeToReset = timeToReset;\n\t\tthis.limit = limit;\n\t\tthis.method = method;\n\t\tthis.hash = hash;\n\t\tthis.url = url;\n\t\tthis.route = route;\n\t\tthis.majorParameter = majorParameter;\n\t\tthis.global = global;\n\t}\n\n\t/**\n\t * The name of the error\n\t */\n\tpublic override get name(): string {\n\t\treturn `${RateLimitError.name}[${this.route}]`;\n\t}\n}\n","import Collection from '@discordjs/collection';\nimport FormData from 'form-data';\nimport { DiscordSnowflake } from '@sapphire/snowflake';\nimport { EventEmitter } from 'node:events';\nimport { Agent as httpsAgent } from 'node:https';\nimport { Agent as httpAgent } from 'node:http';\nimport type { RequestInit, BodyInit } from 'node-fetch';\nimport type { IHandler } from './handlers/IHandler';\nimport { SequentialHandler } from './handlers/SequentialHandler';\nimport type { RESTOptions, RestEvents } from './REST';\nimport { DefaultRestOptions, DefaultUserAgent, RESTEvents } from './utils/constants';\n\n/**\n * Represents a file to be added to the request\n */\nexport interface RawFile {\n\t/**\n\t * The name of the file\n\t */\n\tfileName: string;\n\t/**\n\t * An explicit key to use for key of the formdata field for this file.\n\t * When not provided, the index of the file in the files array is used in the form `files[${index}]`.\n\t * If you wish to alter the placeholder snowflake, you must provide this property in the same form (`files[${placeholder}]`)\n\t */\n\tkey?: string;\n\t/**\n\t * The actual data for the file\n\t */\n\tfileData: string | number | boolean | Buffer;\n}\n\n/**\n * Represents possible data to be given to an endpoint\n */\nexport interface RequestData {\n\t/**\n\t * Whether to append JSON data to form data instead of `payload_json` when sending files\n\t */\n\tappendToFormData?: boolean;\n\t/**\n\t * If this request needs the `Authorization` header\n\t * @default true\n\t */\n\tauth?: boolean;\n\t/**\n\t * The authorization prefix to use for this request, useful if you use this with bearer tokens\n\t * @default 'Bot'\n\t */\n\tauthPrefix?: 'Bot' | 'Bearer';\n\t/**\n\t * The body to send to this request.\n\t * If providing as BodyInit, set `passThroughBody: true`\n\t */\n\tbody?: BodyInit | unknown;\n\t/**\n\t * Files to be attached to this request\n\t */\n\tfiles?: RawFile[] | undefined;\n\t/**\n\t * Additional headers to add to this request\n\t */\n\theaders?: Record<string, string>;\n\t/**\n\t * Whether to pass-through the body property directly to `fetch()`.\n\t * <warn>This only applies when files is NOT present</warn>\n\t */\n\tpassThroughBody?: boolean;\n\t/**\n\t * Query string parameters to append to the called endpoint\n\t */\n\tquery?: URLSearchParams;\n\t/**\n\t * Reason to show in the audit logs\n\t */\n\treason?: string;\n\t/**\n\t * If this request should be versioned\n\t * @default true\n\t */\n\tversioned?: boolean;\n}\n\n/**\n * Possible headers for an API call\n */\nexport interface RequestHeaders {\n\tAuthorization?: string;\n\t'User-Agent': string;\n\t'X-Audit-Log-Reason'?: string;\n}\n\n/**\n * Possible API methods to be used when doing requests\n */\nexport const enum RequestMethod {\n\tDelete = 'delete',\n\tGet = 'get',\n\tPatch = 'patch',\n\tPost = 'post',\n\tPut = 'put',\n}\n\nexport type RouteLike = `/${string}`;\n\n/**\n * Internal request options\n *\n * @internal\n */\nexport interface InternalRequest extends RequestData {\n\tmethod: RequestMethod;\n\tfullRoute: RouteLike;\n}\n\n/**\n * Parsed route data for an endpoint\n *\n * @internal\n */\nexport interface RouteData {\n\tmajorParameter: string;\n\tbucketRoute: string;\n\toriginal: RouteLike;\n}\n\n/**\n * Represents a hash and its associated fields\n *\n * @internal\n */\nexport interface HashData {\n\tvalue: string;\n\tlastAccess: number;\n}\n\nexport interface RequestManager {\n\ton: (<K extends keyof RestEvents>(event: K, listener: (...args: RestEvents[K]) => void) => this) &\n\t\t(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, listener: (...args: any[]) => void) => this);\n\n\tonce: (<K extends keyof RestEvents>(event: K, listener: (...args: RestEvents[K]) => void) => this) &\n\t\t(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, listener: (...args: any[]) => void) => this);\n\n\temit: (<K extends keyof RestEvents>(event: K, ...args: RestEvents[K]) => boolean) &\n\t\t(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, ...args: any[]) => boolean);\n\n\toff: (<K extends keyof RestEvents>(event: K, listener: (...args: RestEvents[K]) => void) => this) &\n\t\t(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, listener: (...args: any[]) => void) => this);\n\n\tremoveAllListeners: (<K extends keyof RestEvents>(event?: K) => this) &\n\t\t(<S extends string | symbol>(event?: Exclude<S, keyof RestEvents>) => this);\n}\n\n/**\n * Represents the class that manages handlers for endpoints\n */\nexport class RequestManager extends EventEmitter {\n\t/**\n\t * The number of requests remaining in the global bucket\n\t */\n\tpublic globalRemaining: number;\n\n\t/**\n\t * The promise used to wait out the global rate limit\n\t */\n\tpublic globalDelay: Promise<void> | null = null;\n\n\t/**\n\t * The timestamp at which the global bucket resets\n\t */\n\tpublic globalReset = -1;\n\n\t/**\n\t * API bucket hashes that are cached from provided routes\n\t */\n\tpublic readonly hashes = new Collection<string, HashData>();\n\n\t/**\n\t * Request handlers created from the bucket hash and the major parameters\n\t */\n\tpublic readonly handlers = new Collection<string, IHandler>();\n\n\t// eslint-disable-next-line @typescript-eslint/explicit-member-accessibility\n\t#token: string | null = null;\n\n\tprivate hashTimer!: NodeJS.Timer;\n\tprivate handlerTimer!: NodeJS.Timer;\n\tprivate agent: httpsAgent | httpAgent | null = null;\n\n\tpublic readonly options: RESTOptions;\n\n\tpublic constructor(options: Partial<RESTOptions>) {\n\t\tsuper();\n\t\tthis.options = { ...DefaultRestOptions, ...options };\n\t\tthis.options.offset = Math.max(0, this.options.offset);\n\t\tthis.globalRemaining = this.options.globalRequestsPerSecond;\n\n\t\t// Start sweepers\n\t\tthis.setupSweepers();\n\t}\n\n\tprivate setupSweepers() {\n\t\tconst validateMaxInterval = (interval: number) => {\n\t\t\tif (interval > 14_400_000) {\n\t\t\t\tthrow new Error('Cannot set an interval greater than 4 hours');\n\t\t\t}\n\t\t};\n\n\t\tif (this.options.hashSweepInterval !== 0 && this.options.hashSweepInterval !== Infinity) {\n\t\t\tvalidateMaxInterval(this.options.hashSweepInterval);\n\t\t\tthis.hashTimer = setInterval(() => {\n\t\t\t\tconst sweptHashes = new Collection<string, HashData>();\n\t\t\t\tconst currentDate = Date.now();\n\n\t\t\t\t// Begin sweeping hash based on lifetimes\n\t\t\t\tthis.hashes.sweep((v, k) => {\n\t\t\t\t\t// `-1` indicates a global hash\n\t\t\t\t\tif (v.lastAccess === -1) return false;\n\n\t\t\t\t\t// Check if lifetime has been exceeded\n\t\t\t\t\tconst shouldSweep = Math.floor(currentDate - v.lastAccess) > this.options.hashLifetime;\n\n\t\t\t\t\t// Add hash to collection of swept hashes\n\t\t\t\t\tif (shouldSweep) {\n\t\t\t\t\t\t// Add to swept hashes\n\t\t\t\t\t\tsweptHashes.set(k, v);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Emit debug information\n\t\t\t\t\tthis.emit(RESTEvents.Debug, `Hash ${v.value} for ${k} swept due to lifetime being exceeded`);\n\n\t\t\t\t\treturn shouldSweep;\n\t\t\t\t});\n\n\t\t\t\t// Fire event\n\t\t\t\tthis.emit(RESTEvents.HashSweep, sweptHashes);\n\t\t\t}, this.options.hashSweepInterval).unref();\n\t\t}\n\n\t\tif (this.options.handlerSweepInterval !== 0 && this.options.handlerSweepInterval !== Infinity) {\n\t\t\tvalidateMaxInterval(this.options.handlerSweepInterval);\n\t\t\tthis.handlerTimer = setInterval(() => {\n\t\t\t\tconst sweptHandlers = new Collection<string, IHandler>();\n\n\t\t\t\t// Begin sweeping handlers based on activity\n\t\t\t\tthis.handlers.sweep((v, k) => {\n\t\t\t\t\tconst { inactive } = v;\n\n\t\t\t\t\t// Collect inactive handlers\n\t\t\t\t\tif (inactive) {\n\t\t\t\t\t\tsweptHandlers.set(k, v);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.emit(RESTEvents.Debug, `Handler ${v.id} for ${k} swept due to being inactive`);\n\t\t\t\t\treturn inactive;\n\t\t\t\t});\n\n\t\t\t\t// Fire event\n\t\t\t\tthis.emit(RESTEvents.HandlerSweep, sweptHandlers);\n\t\t\t}, this.options.handlerSweepInterval).unref();\n\t\t}\n\t}\n\n\t/**\n\t * Sets the authorization token that should be used for requests\n\t * @param token The authorization token to use\n\t */\n\tpublic setToken(token: string) {\n\t\tthis.#token = token;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Queues a request to be sent\n\t * @param request All the information needed to make a request\n\t * @returns The response from the api request\n\t */\n\tpublic async queueRequest(request: InternalRequest): Promise<unknown> {\n\t\t// Generalize the endpoint to its route data\n\t\tconst routeId = RequestManager.generateRouteData(request.fullRoute, request.method);\n\t\t// Get the bucket hash for the generic route, or point to a global route otherwise\n\t\tconst hash = this.hashes.get(`${request.method}:${routeId.bucketRoute}`) ?? {\n\t\t\tvalue: `Global(${request.method}:${routeId.bucketRoute})`,\n\t\t\tlastAccess: -1,\n\t\t};\n\n\t\t// Get the request handler for the obtained hash, with its major parameter\n\t\tconst handler =\n\t\t\tthis.handlers.get(`${hash.value}:${routeId.majorParameter}`) ??\n\t\t\tthis.createHandler(hash.value, routeId.majorParameter);\n\n\t\t// Resolve the request into usable fetch/node-fetch options\n\t\tconst { url, fetchOptions } = this.resolveRequest(request);\n\n\t\t// Queue the request\n\t\treturn handler.queueRequest(routeId, url, fetchOptions, { body: request.body, files: request.files });\n\t}\n\n\t/**\n\t * Creates a new rate limit handler from a hash, based on the hash and the major parameter\n\t * @param hash The hash for the route\n\t * @param majorParameter The major parameter for this handler\n\t * @private\n\t */\n\tprivate createHandler(hash: string, majorParameter: string) {\n\t\t// Create the async request queue to handle requests\n\t\tconst queue = new SequentialHandler(this, hash, majorParameter);\n\t\t// Save the queue based on its id\n\t\tthis.handlers.set(queue.id, queue);\n\n\t\treturn queue;\n\t}\n\n\t/**\n\t * Formats the request data to a usable format for fetch\n\t * @param request The request data\n\t */\n\tprivate resolveRequest(request: InternalRequest): { url: string; fetchOptions: RequestInit } {\n\t\tconst { options } = this;\n\n\t\tthis.agent ??= options.api.startsWith('https')\n\t\t\t? new httpsAgent({ ...options.agent, keepAlive: true })\n\t\t\t: new httpAgent({ ...options.agent, keepAlive: true });\n\n\t\tlet query = '';\n\n\t\t// If a query option is passed, use it\n\t\tif (request.query) {\n\t\t\tconst resolvedQuery = request.query.toString();\n\t\t\tif (resolvedQuery !== '') {\n\t\t\t\tquery = `?${resolvedQuery}`;\n\t\t\t}\n\t\t}\n\n\t\t// Create the required headers\n\t\tconst headers: RequestHeaders = {\n\t\t\t...this.options.headers,\n\t\t\t'User-Agent': `${DefaultUserAgent} ${options.userAgentAppendix}`.trim(),\n\t\t};\n\n\t\t// If this request requires authorization (allowing non-\"authorized\" requests for webhooks)\n\t\tif (request.auth !== false) {\n\t\t\t// If we haven't received a token, throw an error\n\t\t\tif (!this.#token) {\n\t\t\t\tthrow new Error('Expected token to be set for this request, but none was present');\n\t\t\t}\n\n\t\t\theaders.Authorization = `${request.authPrefix ?? 'Bot'} ${this.#token}`;\n\t\t}\n\n\t\t// If a reason was set, set it's appropriate header\n\t\tif (request.reason?.length) {\n\t\t\theaders['X-Audit-Log-Reason'] = encodeURIComponent(request.reason);\n\t\t}\n\n\t\t// Format the full request URL (api base, optional version, endpoint, optional querystring)\n\t\tconst url = `${options.api}${request.versioned === false ? '' : `/v${options.version}`}${\n\t\t\trequest.fullRoute\n\t\t}${query}`;\n\n\t\tlet finalBody: RequestInit['body'];\n\t\tlet additionalHeaders: Record<string, string> = {};\n\n\t\tif (request.files?.length) {\n\t\t\tconst formData = new FormData();\n\n\t\t\t// Attach all files to the request\n\t\t\tfor (const [index, file] of request.files.entries()) {\n\t\t\t\tformData.append(file.key ?? `files[${index}]`, file.fileData, file.fileName);\n\t\t\t}\n\n\t\t\t// If a JSON body was added as well, attach it to the form data, using payload_json unless otherwise specified\n\t\t\t// eslint-disable-next-line no-eq-null\n\t\t\tif (request.body != null) {\n\t\t\t\tif (request.appendToFormData) {\n\t\t\t\t\tfor (const [key, value] of Object.entries(request.body as Record<string, unknown>)) {\n\t\t\t\t\t\tformData.append(key, value);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tformData.append('payload_json', JSON.stringify(request.body));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set the final body to the form data\n\t\t\tfinalBody = formData;\n\t\t\t// Set the additional headers to the form data ones\n\t\t\tadditionalHeaders = formData.getHeaders();\n\n\t\t\t// eslint-disable-next-line no-eq-null\n\t\t} else if (request.body != null) {\n\t\t\tif (request.passThroughBody) {\n\t\t\t\tfinalBody = request.body as BodyInit;\n\t\t\t} else {\n\t\t\t\t// Stringify the JSON data\n\t\t\t\tfinalBody = JSON.stringify(request.body);\n\t\t\t\t// Set the additional headers to specify the content-type\n\t\t\t\tadditionalHeaders = { 'Content-Type': 'application/json' };\n\t\t\t}\n\t\t}\n\n\t\tconst fetchOptions = {\n\t\t\tagent: this.agent,\n\t\t\tbody: finalBody,\n\t\t\t// eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n\t\t\theaders: { ...(request.headers ?? {}), ...additionalHeaders, ...headers } as Record<string, string>,\n\t\t\tmethod: request.method,\n\t\t};\n\n\t\treturn { url, fetchOptions };\n\t}\n\n\t/**\n\t * Stops the hash sweeping interval\n\t */\n\tpublic clearHashSweeper() {\n\t\tclearInterval(this.hashTimer);\n\t}\n\n\t/**\n\t * Stops the request handler sweeping interval\n\t */\n\tpublic clearHandlerSweeper() {\n\t\tclearInterval(this.handlerTimer);\n\t}\n\n\t/**\n\t * Generates route data for an endpoint:method\n\t * @param endpoint The raw endpoint to generalize\n\t * @param method The HTTP method this endpoint is called without\n\t * @private\n\t */\n\tprivate static generateRouteData(endpoint: RouteLike, method: RequestMethod): RouteData {\n\t\tconst majorIdMatch = /^\\/(?:channels|guilds|webhooks)\\/(\\d{16,19})/.exec(endpoint);\n\n\t\t// Get the major id for this route - global otherwise\n\t\tconst majorId = majorIdMatch?.[1] ?? 'global';\n\n\t\tconst baseRoute = endpoint\n\t\t\t// Strip out all ids\n\t\t\t.replace(/\\d{16,19}/g, ':id')\n\t\t\t// Strip out reaction as they fall under the same bucket\n\t\t\t.replace(/\\/reactions\\/(.*)/, '/reactions/:reaction');\n\n\t\tlet exceptions = '';\n\n\t\t// Hard-Code Old Message Deletion Exception (2 week+ old messages are a different bucket)\n\t\t// https://github.com/discord/discord-api-docs/issues/1295\n\t\tif (method === RequestMethod.Delete && baseRoute === '/channels/:id/messages/:id') {\n\t\t\tconst id = /\\d{16,19}$/.exec(endpoint)![0];\n\t\t\tconst snowflake = DiscordSnowflake.deconstruct(id);\n\t\t\tif (Date.now() - Number(snowflake.timestamp) > 1000 * 60 * 60 * 24 * 14) {\n\t\t\t\texceptions += '/Delete Old Message';\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tmajorParameter: majorId,\n\t\t\tbucketRoute: baseRoute + exceptions,\n\t\t\toriginal: endpoint,\n\t\t};\n\t}\n}\n","import { setTimeout as sleep } from 'node:timers/promises';\nimport { AsyncQueue } from '@sapphire/async-queue';\nimport fetch, { RequestInit, Response } from 'node-fetch';\nimport { DiscordAPIError, DiscordErrorData, OAuthErrorData } from '../errors/DiscordAPIError';\nimport { HTTPError } from '../errors/HTTPError';\nimport { RateLimitError } from '../errors/RateLimitError';\nimport type { InternalRequest, RequestManager, RouteData } from '../RequestManager';\nimport { RESTEvents } from '../utils/constants';\nimport { hasSublimit, parseResponse } from '../utils/utils';\nimport type { RateLimitData } from '../REST';\n\n/* Invalid request limiting is done on a per-IP basis, not a per-token basis.\n * The best we can do is track invalid counts process-wide (on the theory that\n * users could have multiple bots run from one process) rather than per-bot.\n * Therefore, store these at file scope here rather than in the client's\n * RESTManager object.\n */\nlet invalidCount = 0;\nlet invalidCountResetTime: number | null = null;\n\nconst enum QueueType {\n\tStandard,\n\tSublimit,\n}\n\n/**\n * The structure used to handle requests for a given bucket\n */\nexport class SequentialHandler {\n\t/**\n\t * The unique id of the handler\n\t */\n\tpublic readonly id: string;\n\n\t/**\n\t * The time this rate limit bucket will reset\n\t */\n\tprivate reset = -1;\n\n\t/**\n\t * The remaining requests that can be made before we are rate limited\n\t */\n\tprivate remaining = 1;\n\n\t/**\n\t * The total number of requests that can be made before we are rate limited\n\t */\n\tprivate limit = Infinity;\n\n\t/**\n\t * The interface used to sequence async requests sequentially\n\t */\n\t// eslint-disable-next-line @typescript-eslint/explicit-member-accessibility\n\t#asyncQueue = new AsyncQueue();\n\n\t/**\n\t * The interface used to sequence sublimited async requests sequentially\n\t */\n\t// eslint-disable-next-line @typescript-eslint/explicit-member-accessibility\n\t#sublimitedQueue: AsyncQueue | null = null;\n\n\t/**\n\t * A promise wrapper for when the sublimited queue is finished being processed or null when not being processed\n\t */\n\t// eslint-disable-next-line @typescript-eslint/explicit-member-accessibility\n\t#sublimitPromise: { promise: Promise<void>; resolve: () => void } | null = null;\n\n\t/**\n\t * Whether the sublimit queue needs to be shifted in the finally block\n\t */\n\t// eslint-disable-next-line @typescript-eslint/explicit-member-accessibility\n\t#shiftSublimit = false;\n\n\t/**\n\t * @param manager The request manager\n\t * @param hash The hash that this RequestHandler handles\n\t * @param majorParameter The major parameter for this handler\n\t */\n\tpublic constructor(\n\t\tprivate readonly manager: RequestManager,\n\t\tprivate readonly hash: string,\n\t\tprivate readonly majorParameter: string,\n\t) {\n\t\tthis.id = `${hash}:${majorParameter}`;\n\t}\n\n\t/**\n\t * If the bucket is currently inactive (no pending requests)\n\t */\n\tpublic get inactive(): boolean {\n\t\treturn (\n\t\t\tthis.#asyncQueue.remaining === 0 &&\n\t\t\t(this.#sublimitedQueue === null || this.#sublimitedQueue.remaining === 0) &&\n\t\t\t!this.limited\n\t\t);\n\t}\n\n\t/**\n\t * If the rate limit bucket is currently limited by the global limit\n\t */\n\tprivate get globalLimited(): boolean {\n\t\treturn this.manager.globalRemaining <= 0 && Date.now() < this.manager.globalReset;\n\t}\n\n\t/**\n\t * If the rate limit bucket is currently limited by its limit\n\t */\n\tprivate get localLimited(): boolean {\n\t\treturn this.remaining <= 0 && Date.now() < this.reset;\n\t}\n\n\t/**\n\t * If the rate limit bucket is currently limited\n\t */\n\tprivate get limited(): boolean {\n\t\treturn this.globalLimited || this.localLimited;\n\t}\n\n\t/**\n\t * The time until queued requests can continue\n\t */\n\tprivate get timeToReset(): number {\n\t\treturn this.reset + this.manager.options.offset - Date.now();\n\t}\n\n\t/**\n\t * Emits a debug message\n\t * @param message The message to debug\n\t */\n\tprivate debug(message: string) {\n\t\tthis.manager.emit(RESTEvents.Debug, `[REST ${this.id}] ${message}`);\n\t}\n\n\t/**\n\t * Delay all requests for the specified amount of time, handling global rate limits\n\t * @param time The amount of time to delay all requests for\n\t * @returns\n\t */\n\tprivate async globalDelayFor(time: number): Promise<void> {\n\t\tawait sleep(time, undefined, { ref: false });\n\t\tthis.manager.globalDelay = null;\n\t}\n\n\t/*\n\t * Determines whether the request should be queued or whether a RateLimitError should be thrown\n\t */\n\tprivate async onRateLimit(rateLimitData: RateLimitData) {\n\t\tconst { options } = this.manager;\n\t\tif (!options.rejectOnRateLimit) return;\n\n\t\tconst shouldThrow =\n\t\t\ttypeof options.rejectOnRateLimit === 'function'\n\t\t\t\t? await options.rejectOnRateLimit(rateLimitData)\n\t\t\t\t: options.rejectOnRateLimit.some((route) => rateLimitData.route.startsWith(route.toLowerCase()));\n\t\tif (shouldThrow) {\n\t\t\tthrow new RateLimitError(rateLimitData);\n\t\t}\n\t}\n\n\t/**\n\t * Queues a request to be sent\n\t * @param routeId The generalized api route with literal ids for major parameters\n\t * @param url The url to do the request on\n\t * @param options All the information needed to make a request\n\t * @param bodyData The data that was used to form the body, passed to any errors generated and for determining whether to sublimit\n\t */\n\tpublic async queueRequest(\n\t\trouteId: RouteData,\n\t\turl: string,\n\t\toptions: RequestInit,\n\t\tbodyData: Pick<InternalRequest, 'files' | 'body'>,\n\t): Promise<unknown> {\n\t\tlet queue = this.#asyncQueue;\n\t\tlet queueType = QueueType.Standard;\n\t\t// Separate sublimited requests when already sublimited\n\t\tif (this.#sublimitedQueue && hasSublimit(routeId.bucketRoute, bodyData.body, options.method)) {\n\t\t\tqueue = this.#sublimitedQueue!;\n\t\t\tqueueType = QueueType.Sublimit;\n\t\t}\n\t\t// Wait for any previous requests to be completed before this one is run\n\t\tawait queue.wait();\n\t\t// This set handles retroactively sublimiting requests\n\t\tif (queueType === QueueType.Standard) {\n\t\t\tif (this.#sublimitedQueue && hasSublimit(routeId.bucketRoute, bodyData.body, options.method)) {\n\t\t\t\t/**\n\t\t\t\t * Remove the request from the standard queue, it should never be possible to get here while processing the\n\t\t\t\t * sublimit queue so there is no need to worry about shifting the wrong request\n\t\t\t\t */\n\t\t\t\tqueue = this.#sublimitedQueue!;\n\t\t\t\tconst wait = queue.wait();\n\t\t\t\tthis.#asyncQueue.shift();\n\t\t\t\tawait wait;\n\t\t\t} else if (this.#sublimitPromise) {\n\t\t\t\t// Stall requests while the sublimit queue gets processed\n\t\t\t\tawait this.#sublimitPromise.promise;\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\t// Make the request, and return the results\n\t\t\treturn await this.runRequest(routeId, url, options, bodyData);\n\t\t} finally {\n\t\t\t// Allow the next request to fire\n\t\t\tqueue.shift();\n\t\t\tif (this.#shiftSublimit) {\n\t\t\t\tthis.#shiftSublimit = false;\n\t\t\t\tthis.#sublimitedQueue?.shift();\n\t\t\t}\n\t\t\t// If this request is the last request in a sublimit\n\t\t\tif (this.#sublimitedQueue?.remaining === 0) {\n\t\t\t\tthis.#sublimitPromise?.resolve();\n\t\t\t\tthis.#sublimitedQueue = null;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * The method that actually makes the request to the api, and updates info about the bucket accordingly\n\t * @param routeId The generalized api route with literal ids for major parameters\n\t * @param url The fully resolved url to make the request to\n\t * @param options The node-fetch options needed to make the request\n\t * @param bodyData The data that was used to form the body, passed to any errors generated\n\t * @param retries The number of retries this request has already attempted (recursion)\n\t */\n\tprivate async runRequest(\n\t\trouteId: RouteData,\n\t\turl: string,\n\t\toptions: RequestInit,\n\t\tbodyData: Pick<InternalRequest, 'files' | 'body'>,\n\t\tretries = 0,\n\t): Promise<unknown> {\n\t\t/*\n\t\t * After calculations have been done, pre-emptively stop further requests\n\t\t * Potentially loop until this task can run if e.g. the global rate limit is hit twice\n\t\t */\n\t\twhile (this.limited) {\n\t\t\tconst isGlobal = this.globalLimited;\n\t\t\tlet limit: number;\n\t\t\tlet timeout: number;\n\t\t\tlet delay: Promise<void>;\n\n\t\t\tif (isGlobal) {\n\t\t\t\t// Set RateLimitData based on the globl limit\n\t\t\t\tlimit = this.manager.options.globalRequestsPerSecond;\n\t\t\t\ttimeout = this.manager.globalReset + this.manager.options.offset - Date.now();\n\t\t\t\t// If this is the first task to reach the global timeout, set the global delay\n\t\t\t\tif (!this.manager.globalDelay) {\n\t\t\t\t\t// The global delay function clears the global delay state when it is resolved\n\t\t\t\t\tthis.manager.globalDelay = this.globalDelayFor(timeout);\n\t\t\t\t}\n\t\t\t\tdelay = this.manager.globalDelay;\n\t\t\t} else {\n\t\t\t\t// Set RateLimitData based on the route-specific limit\n\t\t\t\tlimit = this.limit;\n\t\t\t\ttimeout = this.timeToReset;\n\t\t\t\tdelay = sleep(timeout);\n\t\t\t}\n\t\t\tconst rateLimitData: RateLimitData = {\n\t\t\t\ttimeToReset: timeout,\n\t\t\t\tlimit,\n\t\t\t\tmethod: options.method ?? 'get',\n\t\t\t\thash: this.hash,\n\t\t\t\turl,\n\t\t\t\troute: routeId.bucketRoute,\n\t\t\t\tmajorParameter: this.majorParameter,\n\t\t\t\tglobal: isGlobal,\n\t\t\t};\n\t\t\t// Let library users know they have hit a rate limit\n\t\t\tthis.manager.emit(RESTEvents.RateLimited, rateLimitData);\n\t\t\t// Determine whether a RateLimitError should be thrown\n\t\t\tawait this.onRateLimit(rateLimitData);\n\t\t\t// When not erroring, emit debug for what is happening\n\t\t\tif (isGlobal) {\n\t\t\t\tthis.debug(`Global rate limit hit, blocking all requests for ${timeout}ms`);\n\t\t\t} else {\n\t\t\t\tthis.debug(`Waiting ${timeout}ms for rate limit to pass`);\n\t\t\t}\n\t\t\t// Wait the remaining time left before the rate limit resets\n\t\t\tawait delay;\n\t\t}\n\t\t// As the request goes out, update the global usage information\n\t\tif (!this.manager.globalReset || this.manager.globalReset < Date.now()) {\n\t\t\tthis.manager.globalReset = Date.now() + 1000;\n\t\t\tthis.manager.globalRemaining = this.manager.options.globalRequestsPerSecond;\n\t\t}\n\t\tthis.manager.globalRemaining--;\n\n\t\tconst method = options.method ?? 'get';\n\n\t\tif (this.manager.listenerCount(RESTEvents.Request)) {\n\t\t\tthis.manager.emit(RESTEvents.Request, {\n\t\t\t\tmethod,\n\t\t\t\tpath: routeId.original,\n\t\t\t\troute: routeId.bucketRoute,\n\t\t\t\toptions,\n\t\t\t\tdata: bodyData,\n\t\t\t\tretries,\n\t\t\t});\n\t\t}\n\n\t\tconst controller = new AbortController();\n\t\tconst timeout = setTimeout(() => controller.abort(), this.manager.options.timeout).unref();\n\t\tlet res: Response;\n\n\t\ttry {\n\t\t\t// node-fetch typings are a bit weird, so we have to cast to any to get the correct signature\n\t\t\t// Type 'AbortSignal' is not assignable to type 'import(\"discord.js-modules/node_modules/@types/node-fetch/externals\").AbortSignal'\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\t\t\tres = await fetch(url, { ...options, signal: controller.signal as any });\n\t\t} catch (error: unknown) {\n\t\t\t// Retry the specified number of times for possible timed out requests\n\t\t\tif (error instanceof Error && error.name === 'AbortError' && retries !== this.manager.options.retries) {\n\t\t\t\treturn await this.runRequest(routeId, url, options, bodyData, ++retries);\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tclearTimeout(timeout);\n\t\t}\n\n\t\tif (this.manager.listenerCount(RESTEvents.Response)) {\n\t\t\tthis.manager.emit(\n\t\t\t\tRESTEvents.Response,\n\t\t\t\t{\n\t\t\t\t\tmethod,\n\t\t\t\t\tpath: routeId.original,\n\t\t\t\t\troute: routeId.bucketRoute,\n\t\t\t\t\toptions,\n\t\t\t\t\tdata: bodyData,\n\t\t\t\t\tretries,\n\t\t\t\t},\n\t\t\t\tres.clone(),\n\t\t\t);\n\t\t}\n\n\t\tlet retryAfter = 0;\n\n\t\tconst limit = res.headers.get('X-RateLimit-Limit');\n\t\tconst remaining = res.headers.get('X-RateLimit-Remaining');\n\t\tconst reset = res.headers.get('X-RateLimit-Reset-After');\n\t\tconst hash = res.headers.get('X-RateLimit-Bucket');\n\t\tconst retry = res.headers.get('Retry-After');\n\n\t\t// Update the total number of requests that can be made before the rate limit resets\n\t\tthis.limit = limit ? Number(limit) : Infinity;\n\t\t// Update the number of remaining requests that can be made before the rate limit resets\n\t\tthis.remaining = remaining ? Number(remaining) : 1;\n\t\t// Update the time when this rate limit resets (reset-after is in seconds)\n\t\tthis.reset = reset ? Number(reset) * 1000 + Date.now() + this.manager.options.offset : Date.now();\n\n\t\t// Amount of time in milliseconds until we should retry if rate limited (globally or otherwise)\n\t\tif (retry) retryAfter = Number(retry) * 1000 + this.manager.options.offset;\n\n\t\t// Handle buckets via the hash header retroactively\n\t\tif (hash && hash !== this.hash) {\n\t\t\t// Let library users know when rate limit buckets have been updated\n\t\t\tthis.debug(['Received bucket hash update', `  Old Hash  : ${this.hash}`, `  New Hash  : ${hash}`].join('\\n'));\n\t\t\t// This queue will eventually be eliminated via attrition\n\t\t\tthis.manager.hashes.set(`${method}:${routeId.bucketRoute}`, { value: hash, lastAccess: Date.now() });\n\t\t} else if (hash) {\n\t\t\t// Handle the case where hash value doesn't change\n\t\t\t// Fetch the hash data from the manager\n\t\t\tconst hashData = this.manager.hashes.get(`${method}:${routeId.bucketRoute}`);\n\n\t\t\t// When fetched, update the last access of the hash\n\t\t\tif (hashData) {\n\t\t\t\thashData.lastAccess = Date.now();\n\t\t\t}\n\t\t}\n\n\t\t// Handle retryAfter, which means we have actually hit a rate limit\n\t\tlet sublimitTimeout: number | null = null;\n\t\tif (retryAfter > 0) {\n\t\t\tif (res.headers.get('X-RateLimit-Global')) {\n\t\t\t\tthis.manager.globalRemaining = 0;\n\t\t\t\tthis.manager.globalReset = Date.now() + retryAfter;\n\t\t\t} else if (!this.localLimited) {\n\t\t\t\t/*\n\t\t\t\t * This is a sublimit (e.g. 2 channel name changes/10 minutes) since the headers don't indicate a\n\t\t\t\t * route-wide rate limit. Don't update remaining or reset to avoid rate limiting the whole\n\t\t\t\t * endpoint, just set a reset time on the request itself to avoid retrying too soon.\n\t\t\t\t */\n\t\t\t\tsublimitTimeout = retryAfter;\n\t\t\t}\n\t\t}\n\n\t\t// Count the invalid requests\n\t\tif (res.status === 401 || res.status === 403 || res.status === 429) {\n\t\t\tif (!invalidCountResetTime || invalidCountResetTime < Date.now()) {\n\t\t\t\tinvalidCountResetTime = Date.now() + 1000 * 60 * 10;\n\t\t\t\tinvalidCount = 0;\n\t\t\t}\n\t\t\tinvalidCount++;\n\n\t\t\tconst emitInvalid =\n\t\t\t\tthis.manager.options.invalidRequestWarningInterval > 0 &&\n\t\t\t\tinvalidCount % this.manager.options.invalidRequestWarningInterval === 0;\n\t\t\tif (emitInvalid) {\n\t\t\t\t// Let library users know periodically about invalid requests\n\t\t\t\tthis.manager.emit(RESTEvents.InvalidRequestWarning, {\n\t\t\t\t\tcount: invalidCount,\n\t\t\t\t\tremainingTime: invalidCountResetTime - Date.now(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (res.ok) {\n\t\t\treturn parseResponse(res);\n\t\t} else if (res.status === 429) {\n\t\t\t// A rate limit was hit - this may happen if the route isn't associated with an official bucket hash yet, or when first globally rate limited\n\t\t\tconst isGlobal = this.globalLimited;\n\t\t\tlet limit: number;\n\t\t\tlet timeout: number;\n\n\t\t\tif (isGlobal) {\n\t\t\t\t// Set RateLimitData based on the global limit\n\t\t\t\tlimit = this.manager.options.globalRequestsPerSecond;\n\t\t\t\ttimeout = this.manager.globalReset + this.manager.options.offset - Date.now();\n\t\t\t} else {\n\t\t\t\t// Set RateLimitData based on the route-specific limit\n\t\t\t\tlimit = this.limit;\n\t\t\t\ttimeout = this.timeToReset;\n\t\t\t}\n\t\t\tawait this.onRateLimit({\n\t\t\t\ttimeToReset: timeout,\n\t\t\t\tlimit,\n\t\t\t\tmethod,\n\t\t\t\thash: this.hash,\n\t\t\t\turl,\n\t\t\t\troute: routeId.bucketRoute,\n\t\t\t\tmajorParameter: this.majorParameter,\n\t\t\t\tglobal: isGlobal,\n\t\t\t});\n\t\t\tthis.debug(\n\t\t\t\t[\n\t\t\t\t\t'Encountered unexpected 429 rate limit',\n\t\t\t\t\t`  Global         : ${isGlobal.toString()}`,\n\t\t\t\t\t`  Method         : ${method}`,\n\t\t\t\t\t`  URL            : ${url}`,\n\t\t\t\t\t`  Bucket         : ${routeId.bucketRoute}`,\n\t\t\t\t\t`  Major parameter: ${routeId.majorParameter}`,\n\t\t\t\t\t`  Hash           : ${this.hash}`,\n\t\t\t\t\t`  Limit          : ${limit}`,\n\t\t\t\t\t`  Retry After    : ${retryAfter}ms`,\n\t\t\t\t\t`  Sublimit       : ${sublimitTimeout ? `${sublimitTimeout}ms` : 'None'}`,\n\t\t\t\t].join('\\n'),\n\t\t\t);\n\t\t\t// If caused by a sublimit, wait it out here so other requests on the route can be handled\n\t\t\tif (sublimitTimeout) {\n\t\t\t\t// Normally the sublimit queue will not exist, however, if a sublimit is hit while in the sublimit queue, it will\n\t\t\t\tconst firstSublimit = !this.#sublimitedQueue;\n\t\t\t\tif (firstSublimit) {\n\t\t\t\t\tthis.#sublimitedQueue = new AsyncQueue();\n\t\t\t\t\tvoid this.#sublimitedQueue.wait();\n\t\t\t\t\tthis.#asyncQueue.shift();\n\t\t\t\t}\n\t\t\t\tthis.#sublimitPromise?.resolve();\n\t\t\t\tthis.#sublimitPromise = null;\n\t\t\t\tawait sleep(sublimitTimeout, undefined, { ref: false });\n\t\t\t\tlet resolve: () => void;\n\t\t\t\tconst promise = new Promise<void>((res) => (resolve = res));\n\t\t\t\tthis.#sublimitPromise = { promise, resolve: resolve! };\n\t\t\t\tif (firstSublimit) {\n\t\t\t\t\t// Re-queue this request so it can be shifted by the finally\n\t\t\t\t\tawait this.#asyncQueue.wait();\n\t\t\t\t\tthis.#shiftSublimit = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Since this is not a server side issue, the next request should pass, so we don't bump the retries counter\n\t\t\treturn this.runRequest(routeId, url, options, bodyData, retries);\n\t\t} else if (res.status >= 500 && res.status < 600) {\n\t\t\t// Retry the specified number of times for possible server side issues\n\t\t\tif (retries !== this.manager.options.retries) {\n\t\t\t\treturn this.runRequest(routeId, url, options, bodyData, ++retries);\n\t\t\t}\n\t\t\t// We are out of retries, throw an error\n\t\t\tthrow new HTTPError(res.statusText, res.constructor.name, res.status, method, url, bodyData);\n\t\t} else {\n\t\t\t// Handle possible malformed requests\n\t\t\tif (res.status >= 400 && res.status < 500) {\n\t\t\t\t// If we receive this status code, it means the token we had is no longer valid.\n\t\t\t\tif (res.status === 401) {\n\t\t\t\t\tthis.manager.setToken(null!);\n\t\t\t\t}\n\t\t\t\t// The request will not succeed for some reason, parse the error returned from the api\n\t\t\t\tconst data = (await parseResponse(res)) as DiscordErrorData | OAuthErrorData;\n\t\t\t\t// throw the API error\n\t\t\t\tthrow new DiscordAPIError(data, 'code' in data ? data.code : data.error, res.status, method, url, bodyData);\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t}\n}\n","import type { RESTPatchAPIChannelJSONBody } from 'discord-api-types/v9';\nimport type { Response } from 'node-fetch';\nimport { RequestMethod } from '../RequestManager';\n\n/**\n * Converts the response to usable data\n * @param res The node-fetch response\n */\nexport function parseResponse(res: Response): Promise<unknown> {\n\tif (res.headers.get('Content-Type')?.startsWith('application/json')) {\n\t\treturn res.json();\n\t}\n\n\treturn res.buffer();\n}\n\n/**\n * Check whether a request falls under a sublimit\n * @param bucketRoute The buckets route identifier\n * @param body The options provided as JSON data\n * @param method The HTTP method that will be used to make the request\n * @returns Whether the request falls under a sublimit\n */\nexport function hasSublimit(bucketRoute: string, body?: unknown, method?: string): boolean {\n\t// TODO: Update for new sublimits\n\t// Currently known sublimits:\n\t// Editing channel `name` or `topic`\n\tif (bucketRoute === '/channels/:id') {\n\t\tif (typeof body !== 'object' || body === null) return false;\n\t\t// This should never be a POST body, but just in case\n\t\tif (method !== RequestMethod.Patch) return false;\n\t\tconst castedBody = body as RESTPatchAPIChannelJSONBody;\n\t\treturn ['name', 'topic'].some((key) => Reflect.has(castedBody, key));\n\t}\n\n\treturn false;\n}\n","import { EventEmitter } from 'node:events';\nimport { CDN } from './CDN';\nimport { InternalRequest, RequestData, RequestManager, RequestMethod, RouteLike } from './RequestManager';\nimport { DefaultRestOptions, RESTEvents } from './utils/constants';\nimport type { AgentOptions } from 'node:https';\nimport type { RequestInit, Response } from 'node-fetch';\nimport type { HashData } from './RequestManager';\nimport type Collection from '@discordjs/collection';\nimport type { IHandler } from './handlers/IHandler';\n\n/**\n * Options to be passed when creating the REST instance\n */\nexport interface RESTOptions {\n\t/**\n\t * HTTPS Agent options\n\t * @default {}\n\t */\n\tagent: Omit<AgentOptions, 'keepAlive'>;\n\t/**\n\t * The base api path, without version\n\t * @default 'https://discord.com/api'\n\t */\n\tapi: string;\n\t/**\n\t * The cdn path\n\t * @default 'https://cdn.discordapp.com'\n\t */\n\tcdn: string;\n\t/**\n\t * Additional headers to send for all API requests\n\t * @default {}\n\t */\n\theaders: Record<string, string>;\n\t/**\n\t * The number of invalid REST requests (those that return 401, 403, or 429) in a 10 minute window between emitted warnings (0 for no warnings).\n\t * That is, if set to 500, warnings will be emitted at invalid request number 500, 1000, 1500, and so on.\n\t * @default 0\n\t */\n\tinvalidRequestWarningInterval: number;\n\t/**\n\t * How many requests to allow sending per second (Infinity for unlimited, 50 for the standard global limit used by Discord)\n\t * @default 50\n\t */\n\tglobalRequestsPerSecond: number;\n\t/**\n\t * The extra offset to add to rate limits in milliseconds\n\t * @default 50\n\t */\n\toffset: number;\n\t/**\n\t * Determines how rate limiting and pre-emptive throttling should be handled.\n\t * When an array of strings, each element is treated as a prefix for the request route\n\t * (e.g. `/channels` to match any route starting with `/channels` such as `/channels/:id/messages`)\n\t * for which to throw {@link RateLimitError}s. All other request routes will be queued normally\n\t * @default null\n\t */\n\trejectOnRateLimit: string[] | RateLimitQueueFilter | null;\n\t/**\n\t * The number of retries for errors with the 500 code, or errors\n\t * that timeout\n\t * @default 3\n\t */\n\tretries: number;\n\t/**\n\t * The time to wait in milliseconds before a request is aborted\n\t * @default 15_000\n\t */\n\ttimeout: number;\n\t/**\n\t * Extra information to add to the user agent\n\t * @default `Node.js ${process.version}`\n\t */\n\tuserAgentAppendix: string;\n\t/**\n\t * The version of the API to use\n\t * @default '9'\n\t */\n\tversion: string;\n\t/**\n\t * The amount of time in milliseconds that passes between each hash sweep. (defaults to 4h)\n\t * @default 14_400_000\n\t */\n\thashSweepInterval: number;\n\t/**\n\t * The maximum amount of time a hash can exist in milliseconds without being hit with a request (defaults to 24h)\n\t * @default 86_400_000\n\t */\n\thashLifetime: number;\n\t/**\n\t * The amount of time in milliseconds that passes between each hash sweep. (defaults to 1h)\n\t * @default 3_600_000\n\t */\n\thandlerSweepInterval: number;\n}\n\n/**\n * Data emitted on `RESTEvents.RateLimited`\n */\nexport interface RateLimitData {\n\t/**\n\t * The time, in milliseconds, until the request-lock is reset\n\t */\n\ttimeToReset: number;\n\t/**\n\t * The amount of requests we can perform before locking requests\n\t */\n\tlimit: number;\n\t/**\n\t * The HTTP method being performed\n\t */\n\tmethod: string;\n\t/**\n\t * The bucket hash for this request\n\t */\n\thash: string;\n\t/**\n\t * The full URL for this request\n\t */\n\turl: string;\n\t/**\n\t * The route being hit in this request\n\t */\n\troute: string;\n\t/**\n\t * The major parameter of the route\n\t *\n\t * For example, in `/channels/x`, this will be `x`.\n\t * If there is no major parameter (e.g: `/bot/gateway`) this will be `global`.\n\t */\n\tmajorParameter: string;\n\t/**\n\t * Whether the rate limit that was reached was the global limit\n\t */\n\tglobal: boolean;\n}\n\n/**\n * A function that determines whether the rate limit hit should throw an Error\n */\nexport type RateLimitQueueFilter = (rateLimitData: RateLimitData) => boolean | Promise<boolean>;\n\nexport interface APIRequest {\n\t/**\n\t * The HTTP method used in this request\n\t */\n\tmethod: string;\n\t/**\n\t * The full path used to make the request\n\t */\n\tpath: RouteLike;\n\t/**\n\t * The API route identifying the ratelimit for this request\n\t */\n\troute: string;\n\t/**\n\t * Additional HTTP options for this request\n\t */\n\toptions: RequestInit;\n\t/**\n\t * The data that was used to form the body of this request\n\t */\n\tdata: Pick<InternalRequest, 'files' | 'body'>;\n\t/**\n\t * The number of times this request has been attempted\n\t */\n\tretries: number;\n}\n\nexport interface InvalidRequestWarningData {\n\t/**\n\t * Number of invalid requests that have been made in the window\n\t */\n\tcount: number;\n\t/**\n\t * Time in milliseconds remaining before the count resets\n\t */\n\tremainingTime: number;\n}\n\nexport interface RestEvents {\n\tinvalidRequestWarning: [invalidRequestInfo: InvalidRequestWarningData];\n\trestDebug: [info: string];\n\trateLimited: [rateLimitInfo: RateLimitData];\n\trequest: [request: APIRequest];\n\tresponse: [request: APIRequest, response: Response];\n\tnewListener: [name: string, listener: (...args: any) => void];\n\tremoveListener: [name: string, listener: (...args: any) => void];\n\thashSweep: [sweptHashes: Collection<string, HashData>];\n\thandlerSweep: [sweptHandlers: Collection<string, IHandler>];\n}\n\nexport interface REST {\n\ton: (<K extends keyof RestEvents>(event: K, listener: (...args: RestEvents[K]) => void) => this) &\n\t\t(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, listener: (...args: any[]) => void) => this);\n\n\tonce: (<K extends keyof RestEvents>(event: K, listener: (...args: RestEvents[K]) => void) => this) &\n\t\t(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, listener: (...args: any[]) => void) => this);\n\n\temit: (<K extends keyof RestEvents>(event: K, ...args: RestEvents[K]) => boolean) &\n\t\t(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, ...args: any[]) => boolean);\n\n\toff: (<K extends keyof RestEvents>(event: K, listener: (...args: RestEvents[K]) => void) => this) &\n\t\t(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, listener: (...args: any[]) => void) => this);\n\n\tremoveAllListeners: (<K extends keyof RestEvents>(event?: K) => this) &\n\t\t(<S extends string | symbol>(event?: Exclude<S, keyof RestEvents>) => this);\n}\n\nexport class REST extends EventEmitter {\n\tpublic readonly cdn: CDN;\n\tpublic readonly requestManager: RequestManager;\n\n\tpublic constructor(options: Partial<RESTOptions> = {}) {\n\t\tsuper();\n\t\tthis.cdn = new CDN(options.cdn ?? DefaultRestOptions.cdn);\n\t\tthis.requestManager = new RequestManager(options)\n\t\t\t.on(RESTEvents.Debug, this.emit.bind(this, RESTEvents.Debug))\n\t\t\t.on(RESTEvents.RateLimited, this.emit.bind(this, RESTEvents.RateLimited))\n\t\t\t.on(RESTEvents.InvalidRequestWarning, this.emit.bind(this, RESTEvents.InvalidRequestWarning))\n\t\t\t.on(RESTEvents.HashSweep, this.emit.bind(this, RESTEvents.HashSweep));\n\n\t\tthis.on('newListener', (name, listener) => {\n\t\t\tif (name === RESTEvents.Request || name === RESTEvents.Response) this.requestManager.on(name, listener);\n\t\t});\n\t\tthis.on('removeListener', (name, listener) => {\n\t\t\tif (name === RESTEvents.Request || name === RESTEvents.Response) this.requestManager.off(name, listener);\n\t\t});\n\t}\n\n\t/**\n\t * Sets the authorization token that should be used for requests\n\t * @param token The authorization token to use\n\t */\n\tpublic setToken(token: string) {\n\t\tthis.requestManager.setToken(token);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Runs a get request from the api\n\t * @param fullRoute The full route to query\n\t * @param options Optional request options\n\t */\n\tpublic get(fullRoute: RouteLike, options: RequestData = {}) {\n\t\treturn this.request({ ...options, fullRoute, method: RequestMethod.Get });\n\t}\n\n\t/**\n\t * Runs a delete request from the api\n\t * @param fullRoute The full route to query\n\t * @param options Optional request options\n\t */\n\tpublic delete(fullRoute: RouteLike, options: RequestData = {}) {\n\t\treturn this.request({ ...options, fullRoute, method: RequestMethod.Delete });\n\t}\n\n\t/**\n\t * Runs a post request from the api\n\t * @param fullRoute The full route to query\n\t * @param options Optional request options\n\t */\n\tpublic post(fullRoute: RouteLike, options: RequestData = {}) {\n\t\treturn this.request({ ...options, fullRoute, method: RequestMethod.Post });\n\t}\n\n\t/**\n\t * Runs a put request from the api\n\t * @param fullRoute The full route to query\n\t * @param options Optional request options\n\t */\n\tpublic put(fullRoute: RouteLike, options: RequestData = {}) {\n\t\treturn this.request({ ...options, fullRoute, method: RequestMethod.Put });\n\t}\n\n\t/**\n\t * Runs a patch request from the api\n\t * @param fullRoute The full route to query\n\t * @param options Optional request options\n\t */\n\tpublic patch(fullRoute: RouteLike, options: RequestData = {}) {\n\t\treturn this.request({ ...options, fullRoute, method: RequestMethod.Patch });\n\t}\n\n\t/**\n\t * Runs a request from the api\n\t * @param options Request options\n\t */\n\tpublic request(options: InternalRequest) {\n\t\treturn this.requestManager.queueRequest(options);\n\t}\n}\n"],"mappings":"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","names":[]}