JDA
JDA copied to clipboard
Message Rework
Pull Request Etiquette
- [x] I have checked the PRs for upcoming features/bug fixes.
- [x] I have read the contributing guidelines.
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 onMessageAction
, has been moved explicitly to edit requests and is now calledsetReplace(true)
, which I believe to be more descriptive. -
MessageBuilder#buildAll
has moved to a dedicated util class and works for arbitrary strings/boundariesSplitUtil#split
Interface Hierarchy
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
);
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.
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.