Add Discord bot support for sending appeals to specified channels and integrate with appeal flow

This commit is contained in:
2025-08-23 23:51:45 +02:00
parent 0b4c1ccebf
commit ffddffa8dc
5 changed files with 224 additions and 2 deletions
@@ -1,22 +1,34 @@
package com.alttd.webinterface.bot;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.requests.GatewayIntent;
@Slf4j
public class DiscordBotInstance {
@Getter
private JDA jda;
private volatile boolean ready = false;
public void start(String token) {
public synchronized void start(String token) {
if (jda != null) {
return;
}
jda = JDABuilder.createDefault(token,
GatewayIntent.GUILD_MEMBERS,
GatewayIntent.GUILD_PRESENCES,
GatewayIntent.GUILD_MESSAGES,
GatewayIntent.MESSAGE_CONTENT)
.build();
.addEventListeners(new ReadyListener(() -> {
ready = true;
}))
.build();
}
public boolean isReady() {
return ready && jda != null && jda.getStatus() == JDA.Status.CONNECTED;
}
}
@@ -0,0 +1,64 @@
package com.alttd.webinterface.bot;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@Slf4j
public class DiscordSender {
private static final DiscordSender INSTANCE = new DiscordSender();
private final DiscordBotInstance botInstance = new DiscordBotInstance();
private DiscordSender() {}
public static DiscordSender getInstance() {
return INSTANCE;
}
private void ensureStarted() {
if (botInstance.getJda() != null) return;
String token = Optional.ofNullable(System.getenv("DISCORD_TOKEN"))
.orElse(System.getProperty("DISCORD_TOKEN"));
if (token == null || token.isBlank()) {
log.error("Discord token not found. Set DISCORD_TOKEN as an environment variable or system property.");
return;
}
botInstance.start(token);
}
public void sendMessageToChannels(List<Long> channelIds, String message) {
ensureStarted();
if (botInstance.getJda() == null) {
log.error("JDA not initialized; cannot send Discord message.");
return;
}
try {
if (!botInstance.isReady()) {
botInstance.getJda().awaitReady();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
log.warn("Error while waiting for JDA ready state", e);
}
channelIds.stream()
.filter(Objects::nonNull)
.forEach(id -> {
TextChannel channel = botInstance.getJda().getChannelById(TextChannel.class, id);
if (channel == null) {
log.warn("TextChannel with id {} not found", id);
return;
}
channel.sendMessage(message).queue(
success -> log.debug("Sent message to channel {}", id),
error -> log.error("Failed sending message to channel {}", id, error)
);
});
}
}
@@ -0,0 +1,26 @@
package com.alttd.webinterface.bot;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.events.session.ReadyEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
@Slf4j
@RequiredArgsConstructor
public class ReadyListener extends ListenerAdapter {
private final Runnable onReadyCallback;
@Override
public void onReady(@NotNull ReadyEvent event) {
log.info("JDA is ready. Guilds loaded: {}", event.getJDA().getGuilds().size());
if (onReadyCallback != null) {
try {
onReadyCallback.run();
} catch (Exception e) {
log.error("Error running onReady callback", e);
}
}
}
}