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
15 changes: 11 additions & 4 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 WebhookPayloadEdit = Pick<WebhookPayload, "attachments" | "content" | "embed" | "embeds" | "file" | "allowedMentions" | "components">;
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,11 @@ declare namespace Eris {
name?: string;
tags?: string;
}
interface PartialAttachment {
description?: string;
filename?: string;
id: string | number;
}
interface SelectMenu {
custom_id: string;
disabled?: boolean;
Expand Down Expand Up @@ -1426,6 +1432,7 @@ declare namespace Eris {
}
interface WebhookPayload {
allowedMentions?: AllowedMentions;
attachments?: PartialAttachment[];
auth?: boolean;
avatarURL?: string;
components?: ActionRow[];
Expand Down Expand Up @@ -2354,7 +2361,7 @@ declare namespace Eris {
webhookID: string,
token: string,
messageID: string,
options: MessageWebhookContent
options: WebhookPayloadEdit
): Promise<Message<GuildTextableChannel>>;
emit<K extends keyof ClientEvents>(event: K, ...args: ClientEvents[K]): boolean;
emit(event: string, ...args: any[]): boolean;
Expand Down Expand Up @@ -3172,7 +3179,7 @@ declare namespace Eris {
delete(reason?: string): Promise<void>;
deleteWebhook(token: string): Promise<void>;
edit(content: MessageContent): Promise<Message<T>>;
editWebhook(token: string, options: MessageWebhookContent): Promise<Message<T>>;
editWebhook(token: string, options: WebhookPayloadEdit): Promise<Message<T>>;
getReaction(reaction: string, options?: GetMessageReactionOptions): Promise<User[]>;
/** @deprecated */
getReaction(reaction: string, limit?: number, before?: string, after?: string): Promise<User[]>;
Expand Down
68 changes: 49 additions & 19 deletions lib/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,10 @@ class Client extends EventEmitter {
* @arg {Boolean} [options.data.allowedMentions.repliedUser] Whether or not to mention the author of the message being replied to.
* @arg {Boolean | Array<String>} [options.data.allowedMentions.roles] Whether or not to allow all role mentions, or an array of specific role mentions to allow.
* @arg {Boolean | Array<String>} [options.data.allowedMentions.users] Whether or not to allow all user mentions, or an array of specific user mentions to allow.
* @arg {Array<Object>} [options.data.attachments] An array of attachment objects that will be appended to the message, including new files. Only the provided files will be appended
* @arg {String} [options.data.attachments[].description] The description of the file
* @arg {String} [options.data.attachments[].filename] The name of the file. This is not required if you are attaching a new file
* @arg {Number | String} options.data.attachments[].id The ID of the file. If you are attaching a new file, this would be the index of the file
* @arg {Array<Object>} [options.data.components] An array of component objects
* @arg {String} [options.data.components[].custom_id] The ID of the component (type 2 style 0-4 and type 3 only)
* @arg {Boolean} [options.data.components[].disabled] Whether the component is disabled (type 2 and 3 only)
Expand Down Expand Up @@ -767,6 +771,9 @@ class Client extends EventEmitter {
}
options.data.embeds.push(options.data.embed);
}
if(file) {
options = { payload_json: options }
}
return this.requestHandler.request("POST", Endpoints.INTERACTION_RESPOND(interactionID, interactionToken), true, options, file, "/interactions/:id/:token/callback");
}

Expand All @@ -780,6 +787,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] An array of attachment objects with the filename and description
* @arg {String} [content.attachments[].description] The description of the file
* @arg {String} [content.attachments[].filename] The name of the file
* @arg {Number} content.attachments[].id The index of the 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 @@ -851,6 +862,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 @@ -1674,6 +1688,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] An array of attachment objects that will be appended to the message, including new files. Only the provided files will be appended
* @arg {String} [content.attachments[].description] The description of the file
* @arg {String} [content.attachments[].filename] The name of the file. This is not required if you are attaching a new file
* @arg {Number | String} content.attachments[].id The ID of the file. If you are attaching a new file, this would be the index of the 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 @@ -1718,7 +1736,9 @@ class Client extends EventEmitter {
content.allowed_mentions = this._formatAllowedMentions(content.allowedMentions);
}
}
return this.requestHandler.request("PATCH", Endpoints.CHANNEL_MESSAGE(channelID, messageID), true, content, content.file).then((message) => new Message(message, this));
const file = content.file;
delete content.file
return this.requestHandler.request("PATCH", Endpoints.CHANNEL_MESSAGE(channelID, messageID), true, file ? {payload_json: content} : content, file).then((message) => new Message(message, this));
}

/**
Expand Down Expand Up @@ -1965,13 +1985,17 @@ 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 {Array<Object>} [options.attachments] An array of attachment objects that will be appended to the message, including new files. Only the provided files will be appended
* @arg {String} [options.attachments[].description] The description of the file
* @arg {String} [options.attachments[].filename] The name of the file. This is not required if you are attaching a new file
* @arg {Number | String} options.attachments[].id The ID of the file. If you are attaching a new file, this would be the index of the 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 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 @@ -1994,13 +2018,17 @@ class Client extends EventEmitter {
if(options.allowedMentions) {
options.allowed_mentions = this._formatAllowedMentions(options.allowedMentions);
}
const file = options.file;
delete options.file;

if(options.embed) {
if(!options.embeds) {
options.embeds = [];
}
options.embeds.push(options.embed);
}
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 @@ -2056,15 +2084,19 @@ 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 {Array<Object>} [options.attachments] An array of attachment objects that will be appended to the message, including new files. Only the provided files will be appended
* @arg {String} [options.attachments[].description] The description of the file
* @arg {String} [options.attachments[].filename] The name of the file. This is not required if you are attaching a new file
* @arg {Number | String} options.attachments[].id The ID of the file. If you are attaching a new file, this would be the index of the 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 Down Expand Up @@ -2096,22 +2128,20 @@ class Client extends EventEmitter {
if(options.threadID) {
qs += "&thread_id=" + options.threadID;
}
if(options.allowedMentions) {
options.allowed_mentions = this._formatAllowedMentions(options.allowedMentions);
}
const file = options.file;
delete options.file;

if(options.embed) {
if(!options.embeds) {
options.embeds = [];
if(!data.embeds) {
data.embeds = [];
}
bsian03 marked this conversation as resolved.
Show resolved Hide resolved
options.embeds.push(options.embed);
data.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);
options.avatar_url = options.avatarURL
return this.requestHandler.request("POST", Endpoints.WEBHOOK_TOKEN(webhookID, token) + (qs ? "?" + qs : ""), !!options.auth, options.file ? {payload_json: options} : options, file).then((response) => options.wait ? new Message(response, this) : undefined);
}

/**
Expand Down
37 changes: 17 additions & 20 deletions lib/rest/RequestHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,36 +108,33 @@ 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);
}
if(method === "POST" && url.endsWith("/stickers")) {
data.attach("file", file.file, file.name);
} else {
data.attach("files[0]", file.file, file.name);
}
data = data.finish();
} else {
throw new Error("Invalid file object");
}

if(body) {
for(const key in body) {
data.attach(key, body[key]);
}
}

data = data.finish();
} else if(body) {
if(method === "GET" || method === "DELETE") {
let qs = "";
Expand Down
Loading