Refactor Discord message sending to use MessageForEmbed object and add support for creating threads in targeted channels.

This commit is contained in:
akastijn 2025-11-21 23:39:35 +01:00
parent ec3435dccc
commit 0f11167953
4 changed files with 111 additions and 51 deletions

View File

@ -9,6 +9,7 @@ import com.alttd.altitudeweb.database.litebans.HistoryType;
import com.alttd.altitudeweb.database.litebans.UserType; import com.alttd.altitudeweb.database.litebans.UserType;
import com.alttd.altitudeweb.database.web_db.forms.Appeal; import com.alttd.altitudeweb.database.web_db.forms.Appeal;
import com.alttd.altitudeweb.setup.Connection; import com.alttd.altitudeweb.setup.Connection;
import com.alttd.webinterface.objects.MessageForEmbed;
import com.alttd.webinterface.send_message.DiscordSender; import com.alttd.webinterface.send_message.DiscordSender;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -97,15 +98,9 @@ public class AppealDiscord {
// colorRgb = null (use default), timestamp = appeal.createdAt if available // colorRgb = null (use default), timestamp = appeal.createdAt if available
Instant timestamp = appeal.createdAt() != null ? appeal.createdAt() : Instant.now(); Instant timestamp = appeal.createdAt() != null ? appeal.createdAt() : Instant.now();
DiscordSender.getInstance().sendEmbedToChannels( MessageForEmbed newAppealSubmitted = new MessageForEmbed(
channelIds, "New Appeal Submitted", description, fields, null, timestamp, null);
"New Appeal Submitted", DiscordSender.getInstance().sendEmbedWithThreadToChannels(channelIds, newAppealSubmitted, "Appeal");
description,
fields,
null,
timestamp,
null
);
} }
private CompletableFuture<Integer> getCountAsync(HistoryType type, java.util.UUID uuid) { private CompletableFuture<Integer> getCountAsync(HistoryType type, java.util.UUID uuid) {

View File

@ -5,6 +5,7 @@ import com.alttd.altitudeweb.database.discord.OutputChannel;
import com.alttd.altitudeweb.database.discord.OutputChannelMapper; import com.alttd.altitudeweb.database.discord.OutputChannelMapper;
import com.alttd.altitudeweb.database.web_db.forms.StaffApplication; import com.alttd.altitudeweb.database.web_db.forms.StaffApplication;
import com.alttd.altitudeweb.setup.Connection; import com.alttd.altitudeweb.setup.Connection;
import com.alttd.webinterface.objects.MessageForEmbed;
import com.alttd.webinterface.send_message.DiscordSender; import com.alttd.webinterface.send_message.DiscordSender;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -79,16 +80,15 @@ public class StaffApplicationDiscord {
.toList(); .toList();
Instant timestamp = application.createdAt() != null ? application.createdAt() : Instant.now(); Instant timestamp = application.createdAt() != null ? application.createdAt() : Instant.now();
DiscordSender.getInstance().sendEmbedToChannels( MessageForEmbed messageForEmbed = new MessageForEmbed(
channelIds,
"New Staff Application Submitted", "New Staff Application Submitted",
"Join date: " + (application.joinDate() != null ? application.joinDate().toString() : "unknown") + "Join date: " + (application.joinDate() != null ? application.joinDate().toString() : "unknown") +
"\nSubmitted: " + formatInstant(timestamp), "\nSubmitted: " + formatInstant(timestamp),
fields, fields,
null, null,
timestamp, timestamp,
null null);
); DiscordSender.getInstance().sendEmbedWithThreadToChannels(channelIds, messageForEmbed, "Staff Application");
} }
private String safe(String s) { private String safe(String s) {

View File

@ -0,0 +1,49 @@
package com.alttd.webinterface.objects;
import com.alttd.webinterface.send_message.DiscordSender;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import java.awt.*;
import java.time.Instant;
import java.util.List;
public record MessageForEmbed(String title, String description, List<DiscordSender.EmbedField> fields, Integer colorRgb,
Instant timestamp, String footer) {
public MessageEmbed toEmbed() {
EmbedBuilder eb = new EmbedBuilder();
if (title != null && !title.isBlank()) {
eb.setTitle(title);
}
if (description != null && !description.isBlank()) {
eb.setDescription(description);
}
if (colorRgb != null) {
eb.setColor(new Color(colorRgb));
} else {
eb.setColor(new Color(0xFF8C00)); // default orange
}
eb.setTimestamp(timestamp != null ? timestamp : Instant.now());
if (footer != null && !footer.isBlank()) {
eb.setFooter(footer);
}
if (fields != null) {
for (DiscordSender.EmbedField f : fields) {
if (f == null) {
continue;
}
String name = f.getName() == null ? "" : f.getName();
String value = f.getValue() == null ? "" : f.getValue();
// JDA field value max is 1024; truncate to be safe
if (value.length() > 1024) {
value = value.substring(0, 1021) + "...";
}
eb.addField(new MessageEmbed.Field(name, value, f.isInline()));
}
}
return eb.build();
}
}

View File

@ -1,19 +1,21 @@
package com.alttd.webinterface.send_message; package com.alttd.webinterface.send_message;
import com.alttd.webinterface.bot.DiscordBotInstance; import com.alttd.webinterface.bot.DiscordBotInstance;
import com.alttd.webinterface.objects.MessageForEmbed;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import java.awt.*; import java.util.ArrayList;
import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@Slf4j @Slf4j
public class DiscordSender { public class DiscordSender {
@ -70,12 +72,38 @@ public class DiscordSender {
}); });
} }
public void sendEmbedToChannels(List<Long> channelIds, String title, String description, List<EmbedField> fields, public void sendEmbedWithThreadToChannels(List<Long> channelIds, MessageForEmbed messageForEmbed, String threadName) {
Integer colorRgb, Instant timestamp, String footer) { sendEmbedToChannels(channelIds, messageForEmbed).whenCompleteAsync((result, error) -> {
if (error != null) {
log.error("Failed sending embed to channels", error);
return;
}
result.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(message -> {
message.createThreadChannel(threadName).queue();
});
});
}
public CompletableFuture<List<Optional<Message>>> sendEmbedToChannels(List<Long> channelIds, MessageForEmbed messageForEmbed) {
List<CompletableFuture<Optional<Message>>> futures = new ArrayList<>();
for (Long channelId : channelIds) {
futures.add(sendEmbedToChannel(channelId, messageForEmbed));
}
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v ->
futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
}
public CompletableFuture<Optional<Message>> sendEmbedToChannel(Long channelId, MessageForEmbed messageForEmbed) {
ensureStarted(); ensureStarted();
if (botInstance.getJda() == null) { if (botInstance.getJda() == null) {
log.error("JDA not initialized; cannot send Discord embed."); log.error("JDA not initialized; cannot send Discord embed.");
return; return CompletableFuture.completedFuture(Optional.empty());
} }
try { try {
if (!botInstance.isReady()) { if (!botInstance.isReady()) {
@ -83,43 +111,31 @@ public class DiscordSender {
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
return CompletableFuture.completedFuture(Optional.empty());
} catch (Exception e) { } catch (Exception e) {
log.warn("Error while waiting for JDA ready state", e); log.warn("Error while waiting for JDA ready state", e);
return CompletableFuture.completedFuture(Optional.empty());
} }
EmbedBuilder eb = new EmbedBuilder(); MessageEmbed embed = messageForEmbed.toEmbed();
if (title != null && !title.isBlank()) eb.setTitle(title);
if (description != null && !description.isBlank()) eb.setDescription(description);
if (colorRgb != null) eb.setColor(new Color(colorRgb)); else eb.setColor(new Color(0xFF8C00)); // default orange
eb.setTimestamp(timestamp != null ? timestamp : Instant.now());
if (footer != null && !footer.isBlank()) eb.setFooter(footer);
if (fields != null) { TextChannel channel = botInstance.getJda().getChannelById(TextChannel.class, channelId);
for (EmbedField f : fields) { if (channel == null) {
if (f == null) continue; log.warn("TextChannel with id {} not found when sending embed message", channelId);
String name = f.getName() == null ? "" : f.getName(); return CompletableFuture.completedFuture(Optional.empty());
String value = f.getValue() == null ? "" : f.getValue();
// JDA field value max is 1024; truncate to be safe
if (value.length() > 1024) value = value.substring(0, 1021) + "...";
eb.addField(new MessageEmbed.Field(name, value, f.isInline()));
}
} }
CompletableFuture<Optional<Message>> completableFuture = new CompletableFuture<>();
MessageEmbed embed = eb.build(); channel.sendMessageEmbeds(embed).queue(
message -> {
channelIds.stream() completableFuture.complete(Optional.of(message));
.filter(Objects::nonNull) log.debug("Sent embed to channel {}", channelId);
.forEach(id -> { },
TextChannel channel = botInstance.getJda().getChannelById(TextChannel.class, id); error -> {
if (channel == null) { completableFuture.complete(Optional.empty());
log.warn("TextChannel with id {} not found", id); log.error("Failed sending embed to channel {}", channelId, error);
return; }
} );
channel.sendMessageEmbeds(embed).queue( return completableFuture;
success -> log.debug("Sent embed to channel {}", id),
error -> log.error("Failed sending embed to channel {}", id, error)
);
});
} }
@Data @Data