Co-authored-by: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com>
This commit is contained in:
Lars_und_so
2023-01-09 03:04:04 +01:00
committed by GitHub
parent 1f4b6da226
commit 8b18d06834
5 changed files with 473 additions and 81 deletions

View File

@@ -3,6 +3,7 @@ import type {
Camelize,
CreateGuildEmoji,
CreateMessageOptions,
DiscordApplication,
DiscordChannel,
DiscordCreateMessage,
DiscordEmoji,
@@ -11,8 +12,12 @@ import type {
DiscordUser,
GetMessagesOptions,
ModifyGuildEmoji,
WithReason,
InteractionCallbackData
} from '@discordeno/types'
import { camelize, Collection, delay } from '@discordeno/utils'
import { camelize, Collection, delay, urlToBase64 } from '@discordeno/utils'
import type { DiscordCreateWebhook, DiscordStickerPack, DiscordWebhook } from '../../types/src/discord.js'
import type { DiscordGetGatewayBot } from './../../types/dist/discord.d'
import type { InvalidRequestBucket } from './invalidBucket.js'
import { createInvalidRequestBucket } from './invalidBucket.js'
import { Queue } from './queue.js'
@@ -33,11 +38,35 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
invalidBucket: createInvalidRequestBucket({}),
routes: {
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
},
webhookMessage: (webhookId, token, messageId, options) => {
let url = `/webhooks/${webhookId}/${token}/messages/${messageId}?`
if (options) {
if (options.threadId) url += `thread_id=${options.threadId}`
}
return url
},
// Miscellaneous Endpoints
sessionInfo: () => '/gateway/bot',
// Channel Endpoints
channels: {
webhooks: (channelId) => {
return `/channels/${channelId}/webhooks`
},
channel: (channelId) => {
return `/channels/${channelId}`
},
@@ -82,6 +111,26 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
user(userId: BigString) {
return `/users/${userId}`
},
userBot() {
return '/users/@me'
},
oauth2Application() {
return 'oauth2/applications/@me'
},
gatewayBot() {
return '/gateway/bot'
},
nitroStickerPacks() {
return '/sticker-packs'
},
webhookId: (webhookId: BigString) => {
return `/webhooks/${webhookId}`
},
},
checkRateLimits(url) {
@@ -419,11 +468,64 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
// TODO: other options
} as DiscordCreateMessage)
},
async editBotProfile(options) {
const avatar = options?.botAvatarURL ? await urlToBase64(options?.botAvatarURL) : options?.botAvatarURL
return await rest.patch<DiscordUser>(rest.routes.userBot(), {
username: options.username?.trim(),
avatar,
})
},
async getApplicationInfo() {
return await rest.get<DiscordApplication>(rest.routes.oauth2Application())
},
async getGatewayBot() {
return await rest.get<DiscordGetGatewayBot>(rest.routes.gatewayBot())
},
async getNitroStickerPacks() {
const stickerPacks = await rest.get<DiscordStickerPack[]>(rest.routes.nitroStickerPacks())
return new Collection(
stickerPacks.map((stickerPack) => {
return [BigInt(stickerPack.id), stickerPack]
}),
)
},
async createWebhook(channelId, options) {
return await rest.post<DiscordWebhook>(rest.routes.channels.webhooks(channelId), {
name: options.name,
avatar: options.avatar ? await urlToBase64(options.avatar) : undefined,
reason: options.reason,
} as DiscordCreateWebhook)
},
async deleteWebhook(webhookId, reason) {
return await rest.delete(rest.routes.webhookId(webhookId), { reason })
},
async deleteWebhookMessage(webhookId, token, messageId, options) {
return await rest.delete(rest.routes.webhookMessage(webhookId, token, messageId, options))
},
async deleteWebhookWithToken(webhookId, token) {
return await rest.delete(rest.routes.webhook(webhookId, token))
},
}
return rest
}
export interface DeleteWebhookMessageOptions {
/** id of the thread the message is in */
threadId: BigString
}
export interface CreateRestManagerOptions {
/** The bot token which will be used to make requests. */
token: string
@@ -469,8 +571,23 @@ export interface RestManager {
sessionInfo: () => string
/** A specific user route. */
user: (id: BigString) => string
/** Current bot user route. */
userBot: () => string
// OAuth2
oauth2Application: () => string
// Gateway Bot
gatewayBot: () => string
// Nitro Sticker Packs
nitroStickerPacks: () => string
// Get a Webhook
webhookId: (webhookId: BigString) => string
// Send a Message through a Webhook
webhookMessage: (webhookId: BigString, token: string, messageId: BigString, options?: { threadId?: BigString }) => string
webhook: (webhookId: BigString, token: string, options?: { wait?: boolean; threadId?: BigString }) => string
/** Routes for channel related endpoints. */
channels: {
// Get a Channels Webhooks
webhooks: (channelId: BigString) => string
/** Route for a specific channel. */
channel: (channelId: BigString) => string
/** Route for a specific message */
@@ -636,11 +753,124 @@ export interface RestManager {
* @see {@link https://discord.com/developers/docs/resources/channel#create-message}
*/
sendMessage: (channelId: BigString, options: CreateMessageOptions) => Promise<Camelize<DiscordMessage>>
/**
* Modifies the bot's username or avatar.
* NOTE: username: if changed may cause the bot's discriminator to be randomized.
*/
editBotProfile: (options: { username?: string; botAvatarURL?: string | null }) => Promise<Camelize<DiscordUser>>
/** Get the applications info */
getApplicationInfo: () => Promise<Camelize<DiscordApplication>>
/** Get the bots Gateway metadata that can help during the operation of large or sharded bots. */
getGatewayBot: () => Promise<Camelize<DiscordGetGatewayBot>>
/**
* Returns the list of sticker packs available to Nitro subscribers.
*
* @param bot The bot instance to use to make the request.
* @returns A collection of {@link StickerPack} objects assorted by sticker ID.
*
* @see {@link https://discord.com/developers/docs/resources/sticker#list-nitro-sticker-packs}
*/
getNitroStickerPacks: () => Promise<Collection<bigint, Camelize<DiscordStickerPack>>>
/**
* Creates a webhook.
*
* @param rest - The rest manager to use to make the request.
* @param channelId - The ID of the channel to create the webhook in.
* @param options - The parameters for the creation of the webhook.
* @returns An instance of the created {@link DiscordWebhook}.
*
* @remarks
* Requires the `MANAGE_WEBHOOKS` permission.
*
* ⚠️ The webhook name must not contain the string 'clyde' (case-insensitive).
*
* Fires a _Webhooks Update_ gateway event.
*
* @see {@link https://discord.com/developers/docs/resources/webhook#create-webhook}
*/
createWebhook: (channelId: BigString, options: CreateWebhook) => Promise<Camelize<DiscordWebhook>>
/**
* Deletes a webhook.
*
* @param rest - The rest manager to use to make the request.
* @param webhookId - The ID of the webhook to delete.
*
* @remarks
* Requires the `MANAGE_WEBHOOKS` permission.
*
* Fires a _Webhooks Update_ gateway event.
*
* @see {@link https://discord.com/developers/docs/resources/webhook#delete-webhook}
*/
deleteWebhook: (webhookId: BigString, reason?: string) => Promise<void>
/**
* Deletes a webhook message.
*
* @param rest - The rest manager to use to make the request.
* @param webhookId - The ID of the webhook to delete the message belonging to.
* @param token - The webhook token, used to manage the webhook.
* @param messageId - The ID of the message to delete.
* @param options - The parameters for the deletion of the message.
*
* @remarks
* Fires a _Message Delete_ gateway event.
*
* @see {@link https://discord.com/developers/docs/resources/webhook#delete-webhook}
*/
deleteWebhookMessage: (webhookId: BigString, token: string, messageId: BigString, options?: DeleteWebhookMessageOptions) => Promise<void>
/**
* Deletes a webhook message using the webhook token, thereby bypassing the need for authentication + permissions.
*
* @param rest - The rest manager to use to make the request.
* @param webhookId - The ID of the webhook to delete the message belonging to.
* @param token - The webhook token, used to delete the webhook.
*
* @remarks
* Fires a _Message Delete_ gateway event.
*
* @see {@link https://discord.com/developers/docs/resources/webhook#delete-webhook-with-token}
*/
deleteWebhookWithToken: (webhookId: BigString, token: string) => Promise<void>
/**
* Edits the original webhook message.
*
* @param rest - The rest manager to use to make the request.
* @param webhookId - The ID of the webhook to edit the original message of.
* @param token - The webhook token, used to edit the message.
* @param options - The parameters for the edit of the message.
* @returns An instance of the edited {@link DiscordMessage}.
*
* @remarks
* Fires a _Message Update_ gateway event.
*
* @see {@link https://discord.com/developers/docs/resources/webhook#edit-webhook-message}
*/
editOriginalWebhookMessage: (
webhookId: BigString,
token: string,
options: InteractionCallbackData & { threadId?: BigString }
) => Promise<Camelize<DiscordMessage>>
}
export type RequestMethods = 'GET' | 'POST' | 'DELETE' | 'PATCH'
export type ApiVersions = 9 | 10
export interface CreateWebhook extends WithReason {
/** Name of the webhook (1-80 characters) */
name: string
/** Image url for the default webhook avatar */
avatar?: string | null
}
export interface CreateRequestBodyOptions {
headers?: Record<string, string>
method: RequestMethods

View File

@@ -1,5 +1,6 @@
// import type { FileContent } from './discordeno.js'
// import type {
import type { WebhookTypes } from './shared.js'
// ActivityTypes,
// AllowedMentionsTypes,
// ApplicationCommandOptionTypes,
@@ -42,8 +43,6 @@
// VerificationLevels,
// VideoQualityModes,
// VisibilityTypes,
// WebhookTypes
// } from './shared.js'
import type {
ActivityTypes,
@@ -506,64 +505,64 @@ export interface DiscordAttachment {
ephemeral?: boolean
}
// /** https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-structure */
// export type DiscordWebhook = DiscordIncomingWebhook | DiscordApplicationWebhook
/** https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-structure */
export type DiscordWebhook = DiscordIncomingWebhook | DiscordApplicationWebhook
// export interface DiscordIncomingWebhook {
// /** The type of the webhook */
// type: WebhookTypes
// /** The secure token of the webhook (returned for Incoming Webhooks) */
// token?: string
// /** The url used for executing the webhook (returned by the webhooks OAuth2 flow) */
// url?: string
export interface DiscordIncomingWebhook {
/** The type of the webhook */
type: WebhookTypes
/** The secure token of the webhook (returned for Incoming Webhooks) */
token?: string
/** The url used for executing the webhook (returned by the webhooks OAuth2 flow) */
url?: string
// /** The id of the webhook */
// id: string
// /** The guild id this webhook is for */
// guild_id?: string
// /** The channel id this webhook is for */
// channel_id: string
// /** The user this webhook was created by (not returned when getting a webhook with its token) */
// user?: DiscordUser
// /** The default name of the webhook */
// name: string | null
// /** The default user avatar hash of the webhook */
// avatar: string | null
// /** The bot/OAuth2 application that created this webhook */
// application_id: string | null
// /** The guild of the channel that this webhook is following (returned for Channel Follower Webhooks) */
// source_guild?: Partial<DiscordGuild>
// /** The channel that this webhook is following (returned for Channel Follower Webhooks) */
// source_channel?: Partial<DiscordChannel>
// }
/** The id of the webhook */
id: string
/** The guild id this webhook is for */
guild_id?: string
/** The channel id this webhook is for */
channel_id: string
/** The user this webhook was created by (not returned when getting a webhook with its token) */
user?: DiscordUser
/** The default name of the webhook */
name: string | null
/** The default user avatar hash of the webhook */
avatar: string | null
/** The bot/OAuth2 application that created this webhook */
application_id: string | null
/** The guild of the channel that this webhook is following (returned for Channel Follower Webhooks) */
source_guild?: Partial<DiscordGuild>
/** The channel that this webhook is following (returned for Channel Follower Webhooks) */
source_channel?: Partial<DiscordChannel>
}
// export interface DiscordApplicationWebhook {
// /** The type of the webhook */
// type: WebhookTypes.Application
// /** The secure token of the webhook (returned for Incoming Webhooks) */
// token?: string
// /** The url used for executing the webhook (returned by the webhooks OAuth2 flow) */
// url?: string
export interface DiscordApplicationWebhook {
/** The type of the webhook */
type: WebhookTypes.Application
/** The secure token of the webhook (returned for Incoming Webhooks) */
token?: string
/** The url used for executing the webhook (returned by the webhooks OAuth2 flow) */
url?: string
// /** The id of the webhook */
// id: string
// /** The guild id this webhook is for */
// guild_id?: string | null
// /** The channel id this webhook is for */
// channel_id?: string | null
// /** The user this webhook was created by (not returned when getting a webhook with its token) */
// user?: DiscordUser
// /** The default name of the webhook */
// name: string | null
// /** The default user avatar hash of the webhook */
// avatar: string | null
// /** The bot/OAuth2 application that created this webhook */
// application_id: string | null
// /** The guild of the channel that this webhook is following (returned for Channel Follower Webhooks), field will be absent if the webhook creator has since lost access to the guild where the followed channel resides */
// source_guild?: Partial<DiscordGuild>
// /** The channel that this webhook is following (returned for Channel Follower Webhooks), field will be absent if the webhook creator has since lost access to the guild where the followed channel resides */
// source_channel?: Partial<DiscordChannel>
// }
/** The id of the webhook */
id: string
/** The guild id this webhook is for */
guild_id?: string | null
/** The channel id this webhook is for */
channel_id?: string | null
/** The user this webhook was created by (not returned when getting a webhook with its token) */
user?: DiscordUser
/** The default name of the webhook */
name: string | null
/** The default user avatar hash of the webhook */
avatar: string | null
/** The bot/OAuth2 application that created this webhook */
application_id: string | null
/** The guild of the channel that this webhook is following (returned for Channel Follower Webhooks), field will be absent if the webhook creator has since lost access to the guild where the followed channel resides */
source_guild?: Partial<DiscordGuild>
/** The channel that this webhook is following (returned for Channel Follower Webhooks), field will be absent if the webhook creator has since lost access to the guild where the followed channel resides */
source_channel?: Partial<DiscordChannel>
}
/** https://discord.com/developers/docs/resources/guild#guild-object */
export interface DiscordGuild {
@@ -1315,23 +1314,23 @@ export interface DiscordStickerItem {
format_type: StickerFormatTypes
}
// /** https://discord.com/developers/docs/resources/sticker#sticker-pack-object-sticker-pack-structure */
// export interface DiscordStickerPack {
// /** id of the sticker pack */
// id: string
// /** the stickers in the pack */
// stickers: DiscordSticker[]
// /** name of the sticker pack */
// name: string
// /** id of the pack's SKU */
// sku_id: string
// /** id of a sticker in the pack which is shown as the pack's icon */
// cover_sticker_id?: string
// /** description of the sticker pack */
// description: string
// /** id of the sticker pack's [banner image](https://discord.com/developers/docs/reference#image-formatting) */
// banner_asset_id?: string
// }
/** https://discord.com/developers/docs/resources/sticker#sticker-pack-object-sticker-pack-structure */
export interface DiscordStickerPack {
/** id of the sticker pack */
id: string
/** the stickers in the pack */
stickers: DiscordSticker[]
/** name of the sticker pack */
name: string
/** id of the pack's SKU */
sku_id: string
/** id of a sticker in the pack which is shown as the pack's icon */
cover_sticker_id?: string
/** description of the sticker pack */
description: string
/** id of the sticker pack's [banner image](https://discord.com/developers/docs/reference#image-formatting) */
banner_asset_id?: string
}
export interface DiscordInteraction {
/** Id of the interaction */
@@ -3099,12 +3098,12 @@ export interface DiscordCreateMessage {
// description?: string
// }
// export interface DiscordCreateWebhook {
// /** Name of the webhook (1-80 characters) */
// name: string
// /** Image url for the default webhook avatar */
// avatar?: string | null
// }
export interface DiscordCreateWebhook {
/** Name of the webhook (1-80 characters) */
name: string
/** Image url for the default webhook avatar */
avatar?: string | null
}
// export interface DiscordModifyWebhook {
// /** The default name of the webhook */

View File

@@ -0,0 +1,152 @@
/**
* CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
* Encodes a given Uint8Array, ArrayBuffer or string into RFC4648 base64 representation
* @param data
*/
export function encode(data: ArrayBuffer | string): string {
const uint8 = typeof data === 'string' ? new TextEncoder().encode(data) : data instanceof Uint8Array ? data : new Uint8Array(data)
let result = ''
let i
const l = uint8.length
for (i = 2; i < l; i += 3) {
result += base64abc[uint8[i - 2] >> 2]
result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]
result += base64abc[((uint8[i - 1] & 0x0f) << 2) | (uint8[i] >> 6)]
result += base64abc[uint8[i] & 0x3f]
}
if (i === l + 1) {
// 1 octet yet to write
result += base64abc[uint8[i - 2] >> 2]
result += base64abc[(uint8[i - 2] & 0x03) << 4]
result += '=='
}
if (i === l) {
// 2 octets yet to write
result += base64abc[uint8[i - 2] >> 2]
result += base64abc[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]
result += base64abc[(uint8[i - 1] & 0x0f) << 2]
result += '='
}
return result
}
/**
* CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
* Decodes RFC4648 base64 string into an Uint8Array
* @param data
*/
export function decode(data: string): Uint8Array {
if (data.length % 4 !== 0) {
throw new Error('Unable to parse base64 string.')
}
const index = data.indexOf('=')
if (index !== -1 && index < data.length - 2) {
throw new Error('Unable to parse base64 string.')
}
const missingOctets = data.endsWith('==') ? 2 : data.endsWith('=') ? 1 : 0
const n = data.length
const result = new Uint8Array(3 * (n / 4))
let buffer
for (let i = 0, j = 0; i < n; i += 4, j += 3) {
buffer =
(getBase64Code(data.charCodeAt(i)) << 18) |
(getBase64Code(data.charCodeAt(i + 1)) << 12) |
(getBase64Code(data.charCodeAt(i + 2)) << 6) |
getBase64Code(data.charCodeAt(i + 3))
result[j] = buffer >> 16
result[j + 1] = (buffer >> 8) & 0xff
result[j + 2] = buffer & 0xff
}
return result.subarray(0, result.length - missingOctets)
}
/**
* CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
* @param charCode
*/
function getBase64Code(charCode: number): number {
if (charCode >= base64codes.length) {
throw new Error('Unable to parse base64 string.')
}
const code = base64codes[charCode]
if (code === 255) {
throw new Error('Unable to parse base64 string.')
}
return code
}
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
const base64abc = [
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'+',
'/',
]
// CREDIT: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
const base64codes = [
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255,
0, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 26,
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
]

View File

@@ -1,6 +1,8 @@
export * from './base64.js'
export * from './bucket.js'
export * from './casing.js'
export * from './Collection.js'
export * from './hash.js'
export * from './token.js'
export * from './urlToBase64.js'
export * from './utils.js'

View File

@@ -0,0 +1,9 @@
import { encode } from './base64.js'
/** Converts a url to base 64. Useful for example, uploading/creating server emojis. */
export async function urlToBase64(url: string): Promise<string> {
const buffer = await fetch(url).then(async (res) => await res.arrayBuffer())
const imageStr = encode(buffer)
const type = url.substring(url.lastIndexOf('.') + 1)
return `data:image/${type};base64,${imageStr}`
}