refactor(attachment): don't return attachment builders from API (#7852)

Co-authored-by: Almeida <almeidx@pm.me>
Co-authored-by: A. Román <kyradiscord@gmail.com>
Co-authored-by: SpaceEEC <spaceeec@yahoo.com>
This commit is contained in:
Suneet Tipirneni
2022-06-04 16:33:13 -04:00
committed by GitHub
parent 546d48655f
commit dfadcbc2fd
11 changed files with 166 additions and 89 deletions

View File

@@ -123,6 +123,7 @@ exports.InviteStageInstance = require('./structures/InviteStageInstance');
exports.InviteGuild = require('./structures/InviteGuild'); exports.InviteGuild = require('./structures/InviteGuild');
exports.Message = require('./structures/Message').Message; exports.Message = require('./structures/Message').Message;
exports.Attachment = require('./structures/Attachment'); exports.Attachment = require('./structures/Attachment');
exports.AttachmentBuilder = require('./structures/AttachmentBuilder');
exports.ModalBuilder = require('./structures/ModalBuilder'); exports.ModalBuilder = require('./structures/ModalBuilder');
exports.MessageCollector = require('./structures/MessageCollector'); exports.MessageCollector = require('./structures/MessageCollector');
exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction'); exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction');

View File

@@ -41,7 +41,7 @@ class GuildStickerManager extends CachedManager {
/** /**
* Creates a new custom sticker in the guild. * Creates a new custom sticker in the guild.
* @param {BufferResolvable|Stream|FileOptions|Attachment} file The file for the sticker * @param {BufferResolvable|Stream|JSONEncodable<AttachmentPayload>} file The file for the sticker
* @param {string} name The name for the sticker * @param {string} name The name for the sticker
* @param {string} tags The Discord name of a unicode emoji representing the sticker's expression * @param {string} tags The Discord name of a unicode emoji representing the sticker's expression
* @param {GuildStickerCreateOptions} [options] Options * @param {GuildStickerCreateOptions} [options] Options

View File

@@ -3,74 +3,30 @@
const Util = require('../util/Util'); const Util = require('../util/Util');
/** /**
* Represents an attachment. * @typedef {Object} AttachmentPayload
* @property {?string} name The name of the attachment
* @property {Stream|BufferResolvable} attachment The attachment in this payload
* @property {?string} description The description of the attachment
*/
/**
* Represents an attachment
*/ */
class Attachment { class Attachment {
/** /**
* @param {BufferResolvable|Stream} attachment The file * @param {APIAttachment} data Attachment data
* @param {string} [name=null] The name of the file, if any * @private
* @param {APIAttachment} [data] Extra data
*/ */
constructor(attachment, name = null, data) { constructor({ url, filename, ...data }) {
this.attachment = attachment; this.attachment = url;
/** /**
* The name of this attachment * The name of this attachment
* @type {?string} * @type {string}
*/ */
this.name = name; this.name = filename;
if (data) this._patch(data); if (data) this._patch(data);
} }
/**
* Sets the description of this attachment.
* @param {string} description The description of the file
* @returns {Attachment} This attachment
*/
setDescription(description) {
this.description = description;
return this;
}
/**
* Sets the file of this attachment.
* @param {BufferResolvable|Stream} attachment The file
* @param {string} [name=null] The name of the file, if any
* @returns {Attachment} This attachment
*/
setFile(attachment, name = null) {
this.attachment = attachment;
this.name = name;
return this;
}
/**
* Sets the name of this attachment.
* @param {string} name The name of the file
* @returns {Attachment} This attachment
*/
setName(name) {
this.name = name;
return this;
}
/**
* Sets whether this attachment is a spoiler
* @param {boolean} [spoiler=true] Whether the attachment should be marked as a spoiler
* @returns {Attachment} This attachment
*/
setSpoiler(spoiler = true) {
if (spoiler === this.spoiler) return this;
if (!spoiler) {
while (this.spoiler) {
this.name = this.name.slice('SPOILER_'.length);
}
return this;
}
this.name = `SPOILER_${this.name}`;
return this;
}
_patch(data) { _patch(data) {
/** /**
* The attachment's id * The attachment's id
@@ -164,8 +120,3 @@ class Attachment {
} }
module.exports = Attachment; module.exports = Attachment;
/**
* @external APIAttachment
* @see {@link https://discord.com/developers/docs/resources/channel#attachment-object}
*/

View File

@@ -0,0 +1,110 @@
'use strict';
const Util = require('../util/Util');
/**
* Represents an attachment builder
*/
class AttachmentBuilder {
/**
* @param {BufferResolvable|Stream} attachment The file
* @param {APIAttachment} [data] Extra data
*/
constructor(attachment, data = {}) {
/**
* The file associated with this attachment.
* @type {BufferResolvable|Stream}
*/
this.attachment = attachment;
/**
* The name of this attachment
* @type {?string}
*/
this.name = data.name;
/**
* The description of the attachment
* @type {?string}
*/
this.description = data.description;
}
/**
* Sets the description of this attachment.
* @param {string} description The description of the file
* @returns {AttachmentBuilder} This attachment
*/
setDescription(description) {
this.description = description;
return this;
}
/**
* Sets the file of this attachment.
* @param {BufferResolvable|Stream} attachment The file
* @returns {AttachmentBuilder} This attachment
*/
setFile(attachment) {
this.attachment = attachment;
return this;
}
/**
* Sets the name of this attachment.
* @param {string} name The name of the file
* @returns {AttachmentBuilder} This attachment
*/
setName(name) {
this.name = name;
return this;
}
/**
* Sets whether this attachment is a spoiler
* @param {boolean} [spoiler=true] Whether the attachment should be marked as a spoiler
* @returns {AttachmentBuilder} This attachment
*/
setSpoiler(spoiler = true) {
if (spoiler === this.spoiler) return this;
if (!spoiler) {
while (this.spoiler) {
this.name = this.name.slice('SPOILER_'.length);
}
return this;
}
this.name = `SPOILER_${this.name}`;
return this;
}
/**
* Whether or not this attachment has been marked as a spoiler
* @type {boolean}
* @readonly
*/
get spoiler() {
return Util.basename(this.name).startsWith('SPOILER_');
}
toJSON() {
return Util.flatten(this);
}
/**
* Makes a new builder instance from a preexisting attachment structure.
* @param {JSONEncodable<AttachmentPayload>} other The builder to construct a new instance from
* @returns {AttachmentBuilder}
*/
static from(other) {
return new AttachmentBuilder(other.attachment, {
name: other.name,
description: other.description,
});
}
}
module.exports = AttachmentBuilder;
/**
* @external APIAttachment
* @see {@link https://discord.com/developers/docs/resources/channel#attachment-object}
*/

View File

@@ -133,7 +133,7 @@ class CommandInteraction extends Interaction {
if (attachments) { if (attachments) {
result.attachments = new Collection(); result.attachments = new Collection();
for (const attachment of Object.values(attachments)) { for (const attachment of Object.values(attachments)) {
const patched = new Attachment(attachment.url, attachment.filename, attachment); const patched = new Attachment(attachment);
result.attachments.set(attachment.id, patched); result.attachments.set(attachment.id, patched);
} }
} }
@@ -189,7 +189,7 @@ class CommandInteraction extends Interaction {
if (role) result.role = this.guild?.roles._add(role) ?? role; if (role) result.role = this.guild?.roles._add(role) ?? role;
const attachment = resolved.attachments?.[option.value]; const attachment = resolved.attachments?.[option.value];
if (attachment) result.attachment = new Attachment(attachment.url, attachment.filename, attachment); if (attachment) result.attachment = new Attachment(attachment);
} }
return result; return result;

View File

@@ -159,7 +159,7 @@ class Message extends Base {
this.attachments = new Collection(); this.attachments = new Collection();
if (data.attachments) { if (data.attachments) {
for (const attachment of data.attachments) { for (const attachment of data.attachments) {
this.attachments.set(attachment.id, new Attachment(attachment.url, attachment.filename, attachment)); this.attachments.set(attachment.id, new Attachment(attachment));
} }
} }
} else { } else {
@@ -644,7 +644,8 @@ class Message extends Base {
* Only `MessageFlags.SuppressEmbeds` can be edited. * Only `MessageFlags.SuppressEmbeds` can be edited.
* @property {Attachment[]} [attachments] An array of attachments to keep, * @property {Attachment[]} [attachments] An array of attachments to keep,
* all attachments will be kept if omitted * all attachments will be kept if omitted
* @property {FileOptions[]|BufferResolvable[]|Attachment[]} [files] Files to add to the message * @property {Array<JSONEncodable<AttachmentPayload>>|BufferResolvable[]|Attachment[]|AttachmentBuilder[]} [files]
* Files to add to the message
* @property {ActionRow[]|ActionRowOptions[]} [components] * @property {ActionRow[]|ActionRowOptions[]} [components]
* Action rows containing interactive components for the message (buttons, select menus) * Action rows containing interactive components for the message (buttons, select menus)
*/ */

View File

@@ -224,7 +224,8 @@ class MessagePayload {
/** /**
* Resolves a single file into an object sendable to the API. * Resolves a single file into an object sendable to the API.
* @param {BufferResolvable|Stream|FileOptions|Attachment} fileLike Something that could be resolved to a file * @param {BufferResolvable|Stream|JSONEncodable<AttachmentPayload>} fileLike Something that could
* be resolved to a file
* @returns {Promise<RawFile>} * @returns {Promise<RawFile>}
*/ */
static async resolveFile(fileLike) { static async resolveFile(fileLike) {

View File

@@ -132,7 +132,8 @@ class Webhook {
* @typedef {Object} WebhookEditMessageOptions * @typedef {Object} WebhookEditMessageOptions
* @property {Embed[]|APIEmbed[]} [embeds] See {@link WebhookMessageOptions#embeds} * @property {Embed[]|APIEmbed[]} [embeds] See {@link WebhookMessageOptions#embeds}
* @property {string} [content] See {@link BaseMessageOptions#content} * @property {string} [content] See {@link BaseMessageOptions#content}
* @property {FileOptions[]|BufferResolvable[]|Attachment[]} [files] See {@link BaseMessageOptions#files} * @property {JSONEncodable<AttachmentPayload>|BufferResolvable[]|Attachment[]|AttachmentBuilder[]} [files]
* See {@link BaseMessageOptions#files}
* @property {MessageMentionOptions} [allowedMentions] See {@link BaseMessageOptions#allowedMentions} * @property {MessageMentionOptions} [allowedMentions] See {@link BaseMessageOptions#allowedMentions}
* @property {Attachment[]} [attachments] Attachments to send with the message * @property {Attachment[]} [attachments] Attachments to send with the message
* @property {ActionRow[]|ActionRowOptions[]} [components] * @property {ActionRow[]|ActionRowOptions[]} [components]

View File

@@ -64,7 +64,7 @@ class TextBasedChannel {
* @property {FileOptions[]|BufferResolvable[]|Attachment[]} [files] Files to send with the message * @property {FileOptions[]|BufferResolvable[]|Attachment[]} [files] Files to send with the message
* @property {ActionRow[]|ActionRowOptions[]} [components] * @property {ActionRow[]|ActionRowOptions[]} [components]
* Action rows containing interactive components for the message (buttons, select menus) * Action rows containing interactive components for the message (buttons, select menus)
* @property {Attachment[]} [attachments] Attachments to send in the message * @property {Array<JSONEncodable<AttachmentPayload>>} [attachments] Attachments to send in the message
*/ */
/** /**

View File

@@ -117,6 +117,7 @@ import {
LocalizationMap, LocalizationMap,
LocaleString, LocaleString,
MessageActivityType, MessageActivityType,
APIAttachment,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { ChildProcess } from 'node:child_process'; import { ChildProcess } from 'node:child_process';
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
@@ -1696,9 +1697,22 @@ export class Message<Cached extends boolean = boolean> extends Base {
public inGuild(): this is Message<true> & this; public inGuild(): this is Message<true> & this;
} }
export class Attachment { export class AttachmentBuilder {
public constructor(attachment: BufferResolvable | Stream, name?: string, data?: RawAttachmentData); public constructor(attachment: BufferResolvable | Stream, name?: string, data?: RawAttachmentData);
public attachment: BufferResolvable | Stream;
public description: string | null;
public name: string | null;
public get spoiler(): boolean;
public setDescription(description: string): this;
public setFile(attachment: BufferResolvable | Stream, name?: string): this;
public setName(name: string): this;
public setSpoiler(spoiler?: boolean): this;
public toJSON(): unknown;
public static from(other: JSONEncodable<AttachmentPayload>): AttachmentBuilder;
}
export class Attachment {
private constructor(data: APIAttachment);
public attachment: BufferResolvable | Stream; public attachment: BufferResolvable | Stream;
public contentType: string | null; public contentType: string | null;
public description: string | null; public description: string | null;
@@ -1711,10 +1725,6 @@ export class Attachment {
public get spoiler(): boolean; public get spoiler(): boolean;
public url: string; public url: string;
public width: number | null; public width: number | null;
public setDescription(description: string): this;
public setFile(attachment: BufferResolvable | Stream, name?: string): this;
public setName(name: string): this;
public setSpoiler(spoiler?: boolean): this;
public toJSON(): unknown; public toJSON(): unknown;
} }
@@ -1838,7 +1848,9 @@ export class MessagePayload {
options: string | MessageOptions | WebhookMessageOptions, options: string | MessageOptions | WebhookMessageOptions,
extra?: MessageOptions | WebhookMessageOptions, extra?: MessageOptions | WebhookMessageOptions,
): MessagePayload; ): MessagePayload;
public static resolveFile(fileLike: BufferResolvable | Stream | FileOptions | Attachment): Promise<RawFile>; public static resolveFile(
fileLike: BufferResolvable | Stream | AttachmentPayload | JSONEncodable<AttachmentPayload>,
): Promise<RawFile>;
public makeContent(): string | undefined; public makeContent(): string | undefined;
public resolveBody(): this; public resolveBody(): this;
@@ -3199,7 +3211,7 @@ export class GuildStickerManager extends CachedManager<Snowflake, Sticker, Stick
private constructor(guild: Guild, iterable?: Iterable<RawStickerData>); private constructor(guild: Guild, iterable?: Iterable<RawStickerData>);
public guild: Guild; public guild: Guild;
public create( public create(
file: BufferResolvable | Stream | FileOptions | Attachment, file: BufferResolvable | Stream | AttachmentPayload | JSONEncodable<AttachmentBuilder>,
name: string, name: string,
tags: string, tags: string,
options?: GuildStickerCreateOptions, options?: GuildStickerCreateOptions,
@@ -3939,7 +3951,7 @@ export interface CommandInteractionOption<Cached extends CacheType = CacheType>
member?: CacheTypeReducer<Cached, GuildMember, APIInteractionDataResolvedGuildMember>; member?: CacheTypeReducer<Cached, GuildMember, APIInteractionDataResolvedGuildMember>;
channel?: CacheTypeReducer<Cached, GuildBasedChannel, APIInteractionDataResolvedChannel>; channel?: CacheTypeReducer<Cached, GuildBasedChannel, APIInteractionDataResolvedChannel>;
role?: CacheTypeReducer<Cached, Role, APIRole>; role?: CacheTypeReducer<Cached, Role, APIRole>;
attachment?: Attachment; attachment?: AttachmentBuilder;
message?: GuildCacheMessage<Cached>; message?: GuildCacheMessage<Cached>;
} }
@@ -3949,7 +3961,7 @@ export interface CommandInteractionResolvedData<Cached extends CacheType = Cache
roles?: Collection<Snowflake, CacheTypeReducer<Cached, Role, APIRole>>; roles?: Collection<Snowflake, CacheTypeReducer<Cached, Role, APIRole>>;
channels?: Collection<Snowflake, CacheTypeReducer<Cached, AnyChannel, APIInteractionDataResolvedChannel>>; channels?: Collection<Snowflake, CacheTypeReducer<Cached, AnyChannel, APIInteractionDataResolvedChannel>>;
messages?: Collection<Snowflake, CacheTypeReducer<Cached, Message, APIMessage>>; messages?: Collection<Snowflake, CacheTypeReducer<Cached, Message, APIMessage>>;
attachments?: Collection<Snowflake, Attachment>; attachments?: Collection<Snowflake, AttachmentBuilder>;
} }
export declare const Colors: { export declare const Colors: {
@@ -4241,7 +4253,7 @@ export interface FetchThreadsOptions {
active?: boolean; active?: boolean;
} }
export interface FileOptions { export interface AttachmentPayload {
attachment: BufferResolvable | Stream; attachment: BufferResolvable | Stream;
name?: string; name?: string;
description?: string; description?: string;
@@ -4672,10 +4684,10 @@ export type MessageChannelComponentCollectorOptions<T extends MessageComponentIn
>; >;
export interface MessageEditOptions { export interface MessageEditOptions {
attachments?: Attachment[]; attachments?: JSONEncodable<AttachmentPayload>[];
content?: string | null; content?: string | null;
embeds?: (JSONEncodable<APIEmbed> | APIEmbed)[] | null; embeds?: (JSONEncodable<APIEmbed> | APIEmbed)[] | null;
files?: (FileOptions | BufferResolvable | Stream | Attachment)[]; files?: (AttachmentPayload | BufferResolvable | Stream | AttachmentBuilder)[];
flags?: BitFieldResolvable<MessageFlagsString, number>; flags?: BitFieldResolvable<MessageFlagsString, number>;
allowedMentions?: MessageMentionOptions; allowedMentions?: MessageMentionOptions;
components?: ( components?: (
@@ -4726,10 +4738,10 @@ export interface MessageOptions {
| APIActionRowComponent<APIMessageActionRowComponent> | APIActionRowComponent<APIMessageActionRowComponent>
)[]; )[];
allowedMentions?: MessageMentionOptions; allowedMentions?: MessageMentionOptions;
files?: (FileOptions | BufferResolvable | Stream | Attachment)[]; files?: (Attachment | AttachmentBuilder | BufferResolvable | Stream)[];
reply?: ReplyOptions; reply?: ReplyOptions;
stickers?: StickerResolvable[]; stickers?: StickerResolvable[];
attachments?: Attachment[]; attachments?: (Attachment | AttachmentBuilder)[];
flags?: BitFieldResolvable<Extract<MessageFlagsString, 'SuppressEmbeds'>, number>; flags?: BitFieldResolvable<Extract<MessageFlagsString, 'SuppressEmbeds'>, number>;
} }

View File

@@ -57,7 +57,7 @@ import {
Interaction, Interaction,
InteractionCollector, InteractionCollector,
Message, Message,
Attachment, AttachmentBuilder,
MessageCollector, MessageCollector,
MessageComponentInteraction, MessageComponentInteraction,
MessageReaction, MessageReaction,
@@ -615,7 +615,7 @@ client.on('messageCreate', async message => {
assertIsMessage(channel.send({})); assertIsMessage(channel.send({}));
assertIsMessage(channel.send({ embeds: [] })); assertIsMessage(channel.send({ embeds: [] }));
const attachment = new Attachment('file.png'); const attachment = new AttachmentBuilder('file.png');
const embed = new EmbedBuilder(); const embed = new EmbedBuilder();
assertIsMessage(channel.send({ files: [attachment] })); assertIsMessage(channel.send({ files: [attachment] }));
assertIsMessage(channel.send({ embeds: [embed] })); assertIsMessage(channel.send({ embeds: [embed] }));
@@ -1492,8 +1492,8 @@ expectNotAssignable<ActionRowData<MessageActionRowComponentData>>({
declare const chatInputInteraction: ChatInputCommandInteraction; declare const chatInputInteraction: ChatInputCommandInteraction;
expectType<Attachment>(chatInputInteraction.options.getAttachment('attachment', true)); expectType<AttachmentBuilder>(chatInputInteraction.options.getAttachment('attachment', true));
expectType<Attachment | null>(chatInputInteraction.options.getAttachment('attachment')); expectType<AttachmentBuilder | null>(chatInputInteraction.options.getAttachment('attachment'));
declare const modal: ModalBuilder; declare const modal: ModalBuilder;