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.Message = require('./structures/Message').Message;
exports.Attachment = require('./structures/Attachment');
exports.AttachmentBuilder = require('./structures/AttachmentBuilder');
exports.ModalBuilder = require('./structures/ModalBuilder');
exports.MessageCollector = require('./structures/MessageCollector');
exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction');

View File

@@ -41,7 +41,7 @@ class GuildStickerManager extends CachedManager {
/**
* 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} tags The Discord name of a unicode emoji representing the sticker's expression
* @param {GuildStickerCreateOptions} [options] Options

View File

@@ -3,74 +3,30 @@
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 {
/**
* @param {BufferResolvable|Stream} attachment The file
* @param {string} [name=null] The name of the file, if any
* @param {APIAttachment} [data] Extra data
* @param {APIAttachment} data Attachment data
* @private
*/
constructor(attachment, name = null, data) {
this.attachment = attachment;
constructor({ url, filename, ...data }) {
this.attachment = url;
/**
* The name of this attachment
* @type {?string}
* @type {string}
*/
this.name = name;
this.name = filename;
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) {
/**
* The attachment's id
@@ -164,8 +120,3 @@ class 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) {
result.attachments = new Collection();
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);
}
}
@@ -189,7 +189,7 @@ class CommandInteraction extends Interaction {
if (role) result.role = this.guild?.roles._add(role) ?? role;
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;

View File

@@ -159,7 +159,7 @@ class Message extends Base {
this.attachments = new Collection();
if (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 {
@@ -644,7 +644,8 @@ class Message extends Base {
* Only `MessageFlags.SuppressEmbeds` can be edited.
* @property {Attachment[]} [attachments] An array of attachments to keep,
* 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]
* 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.
* @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>}
*/
static async resolveFile(fileLike) {

View File

@@ -132,7 +132,8 @@ class Webhook {
* @typedef {Object} WebhookEditMessageOptions
* @property {Embed[]|APIEmbed[]} [embeds] See {@link WebhookMessageOptions#embeds}
* @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 {Attachment[]} [attachments] Attachments to send with the message
* @property {ActionRow[]|ActionRowOptions[]} [components]

View File

@@ -64,7 +64,7 @@ class TextBasedChannel {
* @property {FileOptions[]|BufferResolvable[]|Attachment[]} [files] Files to send with the message
* @property {ActionRow[]|ActionRowOptions[]} [components]
* 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,
LocaleString,
MessageActivityType,
APIAttachment,
} from 'discord-api-types/v10';
import { ChildProcess } from 'node:child_process';
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;
}
export class Attachment {
export class AttachmentBuilder {
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 contentType: string | null;
public description: string | null;
@@ -1711,10 +1725,6 @@ export class Attachment {
public get spoiler(): boolean;
public url: string;
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;
}
@@ -1838,7 +1848,9 @@ export class MessagePayload {
options: string | MessageOptions | WebhookMessageOptions,
extra?: MessageOptions | WebhookMessageOptions,
): 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 resolveBody(): this;
@@ -3199,7 +3211,7 @@ export class GuildStickerManager extends CachedManager<Snowflake, Sticker, Stick
private constructor(guild: Guild, iterable?: Iterable<RawStickerData>);
public guild: Guild;
public create(
file: BufferResolvable | Stream | FileOptions | Attachment,
file: BufferResolvable | Stream | AttachmentPayload | JSONEncodable<AttachmentBuilder>,
name: string,
tags: string,
options?: GuildStickerCreateOptions,
@@ -3939,7 +3951,7 @@ export interface CommandInteractionOption<Cached extends CacheType = CacheType>
member?: CacheTypeReducer<Cached, GuildMember, APIInteractionDataResolvedGuildMember>;
channel?: CacheTypeReducer<Cached, GuildBasedChannel, APIInteractionDataResolvedChannel>;
role?: CacheTypeReducer<Cached, Role, APIRole>;
attachment?: Attachment;
attachment?: AttachmentBuilder;
message?: GuildCacheMessage<Cached>;
}
@@ -3949,7 +3961,7 @@ export interface CommandInteractionResolvedData<Cached extends CacheType = Cache
roles?: Collection<Snowflake, CacheTypeReducer<Cached, Role, APIRole>>;
channels?: Collection<Snowflake, CacheTypeReducer<Cached, AnyChannel, APIInteractionDataResolvedChannel>>;
messages?: Collection<Snowflake, CacheTypeReducer<Cached, Message, APIMessage>>;
attachments?: Collection<Snowflake, Attachment>;
attachments?: Collection<Snowflake, AttachmentBuilder>;
}
export declare const Colors: {
@@ -4241,7 +4253,7 @@ export interface FetchThreadsOptions {
active?: boolean;
}
export interface FileOptions {
export interface AttachmentPayload {
attachment: BufferResolvable | Stream;
name?: string;
description?: string;
@@ -4672,10 +4684,10 @@ export type MessageChannelComponentCollectorOptions<T extends MessageComponentIn
>;
export interface MessageEditOptions {
attachments?: Attachment[];
attachments?: JSONEncodable<AttachmentPayload>[];
content?: string | null;
embeds?: (JSONEncodable<APIEmbed> | APIEmbed)[] | null;
files?: (FileOptions | BufferResolvable | Stream | Attachment)[];
files?: (AttachmentPayload | BufferResolvable | Stream | AttachmentBuilder)[];
flags?: BitFieldResolvable<MessageFlagsString, number>;
allowedMentions?: MessageMentionOptions;
components?: (
@@ -4726,10 +4738,10 @@ export interface MessageOptions {
| APIActionRowComponent<APIMessageActionRowComponent>
)[];
allowedMentions?: MessageMentionOptions;
files?: (FileOptions | BufferResolvable | Stream | Attachment)[];
files?: (Attachment | AttachmentBuilder | BufferResolvable | Stream)[];
reply?: ReplyOptions;
stickers?: StickerResolvable[];
attachments?: Attachment[];
attachments?: (Attachment | AttachmentBuilder)[];
flags?: BitFieldResolvable<Extract<MessageFlagsString, 'SuppressEmbeds'>, number>;
}

View File

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