fix(rest): Always encode URI component in routes (#4609)

* fix(rest): Always encode URI component in routes

Instead of using the implicit behavior of `fetch` and `Request` we do it explicitly in the routes.

* Add checks for numbers and digits-only strings

* Add option to disable uri encode
This commit is contained in:
Fleny
2026-01-12 12:12:54 -05:00
committed by GitHub
parent 5ff9822759
commit c1445e4600
+202 -186
View File
@@ -2,29 +2,37 @@ import type { GetMessagesOptions, GetScheduledEventUsers } from '@discordeno/typ
import { isGetMessagesAfter, isGetMessagesAround, isGetMessagesBefore, isGetMessagesLimit } from '@discordeno/utils' import { isGetMessagesAfter, isGetMessagesAround, isGetMessagesBefore, isGetMessagesLimit } from '@discordeno/utils'
import type { RestRoutes } from './typings/routes.js' import type { RestRoutes } from './typings/routes.js'
export function createRoutes(): RestRoutes { /**
* Creates the available discord API routes
*
* @param disableURIEncode Don't encode strings, except where required - Danger: disabling URI encoding may lead to path traversal if unsafe strings are used
* @returns The available discord API routes
*/
export function createRoutes(disableURIEncode: boolean = false): RestRoutes {
const encode: typeof encodeComponent = disableURIEncode ? (x) => x.toString() : encodeComponent
return { return {
webhooks: { webhooks: {
id: (webhookId) => { id: (webhookId) => {
return `/webhooks/${webhookId}` return `/webhooks/${encode(webhookId)}`
}, },
message: (webhookId, token, messageId, options) => { message: (webhookId, token, messageId, options) => {
let url = `/webhooks/${webhookId}/${token}/messages/${messageId}?` let url = `/webhooks/${encode(webhookId)}/${encode(token)}/messages/${encode(messageId)}?`
if (options) { if (options) {
if (options.threadId) url += `thread_id=${options.threadId}` if (options.threadId) url += `thread_id=${encode(options.threadId)}`
if (options.withComponents) url += `&with_components=${options.withComponents}` if (options.withComponents) url += `&with_components=${encode(options.withComponents)}`
} }
return url return url
}, },
webhook: (webhookId, token, options) => { webhook: (webhookId, token, options) => {
let url = `/webhooks/${webhookId}/${token}?` let url = `/webhooks/${encode(webhookId)}/${encode(token)}?`
if (options) { if (options) {
if (options?.wait !== undefined) url += `wait=${options.wait.toString()}` if (options?.wait !== undefined) url += `wait=${encode(options.wait)}`
if (options.threadId) url += `&thread_id=${options.threadId}` if (options.threadId) url += `&thread_id=${encode(options.threadId)}`
if (options.withComponents) url += `&with_components=${options.withComponents}` if (options.withComponents) url += `&with_components=${encode(options.withComponents)}`
} }
return url return url
@@ -34,106 +42,104 @@ export function createRoutes(): RestRoutes {
// Channel Endpoints // Channel Endpoints
channels: { channels: {
bulk: (channelId) => { bulk: (channelId) => {
return `/channels/${channelId}/messages/bulk-delete` return `/channels/${encode(channelId)}/messages/bulk-delete`
}, },
dm: () => { dm: () => {
return '/users/@me/channels' return '/users/@me/channels'
}, },
dmRecipient: (channelId, userId) => { dmRecipient: (channelId, userId) => {
return `/channels/${channelId}/recipients/${userId}` return `/channels/${encode(channelId)}/recipients/${encode(userId)}`
}, },
pin: (channelId, messageId) => { pin: (channelId, messageId) => {
return `/channels/${channelId}/pins/${messageId}` return `/channels/${encode(channelId)}/pins/${encode(messageId)}`
}, },
pins: (channelId) => { pins: (channelId) => {
return `/channels/${channelId}/pins` return `/channels/${encode(channelId)}/pins`
}, },
messagePins: (channelId, options) => { messagePins: (channelId, options) => {
let url = `/channels/${channelId}/messages/pins?` let url = `/channels/${encode(channelId)}/messages/pins?`
if (options) { if (options) {
if (options.before) url += `before=${options.before}` if (options.before) url += `before=${encode(options.before)}`
if (options.limit) url += `&limit=${options.limit}` if (options.limit) url += `&limit=${encode(options.limit)}`
} }
return url return url
}, },
messagePin: (channelId, messageId) => { messagePin: (channelId, messageId) => {
return `/channels/${channelId}/messages/pins/${messageId}` return `/channels/${encode(channelId)}/messages/pins/${encode(messageId)}`
}, },
reactions: { reactions: {
bot: (channelId, messageId, emoji) => { bot: (channelId, messageId, emoji) => {
return `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/@me` return `/channels/${encode(channelId)}/messages/${encode(messageId)}/reactions/${encodeURIComponent(emoji)}/@me`
}, },
user: (channelId, messageId, emoji, userId) => { user: (channelId, messageId, emoji, userId) => {
return `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/${userId}` return `/channels/${encode(channelId)}/messages/${encode(messageId)}/reactions/${encodeURIComponent(emoji)}/${encode(userId)}`
}, },
all: (channelId, messageId) => { all: (channelId, messageId) => {
return `/channels/${channelId}/messages/${messageId}/reactions` return `/channels/${encode(channelId)}/messages/${encode(messageId)}/reactions`
}, },
emoji: (channelId, messageId, emoji, options) => { emoji: (channelId, messageId, emoji, options) => {
let url = `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}?` let url = `/channels/${encode(channelId)}/messages/${encode(messageId)}/reactions/${encodeURIComponent(emoji)}?`
if (options) { if (options) {
if (options.type) url += `type=${options.type}` if (options.type) url += `type=${encode(options.type)}`
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions if (options.after) url += `&after=${encode(options.after)}`
if (options.after) url += `&after=${options.after}` if (options.limit) url += `&limit=${encode(options.limit)}`
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
if (options.limit) url += `&limit=${options.limit}`
} }
return url return url
}, },
message: (channelId, messageId, emoji, options) => { message: (channelId, messageId, emoji, options) => {
let url = `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}?` let url = `/channels/${encode(channelId)}/messages/${encode(messageId)}/reactions/${encodeURIComponent(emoji)}?`
if (options) { if (options) {
if (options.after) url += `after=${options.after}` if (options.after) url += `after=${encode(options.after)}`
if (options.limit) url += `&limit=${options.limit}` if (options.limit) url += `&limit=${encode(options.limit)}`
} }
return url return url
}, },
}, },
webhooks: (channelId) => { webhooks: (channelId) => {
return `/channels/${channelId}/webhooks` return `/channels/${encode(channelId)}/webhooks`
}, },
channel: (channelId) => { channel: (channelId) => {
return `/channels/${channelId}` return `/channels/${encode(channelId)}`
}, },
follow: (channelId) => { follow: (channelId) => {
return `/channels/${channelId}/followers` return `/channels/${encode(channelId)}/followers`
}, },
forum: (channelId) => { forum: (channelId) => {
return `/channels/${channelId}/threads` return `/channels/${encode(channelId)}/threads`
}, },
invites: (channelId) => { invites: (channelId) => {
return `/channels/${channelId}/invites` return `/channels/${encode(channelId)}/invites`
}, },
message: (channelId, messageId) => { message: (channelId, messageId) => {
return `/channels/${channelId}/messages/${messageId}` return `/channels/${encode(channelId)}/messages/${encode(messageId)}`
}, },
messages: (channelId, options?: GetMessagesOptions) => { messages: (channelId, options?: GetMessagesOptions) => {
let url = `/channels/${channelId}/messages?` let url = `/channels/${encode(channelId)}/messages?`
if (options) { if (options) {
if (isGetMessagesAfter(options) && options.after) { if (isGetMessagesAfter(options) && options.after) {
url += `after=${options.after}` url += `after=${encode(options.after)}`
} }
if (isGetMessagesBefore(options) && options.before) { if (isGetMessagesBefore(options) && options.before) {
url += `&before=${options.before}` url += `&before=${encode(options.before)}`
} }
if (isGetMessagesAround(options) && options.around) { if (isGetMessagesAround(options) && options.around) {
url += `&around=${options.around}` url += `&around=${encode(options.around)}`
} }
if (isGetMessagesLimit(options) && options.limit) { if (isGetMessagesLimit(options) && options.limit) {
url += `&limit=${options.limit}` url += `&limit=${encode(options.limit)}`
} }
} }
@@ -141,11 +147,11 @@ export function createRoutes(): RestRoutes {
}, },
overwrite: (channelId, overwriteId) => { overwrite: (channelId, overwriteId) => {
return `/channels/${channelId}/permissions/${overwriteId}` return `/channels/${encode(channelId)}/permissions/${encode(overwriteId)}`
}, },
crosspost: (channelId, messageId) => { crosspost: (channelId, messageId) => {
return `/channels/${channelId}/messages/${messageId}/crosspost` return `/channels/${encode(channelId)}/messages/${encode(messageId)}/crosspost`
}, },
stages: () => { stages: () => {
@@ -153,81 +159,84 @@ export function createRoutes(): RestRoutes {
}, },
stage: (channelId) => { stage: (channelId) => {
return `/stage-instances/${channelId}` return `/stage-instances/${encode(channelId)}`
}, },
// Thread Endpoints // Thread Endpoints
threads: { threads: {
message: (channelId, messageId) => { message: (channelId, messageId) => {
return `/channels/${channelId}/messages/${messageId}/threads` return `/channels/${encode(channelId)}/messages/${encode(messageId)}/threads`
}, },
all: (channelId) => { all: (channelId) => {
return `/channels/${channelId}/threads` return `/channels/${encode(channelId)}/threads`
}, },
active: (guildId) => { active: (guildId) => {
return `/guilds/${guildId}/threads/active` return `/guilds/${encode(guildId)}/threads/active`
}, },
members: (channelId, options) => { members: (channelId, options) => {
let url = `/channels/${channelId}/thread-members?` let url = `/channels/${encode(channelId)}/thread-members?`
if (options) { if (options) {
if (options.withMember) url += `with_member=${options.withMember}` if (options.withMember) url += `with_member=${encode(options.withMember)}`
if (options.limit) url += `&limit=${options.limit}` if (options.limit) url += `&limit=${encode(options.limit)}`
if (options.after) url += `&after=${options.after}` if (options.after) url += `&after=${encode(options.after)}`
} }
return url return url
}, },
me: (channelId) => { me: (channelId) => {
return `/channels/${channelId}/thread-members/@me` return `/channels/${encode(channelId)}/thread-members/@me`
}, },
getUser(channelId, userId, options) { getUser(channelId, userId, options) {
let url = `/channels/${channelId}/thread-members/${userId}?` let url = `/channels/${encode(channelId)}/thread-members/${encode(userId)}?`
if (options) { if (options) {
if (options.withMember) url += `with_member=${options.withMember}` if (options.withMember) url += `with_member=${encode(options.withMember)}`
} }
return url return url
}, },
user: (channelId, userId) => { user: (channelId, userId) => {
return `/channels/${channelId}/thread-members/${userId}` return `/channels/${encode(channelId)}/thread-members/${encode(userId)}`
}, },
archived: (channelId) => { archived: (channelId) => {
return `/channels/${channelId}/threads/archived` return `/channels/${encode(channelId)}/threads/archived`
}, },
public: (channelId, options) => { public: (channelId, options) => {
let url = `/channels/${channelId}/threads/archived/public?` let url = `/channels/${encode(channelId)}/threads/archived/public?`
if (options) { if (options) {
if (options.before) { if (options.before) {
url += `before=${new Date(options.before).toISOString()}` const iso = new Date(options.before).toISOString()
url += `before=${encode(iso)}`
} }
if (options.limit) url += `&limit=${options.limit}` if (options.limit) url += `&limit=${encode(options.limit)}`
} }
return url return url
}, },
private: (channelId, options) => { private: (channelId, options) => {
let url = `/channels/${channelId}/threads/archived/private?` let url = `/channels/${encode(channelId)}/threads/archived/private?`
if (options) { if (options) {
if (options.before) { if (options.before) {
url += `before=${new Date(options.before).toISOString()}` const iso = new Date(options.before).toISOString()
url += `before=${encode(iso)}`
} }
if (options.limit) url += `&limit=${options.limit}` if (options.limit) url += `&limit=${encode(options.limit)}`
} }
return url return url
}, },
joined: (channelId, options) => { joined: (channelId, options) => {
let url = `/channels/${channelId}/users/@me/threads/archived/private?` let url = `/channels/${encode(channelId)}/users/@me/threads/archived/private?`
if (options) { if (options) {
if (options.before) { if (options.before) {
url += `before=${new Date(options.before).toISOString()}` const iso = new Date(options.before).toISOString()
url += `before=${encode(iso)}`
} }
if (options.limit) url += `&limit=${options.limit}` if (options.limit) url += `&limit=${encode(options.limit)}`
} }
return url return url
@@ -235,22 +244,22 @@ export function createRoutes(): RestRoutes {
}, },
typing: (channelId) => { typing: (channelId) => {
return `/channels/${channelId}/typing` return `/channels/${encode(channelId)}/typing`
}, },
polls: { polls: {
votes: (channelId, messageId, answerId, options) => { votes: (channelId, messageId, answerId, options) => {
let url = `/channels/${channelId}/polls/${messageId}/answers/${answerId}?` let url = `/channels/${encode(channelId)}/polls/${encode(messageId)}/answers/${encode(answerId)}?`
if (options) { if (options) {
if (options.after) url += `after=${options.after}` if (options.after) url += `after=${encode(options.after)}`
if (options.limit) url += `&limit=${options.limit}` if (options.limit) url += `&limit=${encode(options.limit)}`
} }
return url return url
}, },
expire: (channelId, messageId) => { expire: (channelId, messageId) => {
return `/channels/${channelId}/polls/${messageId}/expire` return `/channels/${encode(channelId)}/polls/${encode(messageId)}/expire`
}, },
}, },
}, },
@@ -264,167 +273,167 @@ export function createRoutes(): RestRoutes {
let url = '/users/@me/guilds?' let url = '/users/@me/guilds?'
if (options) { if (options) {
if (options.after) url += `after=${options.after}` if (options.after) url += `after=${encode(options.after)}`
if (options.before) url += `&before=${options.before}` if (options.before) url += `&before=${encode(options.before)}`
if (options.limit) url += `&limit=${options.limit}` if (options.limit) url += `&limit=${encode(options.limit)}`
if (options.withCounts) url += `&with_counts=${options.withCounts}` if (options.withCounts) url += `&with_counts=${encode(options.withCounts)}`
} }
return url return url
}, },
auditlogs: (guildId, options) => { auditlogs: (guildId, options) => {
let url = `/guilds/${guildId}/audit-logs?` let url = `/guilds/${encode(guildId)}/audit-logs?`
if (options) { if (options) {
if (options.actionType) url += `action_type=${options.actionType}` if (options.actionType) url += `action_type=${encode(options.actionType)}`
if (options.before) url += `&before=${options.before}` if (options.before) url += `&before=${encode(options.before)}`
if (options.after) url += `&after=${options.after}` if (options.after) url += `&after=${encode(options.after)}`
if (options.limit) url += `&limit=${options.limit}` if (options.limit) url += `&limit=${encode(options.limit)}`
if (options.userId) url += `&user_id=${options.userId}` if (options.userId) url += `&user_id=${encode(options.userId)}`
} }
return url return url
}, },
automod: { automod: {
rule: (guildId, ruleId) => { rule: (guildId, ruleId) => {
return `/guilds/${guildId}/auto-moderation/rules/${ruleId}` return `/guilds/${encode(guildId)}/auto-moderation/rules/${encode(ruleId)}`
}, },
rules: (guildId) => { rules: (guildId) => {
return `/guilds/${guildId}/auto-moderation/rules` return `/guilds/${encode(guildId)}/auto-moderation/rules`
}, },
}, },
channels: (guildId) => { channels: (guildId) => {
return `/guilds/${guildId}/channels` return `/guilds/${encode(guildId)}/channels`
}, },
emoji: (guildId, emojiId) => { emoji: (guildId, emojiId) => {
return `/guilds/${guildId}/emojis/${emojiId}` return `/guilds/${encode(guildId)}/emojis/${encode(emojiId)}`
}, },
emojis: (guildId) => { emojis: (guildId) => {
return `/guilds/${guildId}/emojis` return `/guilds/${encode(guildId)}/emojis`
}, },
events: { events: {
events: (guildId, withUserCount?: boolean) => { events: (guildId, withUserCount?: boolean) => {
let url = `/guilds/${guildId}/scheduled-events?` let url = `/guilds/${encode(guildId)}/scheduled-events?`
if (withUserCount !== undefined) { if (withUserCount !== undefined) {
url += `with_user_count=${withUserCount.toString()}` url += `with_user_count=${encode(withUserCount)}`
} }
return url return url
}, },
event: (guildId, eventId, withUserCount?: boolean) => { event: (guildId, eventId, withUserCount?: boolean) => {
let url = `/guilds/${guildId}/scheduled-events/${eventId}` let url = `/guilds/${encode(guildId)}/scheduled-events/${encode(eventId)}`
if (withUserCount !== undefined) { if (withUserCount !== undefined) {
url += `with_user_count=${withUserCount.toString()}` url += `with_user_count=${encode(withUserCount)}`
} }
return url return url
}, },
users: (guildId, eventId, options?: GetScheduledEventUsers) => { users: (guildId, eventId, options?: GetScheduledEventUsers) => {
let url = `/guilds/${guildId}/scheduled-events/${eventId}/users?` let url = `/guilds/${encode(guildId)}/scheduled-events/${encode(eventId)}/users?`
if (options) { if (options) {
if (options.limit !== undefined) url += `limit=${options.limit}` if (options.limit !== undefined) url += `limit=${encode(options.limit)}`
if (options.withMember !== undefined) { if (options.withMember !== undefined) {
url += `&with_member=${options.withMember.toString()}` url += `&with_member=${encode(options.withMember)}`
} }
if (options.after !== undefined) url += `&after=${options.after}` if (options.after !== undefined) url += `&after=${encode(options.after)}`
if (options.before !== undefined) url += `&before=${options.before}` if (options.before !== undefined) url += `&before=${encode(options.before)}`
} }
return url return url
}, },
}, },
guild(guildId, withCounts) { guild(guildId, withCounts) {
let url = `/guilds/${guildId}?` let url = `/guilds/${encode(guildId)}?`
if (withCounts !== undefined) { if (withCounts !== undefined) {
url += `with_counts=${withCounts.toString()}` url += `with_counts=${encode(withCounts)}`
} }
return url return url
}, },
integration(guildId, integrationId) { integration(guildId, integrationId) {
return `/guilds/${guildId}/integrations/${integrationId}` return `/guilds/${encode(guildId)}/integrations/${encode(integrationId)}`
}, },
integrations: (guildId) => { integrations: (guildId) => {
return `/guilds/${guildId}/integrations?include_applications=true` return `/guilds/${encode(guildId)}/integrations?include_applications=true`
}, },
invite(inviteCode, options) { invite(inviteCode, options) {
let url = `/invites/${inviteCode}?` let url = `/invites/${encode(inviteCode)}?`
if (options) { if (options) {
if (options.withCounts !== undefined) { if (options.withCounts !== undefined) {
url += `with_counts=${options.withCounts.toString()}` url += `with_counts=${encode(options.withCounts)}`
} }
if (options.scheduledEventId) { if (options.scheduledEventId) {
url += `&guild_scheduled_event_id=${options.scheduledEventId}` url += `&guild_scheduled_event_id=${encode(options.scheduledEventId)}`
} }
} }
return url return url
}, },
invites: (guildId) => { invites: (guildId) => {
return `/guilds/${guildId}/invites` return `/guilds/${encode(guildId)}/invites`
}, },
leave: (guildId) => { leave: (guildId) => {
return `/users/@me/guilds/${guildId}` return `/users/@me/guilds/${encode(guildId)}`
}, },
members: { members: {
ban: (guildId, userId) => { ban: (guildId, userId) => {
return `/guilds/${guildId}/bans/${userId}` return `/guilds/${encode(guildId)}/bans/${encode(userId)}`
}, },
bans: (guildId, options) => { bans: (guildId, options) => {
let url = `/guilds/${guildId}/bans?` let url = `/guilds/${encode(guildId)}/bans?`
if (options) { if (options) {
if (options.limit) url += `limit=${options.limit}` if (options.limit) url += `limit=${encode(options.limit)}`
if (options.after) url += `&after=${options.after}` if (options.after) url += `&after=${encode(options.after)}`
if (options.before) url += `&before=${options.before}` if (options.before) url += `&before=${encode(options.before)}`
} }
return url return url
}, },
bulkBan: (guildId) => { bulkBan: (guildId) => {
return `/guilds/${guildId}/bulk-ban` return `/guilds/${encode(guildId)}/bulk-ban`
}, },
bot: (guildId) => { bot: (guildId) => {
return `/guilds/${guildId}/members/@me` return `/guilds/${encode(guildId)}/members/@me`
}, },
member: (guildId, userId) => { member: (guildId, userId) => {
return `/guilds/${guildId}/members/${userId}` return `/guilds/${encode(guildId)}/members/${encode(userId)}`
}, },
currentMember: (guildId) => { currentMember: (guildId) => {
return `/users/@me/guilds/${guildId}/member` return `/users/@me/guilds/${encode(guildId)}/member`
}, },
members: (guildId, options) => { members: (guildId, options) => {
let url = `/guilds/${guildId}/members?` let url = `/guilds/${encode(guildId)}/members?`
if (options !== undefined) { if (options !== undefined) {
if (options.limit) url += `limit=${options.limit}` if (options.limit) url += `limit=${encode(options.limit)}`
if (options.after) url += `&after=${options.after}` if (options.after) url += `&after=${encode(options.after)}`
} }
return url return url
}, },
search: (guildId, query, options) => { search: (guildId, query, options) => {
let url = `/guilds/${guildId}/members/search?query=${encodeURIComponent(query)}` let url = `/guilds/${encode(guildId)}/members/search?query=${encode(query)}`
if (options) { if (options) {
if (options.limit !== undefined) url += `&limit=${options.limit}` if (options.limit !== undefined) url += `&limit=${encode(options.limit)}`
} }
return url return url
}, },
prune: (guildId, options) => { prune: (guildId, options) => {
let url = `/guilds/${guildId}/prune?` let url = `/guilds/${encode(guildId)}/prune?`
if (options) { if (options) {
if (options.days) url += `days=${options.days}` if (options.days) url += `days=${encode(options.days)}`
if (Array.isArray(options.includeRoles)) { if (Array.isArray(options.includeRoles)) {
url += `&include_roles=${options.includeRoles.join(',')}` url += `&include_roles=${encode(options.includeRoles.join(','))}`
} else if (options.includeRoles) { } else if (options.includeRoles) {
url += `&include_roles=${options.includeRoles}` url += `&include_roles=${encode(options.includeRoles)}`
} }
} }
@@ -432,17 +441,17 @@ export function createRoutes(): RestRoutes {
}, },
}, },
preview: (guildId) => { preview: (guildId) => {
return `/guilds/${guildId}/preview` return `/guilds/${encode(guildId)}/preview`
}, },
prune: (guildId, options) => { prune: (guildId, options) => {
let url = `/guilds/${guildId}/prune?` let url = `/guilds/${encode(guildId)}/prune?`
if (options) { if (options) {
if (options.days) url += `days=${options.days}` if (options.days) url += `days=${encode(options.days)}`
if (Array.isArray(options.includeRoles)) { if (Array.isArray(options.includeRoles)) {
url += `&include_roles=${options.includeRoles.join(',')}` url += `&include_roles=${encode(options.includeRoles.join(','))}`
} else if (options.includeRoles) { } else if (options.includeRoles) {
url += `&include_roles=${options.includeRoles}` url += `&include_roles=${encode(options.includeRoles)}`
} }
} }
@@ -450,66 +459,66 @@ export function createRoutes(): RestRoutes {
}, },
roles: { roles: {
one: (guildId, roleId) => { one: (guildId, roleId) => {
return `/guilds/${guildId}/roles/${roleId}` return `/guilds/${encode(guildId)}/roles/${encode(roleId)}`
}, },
all: (guildId) => { all: (guildId) => {
return `/guilds/${guildId}/roles` return `/guilds/${encode(guildId)}/roles`
}, },
member: (guildId, memberId, roleId) => { member: (guildId, memberId, roleId) => {
return `/guilds/${guildId}/members/${memberId}/roles/${roleId}` return `/guilds/${encode(guildId)}/members/${encode(memberId)}/roles/${encode(roleId)}`
}, },
memberCounts: (guildId) => { memberCounts: (guildId) => {
return `/guilds/${guildId}/roles/member-counts` return `/guilds/${guildId}/roles/member-counts`
}, },
}, },
stickers: (guildId) => { stickers: (guildId) => {
return `/guilds/${guildId}/stickers` return `/guilds/${encode(guildId)}/stickers`
}, },
sticker: (guildId, stickerId) => { sticker: (guildId, stickerId) => {
return `/guilds/${guildId}/stickers/${stickerId}` return `/guilds/${encode(guildId)}/stickers/${encode(stickerId)}`
}, },
voice: (guildId, userId) => { voice: (guildId, userId) => {
return `/guilds/${guildId}/voice-states/${userId ?? '@me'}` return `/guilds/${encode(guildId)}/voice-states/${encode(userId ?? '@me')}`
}, },
templates: { templates: {
code: (code) => { code: (code) => {
return `/guilds/templates/${code}` return `/guilds/templates/${encode(code)}`
}, },
guild: (guildId, code) => { guild: (guildId, code) => {
return `/guilds/${guildId}/templates/${code}` return `/guilds/${encode(guildId)}/templates/${encode(code)}`
}, },
all: (guildId) => { all: (guildId) => {
return `/guilds/${guildId}/templates` return `/guilds/${encode(guildId)}/templates`
}, },
}, },
vanity: (guildId) => { vanity: (guildId) => {
return `/guilds/${guildId}/vanity-url` return `/guilds/${encode(guildId)}/vanity-url`
}, },
regions: (guildId) => { regions: (guildId) => {
return `/guilds/${guildId}/regions` return `/guilds/${encode(guildId)}/regions`
}, },
webhooks: (guildId) => { webhooks: (guildId) => {
return `/guilds/${guildId}/webhooks` return `/guilds/${encode(guildId)}/webhooks`
}, },
welcome: (guildId) => { welcome: (guildId) => {
return `/guilds/${guildId}/welcome-screen` return `/guilds/${encode(guildId)}/welcome-screen`
}, },
widget: (guildId) => { widget: (guildId) => {
return `/guilds/${guildId}/widget` return `/guilds/${encode(guildId)}/widget`
}, },
widgetJson: (guildId) => { widgetJson: (guildId) => {
return `/guilds/${guildId}/widget.json` return `/guilds/${encode(guildId)}/widget.json`
}, },
onboarding: (guildId) => { onboarding: (guildId) => {
return `/guilds/${guildId}/onboarding` return `/guilds/${encode(guildId)}/onboarding`
}, },
incidentActions: (guildId) => { incidentActions: (guildId) => {
return `/guilds/${guildId}/incident-actions` return `/guilds/${encode(guildId)}/incident-actions`
}, },
}, },
sticker: (stickerId) => { sticker: (stickerId) => {
return `/stickers/${stickerId}` return `/stickers/${encode(stickerId)}`
}, },
regions: () => { regions: () => {
@@ -521,10 +530,10 @@ export function createRoutes(): RestRoutes {
commands: { commands: {
// Application Endpoints // Application Endpoints
commands: (applicationId, withLocalizations) => { commands: (applicationId, withLocalizations) => {
let url = `/applications/${applicationId}/commands?` let url = `/applications/${encode(applicationId)}/commands?`
if (withLocalizations !== undefined) { if (withLocalizations !== undefined) {
url += `with_localizations=${withLocalizations.toString()}` url += `with_localizations=${encode(withLocalizations)}`
} }
return url return url
@@ -532,30 +541,30 @@ export function createRoutes(): RestRoutes {
guilds: { guilds: {
all(applicationId, guildId, withLocalizations) { all(applicationId, guildId, withLocalizations) {
let url = `/applications/${applicationId}/guilds/${guildId}/commands?` let url = `/applications/${encode(applicationId)}/guilds/${encode(guildId)}/commands?`
if (withLocalizations !== undefined) { if (withLocalizations !== undefined) {
url += `with_localizations=${withLocalizations.toString()}` url += `with_localizations=${encode(withLocalizations)}`
} }
return url return url
}, },
one(applicationId, guildId, commandId) { one(applicationId, guildId, commandId) {
return `/applications/${applicationId}/guilds/${guildId}/commands/${commandId}` return `/applications/${encode(applicationId)}/guilds/${encode(guildId)}/commands/${encode(commandId)}`
}, },
}, },
permissions: (applicationId, guildId) => { permissions: (applicationId, guildId) => {
return `/applications/${applicationId}/guilds/${guildId}/commands/permissions` return `/applications/${encode(applicationId)}/guilds/${encode(guildId)}/commands/permissions`
}, },
permission: (applicationId, guildId, commandId) => { permission: (applicationId, guildId, commandId) => {
return `/applications/${applicationId}/guilds/${guildId}/commands/${commandId}/permissions` return `/applications/${encode(applicationId)}/guilds/${encode(guildId)}/commands/${encode(commandId)}/permissions`
}, },
command: (applicationId, commandId, withLocalizations) => { command: (applicationId, commandId, withLocalizations) => {
let url = `/applications/${applicationId}/commands/${commandId}?` let url = `/applications/${encode(applicationId)}/commands/${encode(commandId)}?`
if (withLocalizations !== undefined) { if (withLocalizations !== undefined) {
url += `withLocalizations=${withLocalizations.toString()}` url += `withLocalizations=${encode(withLocalizations)}`
} }
return url return url
@@ -565,13 +574,13 @@ export function createRoutes(): RestRoutes {
responses: { responses: {
// Interaction Endpoints // Interaction Endpoints
callback: (interactionId, token, options) => { callback: (interactionId, token, options) => {
return `/interactions/${interactionId}/${token}/callback?with_response=${!!options?.withResponse}` return `/interactions/${encode(interactionId)}/${encode(token)}/callback?with_response=${encode(!!options?.withResponse)}`
}, },
original: (interactionId, token) => { original: (interactionId, token) => {
return `/webhooks/${interactionId}/${token}/messages/@original` return `/webhooks/${encode(interactionId)}/${encode(token)}/messages/@original`
}, },
message: (applicationId, token, messageId) => { message: (applicationId, token, messageId) => {
return `/webhooks/${applicationId}/${token}/messages/${messageId}` return `/webhooks/${encode(applicationId)}/${encode(token)}/messages/${encode(messageId)}`
}, },
}, },
}, },
@@ -594,49 +603,49 @@ export function createRoutes(): RestRoutes {
return '/users/@me/connections' return '/users/@me/connections'
}, },
roleConnections: (applicationId) => { roleConnections: (applicationId) => {
return `/users/@me/applications/${applicationId}/role-connection` return `/users/@me/applications/${encode(applicationId)}/role-connection`
}, },
}, },
monetization: { monetization: {
entitlements: (applicationId, options) => { entitlements: (applicationId, options) => {
let url = `/applications/${applicationId}/entitlements?` let url = `/applications/${encode(applicationId)}/entitlements?`
if (options) { if (options) {
if (options.after) url += `after=${options.after}` if (options.after) url += `after=${encode(options.after)}`
if (options.before) url += `&before=${options.before}` if (options.before) url += `&before=${encode(options.before)}`
if (options.excludeEnded) url += `&exclude_ended=${options.excludeEnded}` if (options.excludeEnded) url += `&exclude_ended=${encode(options.excludeEnded)}`
if (options.guildId) url += `&guild_id=${options.guildId}` if (options.guildId) url += `&guild_id=${encode(options.guildId)}`
if (options.limit) url += `&limit=${options.limit}` if (options.limit) url += `&limit=${encode(options.limit)}`
if (options.skuIds) url += `&sku_ids=${options.skuIds.join(',')}` if (options.skuIds) url += `&sku_ids=${encode(options.skuIds.join(','))}`
if (options.userId) url += `&user_id=${options.userId}` if (options.userId) url += `&user_id=${encode(options.userId)}`
} }
return url return url
}, },
entitlement: (applicationId, entitlementId) => { entitlement: (applicationId, entitlementId) => {
return `/applications/${applicationId}/entitlements/${entitlementId}` return `/applications/${encode(applicationId)}/entitlements/${encode(entitlementId)}`
}, },
consumeEntitlement: (applicationId, entitlementId) => { consumeEntitlement: (applicationId, entitlementId) => {
return `/applications/${applicationId}/entitlements/${entitlementId}/consume` return `/applications/${encode(applicationId)}/entitlements/${encode(entitlementId)}/consume`
}, },
skus: (applicationId) => { skus: (applicationId) => {
return `/applications/${applicationId}/skus` return `/applications/${encode(applicationId)}/skus`
}, },
subscription: (skuId, subscriptionId) => { subscription: (skuId, subscriptionId) => {
return `/skus/${skuId}/subscriptions/${subscriptionId}` return `/skus/${encode(skuId)}/subscriptions/${encode(subscriptionId)}`
}, },
subscriptions: (skuId, options) => { subscriptions: (skuId, options) => {
let url = `/skus/${skuId}/subscriptions?` let url = `/skus/${encode(skuId)}/subscriptions?`
if (options) { if (options) {
if (options.after) url += `after=${options.after}` if (options.after) url += `after=${encode(options.after)}`
if (options.before) url += `&before=${options.before}` if (options.before) url += `&before=${encode(options.before)}`
if (options.userId) url += `&user_id=${options.userId}` if (options.userId) url += `&user_id=${encode(options.userId)}`
if (options.limit) url += `&limit=${options.limit}` if (options.limit) url += `&limit=${encode(options.limit)}`
} }
return url return url
@@ -645,16 +654,16 @@ export function createRoutes(): RestRoutes {
soundboard: { soundboard: {
sendSound: (channelId) => { sendSound: (channelId) => {
return `/channels/${channelId}` return `/channels/${encode(channelId)}`
}, },
listDefault: () => { listDefault: () => {
return `/soundboard-default-sounds` return `/soundboard-default-sounds`
}, },
guildSounds: (guildId) => { guildSounds: (guildId) => {
return `/guilds/${guildId}/soundboard-sounds` return `/guilds/${encode(guildId)}/soundboard-sounds`
}, },
guildSound: (guildId, soundId) => { guildSound: (guildId, soundId) => {
return `/guilds/${guildId}/soundboard-sounds/${soundId}` return `/guilds/${encode(guildId)}/soundboard-sounds/${encode(soundId)}`
}, },
}, },
@@ -664,37 +673,37 @@ export function createRoutes(): RestRoutes {
}, },
lobby: (lobbyId) => { lobby: (lobbyId) => {
return `/lobbies/${lobbyId}` return `/lobbies/${encode(lobbyId)}`
}, },
member: (lobbyId, userId) => { member: (lobbyId, userId) => {
return `/lobbies/${lobbyId}/members/${userId}` return `/lobbies/${encode(lobbyId)}/members/${encode(userId)}`
}, },
leave: (lobbyId) => { leave: (lobbyId) => {
return `/lobbies/${lobbyId}/members/@me` return `/lobbies/${encode(lobbyId)}/members/@me`
}, },
link: (lobbyId) => { link: (lobbyId) => {
return `/lobbies/${lobbyId}/channel-linking` return `/lobbies/${encode(lobbyId)}/channel-linking`
}, },
}, },
applicationEmoji(applicationId, emojiId) { applicationEmoji(applicationId, emojiId) {
return `/applications/${applicationId}/emojis/${emojiId}` return `/applications/${encode(applicationId)}/emojis/${encode(emojiId)}`
}, },
applicationEmojis(applicationId) { applicationEmojis(applicationId) {
return `/applications/${applicationId}/emojis` return `/applications/${encode(applicationId)}/emojis`
}, },
applicationRoleConnectionMetadata(applicationId) { applicationRoleConnectionMetadata(applicationId) {
return `/applications/${applicationId}/role-connections/metadata` return `/applications/${encode(applicationId)}/role-connections/metadata`
}, },
// User endpoints // User endpoints
user(userId) { user(userId) {
return `/users/${userId}` return `/users/${encode(userId)}`
}, },
application() { application() {
@@ -702,7 +711,7 @@ export function createRoutes(): RestRoutes {
}, },
applicationActivityInstance(applicationId, instanceId) { applicationActivityInstance(applicationId, instanceId) {
return `/applications/${applicationId}/activity-instances/${instanceId}` return `/applications/${encode(applicationId)}/activity-instances/${encode(instanceId)}`
}, },
currentUser() { currentUser() {
@@ -714,7 +723,7 @@ export function createRoutes(): RestRoutes {
}, },
stickerPack(stickerPackId) { stickerPack(stickerPackId) {
return `/sticker-packs/${stickerPackId}` return `/sticker-packs/${encode(stickerPackId)}`
}, },
stickerPacks() { stickerPacks() {
@@ -722,3 +731,10 @@ export function createRoutes(): RestRoutes {
}, },
} }
} }
function encodeComponent(uriComponent: string | number | bigint | boolean): string {
if (typeof uriComponent !== 'string') return uriComponent.toString()
if (/^\d+$/.test(uriComponent)) return uriComponent
return encodeURIComponent(uriComponent)
}