diff --git a/esm.mjs b/esm.mjs index dcbdbdd5d..8aac8db87 100644 --- a/esm.mjs +++ b/esm.mjs @@ -6,6 +6,7 @@ export default function (token, options) { export const { ApplicationCommand, + Attachment, AutocompleteInteraction, Base, Bucket, diff --git a/index.d.ts b/index.d.ts index b53574110..62d2f4220 100644 --- a/index.d.ts +++ b/index.d.ts @@ -21,6 +21,7 @@ declare namespace Eris { // Application Commands type ApplicationCommandOptions = ApplicationCommandOptionsSubCommand | ApplicationCommandOptionsSubCommandGroup | ApplicationCommandOptionsWithValue; + type ApplicationCommandOptionsAttachment = ApplicationCommandOption; type ApplicationCommandOptionsBoolean = ApplicationCommandOption; type ApplicationCommandOptionsChannel = ApplicationCommandOption; type ApplicationCommandOptionsInteger = ApplicationCommandOptionsIntegerWithAutocomplete | ApplicationCommandOptionsIntegerWithoutAutocomplete | ApplicationCommandOptionsIntegerWithMinMax; @@ -37,7 +38,7 @@ declare namespace Eris { type ApplicationCommandOptionsStringWithAutocomplete = Omit, "choices"> & AutocompleteEnabled; type ApplicationCommandOptionsStringWithoutAutocomplete = Omit, "autocomplete"> & AutocompleteDisabled; type ApplicationCommandOptionsUser = ApplicationCommandOption; - type ApplicationCommandOptionsWithValue = ApplicationCommandOptionsString | ApplicationCommandOptionsInteger | ApplicationCommandOptionsBoolean | ApplicationCommandOptionsUser | ApplicationCommandOptionsChannel | ApplicationCommandOptionsRole | ApplicationCommandOptionsMentionable | ApplicationCommandOptionsNumber; + type ApplicationCommandOptionsWithValue = ApplicationCommandOptionsString | ApplicationCommandOptionsInteger | ApplicationCommandOptionsBoolean | ApplicationCommandOptionsUser | ApplicationCommandOptionsChannel | ApplicationCommandOptionsRole | ApplicationCommandOptionsMentionable | ApplicationCommandOptionsNumber | ApplicationCommandOptionsAttachment; type ApplicationCommandPermissionTypes = Constants["ApplicationCommandPermissionTypes"][keyof Constants["ApplicationCommandPermissionTypes"]]; type ApplicationCommandTypes = Constants["ApplicationCommandTypes"][keyof Constants["ApplicationCommandTypes"]]; type ModalSubmitInteractionDataComponent = ModalSubmitInteractionDataTextInputComponent; @@ -136,6 +137,7 @@ declare namespace Eris { type InteractionContent = Pick; type InteractionContentEdit = Pick; type InteractionDataOptions = InteractionDataOptionsSubCommand | InteractionDataOptionsSubCommandGroup | InteractionDataOptionsWithValue; + type InteractionDataOptionsAttachment = InteractionDataOptionWithValue; type InteractionDataOptionsBoolean = InteractionDataOptionWithValue; type InteractionDataOptionsChannel = InteractionDataOptionWithValue; type InteractionDataOptionsInteger = InteractionDataOptionWithValue; @@ -144,7 +146,7 @@ declare namespace Eris { type InteractionDataOptionsRole = InteractionDataOptionWithValue; type InteractionDataOptionsString = InteractionDataOptionWithValue; type InteractionDataOptionsUser = InteractionDataOptionWithValue; - type InteractionDataOptionsWithValue = InteractionDataOptionsString | InteractionDataOptionsInteger | InteractionDataOptionsBoolean | InteractionDataOptionsUser | InteractionDataOptionsChannel | InteractionDataOptionsRole | InteractionDataOptionsMentionable | InteractionDataOptionsNumber; + type InteractionDataOptionsWithValue = InteractionDataOptionsString | InteractionDataOptionsInteger | InteractionDataOptionsBoolean | InteractionDataOptionsUser | InteractionDataOptionsChannel | InteractionDataOptionsRole | InteractionDataOptionsMentionable | InteractionDataOptionsNumber | InteractionDataOptionsAttachment; type InteractionResponseTypes = Constants["InteractionResponseTypes"][keyof Constants["InteractionResponseTypes"]]; type InteractionTypes = Constants["InteractionTypes"][keyof Constants["InteractionTypes"]]; type LocaleStrings = Constants["Locales"][keyof Constants["Locales"]]; @@ -1515,20 +1517,6 @@ declare namespace Eris { roles?: boolean | string[]; users?: boolean | string[]; } - interface Attachment extends PartialAttachment { - content_type?: string; - duration_secs?: number; - ephemeral?: boolean; - filename: string; - flags?: number; - height?: number; - id: string; - proxy_url: string; - size: number; - url: string; - waveform?: string; - width?: number; - } interface ButtonBase { disabled?: boolean; emoji?: Partial; @@ -2042,6 +2030,24 @@ declare namespace Eris { edit(options: ApplicationCommandEditOptions): Promise>; } + export class Attachment extends Base { + contentType?: string; + description?: string; + durationSeconds?: number; + ephemeral?: boolean; + filename: string; + flags?: number; + height?: number; + id: string; + proxyURL: string; + size: number; + title?: string; + url: string; + waveform?: string; + width?: number; + constructor(data: BaseData); + } + class Base implements SimpleJSON { createdAt: number; id: string; @@ -3072,7 +3078,7 @@ declare namespace Eris { activity?: MessageActivity; application?: MessageApplication; applicationID?: string; - attachments: Attachment[]; + attachments: Collection; author: User; channel: T; channelMentions: string[]; diff --git a/index.js b/index.js index 53b07eefc..51bb9d235 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,7 @@ function Eris(token, options) { } Eris.ApplicationCommand = require("./lib/structures/ApplicationCommand"); +Eris.Attachment = require("./lib/structures/Attachment"); Eris.AutocompleteInteraction = require("./lib/structures/AutocompleteInteraction"); Eris.Base = require("./lib/structures/Base"); Eris.Bucket = require("./lib/util/Bucket"); diff --git a/lib/Constants.d.ts b/lib/Constants.d.ts index f6fe83994..ca2356e9f 100644 --- a/lib/Constants.d.ts +++ b/lib/Constants.d.ts @@ -31,6 +31,7 @@ export default interface Constants { ROLE: 8; MENTIONABLE: 9; NUMBER: 10; + ATTACHMENT: 11; }; ApplicationCommandPermissionTypes: { ROLE: 1; diff --git a/lib/structures/Attachment.js b/lib/structures/Attachment.js new file mode 100644 index 000000000..a5b8b297a --- /dev/null +++ b/lib/structures/Attachment.js @@ -0,0 +1,80 @@ +"use strict"; + +const Base = require("./Base"); + +/** + * Represents an attachment + * @prop {String?} contentType The attachment's media type + * @prop {String?} description The description for the file (max 1024 characters) + * @prop {Number?} durationSeconds The duration of the audio file (currently for voice messages) + * @prop {Boolean?} ephemeral Whether the attachment is ephemeral + * @prop {String} filename The name of file attached + * @prop {Number?} flags The attachment flags combined as a bitfield + * @prop {Number?} height The height of file (if image) + * @prop {String} id The attachment ID + * @prop {String} proxyURL The proxy URL of the attachment + * @prop {Number} size The size of the file in bytes + * @prop {String?} title The title of the file + * @prop {String} url The source URL of the file + * @prop {String?} waveform The base64 encoded bytearray representing a sampled waveform (currently for voice messages) + * @prop {Number?} width The width of file (if image) + */ +class Attachment extends Base { + constructor(data) { + super(data.id); + + this.filename = data.filename; + this.proxyURL = data.proxy_url; + this.size = data.size; + this.url = data.url; + + if (data.content_type !== undefined) { + this.contentType = data.content_type; + } + if (data.description !== undefined) { + this.description = data.description; + } + if (data.duration_secs !== undefined) { + this.durationSeconds = data.duration_secs; + } + if (data.ephemeral !== undefined) { + this.ephemeral = data.ephemeral; + } + if (data.flags !== undefined) { + this.flags = data.flags; + } + if (data.height !== undefined) { + this.height = data.height; + } + if (data.title !== undefined) { + this.title = data.title; + } + if (data.waveform !== undefined) { + this.waveform = data.waveform; + } + if (data.width !== undefined) { + this.width = data.width; + } + } + + toJSON(props = []) { + return super.toJSON([ + "contentType", + "description", + "durationSeconds", + "ephemeral", + "filename", + "flags", + "height", + "proxyURL", + "size", + "title", + "url", + "waveform", + "width", + ...props, + ]); + } +} + +module.exports = Attachment; diff --git a/lib/structures/AutocompleteInteraction.js b/lib/structures/AutocompleteInteraction.js index ae51feb8e..8e2a16527 100644 --- a/lib/structures/AutocompleteInteraction.js +++ b/lib/structures/AutocompleteInteraction.js @@ -16,7 +16,7 @@ const { InteractionResponseTypes } = require("../Constants"); * @prop {Boolean?} data.options[].focused Whether or not the option is focused * @prop {String} data.options[].name The name of the Application Command option * @prop {Array?} data.options[].options The run Application Command options (Mutually exclusive with value) - * @prop {Number} data.options[].type Command option type, 1-10 + * @prop {Number} data.options[].type Command option type, 1-11 * @prop {(String | Number | Boolean)?} data.options[].value The value of the run Application Command (Mutually exclusive with options) * @prop {String?} data.target_id The ID the of user or message targetted by a context menu command * @prop {Number} data.type The [command type](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types) diff --git a/lib/structures/CommandInteraction.js b/lib/structures/CommandInteraction.js index e836588ae..610b6d1e8 100644 --- a/lib/structures/CommandInteraction.js +++ b/lib/structures/CommandInteraction.js @@ -6,6 +6,7 @@ const User = require("./User"); const Role = require("./Role"); const Channel = require("./Channel"); const Message = require("./Message"); +const Attachment = require("./Attachment"); const Collection = require("../util/Collection"); const Permission = require("./Permission"); @@ -22,7 +23,7 @@ const { InteractionResponseTypes } = require("../Constants"); * @prop {Array?} data.options The run Application Command options * @prop {String} data.options[].name The name of the Application Command option * @prop {Array?} data.options[].options The run Application Command options (Mutually exclusive with value) - * @prop {Number} data.options[].type Command option type, 1-10 + * @prop {Number} data.options[].type Command option type, 1-11 * @prop {(String | Number | Boolean)?} data.options[].value The value of the run Application Command (Mutually exclusive with options) * @prop {String?} data.target_id The ID the of user or message targetted by a context menu command * @prop {Number} data.type The [command type](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types) @@ -48,50 +49,58 @@ class CommandInteraction extends Interaction { if (data.data.resolved !== undefined) { // Users if (data.data.resolved.users !== undefined) { - const usermap = new Collection(User); + const userMap = new Collection(User); Object.entries(data.data.resolved.users).forEach(([id, user]) => { - usermap.set(id, this._client.users.update(user, client)); + userMap.set(id, this._client.users.update(user, client)); }); - this.data.resolved.users = usermap; + this.data.resolved.users = userMap; } // Members if (data.data.resolved.members !== undefined) { - const membermap = new Collection(Member); + const memberMap = new Collection(Member); Object.entries(data.data.resolved.members).forEach(([id, member]) => { member.id = id; member.user = { id }; if (this.channel.guild) { - membermap.set(id, this.channel.guild.members.update(member, this.channel.guild)); + memberMap.set(id, this.channel.guild.members.update(member, this.channel.guild)); } else { const guild = this._client.guilds.get(data.guild_id); - membermap.set(id, guild.members.update(member, guild)); + memberMap.set(id, guild.members.update(member, guild)); } }); - this.data.resolved.members = membermap; + this.data.resolved.members = memberMap; } // Roles if (data.data.resolved.roles !== undefined) { - const rolemap = new Collection(Role); + const roleMap = new Collection(Role); Object.entries(data.data.resolved.roles).forEach(([id, role]) => { - rolemap.set(id, new Role(role, this._client)); + roleMap.set(id, new Role(role, this._client)); }); - this.data.resolved.roles = rolemap; + this.data.resolved.roles = roleMap; } // Channels if (data.data.resolved.channels !== undefined) { - const channelmap = new Collection(Channel); + const channelMap = new Collection(Channel); Object.entries(data.data.resolved.channels).forEach(([id, channel]) => { - channelmap.set(id, new Channel(channel, this._client)); + channelMap.set(id, new Channel(channel, this._client)); }); - this.data.resolved.channels = channelmap; + this.data.resolved.channels = channelMap; } // Messages if (data.data.resolved.messages !== undefined) { - const messagemap = new Collection(Message); + const messageMap = new Collection(Message); Object.entries(data.data.resolved.messages).forEach(([id, message]) => { - messagemap.set(id, new Message(message, this._client)); + messageMap.set(id, new Message(message, this._client)); }); - this.data.resolved.messages = messagemap; + this.data.resolved.messages = messageMap; + } + // Attachments + if (data.data.resolved.attachments !== undefined) { + const attachmentMap = new Collection(Attachment); + Object.entries(data.data.resolved.attachments).forEach(([id, attachment]) => { + attachmentMap.set(id, new Attachment(attachment)); + }); + this.data.resolved.attachments = attachmentMap; } } diff --git a/lib/structures/Message.js b/lib/structures/Message.js index e8bf3e303..a073d16dc 100644 --- a/lib/structures/Message.js +++ b/lib/structures/Message.js @@ -4,6 +4,8 @@ const Base = require("./Base"); const Endpoints = require("../rest/Endpoints"); const { MessageFlags } = require("../Constants"); const User = require("./User"); +const Attachment = require("./Attachment"); +const Collection = require("../util/Collection"); /** * Represents a message @@ -61,6 +63,7 @@ class Message extends Base { super(data.id); this._client = client; this.type = data.type || 0; + this.attachments = new Collection(Attachment); this.timestamp = Date.parse(data.timestamp); this.channel = this._client.getChannel(data.channel_id) || { id: data.channel_id, @@ -166,6 +169,12 @@ class Message extends Base { this.member = null; } + if (data.attachments) { + for (const attachment of data.attachments) { + this.attachments.add(attachment, this); + } + } + this.update(data, client); } @@ -194,14 +203,21 @@ class Message extends Base { if (data.pinned !== undefined) { this.pinned = !!data.pinned; } - if (data.edited_timestamp != undefined) { + if (data.edited_timestamp !== undefined) { this.editedTimestamp = Date.parse(data.edited_timestamp); } if (data.tts !== undefined) { this.tts = data.tts; } - if (data.attachments !== undefined) { - this.attachments = data.attachments; + if (data.attachments) { + for (const id of this.attachments.keys()) { + if (!data.attachments.some((attachment) => attachment.id === id)) { + this.attachments.delete(id); + } + } + for (const attachment of data.attachments) { + this.attachments.update(attachment, this); + } } if (data.embeds !== undefined) { this.embeds = data.embeds;