fix(bot,rest,types)!: attachment sending (#2917)

* 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
This commit is contained in:
ITOH
2023-03-31 20:50:53 +00:00
committed by GitHub
parent 18150174ae
commit f5abd83cc5
8 changed files with 72 additions and 65 deletions
+2 -2
View File
@@ -104,8 +104,8 @@ export interface BotInteractionCallbackData {
embeds?: Embed[]
/** Allowed mentions for the message */
allowedMentions?: AllowedMentions
/** The contents of the file being sent */
file?: FileContent | FileContent[]
/** The contents of the files being sent */
files?: FileContent[]
/** The customId you want to use for this modal response. */
customId?: string
/** The title you want to use for this modal response. */
+48 -40
View File
@@ -691,16 +691,16 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
return obj
},
createRequest(options) {
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 (!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)
if (options?.reason !== undefined) {
headers['x-audit-log-reason'] = encodeURIComponent(options?.reason)
}
let body: string | FormData | undefined
@@ -709,38 +709,36 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
// 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.attachments !== undefined) {
if (options?.files !== undefined) {
const form = new FormData()
for (let i = 0; i < options.attachments.length; ++i) {
form.append(`file${i}`, options.attachments[i].blob, options.attachments[i].name)
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))
form.append('payload_json', JSON.stringify({ ...options.body, files: undefined }))
body = form
// TODO: boundary?
// `multipart/form-data; boundary=${form.getBoundary()}`
headers['content-type'] = `multipart/form-data`
} else if (options.body !== undefined) {
// 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
headers['content-type'] = `multipart/form-data`
// 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(options.body)
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) {
if (options?.headers) {
Object.assign(headers, options.headers)
}
return {
body,
headers,
method: options.method,
method,
}
},
@@ -852,7 +850,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
async sendRequest(options) {
const url = options.url.startsWith('https://') ? options.url : `${rest.baseUrl}/v${rest.version}${options.url}`
const payload = rest.createRequest({ method: options.method, url: options.url, body: options.options?.body, ...options.options })
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)
@@ -986,7 +984,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
})
if (!result.ok) {
const err = (await result.json().catch(() => {})) as Record<string, any>
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))
@@ -999,9 +997,9 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
const payload: SendRequestOptions = {
url,
method,
options,
requestBodyOptions: options,
retryCount: 0,
retryRequest: async function (payload: SendRequestOptions) {
retryRequest: async function(payload: SendRequestOptions) {
rest.processRequest(payload)
},
resolve: (data) => {
@@ -1014,24 +1012,24 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
})
},
async get<T = Record<string, unknown>>(url: string, body?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
return camelize(await rest.makeRequest('GET', url, { body })) as Camelize<T>
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, body?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
return camelize(await rest.makeRequest('POST', url, { body })) 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, body?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
camelize(await rest.makeRequest('DELETE', url, { body }))
async delete(url: string, options?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
camelize(await rest.makeRequest('DELETE', url, options))
},
async patch<T = Record<string, unknown>>(url: string, body?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
return camelize(await rest.makeRequest('PATCH', url, { body })) as Camelize<T>
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, body?: Omit<CreateRequestBodyOptions, 'body' | 'method'>) {
return camelize(await rest.makeRequest('PUT', url, { body })) 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) {
@@ -1110,7 +1108,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async createForumThread(channelId, body) {
return await rest.post<DiscordChannel>(rest.routes.channels.forum(channelId), { body })
return await rest.post<DiscordChannel>(rest.routes.channels.forum(channelId), { body, files: body.files })
},
async createInvite(channelId, body = {}) {
@@ -1296,7 +1294,10 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async editFollowupMessage(token, messageId, body) {
return await rest.patch<DiscordMessage>(rest.routes.interactions.responses.message(rest.applicationId, 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) {
@@ -1330,7 +1331,10 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async editOriginalInteractionResponse(token, body) {
return await rest.patch<DiscordMessage>(rest.routes.interactions.responses.original(rest.applicationId, token), { body })
return await rest.patch<DiscordMessage>(rest.routes.interactions.responses.original(rest.applicationId, token), {
body,
files: body.files,
})
},
async editOriginalWebhookMessage(webhookId, token, options) {
@@ -1339,6 +1343,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
type: InteractionResponseTypes.UpdateMessage,
data: options,
},
files: options.files,
})
},
@@ -1378,7 +1383,10 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async editWebhookMessage(webhookId, token, messageId, options) {
return await rest.patch<DiscordMessage>(rest.routes.webhooks.message(webhookId, token, messageId, options), { body: options })
return await rest.patch<DiscordMessage>(rest.routes.webhooks.message(webhookId, token, messageId, options), {
body: options,
files: options.files,
})
},
async editWebhookWithToken(webhookId, token, body) {
@@ -1682,10 +1690,10 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
return await new Promise((resolve, reject) => {
rest.sendRequest({
url: rest.routes.webhooks.webhook(rest.applicationId, token),
method: 'POST',
options: { body: options },
method: 'POST',
requestBodyOptions: { body: options, files: options.files },
retryCount: 0,
retryRequest: async function (options: SendRequestOptions) {
retryRequest: async function(options: SendRequestOptions) {
// TODO: should change to reprocess queue item
await rest.sendRequest(options)
},
@@ -1703,9 +1711,9 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
rest.sendRequest({
url: rest.routes.interactions.responses.callback(interactionId, token),
method: 'POST',
options: { body: options },
requestBodyOptions: { body: options },
retryCount: 0,
retryRequest: async function (options: SendRequestOptions) {
retryRequest: async function(options: SendRequestOptions) {
// TODO: should change to reprocess queue item
await rest.sendRequest(options)
},
@@ -1718,7 +1726,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async sendMessage(channelId, body) {
return await rest.post<DiscordMessage>(rest.routes.channels.messages(channelId), { body })
return await rest.post<DiscordMessage>(rest.routes.channels.messages(channelId), { body, files: body.files })
},
async startThreadWithMessage(channelId, messageId, body) {
+4 -6
View File
@@ -159,7 +159,7 @@ export interface RestManager {
/** Reshapes and modifies the obj as needed to make it ready for discords api. */
changeToDiscordFormat: (obj: any) => any
/** Creates the request body and headers that are necessary to send a request. Will handle different types of methods and everything necessary for discord. */
createRequest: (options: CreateRequestBodyOptions) => RequestBody
createRequestBody: (method: RequestMethods, options?: CreateRequestBodyOptions) => RequestBody
/** This will create a infinite loop running in 1 seconds using tail recursion to keep rate limits clean. When a rate limit resets, this will remove it so the queue can proceed. */
processRateLimitedPaths: () => void
/** Processes the rate limit headers and determines if it needs to be rate limited and returns the bucket id if available */
@@ -2454,12 +2454,10 @@ export interface CreateWebhook {
export interface CreateRequestBodyOptions {
headers?: Record<string, string>
method: RequestMethods
body?: any
unauthorized?: boolean
url?: string
reason?: string
attachments?: FileContent[]
files?: FileContent[]
}
export interface RequestBody {
@@ -2471,7 +2469,7 @@ export interface RequestBody {
export interface SendRequestOptions {
/** The url to send the request to. */
url: string
/** The method to use when sending the request. */
/** The method to use for sending the request. */
method: RequestMethods
/** The amount of times this request has been retried. */
retryCount: number
@@ -2484,7 +2482,7 @@ export interface SendRequestOptions {
/** If this request has a bucket id which it falls under for rate limit */
bucketId?: string
/** Additional request options, used for things like overriding authorization header. */
options?: Record<string, any>
requestBodyOptions?: CreateRequestBodyOptions
}
export interface RestRateLimitedPath {
+1 -1
View File
@@ -57,7 +57,7 @@ describe('Member tests', () => {
it('Send a direct message', async () => {
// DM test only on dd unit testing bot
if (rest.applicationId.toString() !== "770381961553510451") return;
if (rest.applicationId.toString() !== '770381961553510451') return
// Itoh Alt ID
const channel = await rest.getDmChannel(750661528360845322n)
expect(channel?.id).to.exist
+2 -2
View File
@@ -21,7 +21,7 @@ after(async () => {
describe('Send a message', () => {
it('With content', async () => {
const message = await rest.sendMessage('1041029705790402611', { content: 'testing rate limit manager' })
const message = await rest.sendMessage(e2ecache.channel.id, { content: 'testing rate limit manager' })
expect(message.content).to.be.equal('testing rate limit manager')
const edited = await rest.editMessage(message.channelId, message.id, { content: 'testing rate limit manager edited' })
@@ -37,7 +37,7 @@ describe('Send a message', () => {
expect(image).to.not.be.undefined
if (!image) throw new Error('Was not able to fetch the image.')
const message = await rest.sendMessage('1041029705790402611', { file: { blob: image, name: 'gamer' } })
const message = await rest.sendMessage(e2ecache.channel.id, { files: [{ blob: image, name: 'gamer' }] })
expect(message.attachments.length).to.be.greaterThan(0)
const [attachment] = message.attachments
+5 -1
View File
@@ -11,8 +11,12 @@ export const rest = createRestManager({
})
rest.deleteQueueDelay = 10000
const guild = await rest.createGuild({ name: 'ddenotester' });
const channel = await rest.createChannel(guild.id, { name: "ddenotestchannel" });
export const e2ecache = {
guild: await rest.createGuild({ name: 'ddenotester' }),
guild,
channel,
deletedGuild: false,
communityGuildId: E2E_TEST_GUILD_ID,
}
-3
View File
@@ -1,4 +1,3 @@
import type { FileContent } from './discordeno.js'
import type {
ActivityTypes,
AllowedMentionsTypes,
@@ -2564,8 +2563,6 @@ export interface DiscordCreateForumPostWithMessage {
components?: DiscordMessageComponents[]
/** IDs of up to 3 stickers in the server to send in the message */
sticker_ids?: string[]
/** Contents of the file being sent. See {@link https://discord.com/developers/docs/reference#uploading-files Uploading Files} */
file: FileContent | FileContent[] | undefined
/** JSON-encoded body of non-file params, only for multipart/form-data requests. See {@link https://discord.com/developers/docs/reference#uploading-files Uploading Files} */
payload_json?: string
/** Attachment objects with filename and description. See {@link https://discord.com/developers/docs/reference#uploading-files Uploading Files} */
+10 -10
View File
@@ -62,8 +62,8 @@ export interface CreateMessageOptions {
/** When sending, whether to error if the referenced message doesn't exist instead of sending as a normal (non-reply) message, default true */
failIfNotExists: boolean
}
/** The contents of the file being sent */
file?: FileContent | FileContent[]
/** The contents of the files being sent */
files?: FileContent[]
/** The components you would like to have sent in this message */
components?: MessageComponents
/** IDs of up to 3 stickers in the server to send in the message */
@@ -431,8 +431,8 @@ export interface InteractionCallbackData {
embeds?: Array<Camelize<DiscordEmbed>>
/** Allowed mentions for the message */
allowedMentions?: AllowedMentions
/** The contents of the file being sent */
file?: FileContent | FileContent[]
/** The contents of the files being sent */
files?: FileContent[]
/** The customId you want to use for this modal response. */
customId?: string
/** The title you want to use for this modal response. */
@@ -673,8 +673,8 @@ export interface ExecuteWebhook {
avatarUrl?: string
/** True if this is a TTS message */
tts?: boolean
/** The contents of the file being sent */
file?: FileContent | FileContent[]
/** The contents of the files being sent */
files?: FileContent[]
/** Embedded `rich` content */
embeds?: Array<Camelize<DiscordEmbed>>
/** Allowed mentions for the message */
@@ -706,8 +706,8 @@ export interface CreateForumPostWithMessage extends WithReason {
embeds?: Array<Camelize<DiscordEmbed>>
/** Allowed mentions for the message */
allowedMentions?: AllowedMentions
/** The contents of the file being sent */
file?: FileContent | FileContent[]
/** The contents of the files being sent */
files?: FileContent[]
/** The components you would like to have sent in this message */
components?: MessageComponents
}
@@ -890,8 +890,8 @@ export interface EditMessage {
embeds?: Array<Camelize<DiscordEmbed>> | null
/** Edit the flags of the message (only `SUPPRESS_EMBEDS` can currently be set/unset) */
flags?: 4 | null
/** The contents of the file being sent/edited */
file?: FileContent | FileContent[] | null
/** The contents of the files being sent/edited */
files?: FileContent[] | null
/** Allowed mentions for the message */
allowedMentions?: AllowedMentions
/** When specified (adding new attachments), attachments which are not provided in this list will be removed. */