Prepare for webhooks directly through JDA

This commit is contained in:
Vankka 2023-06-09 00:35:44 +03:00
parent 05949fe6b0
commit d6a21b6fca
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
12 changed files with 108 additions and 152 deletions

View File

@ -29,6 +29,7 @@ import com.discordsrv.api.discord.entity.message.ReceivedDiscordMessage;
import com.discordsrv.api.discord.entity.message.SendableDiscordMessage;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.InputStream;
import java.util.Collections;
@ -86,7 +87,7 @@ public interface DiscordMessageChannel extends Snowflake {
* @return a future returning the message after being edited
*/
@NotNull
CompletableFuture<ReceivedDiscordMessage> editMessageById(long id, @NotNull SendableDiscordMessage message);
CompletableFuture<ReceivedDiscordMessage> editMessageById(long id, @NotNull SendableDiscordMessage message, @Nullable Map<String, InputStream> attachments);
/**
* Returns the JDA representation of this object. This should not be used if it can be avoided.

View File

@ -34,7 +34,9 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
@ -147,6 +149,10 @@ public interface ReceivedDiscordMessage extends Snowflake {
@NotNull
CompletableFuture<Void> delete();
default CompletableFuture<ReceivedDiscordMessage> edit(@NotNull SendableDiscordMessage message) {
return edit(message, null);
}
/**
* Edits this message to the provided message.
*
@ -156,7 +162,7 @@ public interface ReceivedDiscordMessage extends Snowflake {
* but the provided {@link SendableDiscordMessage} specifies a webhook username.
*/
@NotNull
CompletableFuture<ReceivedDiscordMessage> edit(SendableDiscordMessage message);
CompletableFuture<ReceivedDiscordMessage> edit(@NotNull SendableDiscordMessage message, @Nullable Map<String, InputStream> attachments);
class Attachment {

View File

@ -42,12 +42,6 @@ dependencies {
// DependencyDownload
api(libs.dependencydownload.runtime)
// Discord Webhooks
runtimeDownloadApi(libs.webhooks) {
// okhttp is already included
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
}
// Apache Commons
runtimeDownloadApi(libs.commons.lang)
runtimeDownloadApi(libs.commons.io)

View File

@ -18,8 +18,6 @@
package com.discordsrv.common.discord.api;
import club.minnced.discord.webhook.WebhookClient;
import club.minnced.discord.webhook.WebhookClientBuilder;
import com.discordsrv.api.discord.DiscordAPI;
import com.discordsrv.api.discord.connection.details.DiscordGatewayIntent;
import com.discordsrv.api.discord.connection.jda.errorresponse.ErrorCallbackContext;
@ -68,7 +66,7 @@ public class DiscordAPIImpl implements DiscordAPI {
private final DiscordSRV discordSRV;
private final DiscordCommandRegistry commandRegistry;
private final AsyncLoadingCache<Long, WebhookClient> cachedClients;
private final AsyncLoadingCache<Long, WebhookClient<Message>> cachedClients;
private final List<ThreadChannelLookup> threadLookups = new CopyOnWriteArrayList<>();
public DiscordAPIImpl(DiscordSRV discordSRV) {
@ -79,11 +77,11 @@ public class DiscordAPIImpl implements DiscordAPI {
.buildAsync(new WebhookCacheLoader());
}
public CompletableFuture<WebhookClient> queryWebhookClient(long channelId) {
public CompletableFuture<WebhookClient<Message>> queryWebhookClient(long channelId) {
return cachedClients.get(channelId);
}
public AsyncLoadingCache<Long, WebhookClient> getCachedClients() {
public AsyncLoadingCache<Long, WebhookClient<Message>> getCachedClients() {
return cachedClients;
}
@ -489,10 +487,10 @@ public class DiscordAPIImpl implements DiscordAPI {
return commandRegistry;
}
private class WebhookCacheLoader implements AsyncCacheLoader<Long, WebhookClient> {
private class WebhookCacheLoader implements AsyncCacheLoader<Long, WebhookClient<Message>> {
@Override
public @NonNull CompletableFuture<WebhookClient> asyncLoad(@NonNull Long channelId, @NonNull Executor executor) {
public @NonNull CompletableFuture<WebhookClient<Message>> asyncLoad(@NonNull Long channelId, @NonNull Executor executor) {
JDA jda = discordSRV.jda();
if (jda == null) {
return notReady();
@ -533,7 +531,7 @@ public class DiscordAPIImpl implements DiscordAPI {
}
}
private class WebhookCacheExpiry implements Expiry<Long, WebhookClient> {
private class WebhookCacheExpiry implements Expiry<Long, WebhookClient<Message>> {
private boolean isConfiguredChannel(Long channelId) {
for (BaseChannelConfig config : discordSRV.config().channels.values()) {

View File

@ -18,9 +18,6 @@
package com.discordsrv.common.discord.api.entity.channel;
import club.minnced.discord.webhook.WebhookClient;
import club.minnced.discord.webhook.receive.ReadonlyMessage;
import club.minnced.discord.webhook.send.WebhookMessageBuilder;
import com.discordsrv.api.discord.entity.channel.DiscordGuildMessageChannel;
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
import com.discordsrv.api.discord.entity.message.ReceivedDiscordMessage;
@ -29,16 +26,22 @@ import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.discord.api.entity.message.ReceivedDiscordMessageImpl;
import com.discordsrv.common.discord.api.entity.message.util.SendableDiscordMessageUtil;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.WebhookClient;
import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel;
import net.dv8tion.jda.api.requests.FluentRestAction;
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.api.utils.FileUpload;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
import net.dv8tion.jda.api.utils.messages.MessageCreateRequest;
import net.dv8tion.jda.api.utils.messages.MessageEditData;
import net.dv8tion.jda.api.utils.messages.MessageEditRequest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
public abstract class AbstractDiscordGuildMessageChannel<T extends GuildMessageChannel>
extends AbstractDiscordMessageChannel<T>
@ -51,7 +54,7 @@ public abstract class AbstractDiscordGuildMessageChannel<T extends GuildMessageC
this.guild = discordSRV.discordAPI().getGuild(channel.getGuild());
}
public CompletableFuture<WebhookClient> queryWebhookClient() {
public CompletableFuture<WebhookClient<Message>> queryWebhookClient() {
return discordSRV.discordAPI().queryWebhookClient(getId());
}
@ -79,48 +82,71 @@ public abstract class AbstractDiscordGuildMessageChannel<T extends GuildMessageC
public CompletableFuture<ReceivedDiscordMessage> sendMessage(
@NotNull SendableDiscordMessage message, @NotNull Map<String, InputStream> attachments
) {
return message(message, (webhookClient, webhookMessage) -> {
for (Map.Entry<String, InputStream> entry : attachments.entrySet()) {
webhookMessage.addFile(entry.getKey(), entry.getValue());
return sendInternal(message, attachments);
}
return webhookClient.send(webhookMessage.build());
}, (channel, msg) -> {
MessageCreateAction action = channel.sendMessage(SendableDiscordMessageUtil.toJDASend(msg));
@SuppressWarnings("unchecked") // Generics
private <R extends MessageCreateRequest<? extends MessageCreateRequest<?>> & RestAction<Message>> CompletableFuture<ReceivedDiscordMessage> sendInternal(SendableDiscordMessage message, Map<String, InputStream> attachments) {
MessageCreateData createData = SendableDiscordMessageUtil.toJDASend(message);
CompletableFuture<R> createRequest;
if (message.isWebhookMessage()) {
createRequest = queryWebhookClient().thenApply(client -> (R) client.sendMessage(createData));
} else {
createRequest = CompletableFuture.completedFuture(((R) channel.sendMessage(createData)));
}
return createRequest
.thenApply(action -> {
for (Map.Entry<String, InputStream> entry : attachments.entrySet()) {
action = action.addFiles(FileUpload.fromData(entry.getValue(), entry.getKey()));
action = (R) action.addFiles(FileUpload.fromData(entry.getValue(), entry.getKey()));
}
return action;
});
})
.thenCompose(RestAction::submit)
.thenApply(msg -> ReceivedDiscordMessageImpl.fromJDA(discordSRV, msg));
}
@Override
public @NotNull CompletableFuture<ReceivedDiscordMessage> editMessageById(long id, @NotNull SendableDiscordMessage message) {
return message(
message,
(client, msg) -> client.edit(id, msg.build()),
(textChannel, msg) -> textChannel.editMessageById(id, SendableDiscordMessageUtil.toJDAEdit(msg))
);
public @NotNull CompletableFuture<ReceivedDiscordMessage> editMessageById(
long id,
@NotNull SendableDiscordMessage message,
@Nullable Map<String, InputStream> attachments
) {
return editInternal(id, message, attachments);
}
private CompletableFuture<ReceivedDiscordMessage> message(
@SuppressWarnings("unchecked") // Generics
private <R extends MessageEditRequest<? extends MessageEditRequest<?>> & RestAction<Message>> CompletableFuture<ReceivedDiscordMessage> editInternal(
long id,
SendableDiscordMessage message,
BiFunction<WebhookClient, WebhookMessageBuilder, CompletableFuture<ReadonlyMessage>> webhookFunction,
BiFunction<T, SendableDiscordMessage, FluentRestAction<? extends Message, ?>> jdaFunction) {
return discordSRV.discordAPI().mapExceptions(() -> {
CompletableFuture<ReceivedDiscordMessage> future;
Map<String, InputStream> attachments
) {
MessageEditData editData = SendableDiscordMessageUtil.toJDAEdit(message);
CompletableFuture<R> editRequest;
if (message.isWebhookMessage()) {
future = queryWebhookClient()
.thenCompose(client -> webhookFunction.apply(
client, SendableDiscordMessageUtil.toWebhook(message)))
.thenApply(msg -> ReceivedDiscordMessageImpl.fromWebhook(discordSRV, msg));
editRequest = queryWebhookClient().thenApply(client -> (R) client.editMessageById(id, editData));
} else {
future = jdaFunction
.apply(channel, message)
.submit()
.thenApply(msg -> ReceivedDiscordMessageImpl.fromJDA(discordSRV, msg));
editRequest = CompletableFuture.completedFuture(((R) channel.editMessageById(id, editData)));
}
return future;
});
return editRequest
.thenApply(action -> {
if (attachments != null) {
List<FileUpload> uploads = new ArrayList<>();
for (Map.Entry<String, InputStream> entry : attachments.entrySet()) {
uploads.add(FileUpload.fromData(entry.getValue(), entry.getKey()));
}
action = (R) action.setFiles(uploads);
} else {
action = (R) action.setAttachments(); // TODO
}
return action;
})
.thenCompose(RestAction::submit)
.thenApply(msg -> ReceivedDiscordMessageImpl.fromJDA(discordSRV, msg));
}
@Override
@ -131,7 +157,7 @@ public abstract class AbstractDiscordGuildMessageChannel<T extends GuildMessageC
} else {
future = discordSRV.discordAPI()
.queryWebhookClient(channel.getIdLong())
.thenCompose(client -> client.delete(id));
.thenCompose(client -> client.deleteMessageById(id).submit());
}
return discordSRV.discordAPI().mapExceptions(future);
}

View File

@ -30,6 +30,7 @@ import net.dv8tion.jda.api.entities.channel.concrete.PrivateChannel;
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
import net.dv8tion.jda.api.utils.FileUpload;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.InputStream;
import java.util.Map;
@ -80,7 +81,11 @@ public class DiscordDMChannelImpl extends AbstractDiscordMessageChannel<PrivateC
}
@Override
public @NotNull CompletableFuture<ReceivedDiscordMessage> editMessageById(long id, @NotNull SendableDiscordMessage message) {
public @NotNull CompletableFuture<ReceivedDiscordMessage> editMessageById(
long id,
@NotNull SendableDiscordMessage message,
@Nullable Map<String, InputStream> attachments
) {
if (message.isWebhookMessage()) {
throw new IllegalArgumentException("Cannot send webhook messages to DMChannels");
}

View File

@ -18,12 +18,13 @@
package com.discordsrv.common.discord.api.entity.channel;
import club.minnced.discord.webhook.WebhookClient;
import com.discordsrv.api.discord.entity.channel.DiscordChannelType;
import com.discordsrv.api.discord.entity.channel.DiscordThreadChannel;
import com.discordsrv.api.discord.entity.channel.DiscordThreadContainer;
import com.discordsrv.api.discord.entity.guild.DiscordGuild;
import com.discordsrv.common.DiscordSRV;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.WebhookClient;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.attribute.IThreadContainer;
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
@ -48,10 +49,10 @@ public class DiscordThreadChannelImpl extends AbstractDiscordGuildMessageChannel
}
@Override
public CompletableFuture<WebhookClient> queryWebhookClient() {
public CompletableFuture<WebhookClient<Message>> queryWebhookClient() {
return discordSRV.discordAPI()
.queryWebhookClient(getParentChannel().getId())
.thenApply(client -> client.onThread(getId()));
.thenApply(client -> null/* client.onThread(getId())*/); // TODO
}
@Override

View File

@ -54,7 +54,7 @@ public class ReceivedDiscordMessageClusterImpl implements ReceivedDiscordMessage
public @NotNull CompletableFuture<ReceivedDiscordMessageCluster> editAll(SendableDiscordMessage newMessage) {
List<CompletableFuture<ReceivedDiscordMessage>> futures = new ArrayList<>(messages.size());
for (ReceivedDiscordMessage message : messages) {
futures.add(message.edit(newMessage));
futures.add(message.edit(newMessage, null));
}
return CompletableFutureUtil.combine(futures).thenApply(ReceivedDiscordMessageClusterImpl::new);

View File

@ -18,12 +18,6 @@
package com.discordsrv.common.discord.api.entity.message;
import club.minnced.discord.webhook.WebhookClient;
import club.minnced.discord.webhook.receive.ReadonlyAttachment;
import club.minnced.discord.webhook.receive.ReadonlyEmbed;
import club.minnced.discord.webhook.receive.ReadonlyMessage;
import club.minnced.discord.webhook.send.WebhookEmbed;
import com.discordsrv.api.color.Color;
import com.discordsrv.api.discord.entity.DiscordUser;
import com.discordsrv.api.discord.entity.channel.DiscordDMChannel;
import com.discordsrv.api.discord.entity.channel.DiscordMessageChannel;
@ -44,14 +38,17 @@ import com.discordsrv.common.future.util.CompletableFutureUtil;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.WebhookClient;
import net.dv8tion.jda.api.requests.ErrorResponse;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
@ -71,7 +68,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
boolean self = false;
if (webhookMessage) {
CompletableFuture<WebhookClient> clientFuture = discordSRV.discordAPI()
CompletableFuture<WebhookClient<Message>> clientFuture = discordSRV.discordAPI()
.getCachedClients()
.getIfPresent(channel instanceof DiscordThreadChannel
? ((DiscordThreadChannel) channel).getParentChannel().getId()
@ -79,7 +76,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
);
if (clientFuture != null) {
long clientId = clientFuture.join().getId();
long clientId = clientFuture.join().getId(); // TODO
self = clientId == user.getId();
}
} else {
@ -113,71 +110,6 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
);
}
public static ReceivedDiscordMessage fromWebhook(DiscordSRV discordSRV, ReadonlyMessage webhookMessage) {
List<DiscordMessageEmbed> mappedEmbeds = new ArrayList<>();
for (ReadonlyEmbed embed : webhookMessage.getEmbeds()) {
List<DiscordMessageEmbed.Field> fields = new ArrayList<>();
for (WebhookEmbed.EmbedField field : embed.getFields()) {
fields.add(new DiscordMessageEmbed.Field(field.getName(), field.getValue(), field.isInline()));
}
Integer color = embed.getColor();
WebhookEmbed.EmbedAuthor author = embed.getAuthor();
WebhookEmbed.EmbedTitle title = embed.getTitle();
ReadonlyEmbed.EmbedImage thumbnail = embed.getThumbnail();
ReadonlyEmbed.EmbedImage image = embed.getImage();
WebhookEmbed.EmbedFooter footer = embed.getFooter();
mappedEmbeds.add(new DiscordMessageEmbed(
color != null ? new Color(color) : null,
author != null ? author.getName() : null,
author != null ? author.getUrl() : null,
author != null ? author.getIconUrl() : null,
title != null ? title.getText() : null,
title != null ? title.getUrl() : null,
embed.getDescription(),
fields,
thumbnail != null ? thumbnail.getUrl() : null,
image != null ? image.getUrl() : null,
embed.getTimestamp(),
footer != null ? footer.getText() : null,
footer != null ? footer.getIconUrl() : null
));
}
DiscordMessageChannel channel = discordSRV.discordAPI().getMessageChannelById(
webhookMessage.getChannelId());
DiscordUser user = discordSRV.discordAPI().getUserById(
webhookMessage.getAuthor().getId());
DiscordGuildMember member = channel instanceof DiscordTextChannel && user != null
? ((DiscordTextChannel) channel).getGuild().getMemberById(user.getId()) : null;
List<Attachment> attachments = new ArrayList<>();
for (ReadonlyAttachment attachment : webhookMessage.getAttachments()) {
attachments.add(new Attachment(
attachment.getFileName(),
attachment.getUrl(),
attachment.getProxyUrl(),
attachment.getSize()
));
}
return new ReceivedDiscordMessageImpl(
discordSRV,
attachments,
true, // These are always from rest responses
channel,
null,
member,
user,
webhookMessage.getChannelId(),
webhookMessage.getId(),
webhookMessage.getContent(),
mappedEmbeds,
true
);
}
private final DiscordSRV discordSRV;
private final List<Attachment> attachments;
private final boolean fromSelf;
@ -305,7 +237,10 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
}
@Override
public @NotNull CompletableFuture<ReceivedDiscordMessage> edit(SendableDiscordMessage message) {
public @NotNull CompletableFuture<ReceivedDiscordMessage> edit(
@NotNull SendableDiscordMessage message,
@Nullable Map<String, InputStream> attachments
) {
if (!webhookMessage && message.isWebhookMessage()) {
throw new IllegalArgumentException("Cannot edit a non-webhook message into a webhook message");
}
@ -315,7 +250,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
return CompletableFutureUtil.failed(new RestErrorResponseException(ErrorResponse.UNKNOWN_CHANNEL));
}
return textChannel.editMessageById(getId(), message);
return textChannel.editMessageById(getId(), message, attachments);
}
//

View File

@ -18,7 +18,6 @@
package com.discordsrv.common.discord.api.entity.message.util;
import club.minnced.discord.webhook.send.WebhookMessageBuilder;
import com.discordsrv.api.discord.entity.interaction.component.actionrow.MessageActionRow;
import com.discordsrv.api.discord.entity.message.AllowedMention;
import com.discordsrv.api.discord.entity.message.DiscordMessageEmbed;
@ -91,10 +90,4 @@ public final class SendableDiscordMessageUtil {
.setComponents(actionRows)
.build();
}
public static WebhookMessageBuilder toWebhook(@NotNull SendableDiscordMessage message) {
return WebhookMessageBuilder.fromJDA(null/*toJDA(message)*/) // TODO: lib update? lib replacement?
.setUsername(message.getWebhookUsername())
.setAvatarUrl(message.getWebhookAvatarUrl());
}
}

View File

@ -235,7 +235,7 @@ public class DiscordMessageMirroringModule extends AbstractModule<DiscordSRV> {
}
SendableDiscordMessage sendableMessage = convert(message, channel, reference.config).build();
channel.editMessageById(reference.messageId, sendableMessage).whenComplete((v, t) -> {
channel.editMessageById(reference.messageId, sendableMessage, null).whenComplete((v, t) -> {
if (t != null) {
discordSRV.logger().error("Failed to update mirrored message in " + channel);
}

View File

@ -56,15 +56,12 @@ dependencyResolutionManagement {
library('findbugs-annotations', 'com.google.code.findbugs', 'jsr305').version('3.0.2')
// JDA
library('jda', 'net.dv8tion', 'JDA').version('5.0.0-beta.9')
library('jda', 'net.dv8tion', 'JDA').version('5.0.0-beta.10')
library('okhttp', 'com.squareup.okhttp3', 'okhttp').version {
prefer '3.14.9'
reject '[4,)' // Kotlin
}
// Discord Webhooks
library('webhooks', 'club.minnced', 'discord-webhooks').version('0.8.2')
// Apache commons
library('commons-lang', 'org.apache.commons', 'commons-lang3').version('3.12.0')
library('commons-io', 'commons-io', 'commons-io').version('2.11.0')