import * as url from 'url'; import { Snowflake } from 'discord-api-types/v10'; import { Readable } from 'node:stream'; import { ReadableStream } from 'node:stream/web'; import { Collection } from '@discordjs/collection'; import { Awaitable } from '@discordjs/util'; import * as undici from 'undici'; import { RequestInit, Dispatcher, Response, BodyInit, Agent } from 'undici'; import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter'; interface IHandler { /** * The unique id of the handler */ readonly id: string; /** * If the bucket is currently inactive (no pending requests) */ get inactive(): boolean; /** * Queues a request to be sent * * @param routeId - The generalized api route with literal ids for major parameters * @param url - The url to do the request on * @param options - All the information needed to make a request * @param requestData - Extra data from the user's request needed for errors and additional processing */ queueRequest(routeId: RouteData, url: string, options: RequestInit, requestData: HandlerRequestData): Promise; } interface RestEvents { handlerSweep: [sweptHandlers: Collection]; hashSweep: [sweptHashes: Collection]; invalidRequestWarning: [invalidRequestInfo: InvalidRequestWarningData]; rateLimited: [rateLimitInfo: RateLimitData]; response: [request: APIRequest, response: ResponseLike]; restDebug: [info: string]; } type RestEventsMap = { [K in keyof RestEvents]: RestEvents[K]; }; /** * Options to be passed when creating the REST instance */ interface RESTOptions { /** * The agent to set globally */ agent: Dispatcher | null; /** * The base api path, without version * * @defaultValue `'https://discord.com/api'` */ api: string; /** * The authorization prefix to use for requests, useful if you want to use * bearer tokens * * @defaultValue `'Bot'` */ authPrefix: 'Bearer' | 'Bot'; /** * The cdn path * * @defaultValue `'https://cdn.discordapp.com'` */ cdn: string; /** * How many requests to allow sending per second (Infinity for unlimited, 50 for the standard global limit used by Discord) * * @defaultValue `50` */ globalRequestsPerSecond: number; /** * The amount of time in milliseconds that passes between each hash sweep. (defaults to 1h) * * @defaultValue `3_600_000` */ handlerSweepInterval: number; /** * The maximum amount of time a hash can exist in milliseconds without being hit with a request (defaults to 24h) * * @defaultValue `86_400_000` */ hashLifetime: number; /** * The amount of time in milliseconds that passes between each hash sweep. (defaults to 4h) * * @defaultValue `14_400_000` */ hashSweepInterval: number; /** * Additional headers to send for all API requests * * @defaultValue `{}` */ headers: Record; /** * 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). * That is, if set to 500, warnings will be emitted at invalid request number 500, 1000, 1500, and so on. * * @defaultValue `0` */ invalidRequestWarningInterval: number; /** * The method called to perform the actual HTTP request given a url and web `fetch` options * For example, to use global fetch, simply provide `makeRequest: fetch` */ makeRequest(url: string, init: RequestInit): Promise; /** * The extra offset to add to rate limits in milliseconds * * @defaultValue `50` */ offset: number; /** * Determines how rate limiting and pre-emptive throttling should be handled. * When an array of strings, each element is treated as a prefix for the request route * (e.g. `/channels` to match any route starting with `/channels` such as `/channels/:id/messages`) * for which to throw {@link RateLimitError}s. All other request routes will be queued normally * * @defaultValue `null` */ rejectOnRateLimit: RateLimitQueueFilter | string[] | null; /** * The number of retries for errors with the 500 code, or errors * that timeout * * @defaultValue `3` */ retries: number; /** * The time to wait in milliseconds before a request is aborted * * @defaultValue `15_000` */ timeout: number; /** * Extra information to add to the user agent * * @defaultValue DefaultUserAgentAppendix */ userAgentAppendix: string; /** * The version of the API to use * * @defaultValue `'10'` */ version: string; } /** * Data emitted on `RESTEvents.RateLimited` */ interface RateLimitData { /** * Whether the rate limit that was reached was the global limit */ global: boolean; /** * The bucket hash for this request */ hash: string; /** * The amount of requests we can perform before locking requests */ limit: number; /** * The major parameter of the route * * For example, in `/channels/x`, this will be `x`. * If there is no major parameter (e.g: `/bot/gateway`) this will be `global`. */ majorParameter: string; /** * The HTTP method being performed */ method: string; /** * The time, in milliseconds, that will need to pass before this specific request can be retried */ retryAfter: number; /** * The route being hit in this request */ route: string; /** * The scope of the rate limit that was hit. * * This can be `user` for rate limits that are per client, `global` for rate limits that affect all clients or `shared` for rate limits that * are shared per resource. */ scope: 'global' | 'shared' | 'user'; /** * The time, in milliseconds, that will need to pass before the sublimit lock for the route resets, and requests that fall under a sublimit * can be retried * * This is only present on certain sublimits, and `0` otherwise */ sublimitTimeout: number; /** * The time, in milliseconds, until the route's request-lock is reset */ timeToReset: number; /** * The full URL for this request */ url: string; } /** * A function that determines whether the rate limit hit should throw an Error */ type RateLimitQueueFilter = (rateLimitData: RateLimitData) => Awaitable; interface APIRequest { /** * The data that was used to form the body of this request */ data: HandlerRequestData; /** * The HTTP method used in this request */ method: string; /** * Additional HTTP options for this request */ options: RequestInit; /** * The full path used to make the request */ path: RouteLike; /** * The number of times this request has been attempted */ retries: number; /** * The API route identifying the ratelimit for this request */ route: string; } interface ResponseLike extends Pick { body: Readable | ReadableStream | null; } interface InvalidRequestWarningData { /** * Number of invalid requests that have been made in the window */ count: number; /** * Time in milliseconds remaining before the count resets */ remainingTime: number; } /** * Represents a file to be added to the request */ interface RawFile { /** * Content-Type of the file */ contentType?: string; /** * The actual data for the file */ data: Buffer | Uint8Array | boolean | number | string; /** * An explicit key to use for key of the formdata field for this file. * When not provided, the index of the file in the files array is used in the form `files[${index}]`. * If you wish to alter the placeholder snowflake, you must provide this property in the same form (`files[${placeholder}]`) */ key?: string; /** * The name of the file */ name: string; } /** * Represents possible data to be given to an endpoint */ interface RequestData { /** * Whether to append JSON data to form data instead of `payload_json` when sending files */ appendToFormData?: boolean; /** * If this request needs the `Authorization` header * * @defaultValue `true` */ auth?: boolean; /** * The authorization prefix to use for this request, useful if you use this with bearer tokens * * @defaultValue `'Bot'` */ authPrefix?: 'Bearer' | 'Bot'; /** * The body to send to this request. * If providing as BodyInit, set `passThroughBody: true` */ body?: BodyInit | unknown; /** * The {@link https://undici.nodejs.org/#/docs/api/Agent | Agent} to use for the request. */ dispatcher?: Agent; /** * Files to be attached to this request */ files?: RawFile[] | undefined; /** * Additional headers to add to this request */ headers?: Record; /** * Whether to pass-through the body property directly to `fetch()`. * This only applies when files is NOT present */ passThroughBody?: boolean; /** * Query string parameters to append to the called endpoint */ query?: URLSearchParams; /** * Reason to show in the audit logs */ reason?: string | undefined; /** * The signal to abort the queue entry or the REST call, where applicable */ signal?: AbortSignal | undefined; /** * If this request should be versioned * * @defaultValue `true` */ versioned?: boolean; } /** * Possible headers for an API call */ interface RequestHeaders { Authorization?: string; 'User-Agent': string; 'X-Audit-Log-Reason'?: string; } /** * Possible API methods to be used when doing requests */ declare enum RequestMethod { Delete = "DELETE", Get = "GET", Patch = "PATCH", Post = "POST", Put = "PUT" } type RouteLike = `/${string}`; /** * Internal request options * * @internal */ interface InternalRequest extends RequestData { fullRoute: RouteLike; method: RequestMethod; } type HandlerRequestData = Pick; /** * Parsed route data for an endpoint * * @internal */ interface RouteData { bucketRoute: string; majorParameter: string; original: RouteLike; } /** * Represents a hash and its associated fields * * @internal */ interface HashData { lastAccess: number; value: string; } declare const DefaultUserAgent: `DiscordBot (https://discord.js.org, ${string})`; /** * The default string to append onto the user agent. */ declare const DefaultUserAgentAppendix: string; declare const DefaultRestOptions: { readonly agent: null; readonly api: "https://discord.com/api"; readonly authPrefix: "Bot"; readonly cdn: "https://cdn.discordapp.com"; readonly headers: {}; readonly invalidRequestWarningInterval: 0; readonly globalRequestsPerSecond: 50; readonly offset: 50; readonly rejectOnRateLimit: null; readonly retries: 3; readonly timeout: 15000; readonly userAgentAppendix: string; readonly version: "10"; readonly hashSweepInterval: 14400000; readonly hashLifetime: 86400000; readonly handlerSweepInterval: 3600000; readonly makeRequest: (url: string, init: undici.RequestInit) => Promise; }; /** * The events that the REST manager emits */ declare enum RESTEvents { Debug = "restDebug", HandlerSweep = "handlerSweep", HashSweep = "hashSweep", InvalidRequestWarning = "invalidRequestWarning", RateLimited = "rateLimited", Response = "response" } declare const ALLOWED_EXTENSIONS: readonly ["webp", "png", "jpg", "jpeg", "gif"]; declare const ALLOWED_STICKER_EXTENSIONS: readonly ["png", "json", "gif"]; declare const ALLOWED_SIZES: readonly [16, 32, 64, 128, 256, 512, 1024, 2048, 4096]; type ImageExtension = (typeof ALLOWED_EXTENSIONS)[number]; type StickerExtension = (typeof ALLOWED_STICKER_EXTENSIONS)[number]; type ImageSize = (typeof ALLOWED_SIZES)[number]; declare const OverwrittenMimeTypes: { readonly 'image/apng': "image/png"; }; declare const BurstHandlerMajorIdKey = "burst"; /** * Prefix for deprecation warnings. * * @internal */ declare const DEPRECATION_WARNING_PREFIX: "DeprecationWarning"; /** * The options used for image URLs */ interface BaseImageURLOptions { /** * The extension to use for the image URL * * @defaultValue `'webp'` */ extension?: ImageExtension; /** * The size specified in the image URL */ size?: ImageSize; } /** * The options used for image URLs with animated content */ interface ImageURLOptions extends BaseImageURLOptions { /** * Whether or not to prefer the static version of an image asset. */ forceStatic?: boolean; } /** * The options to use when making a CDN URL */ interface MakeURLOptions { /** * The allowed extensions that can be used */ allowedExtensions?: readonly string[]; /** * The extension to use for the image URL * * @defaultValue `'webp'` */ extension?: string | undefined; /** * The size specified in the image URL */ size?: ImageSize; } /** * The CDN link builder */ declare class CDN { private readonly base; constructor(base?: string); /** * Generates an app asset URL for a client's asset. * * @param clientId - The client id that has the asset * @param assetHash - The hash provided by Discord for this asset * @param options - Optional options for the asset */ appAsset(clientId: string, assetHash: string, options?: Readonly): string; /** * Generates an app icon URL for a client's icon. * * @param clientId - The client id that has the icon * @param iconHash - The hash provided by Discord for this icon * @param options - Optional options for the icon */ appIcon(clientId: string, iconHash: string, options?: Readonly): string; /** * Generates an avatar URL, e.g. for a user or a webhook. * * @param id - The id that has the icon * @param avatarHash - The hash provided by Discord for this avatar * @param options - Optional options for the avatar */ avatar(id: string, avatarHash: string, options?: Readonly): string; /** * Generates a user avatar decoration URL. * * @param userId - The id of the user * @param userAvatarDecoration - The hash provided by Discord for this avatar decoration * @param options - Optional options for the avatar decoration */ avatarDecoration(userId: string, userAvatarDecoration: string, options?: Readonly): string; /** * Generates a banner URL, e.g. for a user or a guild. * * @param id - The id that has the banner splash * @param bannerHash - The hash provided by Discord for this banner * @param options - Optional options for the banner */ banner(id: string, bannerHash: string, options?: Readonly): string; /** * Generates an icon URL for a channel, e.g. a group DM. * * @param channelId - The channel id that has the icon * @param iconHash - The hash provided by Discord for this channel * @param options - Optional options for the icon */ channelIcon(channelId: string, iconHash: string, options?: Readonly): string; /** * Generates a default avatar URL * * @param index - The default avatar index * @remarks * To calculate the index for a user do `(userId >> 22) % 6`, * or `discriminator % 5` if they're using the legacy username system. */ defaultAvatar(index: number): string; /** * Generates a discovery splash URL for a guild's discovery splash. * * @param guildId - The guild id that has the discovery splash * @param splashHash - The hash provided by Discord for this splash * @param options - Optional options for the splash */ discoverySplash(guildId: string, splashHash: string, options?: Readonly): string; /** * Generates an emoji's URL for an emoji. * * @param emojiId - The emoji id * @param options - Optional options for the emoji */ emoji(emojiId: string, options?: Readonly): string; /** * Generates an emoji's URL for an emoji. * * @param emojiId - The emoji id * @param extension - The extension of the emoji * @deprecated This overload is deprecated. Pass an object containing the extension instead. */ emoji(emojiId: string, extension?: ImageExtension): string; /** * Generates a guild member avatar URL. * * @param guildId - The id of the guild * @param userId - The id of the user * @param avatarHash - The hash provided by Discord for this avatar * @param options - Optional options for the avatar */ guildMemberAvatar(guildId: string, userId: string, avatarHash: string, options?: Readonly): string; /** * Generates a guild member banner URL. * * @param guildId - The id of the guild * @param userId - The id of the user * @param bannerHash - The hash provided by Discord for this banner * @param options - Optional options for the banner */ guildMemberBanner(guildId: string, userId: string, bannerHash: string, options?: Readonly): string; /** * Generates an icon URL, e.g. for a guild. * * @param id - The id that has the icon splash * @param iconHash - The hash provided by Discord for this icon * @param options - Optional options for the icon */ icon(id: string, iconHash: string, options?: Readonly): string; /** * Generates a URL for the icon of a role * * @param roleId - The id of the role that has the icon * @param roleIconHash - The hash provided by Discord for this role icon * @param options - Optional options for the role icon */ roleIcon(roleId: string, roleIconHash: string, options?: Readonly): string; /** * Generates a guild invite splash URL for a guild's invite splash. * * @param guildId - The guild id that has the invite splash * @param splashHash - The hash provided by Discord for this splash * @param options - Optional options for the splash */ splash(guildId: string, splashHash: string, options?: Readonly): string; /** * Generates a sticker URL. * * @param stickerId - The sticker id * @param extension - The extension of the sticker * @privateRemarks * Stickers cannot have a `.webp` extension, so we default to a `.png` */ sticker(stickerId: string, extension?: StickerExtension): string; /** * Generates a sticker pack banner URL. * * @param bannerId - The banner id * @param options - Optional options for the banner */ stickerPackBanner(bannerId: string, options?: Readonly): string; /** * Generates a team icon URL for a team's icon. * * @param teamId - The team id that has the icon * @param iconHash - The hash provided by Discord for this icon * @param options - Optional options for the icon */ teamIcon(teamId: string, iconHash: string, options?: Readonly): string; /** * Generates a cover image for a guild scheduled event. * * @param scheduledEventId - The scheduled event id * @param coverHash - The hash provided by discord for this cover image * @param options - Optional options for the cover image */ guildScheduledEventCover(scheduledEventId: string, coverHash: string, options?: Readonly): string; /** * Constructs the URL for the resource, checking whether or not `hash` starts with `a_` if `dynamic` is set to `true`. * * @param route - The base cdn route * @param hash - The hash provided by Discord for this icon * @param options - Optional options for the link */ private dynamicMakeURL; /** * Constructs the URL for the resource * * @param route - The base cdn route * @param options - The extension/size options for the link */ private makeURL; } interface DiscordErrorFieldInformation { code: string; message: string; } interface DiscordErrorGroupWrapper { _errors: DiscordError[]; } type DiscordError = DiscordErrorFieldInformation | DiscordErrorGroupWrapper | string | { [k: string]: DiscordError; }; interface DiscordErrorData { code: number; errors?: DiscordError; message: string; } interface OAuthErrorData { error: string; error_description?: string; } interface RequestBody { files: RawFile[] | undefined; json: unknown | undefined; } /** * Represents an API error returned by Discord */ declare class DiscordAPIError extends Error { rawError: DiscordErrorData | OAuthErrorData; code: number | string; status: number; method: string; url: string; requestBody: RequestBody; /** * @param rawError - The error reported by Discord * @param code - The error code reported by Discord * @param status - The status code of the response * @param method - The method of the request that erred * @param url - The url of the request that erred * @param bodyData - The unparsed data for the request that errored */ constructor(rawError: DiscordErrorData | OAuthErrorData, code: number | string, status: number, method: string, url: string, bodyData: Pick); /** * The name of the error */ get name(): string; private static getMessage; private static flattenDiscordError; } /** * Represents a HTTP error */ declare class HTTPError extends Error { status: number; method: string; url: string; requestBody: RequestBody; name: string; /** * @param status - The status code of the response * @param statusText - The status text of the response * @param method - The method of the request that erred * @param url - The url of the request that erred * @param bodyData - The unparsed data for the request that errored */ constructor(status: number, statusText: string, method: string, url: string, bodyData: Pick); } declare class RateLimitError extends Error implements RateLimitData { timeToReset: number; limit: number; method: string; hash: string; url: string; route: string; majorParameter: string; global: boolean; retryAfter: number; sublimitTimeout: number; scope: RateLimitData['scope']; constructor({ timeToReset, limit, method, hash, url, route, majorParameter, global, retryAfter, sublimitTimeout, scope, }: RateLimitData); /** * The name of the error */ get name(): string; } /** * Represents the class that manages handlers for endpoints */ declare class REST extends AsyncEventEmitter { #private; /** * The {@link https://undici.nodejs.org/#/docs/api/Agent | Agent} for all requests * performed by this manager. */ agent: Dispatcher | null; readonly cdn: CDN; /** * The number of requests remaining in the global bucket */ globalRemaining: number; /** * The promise used to wait out the global rate limit */ globalDelay: Promise | null; /** * The timestamp at which the global bucket resets */ globalReset: number; /** * API bucket hashes that are cached from provided routes */ readonly hashes: Collection; /** * Request handlers created from the bucket hash and the major parameters */ readonly handlers: Collection; private hashTimer; private handlerTimer; readonly options: RESTOptions; constructor(options?: Partial); private setupSweepers; /** * Runs a get request from the api * * @param fullRoute - The full route to query * @param options - Optional request options */ get(fullRoute: RouteLike, options?: RequestData): Promise; /** * Runs a delete request from the api * * @param fullRoute - The full route to query * @param options - Optional request options */ delete(fullRoute: RouteLike, options?: RequestData): Promise; /** * Runs a post request from the api * * @param fullRoute - The full route to query * @param options - Optional request options */ post(fullRoute: RouteLike, options?: RequestData): Promise; /** * Runs a put request from the api * * @param fullRoute - The full route to query * @param options - Optional request options */ put(fullRoute: RouteLike, options?: RequestData): Promise; /** * Runs a patch request from the api * * @param fullRoute - The full route to query * @param options - Optional request options */ patch(fullRoute: RouteLike, options?: RequestData): Promise; /** * Runs a request from the api * * @param options - Request options */ request(options: InternalRequest): Promise; /** * Sets the default agent to use for requests performed by this manager * * @param agent - The agent to use */ setAgent(agent: Dispatcher): this; /** * Sets the authorization token that should be used for requests * * @param token - The authorization token to use */ setToken(token: string): this; /** * Queues a request to be sent * * @param request - All the information needed to make a request * @returns The response from the api request */ queueRequest(request: InternalRequest): Promise; /** * Creates a new rate limit handler from a hash, based on the hash and the major parameter * * @param hash - The hash for the route * @param majorParameter - The major parameter for this handler * @internal */ private createHandler; /** * Formats the request data to a usable format for fetch * * @param request - The request data */ private resolveRequest; /** * Stops the hash sweeping interval */ clearHashSweeper(): void; /** * Stops the request handler sweeping interval */ clearHandlerSweeper(): void; /** * Generates route data for an endpoint:method * * @param endpoint - The raw endpoint to generalize * @param method - The HTTP method this endpoint is called without * @internal */ private static generateRouteData; } /** * Creates and populates an URLSearchParams instance from an object, stripping * out null and undefined values, while also coercing non-strings to strings. * * @param options - The options to use * @returns A populated URLSearchParams instance */ declare function makeURLSearchParams(options?: Readonly): url.URLSearchParams; /** * Converts the response to usable data * * @param res - The fetch response */ declare function parseResponse(res: ResponseLike): Promise; /** * Calculates the default avatar index for a given user id. * * @param userId - The user id to calculate the default avatar index for */ declare function calculateUserDefaultAvatarIndex(userId: Snowflake): number; /** * The {@link https://github.com/discordjs/discord.js/blob/main/packages/rest#readme | @discordjs/rest} version * that you are currently using. */ declare const version: string; export { ALLOWED_EXTENSIONS, ALLOWED_SIZES, ALLOWED_STICKER_EXTENSIONS, APIRequest, BaseImageURLOptions, BurstHandlerMajorIdKey, CDN, DEPRECATION_WARNING_PREFIX, DefaultRestOptions, DefaultUserAgent, DefaultUserAgentAppendix, DiscordAPIError, DiscordErrorData, HTTPError, HandlerRequestData, HashData, ImageExtension, ImageSize, ImageURLOptions, InternalRequest, InvalidRequestWarningData, MakeURLOptions, OAuthErrorData, OverwrittenMimeTypes, REST, RESTEvents, RESTOptions, RateLimitData, RateLimitError, RateLimitQueueFilter, RawFile, RequestBody, RequestData, RequestHeaders, RequestMethod, ResponseLike, RestEvents, RestEventsMap, RouteData, RouteLike, StickerExtension, calculateUserDefaultAvatarIndex, makeURLSearchParams, parseResponse, version };