From e71dfefc2e69976ce86c44d1c2d6dad676c9d5ef Mon Sep 17 00:00:00 2001 From: almeidx Date: Tue, 27 Aug 2024 13:09:53 +0100 Subject: [PATCH] feat: send voice messages --- .../src/managers/GuildForumThreadManager.js | 3 +- .../discord.js/src/structures/Attachment.js | 7 +- .../src/structures/AttachmentBuilder.js | 71 ++++++++++++++++--- .../src/structures/MessagePayload.js | 3 + .../interfaces/InteractionResponses.js | 4 +- .../structures/interfaces/TextBasedChannel.js | 3 +- packages/discord.js/typings/index.d.ts | 23 ++++-- packages/discord.js/typings/index.test-d.ts | 21 ++++++ 8 files changed, 116 insertions(+), 19 deletions(-) diff --git a/packages/discord.js/src/managers/GuildForumThreadManager.js b/packages/discord.js/src/managers/GuildForumThreadManager.js index f830b98f1a7e..3ce08fdb6069 100644 --- a/packages/discord.js/src/managers/GuildForumThreadManager.js +++ b/packages/discord.js/src/managers/GuildForumThreadManager.js @@ -20,7 +20,8 @@ class GuildForumThreadManager extends ThreadManager { * @typedef {BaseMessageOptions} GuildForumThreadMessageCreateOptions * @property {StickerResolvable} [stickers] The stickers to send with the message * @property {BitFieldResolvable} [flags] The flags to send with the message - * Only `MessageFlags.SuppressEmbeds` and `MessageFlags.SuppressNotifications` can be set. + * Only `MessageFlags.SuppressEmbeds`, `MessageFlags.SuppressNotifications`, and `MessageFlags.IsVoiceMessage` + * can be set. */ /** diff --git a/packages/discord.js/src/structures/Attachment.js b/packages/discord.js/src/structures/Attachment.js index e175e5b60751..365d81f25f65 100644 --- a/packages/discord.js/src/structures/Attachment.js +++ b/packages/discord.js/src/structures/Attachment.js @@ -5,9 +5,12 @@ const { basename, flatten } = require('../util/Util'); /** * @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 + * @property {string} [name] The name of the attachment + * @property {string} [description] The description of the attachment + * @property {title} [title] The title of the attachment + * @property {string} [waveform] The base64 encoded byte array representing a sampled waveform + * @property {number} [duration] The duration of the attachment in seconds */ /** diff --git a/packages/discord.js/src/structures/AttachmentBuilder.js b/packages/discord.js/src/structures/AttachmentBuilder.js index 707234524871..f84b2781ffba 100644 --- a/packages/discord.js/src/structures/AttachmentBuilder.js +++ b/packages/discord.js/src/structures/AttachmentBuilder.js @@ -16,16 +16,45 @@ class AttachmentBuilder { * @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; + + /** + * The title of the attachment + * @type {?string} + */ + this.title = data.title; + + /** + * The base64 encoded byte array representing a sampled waveform + * @type {?string} + */ + this.waveform = data.waveform; + + /** + * The duration of the attachment in seconds + * @type {?number} + */ + this.duration = data.duration; + } + + /** + * Whether this attachment has been marked as a spoiler + * @type {boolean} + * @readonly + */ + get spoiler() { + return basename(this.name).startsWith('SPOILER_'); } /** @@ -58,6 +87,36 @@ class AttachmentBuilder { return this; } + /** + * Sets the title of this attachment. + * @param {string} title The title of the file + * @returns {AttachmentBuilder} This attachment + */ + setTitle(title) { + this.title = title; + return this; + } + + /** + * Sets the waveform of this attachment. + * @param {string} waveform The base64 encoded byte array representing a sampled waveform + * @returns {AttachmentBuilder} This attachment + */ + setWaveform(waveform) { + this.waveform = waveform; + return this; + } + + /** + * Sets the duration of this attachment. + * @param {number} duration The duration of the attachment in seconds + * @returns {AttachmentBuilder} This attachment + */ + setDuration(duration) { + this.duration = duration; + return this; + } + /** * Sets whether this attachment is a spoiler * @param {boolean} [spoiler=true] Whether the attachment should be marked as a spoiler @@ -76,15 +135,6 @@ class AttachmentBuilder { return this; } - /** - * Whether or not this attachment has been marked as a spoiler - * @type {boolean} - * @readonly - */ - get spoiler() { - return basename(this.name).startsWith('SPOILER_'); - } - toJSON() { return flatten(this); } @@ -108,4 +158,7 @@ module.exports = AttachmentBuilder; * @typedef {Object} AttachmentData * @property {string} [name] The name of the attachment * @property {string} [description] The description of the attachment + * @property {string} [title] The title of the attachment + * @property {string} [waveform] The base64 encoded byte array representing a sampled waveform + * @property {number} [duration] The duration of the attachment in seconds */ diff --git a/packages/discord.js/src/structures/MessagePayload.js b/packages/discord.js/src/structures/MessagePayload.js index dad8ffedb026..827b90f425bc 100644 --- a/packages/discord.js/src/structures/MessagePayload.js +++ b/packages/discord.js/src/structures/MessagePayload.js @@ -204,6 +204,9 @@ class MessagePayload { const attachments = this.options.files?.map((file, index) => ({ id: index.toString(), description: file.description, + title: file.name, + waveform: file.waveform, + duration_secs: file.duration, })); if (Array.isArray(this.options.attachments)) { this.options.attachments.push(...(attachments ?? [])); diff --git a/packages/discord.js/src/structures/interfaces/InteractionResponses.js b/packages/discord.js/src/structures/interfaces/InteractionResponses.js index 9f711b517b49..1c104c66bd6d 100644 --- a/packages/discord.js/src/structures/interfaces/InteractionResponses.js +++ b/packages/discord.js/src/structures/interfaces/InteractionResponses.js @@ -41,8 +41,8 @@ class InteractionResponses { * @property {boolean} [ephemeral] Whether the reply should be ephemeral * @property {boolean} [fetchReply] Whether to fetch the reply * @property {MessageFlags} [flags] Which flags to set for the message. - * Only `MessageFlags.Ephemeral`, `MessageFlags.SuppressEmbeds`, and `MessageFlags.SuppressNotifications` - * can be set. + * Only `MessageFlags.Ephemeral`, `MessageFlags.SuppressEmbeds`, `MessageFlags.SuppressNotifications`, + * and `MessageFlags.IsVoiceMessage` can be set. */ /** diff --git a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js index f3f2bf8d6a0b..d8809ff38562 100644 --- a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js +++ b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js @@ -102,7 +102,8 @@ class TextBasedChannel { * that message will be returned and no new message will be created * @property {StickerResolvable[]} [stickers=[]] The stickers to send in the message * @property {MessageFlags} [flags] Which flags to set for the message. - * Only `MessageFlags.SuppressEmbeds` and `MessageFlags.SuppressNotifications` can be set. + * Only `MessageFlags.SuppressEmbeds`, `MessageFlags.SuppressNotifications`, and `MessageFlags.IsVoiceMessage` + * can be set. */ /** diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index a7b00f808e01..282967bf6847 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -2204,10 +2204,16 @@ export class AttachmentBuilder { public attachment: BufferResolvable | Stream; public description: string | null; public name: string | null; + public title: string | null; + public waveform: string | null; + public duration: number | null; public get spoiler(): boolean; public setDescription(description: string): this; public setFile(attachment: BufferResolvable | Stream, name?: string): this; public setName(name: string): this; + public setTitle(title: string): this; + public setWaveform(waveform: string): this; + public setDuration(duration: number): this; public setSpoiler(spoiler?: boolean): this; public toJSON(): unknown; public static from(other: JSONEncodable): AttachmentBuilder; @@ -4752,6 +4758,9 @@ export interface BaseApplicationCommandData { export interface AttachmentData { name?: string; description?: string; + title?: string; + waveform?: string; + duration?: number; } export type CommandOptionDataTypeResolvable = ApplicationCommandOptionType; @@ -5773,6 +5782,9 @@ export interface AttachmentPayload { attachment: BufferResolvable | Stream; name?: string; description?: string; + title?: string; + waveform?: string; + duration?: number; } export type GlobalSweepFilter = () => @@ -6240,8 +6252,11 @@ export interface InteractionReplyOptions extends BaseMessageOptions { ephemeral?: boolean; fetchReply?: boolean; flags?: BitFieldResolvable< - Extract, - MessageFlags.Ephemeral | MessageFlags.SuppressEmbeds | MessageFlags.SuppressNotifications + Extract, + | MessageFlags.Ephemeral + | MessageFlags.SuppressEmbeds + | MessageFlags.SuppressNotifications + | MessageFlags.IsVoiceMessage >; } @@ -6402,8 +6417,8 @@ export interface MessageCreateOptions extends BaseMessageOptions { reply?: ReplyOptions; stickers?: readonly StickerResolvable[]; flags?: BitFieldResolvable< - Extract, - MessageFlags.SuppressEmbeds | MessageFlags.SuppressNotifications + Extract, + MessageFlags.SuppressEmbeds | MessageFlags.SuppressNotifications | MessageFlags.IsVoiceMessage >; } diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index 78093fc55654..a149d0287267 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -33,6 +33,7 @@ import { APIMentionableSelectComponent, APIModalInteractionResponseCallbackData, WebhookType, + MessageFlags, } from 'discord-api-types/v10'; import { ApplicationCommand, @@ -2592,3 +2593,23 @@ declare const poll: Poll; expectType>(await client.fetchStickerPacks()); expectType>(await client.fetchStickerPacks({})); expectType(await client.fetchStickerPacks({ packId: snowflake })); + +await textChannel.send({ + files: [ + new AttachmentBuilder('https://example.com/voice-message.ogg') + .setDuration(2) + .setWaveform('AFUqPDw3Eg2hh4+gopOYj4xthU4='), + ], + flags: MessageFlags.IsVoiceMessage, +}); + +await textChannel.send({ + files: [ + { + attachment: 'https://example.com/voice-message.ogg', + duration: 2, + waveform: 'AFUqPDw3Eg2hh4+gopOYj4xthU4=', + }, + ], + flags: MessageFlags.IsVoiceMessage, +});