JDA icon indicating copy to clipboard operation
JDA copied to clipboard

Message Rework

Open MinnDevelopment opened this issue 2 years ago • 2 comments

Pull Request Etiquette

Changes

  • [x] Internal code
  • [x] Library interface (affecting end-user code)
  • [ ] Documentation
  • [ ] Other: _____

Closes #1675

Description

Reworks the message sending and editing capabilities for more consistency and reducing code duplication.

  • Split send and edit into separate request types (MessageCreateRequest and MessageEditRequest)
  • Split MessageBuilder into MessageEditBuilder and MessageCreateBuilder
  • Support allowed mentions on edits
  • Support file descriptions
  • Major abstraction to cover the 90% overlap between sending and editing messages
  • Allow setting files on message building
  • Message builders no longer return Message interface, instead returning concrete data classes
  • The concept of override(true) that was previously used on MessageAction, has been moved explicitly to edit requests and is now called setReplace(true), which I believe to be more descriptive.
  • MessageBuilder#buildAll has moved to a dedicated util class and works for arbitrary strings/boundaries SplitUtil#split

Interface Hierarchy

image

Example

To send a message to a channel, you can use MessageCreateBuilder:

MessageCreateBuilder builder = new MessageCreateBuilder()
      .setContent("Hello Guys")
      .addActionRow(Button.link("https://github.com/DV8FromTheWorld/JDA", "GitHub"))
      .addFiles(FileUpload.fromData(new File("github_logo.png")));
try (MessageCreateData createData = builder.build()) {
  for (MessageChannel channel : targetChannels) {
    channel.sendMessage(createData).queue();
  }
} // if there is an exception, we should close the file

For message edit requests, use the MessageEditBuilder instead:

Message m = /* ... */ ;
List<AttachedFile> attachments = new ArrayList<>(m.getAttachments()); // keep original attachments
attachments.add(FileUpload.fromData(new File("github_logo.png"))); // add a file to the message
MessageEditBuilder builder = new MessageEditBuilder()
      .setContent("Hello Guys")
      .setActionRow(Button.link("https://github.com/DV8FromTheWorld/JDA", "GitHub"))
      .setAttachments(attachments); // set the attachments to the updated list
try (MessageEditData editData = builder.build()) {
  m.editMessage(editData).queue();
} // if there is an exception, we should close the file

Convert between edit and send:

// Allows you to make an edit from a create request.
// This will be a *replace*, causing the entire message to be replaced by this new data.
MessageEditData editData = MessageEditData.fromCreateData(createData);
// Only sets the parts that were explicitly edited (if you only called setContent, it will only have content)
MessageCreateData createData = MessageCreateData.fromEditData(editData);

Similarly, you can also use fromMessage(Message) on each of these. Those factories have the same kind of semantics.

Note: In theory, an extension library could always use MessageEditData and simply require users to convert it. There is no real downside to that, other than user boilerplate.

Breaking Changes

Before After
retainFiles* setAttachments
setActionRows setComponents
addActionRows addComponents
allowedMentions setAllowedMentions
MessageAction#content MessageRequest#setContent
MessageAction#tts MessageCreateAction#setTTS
MessageBuilder MessageCreateBuilder and MessageEditBuilder
WebhookMessageUpdateAction WebhookMessageEditAction
WebhookMessageAction WebhookMessageCreateAction
MessageAction MessageCreateAction and MessageEditAction
sendMessage(Message) sendMessage(MessageCreateData)
editMessage(Message) editMessage(MessageEditData)
sendFile(...) sendFiles(FileUpload...)
addFile(...) addFiles(...), setFiles(...), and setAttachments(...)
editMessage(InputStream, ...) editMessageAttachments(AttachedFile...)

MessageBuilder#buildAll has been replaced by SplitUtil#split:

Before:

List<Message> messages = new MessageBuilder().setContent(input).buildAll();

After:

List<String> messages = SplitUtil.split(
    input,            // input string of arbitrary length
    2000,             // the split limit, can be arbitrary (>0)
    true,             // whether to trim the strings (empty will be discarded)
    Strategy.NEWLINE, // split on '\n' characters if possible
    Strategy.ANYWHERE // otherwise split on the limit
);

MinnDevelopment avatar Jul 22 '22 12:07 MinnDevelopment

I've added SplitUtil as a replacement for MessageBuilder#buildAll.

Example:

String longtext = ...;
for (String part : SplitUtil.split(longtext, 2000, true, SplitUtil.Strategy.NEWLINE, SplitUtil.Strategy.ANYWHERE)) {
    channel.sendMessage(part).queue();
}

This will try to first find a suitable substring using '\n' as a delimiter, and if it cannot find it anywhere in the next 2000 characters, it will split exactly at the limit.

MinnDevelopment avatar Jul 24 '22 17:07 MinnDevelopment

I downgraded the gateway API to v9 again, since v10 seems to unnecessarily enforce the content intent before the deadline. You will still get a warning if you disable the intent. Also, if you do not have access to the intent due to missing approval by discord, you can still enable it in v9. This allows bots to smoothly transition from content to no content, but will also break every bot that enables it without access on the deadline.

A library has no way to tell if the bot is approved and whether it would break on the deadline, this makes it extremely difficult to avoid these kinds of situations and prepare bot devs properly.

This a really strange enforcement and makes it very unintuitive for bots to transition, but that's just what we gotta deal with downstream.

MinnDevelopment avatar Jul 26 '22 11:07 MinnDevelopment