mirror of
https://github.com/discordeno/discordeno.git
synced 2026-09-17 08:47:22 +00:00
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:
@@ -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. */
|
||||
|
||||
@@ -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) {
|
||||
@@ -1683,9 +1691,9 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
|
||||
rest.sendRequest({
|
||||
url: rest.routes.webhooks.webhook(rest.applicationId, token),
|
||||
method: 'POST',
|
||||
options: { body: options },
|
||||
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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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} */
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user