Skip to content

Commit

Permalink
Support sending voice messages (#2738)
Browse files Browse the repository at this point in the history
  • Loading branch information
MinnDevelopment authored Oct 4, 2024
1 parent 8007014 commit 77ec412
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 5 deletions.
7 changes: 7 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -2253,6 +2253,13 @@ default MessageCreateAction replyFiles(@Nonnull Collection<? extends FileUpload>
*/
boolean isSuppressedNotifications();

/**
* Whether this message is a voice message.
*
* @return True, if this is a voice message
*/
boolean isVoiceMessage();

/**
* Returns a possibly {@code null} {@link ThreadChannel ThreadChannel} that was started from this message.
* This can be {@code null} due to no ThreadChannel being started from it or the ThreadChannel later being deleted.
Expand Down
81 changes: 79 additions & 2 deletions src/main/java/net/dv8tion/jda/api/utils/FileUpload.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Base64;
import java.util.function.Supplier;

/**
Expand All @@ -51,6 +54,9 @@ public class FileUpload implements Closeable, AttachedFile
private String name;
private TypedBody<?> body;
private String description;
private MediaType mediaType = Requester.MEDIA_TYPE_OCTET;
private byte[] waveform;
private double durationSeconds;

protected FileUpload(InputStream resource, String name)
{
Expand Down Expand Up @@ -358,6 +364,70 @@ public FileUpload setDescription(@Nullable String description)
return this;
}

/**
* Turns this attachment into a voice message with the provided waveform.
*
* @param mediaType
* The audio type for the attached audio file. Should be {@code audio/ogg} or similar.
* @param waveform
* The waveform of the audio, which is a low frequency sampling up to 256 bytes.
* @param duration
* The actual duration of the audio data.
*
* @throws IllegalArgumentException
* If null is provided or the waveform is not between 1 and 256 bytes long.
*
* @return The same FileUpload instance configured as a voice message attachment
*/
@Nonnull
public FileUpload asVoiceMessage(@Nonnull MediaType mediaType, @Nonnull byte[] waveform, @Nonnull Duration duration)
{
Checks.notNull(duration, "Duration");
return this.asVoiceMessage(mediaType, waveform, duration.toNanos() / 1_000_000_000.0);
}

/**
* Turns this attachment into a voice message with the provided waveform.
*
* @param mediaType
* The audio type for the attached audio file. Should be {@code audio/ogg} or similar.
* @param waveform
* The waveform of the audio, which is a low frequency sampling up to 256 bytes.
* @param durationSeconds
* The actual duration of the audio data in seconds.
*
* @throws IllegalArgumentException
* If null is provided or the waveform is not between 1 and 256 bytes long.
*
* @return The same FileUpload instance configured as a voice message attachment
*/
@Nonnull
public FileUpload asVoiceMessage(@Nonnull MediaType mediaType, @Nonnull byte[] waveform, double durationSeconds)
{
Checks.notNull(mediaType, "Media type");
Checks.notNull(waveform, "Waveform");
Checks.check(waveform.length > 0 && waveform.length <= 256, "Waveform must be between 1 and 256 bytes long");
Checks.check(Double.isFinite(durationSeconds), "Duration must be a finite number");
Checks.check(durationSeconds > 0, "Duration must be positive");
this.waveform = waveform;
this.durationSeconds = durationSeconds;
this.mediaType = mediaType;
return this;
}

/**
* Whether this attachment is a valid voice message attachment.
*
* @return True, if this is a voice message attachment.
*/
public boolean isVoiceMessage()
{
return this.mediaType.type().equals("audio")
&& this.durationSeconds > 0.0
&& this.waveform != null
&& this.waveform.length > 0;
}

/**
* The filename for the file.
*
Expand Down Expand Up @@ -425,17 +495,24 @@ public synchronized RequestBody getRequestBody(@Nonnull MediaType type)
@SuppressWarnings("ConstantConditions")
public synchronized void addPart(@Nonnull MultipartBody.Builder builder, int index)
{
builder.addFormDataPart("files[" + index + "]", name, getRequestBody(Requester.MEDIA_TYPE_OCTET));
builder.addFormDataPart("files[" + index + "]", name, getRequestBody(mediaType));
}

@Nonnull
@Override
public DataObject toAttachmentData(int index)
{
return DataObject.empty()
DataObject attachment = DataObject.empty()
.put("id", index)
.put("description", description == null ? "" : description)
.put("content_type", mediaType.toString())
.put("filename", name);
if (waveform != null && durationSeconds > 0)
{
attachment.put("waveform", new String(Base64.getEncoder().encode(waveform), StandardCharsets.UTF_8));
attachment.put("duration_secs", durationSeconds);
}
return attachment;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.Helpers;
import net.dv8tion.jda.internal.utils.IOUtil;
import org.jetbrains.annotations.NotNull;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -181,7 +182,10 @@ public MessageCreateBuilder setFiles(@Nullable Collection<? extends FileUpload>
Checks.noneNull(files, "Files");
this.files.clear();
if (files != null)
{
this.files.addAll(files);
this.setVoiceMessageIfApplicable(files);
}
return this;
}

Expand Down Expand Up @@ -213,6 +217,7 @@ public MessageCreateBuilder addFiles(@Nonnull Collection<? extends FileUpload> f
{
Checks.noneNull(files, "Files");
this.files.addAll(files);
this.setVoiceMessageIfApplicable(files);
return this;
}

Expand All @@ -228,13 +233,24 @@ public MessageCreateBuilder setTTS(boolean tts)
@Override
public MessageCreateBuilder setSuppressedNotifications(boolean suppressed)
{
if(suppressed)
if (suppressed)
messageFlags |= Message.MessageFlag.NOTIFICATIONS_SUPPRESSED.getValue();
else
messageFlags &= ~Message.MessageFlag.NOTIFICATIONS_SUPPRESSED.getValue();
return this;
}

@Nonnull
@Override
public MessageCreateBuilder setVoiceMessage(boolean voiceMessage)
{
if (voiceMessage)
messageFlags |= Message.MessageFlag.IS_VOICE_MESSAGE.getValue();
else
messageFlags &= ~Message.MessageFlag.IS_VOICE_MESSAGE.getValue();
return this;
}

@Override
public boolean isEmpty()
{
Expand Down Expand Up @@ -291,4 +307,10 @@ public MessageCreateBuilder closeFiles()
files.clear();
return this;
}

private void setVoiceMessageIfApplicable(@NotNull Collection<? extends FileUpload> files)
{
if (files.stream().anyMatch(FileUpload::isVoiceMessage))
this.setVoiceMessage(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -270,13 +270,23 @@ public boolean isTTS()
/**
* Whether this message is silent.
*
* @return True, if the message will not trigger push and desktop notifications
* @return True, if the message will not trigger push and desktop notifications.
*/
public boolean isSuppressedNotifications()
{
return (flags & Message.MessageFlag.NOTIFICATIONS_SUPPRESSED.getValue()) != 0;
}

/**
* Whether this message is intended as a voice message.
*
* @return True, if this message is intended as a voice message.
*/
public boolean isVoiceMessage()
{
return (flags & Message.MessageFlag.IS_VOICE_MESSAGE.getValue()) != 0;
}

/**
* The IDs for users which are allowed to be mentioned, or an empty list.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.utils.FileUpload;
import net.dv8tion.jda.internal.utils.Checks;
import okhttp3.MediaType;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -339,11 +340,24 @@ default R addFiles(@Nonnull FileUpload... files)
* @param suppressed
* True, if this message should not trigger push/desktop notifications
*
* @return The same reply action, for chaining convenience
* @return The same instance for chaining
*/
@Nonnull
R setSuppressedNotifications(boolean suppressed);

/**
* Whether this message should be considered a voice message.
* <br>Voice messages must upload a valid voice message attachment, using {@link FileUpload#asVoiceMessage(MediaType, byte[], double)}.
*
* @param voiceMessage
* True, if this message is a voice message.
* Turned on automatically if attachment is a valid voice message attachment.
*
* @return The same instance for chaining
*/
@Nonnull
R setVoiceMessage(boolean voiceMessage);

/**
* Applies the provided {@link MessageCreateData} to this request.
*
Expand Down Expand Up @@ -372,6 +386,7 @@ default R applyData(@Nonnull MessageCreateData data)
.setTTS(data.isTTS())
.setSuppressEmbeds(data.isSuppressEmbeds())
.setSuppressedNotifications(data.isSuppressedNotifications())
.setVoiceMessage(data.isVoiceMessage())
.setComponents(layoutComponents)
.setPoll(data.getPoll())
.setFiles(data.getFiles());
Expand All @@ -390,6 +405,7 @@ default R applyMessage(@Nonnull Message message)
.setEmbeds(embeds)
.setTTS(message.isTTS())
.setSuppressedNotifications(message.isSuppressedNotifications())
.setVoiceMessage(message.isVoiceMessage())
.setComponents(message.getActionRows())
.setPoll(message.getPoll() != null ? MessagePollData.from(message.getPoll()) : null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,12 @@ public boolean isSuppressedNotifications()
return (this.flags & MessageFlag.NOTIFICATIONS_SUPPRESSED.getValue()) != 0;
}

@Override
public boolean isVoiceMessage()
{
return (this.flags & MessageFlag.IS_VOICE_MESSAGE.getValue()) != 0;
}

@Nullable
@Override
public ThreadChannel getStartedThread()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,12 @@ default R setSuppressedNotifications(boolean suppressed)
getBuilder().setSuppressedNotifications(suppressed);
return (R) this;
}

@Nonnull
@Override
default R setVoiceMessage(boolean voiceMessage)
{
getBuilder().setVoiceMessage(voiceMessage);
return (R) this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.requests.Requester;
import net.dv8tion.jda.internal.utils.EncodingUtil;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import org.jetbrains.annotations.Contract;
import org.mockito.ThrowingConsumer;

Expand Down Expand Up @@ -74,6 +76,21 @@ public RestActionAssertions checkAssertions(@Nonnull ThrowingConsumer<Request<?>
return this;
}

@CheckReturnValue
@Contract("->this")
public RestActionAssertions hasMultipartBody()
{
return checkAssertions(request -> {
RequestBody body = request.getBody();
assertThat(body).isNotNull();
MediaType mediaType = body.contentType();
assertThat(mediaType).isNotNull();

assertThat(mediaType.toString())
.startsWith("multipart/form-data; boundary=");
});
}

@CheckReturnValue
@Contract("_->this")
public RestActionAssertions hasBodyEqualTo(@Nonnull DataObject expected)
Expand Down
Loading

0 comments on commit 77ec412

Please sign in to comment.