diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordMessageChannel.java b/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordMessageChannel.java index 27b17b5a..833af82f 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordMessageChannel.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordMessageChannel.java @@ -32,7 +32,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.InputStream; -import java.util.Collections; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -54,21 +53,7 @@ public interface DiscordMessageChannel extends Snowflake { * @return a future returning the message after being sent */ @NotNull - default CompletableFuture sendMessage(@NotNull SendableDiscordMessage message) { - return sendMessage(message, Collections.emptyMap()); - } - - /** - * Sends the provided message to the channel with the provided attachments. - * - * @param message the message to send to the channel - * @param attachments the attachments (in a map of file name and input stream pairs) to include in the message, the streams will be closed upon execution - * @return a future returning the message after being sent - */ - CompletableFuture sendMessage( - @NotNull SendableDiscordMessage message, - @NotNull Map attachments - ); + CompletableFuture sendMessage(@NotNull SendableDiscordMessage message); /** * Deletes the message identified by the id. @@ -87,7 +72,7 @@ public interface DiscordMessageChannel extends Snowflake { * @return a future returning the message after being edited */ @NotNull - CompletableFuture editMessageById(long id, @NotNull SendableDiscordMessage message, @Nullable Map attachments); + CompletableFuture editMessageById(long id, @NotNull SendableDiscordMessage message); /** * Returns the JDA representation of this object. This should not be used if it can be avoided. diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/message/ReceivedDiscordMessage.java b/api/src/main/java/com/discordsrv/api/discord/entity/message/ReceivedDiscordMessage.java index 68856246..e03a143d 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/message/ReceivedDiscordMessage.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/message/ReceivedDiscordMessage.java @@ -35,9 +35,7 @@ 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; /** @@ -151,10 +149,6 @@ public interface ReceivedDiscordMessage extends Snowflake { @NotNull CompletableFuture delete(); - default CompletableFuture edit(@NotNull SendableDiscordMessage message) { - return edit(message, null); - } - /** * Edits this message to the provided message. * @@ -163,8 +157,7 @@ public interface ReceivedDiscordMessage extends Snowflake { * @throws IllegalArgumentException if the message is not a webhook message, * but the provided {@link SendableDiscordMessage} specifies a webhook username. */ - @NotNull - CompletableFuture edit(@NotNull SendableDiscordMessage message, @Nullable Map attachments); + CompletableFuture edit(@NotNull SendableDiscordMessage message); class Attachment { diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/message/SendableDiscordMessage.java b/api/src/main/java/com/discordsrv/api/discord/entity/message/SendableDiscordMessage.java index 973489c6..cbcb3e32 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/message/SendableDiscordMessage.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/message/SendableDiscordMessage.java @@ -31,8 +31,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; +import java.io.InputStream; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; @@ -107,6 +109,8 @@ public interface SendableDiscordMessage { return getWebhookUsername() != null; } + Map getAttachments(); + @SuppressWarnings("UnusedReturnValue") // API interface Builder { @@ -238,6 +242,20 @@ public interface SendableDiscordMessage { @NotNull Builder setWebhookAvatarUrl(String webhookAvatarUrl); + /** + * Adds an attachment to this builder. + * @param inputStream an input stream containing the file contents + * @param fileName the name of the file + * @return the builder, useful for chaining + */ + Builder addAttachment(InputStream inputStream, String fileName); + + /** + * Checks if this builder has any sendable content. + * @return {@code true} if there is no sendable content + */ + boolean isEmpty(); + /** * Builds a {@link SendableDiscordMessage} from this builder. * @return the new {@link SendableDiscordMessage} diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/message/impl/SendableDiscordMessageImpl.java b/api/src/main/java/com/discordsrv/api/discord/entity/message/impl/SendableDiscordMessageImpl.java index 093bbb4d..d66dd286 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/message/impl/SendableDiscordMessageImpl.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/message/impl/SendableDiscordMessageImpl.java @@ -37,6 +37,7 @@ import net.dv8tion.jda.api.entities.MessageEmbed; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.InputStream; import java.util.*; import java.util.function.Function; import java.util.regex.Matcher; @@ -50,6 +51,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage { private final Set allowedMentions; private final String webhookUsername; private final String webhookAvatarUrl; + private final Map attachments; protected SendableDiscordMessageImpl( String content, @@ -57,7 +59,8 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage { List actionRows, Set allowedMentions, String webhookUsername, - String webhookAvatarUrl + String webhookAvatarUrl, + Map attachments ) { this.content = content; this.embeds = Collections.unmodifiableList(embeds); @@ -65,6 +68,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage { this.allowedMentions = Collections.unmodifiableSet(allowedMentions); this.webhookUsername = webhookUsername; this.webhookAvatarUrl = webhookAvatarUrl; + this.attachments = Collections.unmodifiableMap(attachments); } @Override @@ -98,6 +102,11 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage { return webhookAvatarUrl; } + @Override + public Map getAttachments() { + return attachments; + } + public static class BuilderImpl implements SendableDiscordMessage.Builder { private String content; @@ -106,6 +115,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage { private final Set allowedMentions = new LinkedHashSet<>(); private String webhookUsername; private String webhookAvatarUrl; + private final Map attachments = new LinkedHashMap<>(); @Override public String getContent() { @@ -205,9 +215,20 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage { return this; } + @Override + public Builder addAttachment(InputStream inputStream, String fileName) { + this.attachments.put(inputStream, fileName); + return this; + } + + @Override + public boolean isEmpty() { + return (content == null || content.isEmpty()) && embeds.isEmpty() && attachments.isEmpty() && actionRows.isEmpty(); + } + @Override public @NotNull SendableDiscordMessage build() { - return new SendableDiscordMessageImpl(content, embeds, actionRows, allowedMentions, webhookUsername, webhookAvatarUrl); + return new SendableDiscordMessageImpl(content, embeds, actionRows, allowedMentions, webhookUsername, webhookAvatarUrl, attachments); } @Override diff --git a/common/src/main/java/com/discordsrv/common/discord/api/entity/channel/AbstractDiscordGuildMessageChannel.java b/common/src/main/java/com/discordsrv/common/discord/api/entity/channel/AbstractDiscordGuildMessageChannel.java index 6ef90cdb..a26f49c6 100644 --- a/common/src/main/java/com/discordsrv/common/discord/api/entity/channel/AbstractDiscordGuildMessageChannel.java +++ b/common/src/main/java/com/discordsrv/common/discord/api/entity/channel/AbstractDiscordGuildMessageChannel.java @@ -29,18 +29,12 @@ 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.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; public abstract class AbstractDiscordGuildMessageChannel @@ -79,14 +73,12 @@ public abstract class AbstractDiscordGuildMessageChannel sendMessage( - @NotNull SendableDiscordMessage message, @NotNull Map attachments - ) { - return sendInternal(message, attachments); + public @NotNull CompletableFuture sendMessage(@NotNull SendableDiscordMessage message) { + return sendInternal(message); } @SuppressWarnings("unchecked") // Generics - private > & RestAction> CompletableFuture sendInternal(SendableDiscordMessage message, Map attachments) { + private > & RestAction> CompletableFuture sendInternal(SendableDiscordMessage message) { MessageCreateData createData = SendableDiscordMessageUtil.toJDASend(message); CompletableFuture createRequest; @@ -101,12 +93,6 @@ public abstract class AbstractDiscordGuildMessageChannel { - for (Map.Entry entry : attachments.entrySet()) { - action = (R) action.addFiles(FileUpload.fromData(entry.getValue(), entry.getKey())); - } - return action; - }) .thenCompose(RestAction::submit) .thenApply(msg -> ReceivedDiscordMessageImpl.fromJDA(discordSRV, msg)); } @@ -114,17 +100,15 @@ public abstract class AbstractDiscordGuildMessageChannel editMessageById( long id, - @NotNull SendableDiscordMessage message, - @Nullable Map attachments + @NotNull SendableDiscordMessage message ) { - return editInternal(id, message, attachments); + return editInternal(id, message); } @SuppressWarnings("unchecked") // Generics private > & RestAction> CompletableFuture editInternal( long id, - SendableDiscordMessage message, - Map attachments + SendableDiscordMessage message ) { MessageEditData editData = SendableDiscordMessageUtil.toJDAEdit(message); @@ -136,19 +120,6 @@ public abstract class AbstractDiscordGuildMessageChannel { - if (attachments != null) { - List uploads = new ArrayList<>(); - for (Map.Entry 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)); } diff --git a/common/src/main/java/com/discordsrv/common/discord/api/entity/channel/DiscordDMChannelImpl.java b/common/src/main/java/com/discordsrv/common/discord/api/entity/channel/DiscordDMChannelImpl.java index a649afd5..4e428f58 100644 --- a/common/src/main/java/com/discordsrv/common/discord/api/entity/channel/DiscordDMChannelImpl.java +++ b/common/src/main/java/com/discordsrv/common/discord/api/entity/channel/DiscordDMChannelImpl.java @@ -28,12 +28,8 @@ import com.discordsrv.common.discord.api.entity.message.util.SendableDiscordMess import net.dv8tion.jda.api.entities.User; 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; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -53,18 +49,12 @@ public class DiscordDMChannelImpl extends AbstractDiscordMessageChannel sendMessage( - @NotNull SendableDiscordMessage message, - @NotNull Map attachments - ) { + public CompletableFuture sendMessage(@NotNull SendableDiscordMessage message) { if (message.isWebhookMessage()) { throw new IllegalArgumentException("Cannot send webhook messages to DMChannels"); } MessageCreateAction action = channel.sendMessage(SendableDiscordMessageUtil.toJDASend(message)); - for (Map.Entry entry : attachments.entrySet()) { - action = action.addFiles(FileUpload.fromData(entry.getValue(), entry.getKey())); - } CompletableFuture future = action.submit() .thenApply(msg -> ReceivedDiscordMessageImpl.fromJDA(discordSRV, msg)); @@ -83,8 +73,7 @@ public class DiscordDMChannelImpl extends AbstractDiscordMessageChannel editMessageById( long id, - @NotNull SendableDiscordMessage message, - @Nullable Map attachments + @NotNull SendableDiscordMessage message ) { if (message.isWebhookMessage()) { throw new IllegalArgumentException("Cannot send webhook messages to DMChannels"); diff --git a/common/src/main/java/com/discordsrv/common/discord/api/entity/message/ReceivedDiscordMessageClusterImpl.java b/common/src/main/java/com/discordsrv/common/discord/api/entity/message/ReceivedDiscordMessageClusterImpl.java index 8688e8c0..bace4108 100644 --- a/common/src/main/java/com/discordsrv/common/discord/api/entity/message/ReceivedDiscordMessageClusterImpl.java +++ b/common/src/main/java/com/discordsrv/common/discord/api/entity/message/ReceivedDiscordMessageClusterImpl.java @@ -54,7 +54,7 @@ public class ReceivedDiscordMessageClusterImpl implements ReceivedDiscordMessage public @NotNull CompletableFuture editAll(SendableDiscordMessage newMessage) { List> futures = new ArrayList<>(messages.size()); for (ReceivedDiscordMessage message : messages) { - futures.add(message.edit(newMessage, null)); + futures.add(message.edit(newMessage)); } return CompletableFutureUtil.combine(futures).thenApply(ReceivedDiscordMessageClusterImpl::new); diff --git a/common/src/main/java/com/discordsrv/common/discord/api/entity/message/ReceivedDiscordMessageImpl.java b/common/src/main/java/com/discordsrv/common/discord/api/entity/message/ReceivedDiscordMessageImpl.java index 4e10cff0..9b59a2e7 100644 --- a/common/src/main/java/com/discordsrv/common/discord/api/entity/message/ReceivedDiscordMessageImpl.java +++ b/common/src/main/java/com/discordsrv/common/discord/api/entity/message/ReceivedDiscordMessageImpl.java @@ -45,10 +45,8 @@ 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 { @@ -238,8 +236,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage { @Override public @NotNull CompletableFuture edit( - @NotNull SendableDiscordMessage message, - @Nullable Map attachments + @NotNull SendableDiscordMessage message ) { if (!webhookMessage && message.isWebhookMessage()) { throw new IllegalArgumentException("Cannot edit a non-webhook message into a webhook message"); @@ -250,7 +247,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage { return CompletableFutureUtil.failed(new RestErrorResponseException(ErrorResponse.UNKNOWN_CHANNEL)); } - return textChannel.editMessageById(getId(), message, attachments); + return textChannel.editMessageById(getId(), message); } // diff --git a/common/src/main/java/com/discordsrv/common/discord/api/entity/message/util/SendableDiscordMessageUtil.java b/common/src/main/java/com/discordsrv/common/discord/api/entity/message/util/SendableDiscordMessageUtil.java index e945afd5..fd81c9f3 100644 --- a/common/src/main/java/com/discordsrv/common/discord/api/entity/message/util/SendableDiscordMessageUtil.java +++ b/common/src/main/java/com/discordsrv/common/discord/api/entity/message/util/SendableDiscordMessageUtil.java @@ -25,11 +25,14 @@ import com.discordsrv.api.discord.entity.message.SendableDiscordMessage; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.utils.FileUpload; import net.dv8tion.jda.api.utils.messages.*; import org.jetbrains.annotations.NotNull; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Set; public final class SendableDiscordMessageUtil { @@ -61,12 +64,18 @@ public final class SendableDiscordMessageUtil { embeds.add(embed.toJDA()); } + List uploads = new ArrayList<>(); + for (Map.Entry attachment : message.getAttachments().entrySet()) { + uploads.add(FileUpload.fromData(attachment.getKey(), attachment.getValue())); + } + return (T) builder .setContent(message.getContent()) .setEmbeds(embeds) .setAllowedMentions(allowedTypes) .mentionUsers(allowedUsers.stream().mapToLong(l -> l).toArray()) - .mentionRoles(allowedRoles.stream().mapToLong(l -> l).toArray()); + .mentionRoles(allowedRoles.stream().mapToLong(l -> l).toArray()) + .setFiles(uploads); } public static MessageCreateData toJDASend(@NotNull SendableDiscordMessage message) { diff --git a/common/src/main/java/com/discordsrv/common/messageforwarding/discord/DiscordMessageMirroringModule.java b/common/src/main/java/com/discordsrv/common/messageforwarding/discord/DiscordMessageMirroringModule.java index 40e2c181..d74e7bfa 100644 --- a/common/src/main/java/com/discordsrv/common/messageforwarding/discord/DiscordMessageMirroringModule.java +++ b/common/src/main/java/com/discordsrv/common/messageforwarding/discord/DiscordMessageMirroringModule.java @@ -121,7 +121,7 @@ public class DiscordMessageMirroringModule extends AbstractModule { } MirroringConfig.AttachmentConfig attachmentConfig = config.attachments; - int maxSize = attachmentConfig.maximumSizeKb; + int maxSize = attachmentConfig.maximumSizeKb * 1000; boolean embedAttachments = attachmentConfig.embedAttachments; if (maxSize >= 0 || embedAttachments) { for (ReceivedDiscordMessage.Attachment attachment : message.getAttachments()) { @@ -129,10 +129,11 @@ public class DiscordMessageMirroringModule extends AbstractModule { continue; } - if (maxSize == 0 || attachment.sizeBytes() <= (maxSize * 1000)) { + if (maxSize == 0 || attachment.sizeBytes() <= maxSize) { Request request = new Request.Builder() - .url(attachment.proxyUrl()) + .url(attachment.url()) .get() + .addHeader("Accept", "*/*") .build(); byte[] bytes = null; @@ -187,26 +188,40 @@ public class DiscordMessageMirroringModule extends AbstractModule { messageBuilder.addEmbed(attachmentEmbed.build()); } - int maxSize = attachmentConfig.maximumSizeKb; - Map currentAttachments; - if (!attachments.isEmpty() && maxSize > 0) { - currentAttachments = new LinkedHashMap<>(); + int maxSize = attachmentConfig.maximumSizeKb * 1000; + List streams = new ArrayList<>(); + if (!attachments.isEmpty() && maxSize >= 0) { attachments.forEach((attachment, bytes) -> { - if (bytes != null && attachment.sizeBytes() <= maxSize) { - currentAttachments.put(attachment.fileName(), new ByteArrayInputStream(bytes)); + if (bytes != null && (maxSize == 0 || attachment.sizeBytes() <= maxSize)) { + InputStream stream = new ByteArrayInputStream(bytes); + streams.add(stream); + messageBuilder.addAttachment(stream, attachment.fileName()); } }); - } else { - currentAttachments = Collections.emptyMap(); + } + + if (messageBuilder.isEmpty()) { + logger().debug("Nothing to mirror to " + mirrorChannel + ", skipping"); + for (InputStream stream : streams) { + try { + stream.close(); + } catch (IOException ignored) {} + } + return; } CompletableFuture future = - mirrorChannel.sendMessage(messageBuilder.build(), currentAttachments) + mirrorChannel.sendMessage(messageBuilder.build()) .thenApply(msg -> new MirroredMessage(msg, config)); mirrorFutures.add(future); future.exceptionally(t2 -> { logger().error("Failed to mirror message to " + mirrorChannel, t2); + for (InputStream stream : streams) { + try { + stream.close(); + } catch (IOException ignored) {} + } return null; }); } @@ -249,7 +264,7 @@ public class DiscordMessageMirroringModule extends AbstractModule { } SendableDiscordMessage sendableMessage = convert(message, channel, reference.config).build(); - channel.editMessageById(reference.messageId, sendableMessage, null).exceptionally(t -> { + channel.editMessageById(reference.messageId, sendableMessage).exceptionally(t -> { logger().error("Failed to update mirrored message in " + channel); return null; });