mirror of
https://github.com/discordeno/discordeno.git
synced 2026-05-21 02:40:08 +00:00
* fix(bot,rest,types)!: attachment sending * apply code suggestions * forgot to add that * this should not be there i guess * maybe spell it right * actually revert the attachments rename * Change how method gets passed * more stuff * improve function name
1805 lines
62 KiB
TypeScript
1805 lines
62 KiB
TypeScript
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
/* eslint-disable no-const-assign */
|
|
import { InteractionResponseTypes } from '@discordeno/types'
|
|
import {
|
|
calculateBits,
|
|
camelize,
|
|
camelToSnakeCase,
|
|
delay,
|
|
encode,
|
|
getBotIdFromToken,
|
|
isGetMessagesAfter,
|
|
isGetMessagesAround,
|
|
isGetMessagesBefore,
|
|
isGetMessagesLimit,
|
|
logger,
|
|
processReactionString,
|
|
urlToBase64,
|
|
} from '@discordeno/utils'
|
|
import fetch from 'node-fetch'
|
|
|
|
import { createInvalidRequestBucket } from './invalidBucket.js'
|
|
import { Queue } from './queue.js'
|
|
|
|
import type {
|
|
BigString,
|
|
Camelize,
|
|
DiscordApplication,
|
|
DiscordApplicationCommand,
|
|
DiscordApplicationCommandPermissions,
|
|
DiscordAuditLog,
|
|
DiscordAutoModerationRule,
|
|
DiscordBan,
|
|
DiscordChannel,
|
|
DiscordEmoji,
|
|
DiscordFollowedChannel,
|
|
DiscordGetGatewayBot,
|
|
DiscordGuild,
|
|
DiscordGuildPreview,
|
|
DiscordGuildWidget,
|
|
DiscordGuildWidgetSettings,
|
|
DiscordIntegration,
|
|
DiscordInvite,
|
|
DiscordInviteMetadata,
|
|
DiscordListActiveThreads,
|
|
DiscordListArchivedThreads,
|
|
DiscordMember,
|
|
DiscordMemberWithUser,
|
|
DiscordMessage,
|
|
DiscordPrunedCount,
|
|
DiscordRole,
|
|
DiscordScheduledEvent,
|
|
DiscordStageInstance,
|
|
DiscordSticker,
|
|
DiscordStickerPack,
|
|
DiscordTemplate,
|
|
DiscordThreadMember,
|
|
DiscordUser,
|
|
DiscordVanityUrl,
|
|
DiscordVoiceRegion,
|
|
DiscordWebhook,
|
|
DiscordWelcomeScreen,
|
|
FileContent,
|
|
GetMessagesOptions,
|
|
GetScheduledEventUsers,
|
|
MfaLevels,
|
|
ModifyGuildTemplate,
|
|
} from '@discordeno/types'
|
|
import type { CreateRequestBodyOptions, CreateRestManagerOptions, RestManager, SendRequestOptions } from './types.js'
|
|
|
|
// TODO: make dynamic based on package.json file
|
|
const version = '19.0.0-alpha.1'
|
|
|
|
export function createRestManager(options: CreateRestManagerOptions): RestManager {
|
|
// Falsy token string check
|
|
if (!options.token) throw new Error('You must provide a valid token.')
|
|
|
|
const rest: RestManager = {
|
|
token: options.token,
|
|
applicationId: options.applicationId ? BigInt(options.applicationId) : getBotIdFromToken(options.token),
|
|
version: options.version ?? 10,
|
|
baseUrl: options.proxy?.baseUrl ?? 'https://discord.com/api',
|
|
maxRetryCount: Infinity,
|
|
globallyRateLimited: false,
|
|
processingRateLimitedPaths: false,
|
|
deleteQueueDelay: 60000,
|
|
queues: new Map(),
|
|
rateLimitedPaths: new Map(),
|
|
invalidBucket: createInvalidRequestBucket({}),
|
|
authorization: options.proxy?.authorization,
|
|
|
|
routes: {
|
|
webhooks: {
|
|
id: (webhookId) => {
|
|
return `/webhooks/${webhookId}`
|
|
},
|
|
message: (webhookId, token, messageId, options) => {
|
|
let url = `/webhooks/${webhookId}/${token}/messages/${messageId}?`
|
|
|
|
if (options) {
|
|
if (options.threadId) url += `thread_id=${options.threadId}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
original: (webhookId, token, options) => {
|
|
let url = `/webhooks/${webhookId}/${token}/messages/@original?`
|
|
|
|
if (options) {
|
|
if (options.threadId) url += `thread_id=${options.threadId}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
webhook: (webhookId, token, options) => {
|
|
let url = `/webhooks/${webhookId}/${token}?`
|
|
|
|
if (options) {
|
|
if (options?.wait !== undefined) url += `wait=${options.wait.toString()}`
|
|
if (options.threadId) url += `thread_id=${options.threadId}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
},
|
|
|
|
// Miscellaneous Endpoints
|
|
sessionInfo: () => rest.routes.gatewayBot(),
|
|
|
|
// Channel Endpoints
|
|
channels: {
|
|
bulk: (channelId) => {
|
|
return `/channels/${channelId}/messages/bulk-delete`
|
|
},
|
|
dm: () => {
|
|
return '/users/@me/channels'
|
|
},
|
|
pin: (channelId, messageId) => {
|
|
return `/channels/${channelId}/pins/${messageId}`
|
|
},
|
|
pins: (channelId) => {
|
|
return `/channels/${channelId}/pins`
|
|
},
|
|
reactions: {
|
|
bot: (channelId, messageId, emoji) => {
|
|
return `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/@me`
|
|
},
|
|
user: (channelId, messageId, emoji, userId) => {
|
|
return `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/${userId}`
|
|
},
|
|
all: (channelId, messageId) => {
|
|
return `/channels/${channelId}/messages/${messageId}/reactions`
|
|
},
|
|
emoji: (channelId, messageId, emoji, options) => {
|
|
let url = `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}?`
|
|
|
|
if (options) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.after) url += `after=${options.after}`
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.limit) url += `&limit=${options.limit}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
message: (channelId, messageId, emoji, options) => {
|
|
let url = `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}?`
|
|
|
|
if (options) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.after) url += `after=${options.after}`
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.limit) url += `&limit=${options.limit}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
},
|
|
webhooks: (channelId) => {
|
|
return `/channels/${channelId}/webhooks`
|
|
},
|
|
|
|
channel: (channelId) => {
|
|
return `/channels/${channelId}`
|
|
},
|
|
|
|
follow: (channelId) => {
|
|
return `/channels/${channelId}/followers`
|
|
},
|
|
|
|
forum: (channelId) => {
|
|
return `/channels/${channelId}/threads?has_message=true`
|
|
},
|
|
|
|
invites: (channelId) => {
|
|
return `/channels/${channelId}/invites`
|
|
},
|
|
|
|
message: (channelId, messageId) => {
|
|
return `/channels/${channelId}/messages/${messageId}`
|
|
},
|
|
|
|
messages: (channelId, options?: GetMessagesOptions) => {
|
|
let url = `/channels/${channelId}/messages?`
|
|
|
|
if (options) {
|
|
if (isGetMessagesAfter(options) && options.after) {
|
|
url += `after=${options.after}`
|
|
}
|
|
if (isGetMessagesBefore(options) && options.before) {
|
|
url += `&before=${options.before}`
|
|
}
|
|
if (isGetMessagesAround(options) && options.around) {
|
|
url += `&around=${options.around}`
|
|
}
|
|
if (isGetMessagesLimit(options) && options.limit) {
|
|
url += `&limit=${options.limit}`
|
|
}
|
|
}
|
|
|
|
return url
|
|
},
|
|
|
|
overwrite: (channelId, overwriteId) => {
|
|
return `/channels/${channelId}/permissions/${overwriteId}`
|
|
},
|
|
|
|
crosspost: (channelId, messageId) => {
|
|
return `/channels/${channelId}/messages/${messageId}/crosspost`
|
|
},
|
|
|
|
stages: () => {
|
|
return '/stage-instances'
|
|
},
|
|
|
|
stage: (channelId) => {
|
|
return `/stage-instances/${channelId}`
|
|
},
|
|
|
|
// Thread Endpoints
|
|
threads: {
|
|
message: (channelId, messageId) => {
|
|
return `/channels/${channelId}/messages/${messageId}/threads`
|
|
},
|
|
all: (channelId) => {
|
|
return `/channels/${channelId}/threads`
|
|
},
|
|
active: (guildId) => {
|
|
return `/guilds/${guildId}/threads/active`
|
|
},
|
|
members: (channelId) => {
|
|
return `/channels/${channelId}/thread-members`
|
|
},
|
|
me: (channelId) => {
|
|
return `/channels/${channelId}/thread-members/@me`
|
|
},
|
|
user: (channelId, userId) => {
|
|
return `/channels/${channelId}/thread-members/${userId}`
|
|
},
|
|
archived: (channelId) => {
|
|
return `/channels/${channelId}/threads/archived`
|
|
},
|
|
public: (channelId, options) => {
|
|
let url = `/channels/${channelId}/threads/archived/public?`
|
|
|
|
if (options) {
|
|
if (options.before) {
|
|
url += `before=${new Date(options.before).toISOString()}`
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.limit) url += `&limit=${options.limit}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
private: (channelId, options) => {
|
|
let url = `/channels/${channelId}/threads/archived/private?`
|
|
|
|
if (options) {
|
|
if (options.before) {
|
|
url += `before=${new Date(options.before).toISOString()}`
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.limit) url += `&limit=${options.limit}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
joined: (channelId, options) => {
|
|
let url = `/channels/${channelId}/users/@me/threads/archived/private?`
|
|
|
|
if (options) {
|
|
if (options.before) {
|
|
url += `before=${new Date(options.before).toISOString()}`
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.limit) url += `&limit=${options.limit}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
},
|
|
|
|
typing: (channelId) => {
|
|
return `/channels/${channelId}/typing`
|
|
},
|
|
},
|
|
|
|
// Guild Endpoints
|
|
guilds: {
|
|
all: () => {
|
|
return '/guilds'
|
|
},
|
|
auditlogs: (guildId, options) => {
|
|
let url = `/guilds/${guildId}/audit-logs?`
|
|
|
|
if (options) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.actionType) url += `action_type=${options.actionType}`
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.before) url += `&before=${options.before}`
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.after) url += `&after=${options.after}`
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.limit) url += `&limit=${options.limit}`
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.userId) url += `&user_id=${options.userId}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
automod: {
|
|
rule: (guildId, ruleId) => {
|
|
return `/guilds/${guildId}/auto-moderation/rules/${ruleId}`
|
|
},
|
|
rules: (guildId) => {
|
|
return `/guilds/${guildId}/auto-moderation/rules`
|
|
},
|
|
},
|
|
channels: (guildId) => {
|
|
return `/guilds/${guildId}/channels`
|
|
},
|
|
emoji: (guildId, emojiId) => {
|
|
return `/guilds/${guildId}/emojis/${emojiId}`
|
|
},
|
|
emojis: (guildId) => {
|
|
return `/guilds/${guildId}/emojis`
|
|
},
|
|
events: {
|
|
events: (guildId, withUserCount?: boolean) => {
|
|
let url = `/guilds/${guildId}/scheduled-events?`
|
|
|
|
if (withUserCount !== undefined) {
|
|
url += `with_user_count=${withUserCount.toString()}`
|
|
}
|
|
return url
|
|
},
|
|
event: (guildId, eventId, withUserCount?: boolean) => {
|
|
let url = `/guilds/${guildId}/scheduled-events/${eventId}`
|
|
|
|
if (withUserCount !== undefined) {
|
|
url += `with_user_count=${withUserCount.toString()}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
users: (guildId, eventId, options?: GetScheduledEventUsers) => {
|
|
let url = `/guilds/${guildId}/scheduled-events/${eventId}/users?`
|
|
|
|
if (options) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.limit !== undefined) url += `limit=${options.limit}`
|
|
if (options.withMember !== undefined) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
url += `&with_member=${options.withMember.toString()}`
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.after !== undefined) url += `&after=${options.after}`
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.before !== undefined) url += `&before=${options.before}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
},
|
|
guild(guildId, withCounts) {
|
|
let url = `/guilds/${guildId}?`
|
|
|
|
if (withCounts !== undefined) {
|
|
url += `with_counts=${withCounts.toString()}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
integration(guildId, integrationId) {
|
|
return `/guilds/${guildId}/integrations/${integrationId}`
|
|
},
|
|
integrations: (guildId) => {
|
|
return `/guilds/${guildId}/integrations?include_applications=true`
|
|
},
|
|
invite(inviteCode, options) {
|
|
let url = `/invites/${inviteCode}?`
|
|
|
|
if (options) {
|
|
if (options.withCounts !== undefined) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
url += `with_counts=${options.withCounts.toString()}`
|
|
}
|
|
if (options.withExpiration !== undefined) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
url += `&with_expiration=${options.withExpiration.toString()}`
|
|
}
|
|
if (options.scheduledEventId) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
url += `&guild_scheduled_event_id=${options.scheduledEventId}`
|
|
}
|
|
}
|
|
|
|
return url
|
|
},
|
|
invites: (guildId) => {
|
|
return `/guilds/${guildId}/invites`
|
|
},
|
|
leave: (guildId) => {
|
|
return `/users/@me/guilds/${guildId}`
|
|
},
|
|
members: {
|
|
ban: (guildId, userId) => {
|
|
return `/guilds/${guildId}/bans/${userId}`
|
|
},
|
|
bans: (guildId, options) => {
|
|
let url = `/guilds/${guildId}/bans?`
|
|
|
|
if (options) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.limit) url += `limit=${options.limit}`
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.after) url += `&after=${options.after}`
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.before) url += `&before=${options.before}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
bot: (guildId) => {
|
|
return `/guilds/${guildId}/members/@me`
|
|
},
|
|
member: (guildId, userId) => {
|
|
return `/guilds/${guildId}/members/${userId}`
|
|
},
|
|
members: (guildId, options) => {
|
|
let url = `/guilds/${guildId}/members?`
|
|
|
|
if (options !== undefined) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.limit) url += `limit=${options.limit}`
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.after) url += `&after=${options.after}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
search: (guildId, query, options) => {
|
|
let url = `/guilds/${guildId}/members/search?query=${encodeURIComponent(query)}`
|
|
|
|
if (options) {
|
|
if (options.limit !== undefined) url += `&limit=${options.limit}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
prune: (guildId, options) => {
|
|
let url = `/guilds/${guildId}/prune?`
|
|
|
|
if (options) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.days) url += `days=${options.days}`
|
|
if (Array.isArray(options.includeRoles)) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
url += `&include_roles=${options.includeRoles.join(',')}`
|
|
} else if (options.includeRoles) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
url += `&include_roles=${options.includeRoles}`
|
|
}
|
|
}
|
|
|
|
return url
|
|
},
|
|
},
|
|
mfa: (guildId) => `/guilds/${guildId}/mfa`,
|
|
preview: (guildId) => {
|
|
return `/guilds/${guildId}/preview`
|
|
},
|
|
prune: (guildId, options) => {
|
|
let url = `/guilds/${guildId}/prune?`
|
|
|
|
if (options) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
if (options.days) url += `days=${options.days}`
|
|
if (Array.isArray(options.includeRoles)) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
url += `&include_roles=${options.includeRoles.join(',')}`
|
|
} else if (options.includeRoles) {
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
url += `&include_roles=${options.includeRoles}`
|
|
}
|
|
}
|
|
|
|
return url
|
|
},
|
|
roles: {
|
|
one: (guildId, roleId) => {
|
|
return `/guilds/${guildId}/roles/${roleId}`
|
|
},
|
|
all: (guildId) => {
|
|
return `/guilds/${guildId}/roles`
|
|
},
|
|
member: (guildId, memberId, roleId) => {
|
|
return `/guilds/${guildId}/members/${memberId}/roles/${roleId}`
|
|
},
|
|
},
|
|
stickers: (guildId) => {
|
|
return `/guilds/${guildId}/stickers`
|
|
},
|
|
sticker: (guildId, stickerId) => {
|
|
return `/guilds/${guildId}/stickers/${stickerId}`
|
|
},
|
|
voice: (guildId, userId) => {
|
|
return `/guilds/${guildId}/voice-states/${userId ?? '@me'}`
|
|
},
|
|
templates: {
|
|
code: (code) => {
|
|
return `/guilds/templates/${code}`
|
|
},
|
|
guild: (guildId, code) => {
|
|
return `/guilds/${guildId}/templates/${code}`
|
|
},
|
|
all: (guildId) => {
|
|
return `/guilds/${guildId}/templates`
|
|
},
|
|
},
|
|
vanity: (guildId) => {
|
|
return `/guilds/${guildId}/vanity-url`
|
|
},
|
|
regions: (guildId) => {
|
|
return `/guilds/${guildId}/regions`
|
|
},
|
|
webhooks: (guildId) => {
|
|
return `/guilds/${guildId}/webhooks`
|
|
},
|
|
welcome: (guildId) => {
|
|
return `/guilds/${guildId}/welcome-screen`
|
|
},
|
|
widget: (guildId) => {
|
|
return `/guilds/${guildId}/widget`
|
|
},
|
|
widgetJson: (guildId) => {
|
|
return `/guilds/${guildId}/widget.json`
|
|
},
|
|
},
|
|
|
|
sticker: (stickerId: BigString) => {
|
|
return `/stickers/${stickerId}`
|
|
},
|
|
|
|
regions: () => {
|
|
return '/voice/regions'
|
|
},
|
|
|
|
// Interaction Endpoints
|
|
interactions: {
|
|
commands: {
|
|
// Application Endpoints
|
|
commands: (applicationId) => {
|
|
return `/applications/${applicationId}/commands`
|
|
},
|
|
|
|
guilds: {
|
|
all(applicationId, guildId) {
|
|
return `/applications/${applicationId}/guilds/${guildId}/commands`
|
|
},
|
|
|
|
one(applicationId, guildId, commandId, withLocalizations) {
|
|
let url = `/applications/${applicationId}/guilds/${guildId}/commands/${commandId}?`
|
|
|
|
if (withLocalizations !== undefined) {
|
|
url += `with_localizations=${withLocalizations.toString()}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
},
|
|
permissions: (applicationId, guildId) => {
|
|
return `/applications/${applicationId}/guilds/${guildId}/commands/permissions`
|
|
},
|
|
permission: (applicationId, guildId, commandId) => {
|
|
return `/applications/${applicationId}/guilds/${guildId}/commands/${commandId}/permissions`
|
|
},
|
|
command: (applicationId, commandId, withLocalizations) => {
|
|
let url = `/applications/${applicationId}/commands/${commandId}?`
|
|
|
|
if (withLocalizations !== undefined) {
|
|
url += `withLocalizations=${withLocalizations.toString()}`
|
|
}
|
|
|
|
return url
|
|
},
|
|
},
|
|
|
|
responses: {
|
|
// Interaction Endpoints
|
|
callback: (interactionId, token) => {
|
|
return `/interactions/${interactionId}/${token}/callback`
|
|
},
|
|
original: (interactionId, token) => {
|
|
return `/webhooks/${interactionId}/${token}/messages/@original`
|
|
},
|
|
message: (applicationId, token, messageId) => {
|
|
return `/webhooks/${applicationId}/${token}/messages/${messageId}`
|
|
},
|
|
},
|
|
},
|
|
|
|
// User endpoints
|
|
user(userId) {
|
|
return `/users/${userId}`
|
|
},
|
|
|
|
userBot() {
|
|
return '/users/@me'
|
|
},
|
|
|
|
oauth2Application() {
|
|
return 'oauth2/applications/@me'
|
|
},
|
|
|
|
gatewayBot() {
|
|
return '/gateway/bot'
|
|
},
|
|
|
|
nitroStickerPacks() {
|
|
return '/sticker-packs'
|
|
},
|
|
},
|
|
|
|
checkRateLimits(url) {
|
|
const ratelimited = rest.rateLimitedPaths.get(url)
|
|
const global = rest.rateLimitedPaths.get('global')
|
|
const now = Date.now()
|
|
|
|
if (ratelimited && now < ratelimited.resetTimestamp) {
|
|
return ratelimited.resetTimestamp - now
|
|
}
|
|
|
|
if (global && now < global.resetTimestamp) {
|
|
return global.resetTimestamp - now
|
|
}
|
|
|
|
return false
|
|
},
|
|
|
|
changeToDiscordFormat(obj: any): any {
|
|
if (obj === null) return null
|
|
|
|
if (typeof obj === 'object') {
|
|
if (Array.isArray(obj)) {
|
|
return obj.map((item) => rest.changeToDiscordFormat(item))
|
|
}
|
|
|
|
const newObj: any = {}
|
|
|
|
for (const key of Object.keys(obj)) {
|
|
// Keys that dont require snake casing
|
|
if (['permissions', 'allow', 'deny'].includes(key)) {
|
|
newObj[key] = calculateBits(obj[key])
|
|
continue
|
|
}
|
|
|
|
if (key === 'defaultMemberPermissions') {
|
|
newObj.default_member_permissions = calculateBits(obj[key])
|
|
continue
|
|
}
|
|
|
|
newObj[camelToSnakeCase(key)] = rest.changeToDiscordFormat(obj[key])
|
|
}
|
|
|
|
return newObj
|
|
}
|
|
|
|
if (typeof obj === 'bigint') return obj.toString()
|
|
|
|
return obj
|
|
},
|
|
|
|
createRequestBody(method, options) {
|
|
const headers: Record<string, string> = {
|
|
'user-agent': `DiscordBot (https://github.com/discordeno/discordeno, v${version})`,
|
|
}
|
|
|
|
if (!options?.unauthorized) headers.authorization = `Bot ${rest.token}`
|
|
|
|
// IF A REASON IS PROVIDED ENCODE IT IN HEADERS
|
|
if (options?.reason !== undefined) {
|
|
headers['x-audit-log-reason'] = encodeURIComponent(options?.reason)
|
|
}
|
|
|
|
let body: string | FormData | undefined
|
|
|
|
// TODO: check if we need to add specific check for GET method
|
|
// Since GET does not allow bodies
|
|
|
|
// Have to check for attachments first, since body then has to be send in a different way.
|
|
if (options?.files !== undefined) {
|
|
const form = new FormData()
|
|
for (let i = 0; i < options.files.length; ++i) {
|
|
form.append(`file${i}`, options.files[i].blob, options.files[i].name)
|
|
}
|
|
|
|
form.append('payload_json', JSON.stringify({ ...options.body, files: undefined }))
|
|
|
|
body = form
|
|
|
|
// No need to set the `content-type` header since `fetch` does that automatically for us when we use a `FormData` object.
|
|
} else if (options?.body !== undefined) {
|
|
if (options.body instanceof FormData) {
|
|
body = options.body
|
|
// No need to set the `content-type` header since `fetch` does that automatically for us when we use a `FormData` object.
|
|
} else {
|
|
body = JSON.stringify(rest.changeToDiscordFormat(options.body))
|
|
headers['content-type'] = `application/json`
|
|
}
|
|
}
|
|
|
|
// SOMETIMES SPECIAL HEADERS (E.G. CUSTOM AUTHORIZATION) NEED TO BE USED
|
|
if (options?.headers) {
|
|
Object.assign(headers, options.headers)
|
|
}
|
|
|
|
return {
|
|
body,
|
|
headers,
|
|
method,
|
|
}
|
|
},
|
|
|
|
processRateLimitedPaths() {
|
|
const now = Date.now()
|
|
|
|
for (const [key, value] of rest.rateLimitedPaths.entries()) {
|
|
// rest.debug(
|
|
// `[REST - processRateLimitedPaths] Running for of loop. ${
|
|
// value.resetTimestamp - now
|
|
// }`
|
|
// )
|
|
// If the time has not reached cancel
|
|
if (value.resetTimestamp > now) continue
|
|
|
|
// Rate limit is over, delete the rate limiter
|
|
rest.rateLimitedPaths.delete(key)
|
|
// If it was global, also mark the global value as false
|
|
if (key === 'global') rest.globallyRateLimited = false
|
|
}
|
|
|
|
// ALL PATHS ARE CLEARED CAN CANCEL OUT!
|
|
if (rest.rateLimitedPaths.size === 0) {
|
|
rest.processingRateLimitedPaths = false
|
|
} else {
|
|
rest.processingRateLimitedPaths = true
|
|
// RECHECK IN 1 SECOND
|
|
setTimeout(() => {
|
|
// rest.debug('[REST - processRateLimitedPaths] Running setTimeout.')
|
|
rest.processRateLimitedPaths()
|
|
}, 1000)
|
|
}
|
|
},
|
|
|
|
/** Processes the rate limit headers and determines if it needs to be rate limited and returns the bucket id if available */
|
|
processHeaders(url, headers) {
|
|
let rateLimited = false
|
|
|
|
// GET ALL NECESSARY HEADERS
|
|
const remaining = headers.get('x-ratelimit-remaining')
|
|
const retryAfter = headers.get('x-ratelimit-reset-after')
|
|
const reset = Date.now() + Number(retryAfter) * 1000
|
|
const global = headers.get('x-ratelimit-global')
|
|
// undefined override null needed for typings
|
|
const bucketId = headers.get('x-ratelimit-bucket') ?? undefined
|
|
const limit = headers.get('x-ratelimit-limit')
|
|
|
|
rest.queues.get(url)?.handleCompletedRequest({
|
|
remaining: remaining ? Number(remaining) : undefined,
|
|
interval: retryAfter ? Number(retryAfter) * 1000 : undefined,
|
|
max: limit ? Number(limit) : undefined,
|
|
})
|
|
|
|
// IF THERE IS NO REMAINING RATE LIMIT, MARK IT AS RATE LIMITED
|
|
if (remaining === '0') {
|
|
rateLimited = true
|
|
|
|
// SAVE THE URL AS LIMITED, IMPORTANT FOR NEW REQUESTS BY USER WITHOUT BUCKET
|
|
rest.rateLimitedPaths.set(url, {
|
|
url,
|
|
resetTimestamp: reset,
|
|
bucketId,
|
|
})
|
|
|
|
// SAVE THE BUCKET AS LIMITED SINCE DIFFERENT URLS MAY SHARE A BUCKET
|
|
if (bucketId) {
|
|
rest.rateLimitedPaths.set(bucketId, {
|
|
url,
|
|
resetTimestamp: reset,
|
|
bucketId,
|
|
})
|
|
}
|
|
}
|
|
|
|
// IF THERE IS NO REMAINING GLOBAL LIMIT, MARK IT RATE LIMITED GLOBALLY
|
|
if (global) {
|
|
const retryAfter = headers.get('retry-after')
|
|
const globalReset = Date.now() + Number(retryAfter) * 1000
|
|
// rest.debug(
|
|
// `[REST = Globally Rate Limited] URL: ${url} | Global Rest: ${globalReset}`
|
|
// )
|
|
rest.globallyRateLimited = true
|
|
rateLimited = true
|
|
|
|
setTimeout(() => {
|
|
rest.globallyRateLimited = false
|
|
}, globalReset)
|
|
|
|
rest.rateLimitedPaths.set('global', {
|
|
url: 'global',
|
|
resetTimestamp: globalReset,
|
|
bucketId,
|
|
})
|
|
|
|
if (bucketId) {
|
|
rest.rateLimitedPaths.set(bucketId, {
|
|
url: 'global',
|
|
resetTimestamp: globalReset,
|
|
bucketId,
|
|
})
|
|
}
|
|
}
|
|
|
|
if (rateLimited && !rest.processingRateLimitedPaths) {
|
|
rest.processRateLimitedPaths()
|
|
}
|
|
return rateLimited ? bucketId : undefined
|
|
},
|
|
|
|
async sendRequest(options) {
|
|
const url = options.url.startsWith('https://') ? options.url : `${rest.baseUrl}/v${rest.version}${options.url}`
|
|
const payload = rest.createRequestBody(options.method, options.requestBodyOptions)
|
|
|
|
logger.debug(`sending request to ${url}`, 'with payload:', { ...payload, headers: { ...payload.headers, authorization: 'Bot tokenhere' } })
|
|
const response = await fetch(url, payload)
|
|
logger.debug(`request fetched from ${url} with status ${response.status} & ${response.statusText}`)
|
|
|
|
// Set the bucket id if it was available on the headers
|
|
const bucketId = rest.processHeaders(rest.simplifyUrl(options.url, options.method), response.headers)
|
|
if (bucketId) options.bucketId = bucketId
|
|
|
|
if (response.status < 200 || response.status >= 400) {
|
|
logger.debug(`Request to ${url} failed.`)
|
|
|
|
if (response.status === 429) {
|
|
logger.debug(`Request to ${url} was ratelimited.`)
|
|
// Too many attempts, get rid of request from queue.
|
|
if (options.retryCount++ >= rest.maxRetryCount) {
|
|
logger.debug(`Request to ${url} exceeded the maximum allowed retries.`, 'with payload:', payload)
|
|
// rest.debug(`[REST - RetriesMaxed] ${JSON.stringify(options)}`)
|
|
// Remove item from queue to prevent retry
|
|
options.reject({
|
|
ok: false,
|
|
status: response.status,
|
|
error: 'The options was rate limited and it maxed out the retries limit.',
|
|
})
|
|
return
|
|
}
|
|
|
|
// Rate limited, add back to queue
|
|
rest.invalidBucket.handleCompletedRequest(response.status, response.headers.get('X-RateLimit-Scope') === 'shared')
|
|
|
|
const resetAfter = response.headers.get('x-ratelimit-reset-after')
|
|
if (resetAfter) await delay(Number(resetAfter) * 1000)
|
|
// process the response to prevent mem leak
|
|
await response.json()
|
|
|
|
return await options.retryRequest?.(options)
|
|
}
|
|
|
|
options.reject({ ok: false, status: response.status, body: JSON.stringify(await response.json()) })
|
|
return
|
|
}
|
|
|
|
const is204 = response.status === 204
|
|
const json = is204 ? undefined : await response.json()
|
|
// Discord sometimes sends no response with 204 code
|
|
options.resolve({ ok: true, status: response.status, body: JSON.stringify(json) })
|
|
},
|
|
|
|
simplifyUrl(url, method) {
|
|
const parts = url.split('/')
|
|
const secondLastPart = parts[parts.length - 2]
|
|
|
|
if (secondLastPart === 'channels' || secondLastPart === 'guilds') {
|
|
return url
|
|
}
|
|
|
|
if (secondLastPart === 'reactions' || parts[parts.length - 1] === '@me') {
|
|
parts.splice(-2)
|
|
parts.push('reactions')
|
|
} else {
|
|
parts.splice(-1)
|
|
parts.push('x')
|
|
}
|
|
|
|
if (parts[parts.length - 3] === 'reactions') {
|
|
parts.splice(-2)
|
|
}
|
|
|
|
if (method === 'DELETE' && secondLastPart === 'messages') {
|
|
return `D${parts.join('/')}`
|
|
}
|
|
|
|
return parts.join('/')
|
|
},
|
|
|
|
processRequest(request: SendRequestOptions) {
|
|
const route = request.url.substring(request.url.indexOf('api/'))
|
|
const parts = route.split('/')
|
|
// Remove the api/
|
|
parts.shift()
|
|
// Removes the /v#/
|
|
if (parts[0]?.startsWith('v')) parts.shift()
|
|
// Set the full url to discord api in case it was recieved in a proxy rest
|
|
request.url = `${rest.baseUrl}/v${rest.version}/${parts.join('/')}`
|
|
|
|
const url = rest.simplifyUrl(request.url, request.method)
|
|
const queue = rest.queues.get(url)
|
|
|
|
if (queue !== undefined) {
|
|
queue.makeRequest(request)
|
|
} else {
|
|
// CREATES A NEW QUEUE
|
|
const bucketQueue = new Queue(rest, { url, deleteQueueDelay: rest.deleteQueueDelay })
|
|
|
|
// Add request to queue
|
|
bucketQueue.makeRequest(request)
|
|
// Save queue
|
|
rest.queues.set(url, bucketQueue)
|
|
}
|
|
},
|
|
|
|
async makeRequest(method, url, options) {
|
|
if (!rest.baseUrl.startsWith('https://discord.com') && url[0] === '/') {
|
|
// Special handling for sending blobs across http to proxy
|
|
// TODO: fix this hacky handling
|
|
if (!(options?.body instanceof FormData) && !Array.isArray(options?.body) && options?.body?.file) {
|
|
if (!Array.isArray(options.body.file)) {
|
|
options.body.file = [options.body.file]
|
|
}
|
|
// convert blobs to string before sending to proxy
|
|
options.body.file = await Promise.all(
|
|
(options.body.file as FileContent[]).map(async (f: FileContent) => {
|
|
const url = encode(await f.blob.arrayBuffer())
|
|
|
|
return { name: f.name, blob: `data:${f.blob.type};base64,${url}` }
|
|
}),
|
|
)
|
|
}
|
|
|
|
const headers: HeadersInit = {
|
|
Authorization: rest.authorization ?? '',
|
|
}
|
|
if (options?.body) {
|
|
headers['Content-Type'] = 'application/json'
|
|
}
|
|
|
|
const result = await fetch(`${rest.baseUrl}${url}`, {
|
|
body: options?.body ? JSON.stringify(options.body) : undefined,
|
|
headers,
|
|
method,
|
|
})
|
|
|
|
if (!result.ok) {
|
|
const err = (await result.json().catch(() => { })) as Record<string, any>
|
|
// Legacy Handling to not break old code or when body is missing
|
|
if (!err?.body) throw new Error(`Error: ${err.message ?? result.statusText}`)
|
|
throw new Error(JSON.stringify(err))
|
|
}
|
|
|
|
return result.status !== 204 ? ((await result.json()) as any) : undefined
|
|
}
|
|
|
|
return await new Promise((resolve, reject) => {
|
|
const payload: SendRequestOptions = {
|
|
url,
|
|
method,
|
|
requestBodyOptions: options,
|
|
retryCount: 0,
|
|
retryRequest: async function(payload: SendRequestOptions) {
|
|
rest.processRequest(payload)
|
|
},
|
|
resolve: (data) => {
|
|
resolve(data.status !== 204 ? JSON.parse(data.body ?? '{}') : undefined)
|
|
},
|
|
reject,
|
|
}
|
|
|
|
rest.processRequest(payload)
|
|
})
|
|
},
|
|
|
|
async get<T = Record<string, unknown>>(url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
|
|
return camelize(await rest.makeRequest('GET', url, options)) as Camelize<T>
|
|
},
|
|
|
|
async post<T = Record<string, unknown>>(url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
|
|
return camelize(await rest.makeRequest('POST', url, options)) as Camelize<T>
|
|
},
|
|
|
|
async delete(url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
|
|
camelize(await rest.makeRequest('DELETE', url, options))
|
|
},
|
|
|
|
async patch<T = Record<string, unknown>>(url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
|
|
return camelize(await rest.makeRequest('PATCH', url, options)) as Camelize<T>
|
|
},
|
|
|
|
async put<T = void>(url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
|
|
return camelize(await rest.makeRequest('PUT', url, options)) as Camelize<T>
|
|
},
|
|
|
|
async addReaction(channelId, messageId, reaction) {
|
|
reaction = processReactionString(reaction)
|
|
|
|
await rest.put(rest.routes.channels.reactions.bot(channelId, messageId, reaction))
|
|
},
|
|
|
|
async addReactions(channelId, messageId, reactions, ordered = false) {
|
|
if (!ordered) {
|
|
await Promise.all(
|
|
reactions.map(async (reaction) => {
|
|
await rest.addReaction(channelId, messageId, reaction)
|
|
}),
|
|
)
|
|
return
|
|
}
|
|
|
|
for (const reaction of reactions) {
|
|
await rest.addReaction(channelId, messageId, reaction)
|
|
}
|
|
},
|
|
|
|
async addRole(guildId, userId, roleId, reason) {
|
|
await rest.put(rest.routes.guilds.roles.member(guildId, userId, roleId), { reason })
|
|
},
|
|
|
|
async addThreadMember(channelId, userId) {
|
|
await rest.put(rest.routes.channels.threads.user(channelId, userId))
|
|
},
|
|
|
|
async createAutomodRule(guildId, body) {
|
|
return await rest.post<DiscordAutoModerationRule>(rest.routes.guilds.automod.rules(guildId), { body })
|
|
},
|
|
|
|
async createChannel(guildId, body) {
|
|
return await rest.post<DiscordChannel>(rest.routes.guilds.channels(guildId), { body })
|
|
},
|
|
|
|
async createEmoji(guildId, body) {
|
|
return await rest.post<DiscordEmoji>(rest.routes.guilds.emojis(guildId), { body })
|
|
},
|
|
|
|
async createGlobalApplicationCommand(body) {
|
|
return await rest.post<DiscordApplicationCommand>(rest.routes.interactions.commands.commands(rest.applicationId), { body })
|
|
},
|
|
|
|
async createGuild(body) {
|
|
return await rest.post<DiscordGuild>(rest.routes.guilds.all(), { body })
|
|
},
|
|
|
|
async createGuildApplicationCommand(body, guildId) {
|
|
return await rest.post<DiscordApplicationCommand>(rest.routes.interactions.commands.guilds.all(rest.applicationId, guildId), { body })
|
|
},
|
|
|
|
async createGuildFromTemplate(templateCode, body) {
|
|
if (body.icon) {
|
|
body.icon = await urlToBase64(body.icon)
|
|
}
|
|
|
|
return await rest.post<DiscordGuild>(rest.routes.guilds.templates.code(templateCode), { body })
|
|
},
|
|
|
|
async createGuildSticker(guildId, options) {
|
|
const form = new FormData()
|
|
form.append('file', options.file.blob, options.file.name)
|
|
form.append('name', options.name)
|
|
form.append('description', options.description)
|
|
form.append('tags', options.tags)
|
|
|
|
return await rest.post<DiscordSticker>(rest.routes.guilds.stickers(guildId), { body: form })
|
|
},
|
|
|
|
async createGuildTemplate(guildId, body) {
|
|
return await rest.post<DiscordTemplate>(rest.routes.guilds.templates.all(guildId), { body })
|
|
},
|
|
|
|
async createForumThread(channelId, body) {
|
|
return await rest.post<DiscordChannel>(rest.routes.channels.forum(channelId), { body, files: body.files })
|
|
},
|
|
|
|
async createInvite(channelId, body = {}) {
|
|
return await rest.post<DiscordInvite>(rest.routes.channels.invites(channelId), { body })
|
|
},
|
|
|
|
async createRole(guildId, body, reason) {
|
|
return await rest.post<DiscordRole>(rest.routes.guilds.roles.all(guildId), { body, reason })
|
|
},
|
|
|
|
async createScheduledEvent(guildId, body) {
|
|
return await rest.post<DiscordScheduledEvent>(rest.routes.guilds.events.events(guildId), { body })
|
|
},
|
|
|
|
async createStageInstance(body) {
|
|
return await rest.post<DiscordStageInstance>(rest.routes.channels.stages(), { body })
|
|
},
|
|
|
|
async createWebhook(channelId, options, reason) {
|
|
return await rest.post<DiscordWebhook>(rest.routes.channels.webhooks(channelId), {
|
|
body: {
|
|
name: options.name,
|
|
avatar: options.avatar ? await urlToBase64(options.avatar) : undefined,
|
|
},
|
|
reason,
|
|
})
|
|
},
|
|
|
|
async deleteAutomodRule(guildId, ruleId, reason) {
|
|
await rest.delete(rest.routes.guilds.automod.rule(guildId, ruleId), { reason })
|
|
},
|
|
|
|
async deleteChannel(channelId, reason) {
|
|
await rest.delete(rest.routes.channels.channel(channelId), {
|
|
reason,
|
|
})
|
|
},
|
|
|
|
async deleteChannelPermissionOverride(channelId, overwriteId, reason) {
|
|
await rest.delete(rest.routes.channels.overwrite(channelId, overwriteId), { reason })
|
|
},
|
|
|
|
async deleteEmoji(guildId, id, reason) {
|
|
await rest.delete(rest.routes.guilds.emoji(guildId, id), { reason })
|
|
},
|
|
|
|
async deleteFollowupMessage(token, messageId) {
|
|
await rest.delete(rest.routes.interactions.responses.message(rest.applicationId, token, messageId))
|
|
},
|
|
|
|
async deleteGlobalApplicationCommand(commandId) {
|
|
await rest.delete(rest.routes.interactions.commands.command(rest.applicationId, commandId))
|
|
},
|
|
|
|
async deleteGuild(guildId) {
|
|
await rest.delete(rest.routes.guilds.guild(guildId))
|
|
},
|
|
|
|
async deleteGuildApplicationCommand(commandId, guildId) {
|
|
await rest.delete(rest.routes.interactions.commands.guilds.one(rest.applicationId, guildId, commandId))
|
|
},
|
|
|
|
async deleteGuildSticker(guildId, stickerId, reason) {
|
|
await rest.delete(rest.routes.guilds.sticker(guildId, stickerId), { reason })
|
|
},
|
|
|
|
async deleteGuildTemplate(guildId, templateCode) {
|
|
await rest.delete(rest.routes.guilds.templates.guild(guildId, templateCode))
|
|
},
|
|
|
|
async deleteIntegration(guildId, integrationId) {
|
|
await rest.delete(rest.routes.guilds.integration(guildId, integrationId))
|
|
},
|
|
|
|
async deleteInvite(inviteCode, reason) {
|
|
await rest.delete(rest.routes.guilds.invite(inviteCode), { reason })
|
|
},
|
|
|
|
async deleteMessage(channelId, messageId, reason) {
|
|
await rest.delete(rest.routes.channels.message(channelId, messageId), { reason })
|
|
},
|
|
|
|
async deleteMessages(channelId, messageIds, reason) {
|
|
await rest.post(rest.routes.channels.bulk(channelId), {
|
|
body: {
|
|
messages: messageIds.slice(0, 100).map((id) => id.toString()),
|
|
},
|
|
reason,
|
|
})
|
|
},
|
|
|
|
async deleteOriginalInteractionResponse(token) {
|
|
await rest.delete(rest.routes.interactions.responses.original(rest.applicationId, token))
|
|
},
|
|
|
|
async deleteOwnReaction(channelId, messageId, reaction) {
|
|
reaction = processReactionString(reaction)
|
|
|
|
await rest.delete(rest.routes.channels.reactions.bot(channelId, messageId, reaction))
|
|
},
|
|
|
|
async deleteReactionsAll(channelId, messageId) {
|
|
await rest.delete(rest.routes.channels.reactions.all(channelId, messageId))
|
|
},
|
|
|
|
async deleteReactionsEmoji(channelId, messageId, reaction) {
|
|
reaction = processReactionString(reaction)
|
|
|
|
await rest.delete(rest.routes.channels.reactions.emoji(channelId, messageId, reaction))
|
|
},
|
|
|
|
async deleteRole(guildId, roleId) {
|
|
await rest.delete(rest.routes.guilds.roles.one(guildId, roleId))
|
|
},
|
|
|
|
async deleteScheduledEvent(guildId, eventId) {
|
|
await rest.delete(rest.routes.guilds.events.event(guildId, eventId))
|
|
},
|
|
|
|
async deleteStageInstance(channelId, reason) {
|
|
await rest.delete(rest.routes.channels.stage(channelId), { reason })
|
|
},
|
|
|
|
async deleteUserReaction(channelId, messageId, userId, reaction) {
|
|
reaction = processReactionString(reaction)
|
|
|
|
await rest.delete(rest.routes.channels.reactions.user(channelId, messageId, reaction, userId))
|
|
},
|
|
|
|
async deleteWebhook(webhookId, reason) {
|
|
await rest.delete(rest.routes.webhooks.id(webhookId), { reason })
|
|
},
|
|
|
|
async deleteWebhookMessage(webhookId, token, messageId, options) {
|
|
await rest.delete(rest.routes.webhooks.message(webhookId, token, messageId, options))
|
|
},
|
|
|
|
async deleteWebhookWithToken(webhookId, token) {
|
|
await rest.delete(rest.routes.webhooks.webhook(webhookId, token))
|
|
},
|
|
|
|
async editApplicationCommandPermissions(guildId, commandId, bearerToken, permissions) {
|
|
return await rest.put<DiscordApplicationCommandPermissions>(
|
|
rest.routes.interactions.commands.permission(rest.applicationId, guildId, commandId),
|
|
{
|
|
body: {
|
|
permissions,
|
|
},
|
|
headers: { authorization: `Bearer ${bearerToken}` },
|
|
},
|
|
)
|
|
},
|
|
|
|
async editAutomodRule(guildId, ruleId, body) {
|
|
return await rest.patch<DiscordAutoModerationRule>(rest.routes.guilds.automod.rule(guildId, ruleId), { body })
|
|
},
|
|
|
|
async editBotProfile(options) {
|
|
const avatar = options?.botAvatarURL ? await urlToBase64(options?.botAvatarURL) : options?.botAvatarURL
|
|
|
|
return await rest.patch<DiscordUser>(rest.routes.userBot(), {
|
|
body: {
|
|
username: options.username?.trim(),
|
|
avatar,
|
|
},
|
|
})
|
|
},
|
|
|
|
async editChannel(channelId, body) {
|
|
return await rest.patch<DiscordChannel>(rest.routes.channels.channel(channelId), { body })
|
|
},
|
|
|
|
async editChannelPermissionOverrides(channelId, body) {
|
|
await rest.put(rest.routes.channels.overwrite(channelId, body.id), { body })
|
|
},
|
|
|
|
async editChannelPositions(guildId, body) {
|
|
await rest.patch(rest.routes.guilds.channels(guildId), { body })
|
|
},
|
|
|
|
async editEmoji(guildId, id, body) {
|
|
return await rest.patch<DiscordEmoji>(rest.routes.guilds.emoji(guildId, id), { body })
|
|
},
|
|
|
|
async editFollowupMessage(token, messageId, body) {
|
|
return await rest.patch<DiscordMessage>(rest.routes.interactions.responses.message(rest.applicationId, token, messageId), {
|
|
body,
|
|
files: body.files,
|
|
})
|
|
},
|
|
|
|
async editGlobalApplicationCommand(commandId, body) {
|
|
return await rest.patch<DiscordApplicationCommand>(rest.routes.interactions.commands.command(rest.applicationId, commandId), { body })
|
|
},
|
|
|
|
async editGuild(guildId, body) {
|
|
return await rest.patch<DiscordGuild>(rest.routes.guilds.guild(guildId), { body })
|
|
},
|
|
|
|
async editGuildApplicationCommand(commandId, guildId, body) {
|
|
return await rest.patch<DiscordApplicationCommand>(rest.routes.interactions.commands.guilds.one(rest.applicationId, guildId, commandId), {
|
|
body,
|
|
})
|
|
},
|
|
|
|
async editGuildMfaLevel(guildId: BigString, mfaLevel: MfaLevels, reason?: string): Promise<void> {
|
|
await rest.post(rest.routes.guilds.mfa(guildId), { body: { level: mfaLevel }, reason })
|
|
},
|
|
|
|
async editGuildSticker(guildId, stickerId, body) {
|
|
return await rest.patch<DiscordSticker>(rest.routes.guilds.sticker(guildId, stickerId), { body })
|
|
},
|
|
|
|
async editGuildTemplate(guildId, templateCode: string, body: ModifyGuildTemplate): Promise<Camelize<DiscordTemplate>> {
|
|
return await rest.patch<DiscordTemplate>(rest.routes.guilds.templates.guild(guildId, templateCode), { body })
|
|
},
|
|
|
|
async editMessage(channelId, messageId, body) {
|
|
return await rest.patch<DiscordMessage>(rest.routes.channels.message(channelId, messageId), { body })
|
|
},
|
|
|
|
async editOriginalInteractionResponse(token, body) {
|
|
return await rest.patch<DiscordMessage>(rest.routes.interactions.responses.original(rest.applicationId, token), {
|
|
body,
|
|
files: body.files,
|
|
})
|
|
},
|
|
|
|
async editOriginalWebhookMessage(webhookId, token, options) {
|
|
return await rest.patch<DiscordMessage>(rest.routes.webhooks.original(webhookId, token, options), {
|
|
body: {
|
|
type: InteractionResponseTypes.UpdateMessage,
|
|
data: options,
|
|
},
|
|
files: options.files,
|
|
})
|
|
},
|
|
|
|
async editOwnVoiceState(guildId, options) {
|
|
await rest.patch(rest.routes.guilds.voice(guildId), {
|
|
body: {
|
|
...options,
|
|
request_to_speak_timestamp: options.requestToSpeakTimestamp
|
|
? new Date(options.requestToSpeakTimestamp).toISOString()
|
|
: options.requestToSpeakTimestamp,
|
|
},
|
|
})
|
|
},
|
|
|
|
async editScheduledEvent(guildId, eventId, body) {
|
|
return await rest.patch<DiscordScheduledEvent>(rest.routes.guilds.events.event(guildId, eventId), { body })
|
|
},
|
|
|
|
async editRole(guildId, roleId, body) {
|
|
return await rest.patch<DiscordRole>(rest.routes.guilds.roles.one(guildId, roleId), { body })
|
|
},
|
|
|
|
async editRolePositions(guildId, body) {
|
|
return await rest.patch<DiscordRole[]>(rest.routes.guilds.roles.all(guildId), { body })
|
|
},
|
|
|
|
async editStageInstance(channelId, topic, reason?: string) {
|
|
return await rest.patch<DiscordStageInstance>(rest.routes.channels.stage(channelId), { body: { topic }, reason })
|
|
},
|
|
|
|
async editUserVoiceState(guildId, options) {
|
|
await rest.patch(rest.routes.guilds.voice(guildId, options.userId), { body: options })
|
|
},
|
|
|
|
async editWebhook(webhookId, body) {
|
|
return await rest.patch<DiscordWebhook>(rest.routes.webhooks.id(webhookId), { body })
|
|
},
|
|
|
|
async editWebhookMessage(webhookId, token, messageId, options) {
|
|
return await rest.patch<DiscordMessage>(rest.routes.webhooks.message(webhookId, token, messageId, options), {
|
|
body: options,
|
|
files: options.files,
|
|
})
|
|
},
|
|
|
|
async editWebhookWithToken(webhookId, token, body) {
|
|
return await rest.patch<DiscordWebhook>(rest.routes.webhooks.webhook(webhookId, token), { body })
|
|
},
|
|
|
|
async editWelcomeScreen(guildId, body) {
|
|
return await rest.patch<DiscordWelcomeScreen>(rest.routes.guilds.welcome(guildId), { body })
|
|
},
|
|
|
|
async editWidgetSettings(guildId, body) {
|
|
return await rest.patch<DiscordGuildWidgetSettings>(rest.routes.guilds.widget(guildId), { body })
|
|
},
|
|
|
|
async executeWebhook(webhookId, token, options) {
|
|
return await rest.post<DiscordMessage>(rest.routes.webhooks.webhook(webhookId, token, options), { body: options })
|
|
},
|
|
|
|
async followAnnouncement(sourceChannelId, targetChannelId) {
|
|
return await rest.post<DiscordFollowedChannel>(rest.routes.channels.follow(sourceChannelId), {
|
|
body: {
|
|
webhook_channel_id: targetChannelId,
|
|
},
|
|
})
|
|
},
|
|
|
|
async getActiveThreads(guildId) {
|
|
return await rest.get<DiscordListActiveThreads>(rest.routes.channels.threads.active(guildId))
|
|
},
|
|
|
|
async getApplicationCommandPermission(guildId, commandId) {
|
|
return await rest.get<DiscordApplicationCommandPermissions>(
|
|
rest.routes.interactions.commands.permission(rest.applicationId, guildId, commandId),
|
|
)
|
|
},
|
|
|
|
async getApplicationCommandPermissions(guildId) {
|
|
return await rest.get<DiscordApplicationCommandPermissions[]>(rest.routes.interactions.commands.permissions(rest.applicationId, guildId))
|
|
},
|
|
|
|
async getApplicationInfo() {
|
|
return await rest.get<DiscordApplication>(rest.routes.oauth2Application())
|
|
},
|
|
|
|
async getAuditLog(guildId, options) {
|
|
return await rest.get<DiscordAuditLog>(rest.routes.guilds.auditlogs(guildId, options))
|
|
},
|
|
|
|
async getAutomodRule(guildId, ruleId) {
|
|
return await rest.get<DiscordAutoModerationRule>(rest.routes.guilds.automod.rule(guildId, ruleId))
|
|
},
|
|
|
|
async getAutomodRules(guildId) {
|
|
return await rest.get<DiscordAutoModerationRule[]>(rest.routes.guilds.automod.rules(guildId))
|
|
},
|
|
|
|
async getAvailableVoiceRegions() {
|
|
return await rest.get<DiscordVoiceRegion[]>(rest.routes.regions())
|
|
},
|
|
|
|
async getBan(guildId, userId) {
|
|
return await rest.get<DiscordBan>(rest.routes.guilds.members.ban(guildId, userId))
|
|
},
|
|
|
|
async getBans(guildId, options) {
|
|
return await rest.get<DiscordBan[]>(rest.routes.guilds.members.bans(guildId, options))
|
|
},
|
|
|
|
async getChannel(id) {
|
|
return await rest.get<DiscordChannel>(rest.routes.channels.channel(id))
|
|
},
|
|
|
|
async getChannelInvites(channelId) {
|
|
return await rest.get<DiscordInviteMetadata[]>(rest.routes.channels.invites(channelId))
|
|
},
|
|
|
|
async getChannels(guildId) {
|
|
return await rest.get<DiscordChannel[]>(rest.routes.guilds.channels(guildId))
|
|
},
|
|
|
|
async getChannelWebhooks(channelId) {
|
|
return await rest.get<DiscordWebhook[]>(rest.routes.channels.webhooks(channelId))
|
|
},
|
|
|
|
async getDmChannel(userId) {
|
|
return await rest.post<DiscordChannel>(rest.routes.channels.dm(), {
|
|
body: { recipient_id: userId },
|
|
})
|
|
},
|
|
|
|
async getEmoji(guildId, emojiId) {
|
|
return await rest.get<DiscordEmoji>(rest.routes.guilds.emoji(guildId, emojiId))
|
|
},
|
|
|
|
async getEmojis(guildId) {
|
|
return await rest.get<DiscordEmoji[]>(rest.routes.guilds.emojis(guildId))
|
|
},
|
|
|
|
async getFollowupMessage(token, messageId) {
|
|
return await rest.get<DiscordMessage>(rest.routes.interactions.responses.message(rest.applicationId, token, messageId))
|
|
},
|
|
|
|
async getGatewayBot() {
|
|
return await rest.get<DiscordGetGatewayBot>(rest.routes.gatewayBot())
|
|
},
|
|
|
|
async getGlobalApplicationCommand(commandId) {
|
|
return await rest.get<DiscordApplicationCommand>(rest.routes.interactions.commands.command(rest.applicationId, commandId))
|
|
},
|
|
|
|
async getGlobalApplicationCommands() {
|
|
return await rest.get<DiscordApplicationCommand[]>(rest.routes.interactions.commands.commands(rest.applicationId))
|
|
},
|
|
|
|
async getGuild(guildId, options = { counts: true }) {
|
|
return await rest.get<DiscordGuild>(rest.routes.guilds.guild(guildId, options.counts))
|
|
},
|
|
|
|
async getGuildApplicationCommand(commandId, guildId) {
|
|
return await rest.get<DiscordApplicationCommand>(rest.routes.interactions.commands.guilds.one(rest.applicationId, guildId, commandId))
|
|
},
|
|
|
|
async getGuildApplicationCommands(guildId) {
|
|
return await rest.get<DiscordApplicationCommand[]>(rest.routes.interactions.commands.guilds.all(rest.applicationId, guildId))
|
|
},
|
|
|
|
async getGuildPreview(guildId) {
|
|
return await rest.get<DiscordGuildPreview>(rest.routes.guilds.preview(guildId))
|
|
},
|
|
|
|
async getGuildTemplate(templateCode) {
|
|
return await rest.get<DiscordTemplate>(rest.routes.guilds.templates.code(templateCode))
|
|
},
|
|
|
|
async getGuildTemplates(guildId) {
|
|
return await rest.get<DiscordTemplate[]>(rest.routes.guilds.templates.all(guildId))
|
|
},
|
|
|
|
async getGuildWebhooks(guildId) {
|
|
return await rest.get<DiscordWebhook[]>(rest.routes.guilds.webhooks(guildId))
|
|
},
|
|
|
|
async getIntegrations(guildId) {
|
|
return await rest.get<DiscordIntegration[]>(rest.routes.guilds.integrations(guildId))
|
|
},
|
|
|
|
async getInvite(inviteCode, options) {
|
|
return await rest.get<DiscordInviteMetadata>(rest.routes.guilds.invite(inviteCode, options))
|
|
},
|
|
|
|
async getInvites(guildId) {
|
|
return await rest.get<DiscordInviteMetadata[]>(rest.routes.guilds.invites(guildId))
|
|
},
|
|
|
|
async getMessage(channelId, messageId) {
|
|
return await rest.get<DiscordMessage>(rest.routes.channels.message(channelId, messageId))
|
|
},
|
|
|
|
async getMessages(channelId, options) {
|
|
return await rest.get<DiscordMessage[]>(rest.routes.channels.messages(channelId, options))
|
|
},
|
|
|
|
async getNitroStickerPacks() {
|
|
return await rest.get<DiscordStickerPack[]>(rest.routes.nitroStickerPacks())
|
|
},
|
|
|
|
async getOriginalInteractionResponse(token) {
|
|
return await rest.get<DiscordMessage>(rest.routes.interactions.responses.original(rest.applicationId, token))
|
|
},
|
|
|
|
async getPinnedMessages(channelId) {
|
|
return await rest.get<DiscordMessage[]>(rest.routes.channels.pins(channelId))
|
|
},
|
|
|
|
async getPrivateArchivedThreads(channelId, options) {
|
|
return await rest.get<DiscordListArchivedThreads>(rest.routes.channels.threads.private(channelId, options))
|
|
},
|
|
|
|
async getPrivateJoinedArchivedThreads(channelId, options) {
|
|
return await rest.get<DiscordListArchivedThreads>(rest.routes.channels.threads.joined(channelId, options))
|
|
},
|
|
|
|
async getPruneCount(guildId, options) {
|
|
return await rest.get<DiscordPrunedCount>(rest.routes.guilds.prune(guildId, options))
|
|
},
|
|
|
|
async getPublicArchivedThreads(channelId, options) {
|
|
return await rest.get<DiscordListArchivedThreads>(rest.routes.channels.threads.public(channelId, options))
|
|
},
|
|
|
|
async getRoles(guildId) {
|
|
return await rest.get<DiscordRole[]>(rest.routes.guilds.roles.all(guildId))
|
|
},
|
|
|
|
async getScheduledEvent(guildId, eventId, options) {
|
|
return await rest.get<DiscordScheduledEvent>(rest.routes.guilds.events.event(guildId, eventId, options?.withUserCount))
|
|
},
|
|
|
|
async getScheduledEvents(guildId, options) {
|
|
return await rest.get<DiscordScheduledEvent[]>(rest.routes.guilds.events.events(guildId, options?.withUserCount))
|
|
},
|
|
|
|
async getScheduledEventUsers(guildId, eventId, options) {
|
|
return await rest.get<Array<{ user: DiscordUser; member?: DiscordMember }>>(rest.routes.guilds.events.users(guildId, eventId, options))
|
|
},
|
|
|
|
async getSessionInfo() {
|
|
return await rest.getGatewayBot()
|
|
},
|
|
|
|
async getStageInstance(channelId) {
|
|
return await rest.get<DiscordStageInstance>(rest.routes.channels.stage(channelId))
|
|
},
|
|
|
|
async getSticker(stickerId: BigString) {
|
|
return await rest.get<DiscordSticker>(rest.routes.sticker(stickerId))
|
|
},
|
|
|
|
async getGuildSticker(guildId, stickerId) {
|
|
return await rest.get<DiscordSticker>(rest.routes.guilds.sticker(guildId, stickerId))
|
|
},
|
|
|
|
async getGuildStickers(guildId) {
|
|
return await rest.get<DiscordSticker[]>(rest.routes.guilds.stickers(guildId))
|
|
},
|
|
|
|
async getThreadMember(channelId, userId) {
|
|
return await rest.get<DiscordThreadMember>(rest.routes.channels.threads.user(channelId, userId))
|
|
},
|
|
|
|
async getThreadMembers(channelId) {
|
|
return await rest.get<DiscordThreadMember[]>(rest.routes.channels.threads.members(channelId))
|
|
},
|
|
|
|
async getReactions(channelId, messageId, reaction, options) {
|
|
return await rest.get<DiscordUser[]>(rest.routes.channels.reactions.message(channelId, messageId, reaction, options))
|
|
},
|
|
|
|
async getUser(id) {
|
|
return await rest.get<DiscordUser>(rest.routes.user(id))
|
|
},
|
|
|
|
async getVanityUrl(guildId) {
|
|
return await rest.get<DiscordVanityUrl>(rest.routes.guilds.vanity(guildId))
|
|
},
|
|
|
|
async getVoiceRegions(guildId) {
|
|
return await rest.get<DiscordVoiceRegion[]>(rest.routes.guilds.regions(guildId))
|
|
},
|
|
|
|
async getWebhook(webhookId) {
|
|
return await rest.get<DiscordWebhook>(rest.routes.webhooks.id(webhookId))
|
|
},
|
|
|
|
async getWebhookMessage(webhookId, token, messageId, options) {
|
|
return await rest.get<DiscordMessage>(rest.routes.webhooks.message(webhookId, token, messageId, options))
|
|
},
|
|
|
|
async getWebhookWithToken(webhookId, token) {
|
|
return await rest.get<DiscordWebhook>(rest.routes.webhooks.webhook(webhookId, token))
|
|
},
|
|
|
|
async getWelcomeScreen(guildId) {
|
|
return await rest.get<DiscordWelcomeScreen>(rest.routes.guilds.welcome(guildId))
|
|
},
|
|
|
|
async getWidget(guildId) {
|
|
return await rest.get<DiscordGuildWidget>(rest.routes.guilds.widgetJson(guildId))
|
|
},
|
|
|
|
async getWidgetSettings(guildId) {
|
|
return await rest.get<DiscordGuildWidgetSettings>(rest.routes.guilds.widget(guildId))
|
|
},
|
|
|
|
async joinThread(channelId) {
|
|
await rest.put(rest.routes.channels.threads.me(channelId))
|
|
},
|
|
|
|
async leaveGuild(guildId) {
|
|
await rest.delete(rest.routes.guilds.leave(guildId))
|
|
},
|
|
|
|
async leaveThread(channelId) {
|
|
await rest.delete(rest.routes.channels.threads.me(channelId))
|
|
},
|
|
|
|
async publishMessage(channelId, messageId) {
|
|
return await rest.post<DiscordMessage>(rest.routes.channels.crosspost(channelId, messageId))
|
|
},
|
|
|
|
async removeRole(guildId, userId, roleId, reason) {
|
|
await rest.delete(rest.routes.guilds.roles.member(guildId, userId, roleId), { reason })
|
|
},
|
|
|
|
async removeThreadMember(channelId, userId) {
|
|
await rest.delete(rest.routes.channels.threads.user(channelId, userId))
|
|
},
|
|
|
|
// TODO: why that
|
|
async sendFollowupMessage(token, options) {
|
|
return await new Promise((resolve, reject) => {
|
|
rest.sendRequest({
|
|
url: rest.routes.webhooks.webhook(rest.applicationId, token),
|
|
method: 'POST',
|
|
requestBodyOptions: { body: options, files: options.files },
|
|
retryCount: 0,
|
|
retryRequest: async function(options: SendRequestOptions) {
|
|
// TODO: should change to reprocess queue item
|
|
await rest.sendRequest(options)
|
|
},
|
|
resolve: (data) => {
|
|
resolve(data.status !== 204 ? JSON.parse(data.body ?? '{}') : undefined)
|
|
},
|
|
reject,
|
|
})
|
|
})
|
|
},
|
|
|
|
// TODO: why that
|
|
async sendInteractionResponse(interactionId, token, options) {
|
|
await new Promise((resolve, reject) => {
|
|
rest.sendRequest({
|
|
url: rest.routes.interactions.responses.callback(interactionId, token),
|
|
method: 'POST',
|
|
requestBodyOptions: { body: options },
|
|
retryCount: 0,
|
|
retryRequest: async function(options: SendRequestOptions) {
|
|
// TODO: should change to reprocess queue item
|
|
await rest.sendRequest(options)
|
|
},
|
|
resolve: (data) => {
|
|
resolve(data.status !== 204 ? JSON.parse(data.body ?? '{}') : undefined)
|
|
},
|
|
reject,
|
|
})
|
|
})
|
|
},
|
|
|
|
async sendMessage(channelId, body) {
|
|
return await rest.post<DiscordMessage>(rest.routes.channels.messages(channelId), { body, files: body.files })
|
|
},
|
|
|
|
async startThreadWithMessage(channelId, messageId, body) {
|
|
return await rest.post<DiscordChannel>(rest.routes.channels.threads.message(channelId, messageId), { body })
|
|
},
|
|
|
|
async startThreadWithoutMessage(channelId, body) {
|
|
return await rest.post<DiscordChannel>(rest.routes.channels.threads.all(channelId), { body })
|
|
},
|
|
|
|
async syncGuildTemplate(guildId) {
|
|
return await rest.put<DiscordTemplate>(rest.routes.guilds.templates.all(guildId))
|
|
},
|
|
|
|
async banMember(guildId, userId, body) {
|
|
await rest.put<void>(rest.routes.guilds.members.ban(guildId, userId), { body })
|
|
},
|
|
|
|
async editBotMember(guildId, body) {
|
|
return await rest.patch<DiscordMember>(rest.routes.guilds.members.bot(guildId), { body })
|
|
},
|
|
|
|
async editMember(guildId, userId, body) {
|
|
return await rest.patch<DiscordMemberWithUser>(rest.routes.guilds.members.member(guildId, userId), { body })
|
|
},
|
|
|
|
async getMember(guildId, userId) {
|
|
return await rest.get<DiscordMemberWithUser>(rest.routes.guilds.members.member(guildId, userId))
|
|
},
|
|
|
|
async getMembers(guildId, options) {
|
|
return await rest.get<DiscordMemberWithUser[]>(rest.routes.guilds.members.members(guildId, options))
|
|
},
|
|
|
|
async kickMember(guildId, userId, reason) {
|
|
await rest.delete(rest.routes.guilds.members.member(guildId, userId), {
|
|
reason,
|
|
})
|
|
},
|
|
|
|
async pinMessage(channelId, messageId, reason) {
|
|
await rest.put(rest.routes.channels.pin(channelId, messageId), { reason })
|
|
},
|
|
|
|
async pruneMembers(guildId, body) {
|
|
return await rest.post<{ pruned: number | null }>(rest.routes.guilds.members.prune(guildId), { body })
|
|
},
|
|
|
|
async searchMembers(guildId, query, options) {
|
|
return await rest.get<DiscordMemberWithUser[]>(rest.routes.guilds.members.search(guildId, query, options))
|
|
},
|
|
|
|
async unbanMember(guildId, userId) {
|
|
await rest.delete(rest.routes.guilds.members.ban(guildId, userId))
|
|
},
|
|
|
|
async unpinMessage(channelId, messageId, reason) {
|
|
await rest.delete(rest.routes.channels.pin(channelId, messageId), { reason })
|
|
},
|
|
|
|
async triggerTypingIndicator(channelId) {
|
|
await rest.post(rest.routes.channels.typing(channelId))
|
|
},
|
|
|
|
async upsertGlobalApplicationCommands(body) {
|
|
return await rest.put<DiscordApplicationCommand[]>(rest.routes.interactions.commands.commands(rest.applicationId), { body })
|
|
},
|
|
|
|
async upsertGuildApplicationCommands(guildId, body) {
|
|
return await rest.put<DiscordApplicationCommand[]>(rest.routes.interactions.commands.guilds.all(rest.applicationId, guildId), { body })
|
|
},
|
|
}
|
|
|
|
return rest
|
|
}
|