Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework file attachments #1285

Merged
merged 15 commits into from
Jan 5, 2024
Merged
12 changes: 10 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ declare namespace Eris {
type StageInstancePrivacyLevel = Constants["StageInstancePrivacyLevel"][keyof Constants["StageInstancePrivacyLevel"]];

// Webhook
type MessageWebhookContent = Pick<WebhookPayload, "content" | "embeds" | "file" | "allowedMentions" | "components">;
type MessageWebhookContent = Pick<WebhookPayload, "attachments" | "content" | "embeds" | "file" | "allowedMentions" | "components">;
DonovanDMC marked this conversation as resolved.
Show resolved Hide resolved
type WebhookTypes = Constants["WebhookTypes"][keyof Constants["WebhookTypes"]];

// INTERFACES
Expand Down Expand Up @@ -1108,6 +1108,7 @@ declare namespace Eris {
}
interface AdvancedMessageContent {
allowedMentions?: AllowedMentions;
attachments?: PartialAttachment[];
components?: ActionRow[];
content?: string;
embed?: EmbedOptions;
Expand All @@ -1128,7 +1129,7 @@ declare namespace Eris {
roles?: boolean | string[];
users?: boolean | string[];
}
interface Attachment {
interface Attachment extends PartialAttachment {
content_type?: string;
ephemeral?: boolean;
filename: string;
Expand All @@ -1153,6 +1154,12 @@ declare namespace Eris {
name?: string;
tags?: string;
}
interface PartialAttachment {
description?: string;
filename?: string;
/** When creating an attachment, the ID is the index number of the file you wish to add relevant attributes to. When editing the attachment, this is the normal attachment ID */
id: string | number;
}
interface SelectMenu {
custom_id: string;
disabled?: boolean;
Expand Down Expand Up @@ -1426,6 +1433,7 @@ declare namespace Eris {
}
interface WebhookPayload {
allowedMentions?: AllowedMentions;
attachments?: PartialAttachment[];
auth?: boolean;
avatarURL?: string;
components?: ActionRow[];
Expand Down
64 changes: 44 additions & 20 deletions lib/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,10 @@ class Client extends EventEmitter {
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Boolean} [content.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Array<Object>} [content.attachments] Attachment objects to add the filename and description
* @arg {String} [content.attachments[].description] The file description
* @arg {String} [content.attachments[].filename] The file's name. You shouldn't need to use this as you provide the filename when attaching the file
* @arg {Number} content.attachments[].id The index ID of the attaching file
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
Expand Down Expand Up @@ -846,6 +850,9 @@ class Client extends EventEmitter {
this.emit("warn", "[DEPRECATED] content.messageReferenceID is deprecated. Use content.messageReference instead");
content.message_reference = {message_id: content.messageReferenceID};
}
if(file) {
content = {payload_json: content};
}
bsian03 marked this conversation as resolved.
Show resolved Hide resolved
}
return this.requestHandler.request("POST", Endpoints.CHANNEL_MESSAGES(channelID), true, content, file).then((message) => new Message(message, this));
}
Expand Down Expand Up @@ -1667,6 +1674,10 @@ class Client extends EventEmitter {
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.attachments] Attachment objects to retain and/or edit the name and/or description for currently attached files and add the filename and description for new files
* @arg {String} [content.attachments[].description] The file description
* @arg {String} [content.attachments[].filename] The file's name. This is not needed if you are attaching a new file
* @arg {Number} content.attachments[].id The file's ID. If you are attaching a new file, this would be the index ID of the attaching file
DonovanDMC marked this conversation as resolved.
Show resolved Hide resolved
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
Expand Down Expand Up @@ -1958,13 +1969,18 @@ class Client extends EventEmitter {
* @arg {Boolean} [options.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [options.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [options.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Boolean} [options.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Array<Object>} [options.attachments] Attachment objects to retain and/or edit the name and/or description for currently attached files and add the filename and description for new files
* @arg {String} [options.attachments[].description] The file description
* @arg {String} [options.attachments[].filename] The file's name. This is not needed if you are attaching a new file
* @arg {Number} options.attachments[].id The file's ID. If you are attaching a new file, this would be the index ID of the attaching file
DonovanDMC marked this conversation as resolved.
Show resolved Hide resolved
* @arg {Array<Object>} [options.components] An array of component objects
* @arg {String} [options.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [options.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [options.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [options.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Number} [options.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [options.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [options.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [options.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [options.components[].options[].description] The description for this option
Expand All @@ -1987,13 +2003,14 @@ class Client extends EventEmitter {
if(options.allowedMentions) {
options.allowed_mentions = this._formatAllowedMentions(options.allowedMentions);
}
if(options.embed) {
if(!options.embeds) {
options.embeds = [];
}
options.embeds.push(options.embed);
const file = options.file;
delete options.file;

if(options.embed && !options.embeds) {
options.embeds = [options.embed];
bsian03 marked this conversation as resolved.
Show resolved Hide resolved
}
bsian03 marked this conversation as resolved.
Show resolved Hide resolved
return this.requestHandler.request("PATCH", Endpoints.WEBHOOK_MESSAGE(webhookID, token, messageID), false, options, options.file).then((response) => new Message(response, this));

return this.requestHandler.request("PATCH", Endpoints.WEBHOOK_MESSAGE(webhookID, token, messageID), false, file ? {payload_json: options} : options, file).then((response) => new Message(response, this));
bsian03 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -2049,15 +2066,20 @@ class Client extends EventEmitter {
* @arg {Boolean} [options.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [options.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [options.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Boolean} [options.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Array<Object>} [options.attachments] Attachment objects to add the filename and description
* @arg {String} [options.attachments[].description] The file description
* @arg {String} [options.attachments[].filename] The file's name. You shouldn't need to use this as you provide the filename when attaching the file
* @arg {Number} options.attachments[].id The index ID of the attaching file
* @arg {Boolean} [options.auth=false] Whether or not to authenticate with the bot token.
* @arg {String} [options.avatarURL] A URL for a custom avatar, defaults to webhook default avatar if not specified
* @arg {Array<Object>} [options.components] An array of component objects
* @arg {String} [options.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [options.components[].disabled] Whether the component is disabled (type 2 and 3 only)
* @arg {Object} [options.components[].emoji] The emoji to be displayed in the component (type 2)
* @arg {String} [options.components[].label] The label to be displayed in the component (type 2)
* @arg {Number} [content.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [content.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Number} [options.components[].max_values] The maximum number of items that can be chosen (1-25, default 1)
* @arg {Number} [options.components[].min_values] The minimum number of items that must be chosen (0-25, default 1)
* @arg {Array<Object>} [options.components[].options] The options for this component (type 3 only)
* @arg {Boolean} [options.components[].options[].default] Whether this option should be the default value selected
* @arg {String} [options.components[].options[].description] The description for this option
Expand All @@ -2082,6 +2104,17 @@ class Client extends EventEmitter {
* @returns {Promise<Message?>}
*/
executeWebhook(webhookID, token, options) {
const data = {
content: options.content,
embeds: options.embeds,
username: options.username,
avatar_url: options.avatarURL,
tts: options.tts,
flags: options.flags,
allowed_mentions: this._formatAllowedMentions(options.allowedMentions),
components: options.components
};

bsian03 marked this conversation as resolved.
Show resolved Hide resolved
let qs = "";
if(options.wait) {
qs += "&wait=true";
Expand All @@ -2095,16 +2128,7 @@ class Client extends EventEmitter {
}
bsian03 marked this conversation as resolved.
Show resolved Hide resolved
options.embeds.push(options.embed);
}
return this.requestHandler.request("POST", Endpoints.WEBHOOK_TOKEN(webhookID, token) + (qs ? "?" + qs : ""), !!options.auth, {
content: options.content,
embeds: options.embeds,
username: options.username,
avatar_url: options.avatarURL,
tts: options.tts,
flags: options.flags,
allowed_mentions: this._formatAllowedMentions(options.allowedMentions),
components: options.components
}, options.file).then((response) => options.wait ? new Message(response, this) : undefined);
return this.requestHandler.request("POST", Endpoints.WEBHOOK_TOKEN(webhookID, token) + (qs ? "?" + qs : ""), !!options.auth, options.file ? {payload_json: data} : data, options.file).then((response) => options.wait ? new Message(response, this) : undefined);
bsian03 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
32 changes: 11 additions & 21 deletions lib/rest/RequestHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,36 +108,26 @@ class RequestHandler {
}
}
if(file) {
data = new MultipartData();
headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
if(Array.isArray(file)) {
data = new MultipartData();
headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
file.forEach(function(f) {
file.forEach(function(f, i) {
if(!f.file) {
return;
}
data.attach(f.name, f.file, f.name);
data.attach(`files[${i}]`, f.file, f.name);
});
if(body) {
data.attach("payload_json", body);
}
data = data.finish();
} else if(file.file) {
data = new MultipartData();
headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
data.attach("file", file.file, file.name);
if(body) {
if(method === "POST" && url.endsWith("/stickers")) {
for(const key in body) {
data.attach(key, body[key]);
}
} else {
data.attach("payload_json", body);
}
}
data = data.finish();
data.attach("files[0]", file.file, file.name);
} else {
throw new Error("Invalid file object");
}
if(body) {
for(const key in body) {
data.attach(key, body[key]);
}
}
data = data.finish();
DonovanDMC marked this conversation as resolved.
Show resolved Hide resolved
} else if(body) {
if(method === "GET" || method === "DELETE") {
let qs = "";
Expand Down
9 changes: 9 additions & 0 deletions lib/structures/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,10 @@ class Message extends Base {
* @arg {Boolean} [content.allowedMentions.everyone] Whether or not to allow @everyone/@here.
* @arg {Boolean | Array<String>} [content.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [content.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [content.attachments] Attachment objects to retain and/or edit the name and/or description for currently attached files and add the filename and description for new files
* @arg {String} [content.attachments[].description] The file description
* @arg {String} [content.attachments[].filename] The file's name. This is not needed if you are attaching a new file
* @arg {Number} content.attachments[].id The file's ID. If you are attaching a new file, this would be the index ID of the attaching file
* @arg {Array<Object>} [content.components] An array of component objects
* @arg {String} [content.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [content.components[].disabled] Whether the component is disabled (type 2 and 3 only)
Expand Down Expand Up @@ -491,6 +495,11 @@ class Message extends Base {
* @arg {Boolean} [options.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [options.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [options.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Boolean} [options.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Array<Object>} [options.attachments] Attachment objects to retain and/or edit the name and/or description for currently attached files and add the filename and description for new files
* @arg {String} [options.attachments[].description] The file description
* @arg {String} [options.attachments[].filename] The file's name. This is not needed if you are attaching a new file
* @arg {Number} options.attachments[].id The file's ID. If you are attaching a new file, this would be the index ID of the attaching file
* @arg {Array<Object>} [options.components] An array of component objects
* @arg {String} [options.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [options.components[].disabled] Whether the component is disabled (type 2 only)
Expand Down
Loading