feat: send voice messages (#11493)

* feat: send voice messages

* docs: shortened voice message note

* fix(AttachmentBuilder): match nullable types
This commit is contained in:
ducktrshessami
2026-07-15 00:04:50 +01:00
committed by GitHub
parent 8b342a14a3
commit fed811a669
9 changed files with 126 additions and 15 deletions
@@ -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
* <info>Only `MessageFlags.SuppressEmbeds` and `MessageFlags.SuppressNotifications` can be set.</info>
* <info>Only {@link MessageFlags.SuppressEmbeds}, {@link MessageFlags.SuppressNotifications}, and
* {@link MessageFlags.IsVoiceMessage} can be set.</info>
*/
/**
@@ -6,9 +6,12 @@ const { 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 bytearray representing a sampled waveform (for voice messages)
* @property {number} [duration] The duration of the attachment in seconds (for voice messages)
*/
/**
@@ -106,7 +109,8 @@ class Attachment {
if ('duration_secs' in data) {
/**
* The duration of this attachment in seconds
* <info>This will only be available if the attachment is an audio file.</info>
* <info>This will only be available if the attachment is the audio file from a voice message.</info>
*
* @type {?number}
*/
this.duration = data.duration_secs;
@@ -117,7 +121,8 @@ class Attachment {
if ('waveform' in data) {
/**
* The base64 encoded byte array representing a sampled waveform
* <info>This will only be available if the attachment is an audio file.</info>
* <info>This will only be available if this attachment is the audio file from a voice message.</info>
*
* @type {?string}
*/
this.waveform = data.waveform;
@@ -16,16 +16,41 @@ class AttachmentBuilder {
* @type {BufferResolvable|Stream}
*/
this.attachment = attachment;
/**
* The name of this attachment
* @type {?string}
*/
this.name = data.name;
this.name = data.name ?? null;
/**
* The description of the attachment
* @type {?string}
*/
this.description = data.description;
this.description = data.description ?? null;
/**
* The title of the attachment
*
* @type {?string}
*/
this.title = data.title ?? null;
/**
* The base64 encoded byte array representing a sampled waveform
* <info>This is only for voice message attachments.</info>
*
* @type {?string}
*/
this.waveform = data.waveform ?? null;
/**
* The duration of the attachment in seconds
* <info>This is only for voice message attachments.</info>
*
* @type {?number}
*/
this.duration = data.duration ?? null;
}
/**
@@ -58,6 +83,41 @@ 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.
* <info>This is only for voice message attachments.</info>
*
* @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.
* <info>This is only for voice message attachments.</info>
*
* @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
@@ -108,4 +168,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 bytearray representing a sampled waveform (for voice messages)
* @property {number} [duration] The duration of the attachment in seconds (for voice messages)
*/
@@ -215,6 +215,9 @@ class MessagePayload {
const attachments = this.options.files?.map((file, index) => ({
id: index.toString(),
description: file.description,
title: file.title,
waveform: file.waveform,
duration_secs: file.duration,
}));
if (Array.isArray(this.options.attachments)) {
this.options.attachments.push(...(attachments ?? []));
@@ -129,7 +129,7 @@ class Webhook {
* @typedef {BaseMessageOptionsWithPoll} WebhookMessageCreateOptions
* @property {boolean} [tts=false] Whether the message should be spoken aloud
* @property {MessageFlags} [flags] Which flags to set for the message.
* <info>Only the {@link MessageFlags.SuppressEmbeds} flag can be set.</info>
* <info>Only {@link MessageFlags.SuppressEmbeds} and {@link MessageFlags.IsVoiceMessage} can be set.</info>
* @property {string} [username=this.name] Username override for the message
* @property {string} [avatarURL] Avatar URL override for the message
* @property {Snowflake} [threadId] The id of the thread in the channel to send to.
@@ -50,8 +50,8 @@ class InteractionResponses {
* @property {boolean} [fetchReply] Whether to fetch the reply
* <warn>This option is deprecated. Use `withResponse` or fetch the response instead.</warn>
* @property {MessageFlagsResolvable} [flags] Which flags to set for the message.
* <info>Only `MessageFlags.Ephemeral`, `MessageFlags.SuppressEmbeds`, and `MessageFlags.SuppressNotifications`
* can be set.</info>
* <info>Only {@link MessageFlags.Ephemeral}, {@link MessageFlags.SuppressEmbeds},
* {@link MessageFlags.SuppressNotifications}, and {@link MessageFlags.IsVoiceMessage} can be set.</info>
*/
/**
@@ -110,8 +110,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.
* <info>Only {@link MessageFlags.SuppressEmbeds}, {@link MessageFlags.SuppressNotifications} and
* {@link MessageFlags.IsComponentsV2} can be set.</info>
* <info>Only {@link MessageFlags.SuppressEmbeds}, {@link MessageFlags.SuppressNotifications},
* {@link MessageFlags.IsComponentsV2}, and {@link MessageFlags.IsVoiceMessage} can be set.</info>
* <info>{@link MessageFlags.IsComponentsV2} is required if passing components that aren't action rows</info>
*/
+22 -3
View File
@@ -2565,10 +2565,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<AttachmentPayload>): AttachmentBuilder;
@@ -5572,6 +5578,9 @@ export interface BaseApplicationCommandData {
export interface AttachmentData {
name?: string;
description?: string;
duration?: number;
title?: string;
waveform?: string;
}
export type CommandOptionDataTypeResolvable = ApplicationCommandOptionType;
@@ -6695,6 +6704,9 @@ export interface AttachmentPayload {
attachment: BufferResolvable | Stream;
name?: string;
description?: string;
duration?: number;
title?: string;
waveform?: string;
}
export type GlobalSweepFilter<Key, Value> = () =>
@@ -7244,8 +7256,12 @@ export interface InteractionReplyOptions extends BaseMessageOptionsWithPoll {
fetchReply?: boolean;
flags?:
| BitFieldResolvable<
Extract<MessageFlagsString, 'Ephemeral' | 'SuppressEmbeds' | 'SuppressNotifications' | 'IsComponentsV2'>,
Extract<
MessageFlagsString,
'Ephemeral' | 'IsVoiceMessage' | 'SuppressEmbeds' | 'SuppressNotifications' | 'IsComponentsV2'
>,
| MessageFlags.Ephemeral
| MessageFlags.IsVoiceMessage
| MessageFlags.SuppressEmbeds
| MessageFlags.SuppressNotifications
| MessageFlags.IsComponentsV2
@@ -7453,8 +7469,11 @@ export interface MessageCreateOptions extends BaseMessageOptionsWithPoll {
sharedClientTheme?: JSONEncodable<APIMessageSharedClientTheme> | SharedClientTheme;
flags?:
| BitFieldResolvable<
Extract<MessageFlagsString, 'SuppressEmbeds' | 'SuppressNotifications' | 'IsComponentsV2'>,
MessageFlags.SuppressEmbeds | MessageFlags.SuppressNotifications | MessageFlags.IsComponentsV2
Extract<MessageFlagsString, 'IsVoiceMessage' | 'SuppressEmbeds' | 'SuppressNotifications' | 'IsComponentsV2'>,
| MessageFlags.IsVoiceMessage
| MessageFlags.SuppressEmbeds
| MessageFlags.SuppressNotifications
| MessageFlags.IsComponentsV2
>
| undefined;
}
@@ -3082,3 +3082,23 @@ declare const authorizingIntegrationOwners: AuthorizingIntegrationOwners;
expectType<User | null>(authorizingIntegrationOwners.user);
expectType<Snowflake | undefined>(authorizingIntegrationOwners[ApplicationIntegrationType.GuildInstall]);
}
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,
});