refactor: move the discord message handler into a separate class to make the mess only live in that class

refactor: use ConcurrentHashMap instead of HashMap in the discord message queue
This commit is contained in:
ChomeNS
2025-04-11 14:08:39 +07:00
parent 9e486ebbe1
commit 359cf9a19c
4 changed files with 409 additions and 371 deletions

View File

@@ -230,7 +230,7 @@ public class Main {
} catch (final InterruptedException ignored) { }
}
discord.jda.shutdown();
if (discord != null && discord.jda != null) discord.jda.shutdown();
}
if (callSystemExit) System.exit(exitCode);

View File

@@ -0,0 +1,357 @@
package me.chayapak1.chomens_bot.discord;
import me.chayapak1.chomens_bot.Bot;
import me.chayapak1.chomens_bot.Main;
import me.chayapak1.chomens_bot.command.contexts.DiscordCommandContext;
import me.chayapak1.chomens_bot.util.ChatMessageUtilities;
import me.chayapak1.chomens_bot.util.ComponentUtilities;
import net.dv8tion.jda.api.JDA;
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.Role;
import net.dv8tion.jda.api.entities.messages.MessageSnapshot;
import net.dv8tion.jda.api.entities.sticker.StickerItem;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class GuildMessageEventHandler extends ListenerAdapter {
private final String prefix;
private final Component messagePrefix;
private final JDA jda;
public GuildMessageEventHandler (
final JDA jda,
final String prefix,
final Component messagePrefix
) {
this.jda = jda;
this.prefix = prefix;
this.messagePrefix = messagePrefix;
jda.addEventListener(this);
}
@Override
public void onMessageReceived (@NotNull final MessageReceivedEvent event) {
if (event.getAuthor().getId().equals(jda.getSelfUser().getId())) return;
for (final Bot bot : Main.bots) {
final String channelId = Main.discord.servers.get(bot.getServerString(true));
if (!bot.loggedIn || !event.getChannel().getId().equals(channelId)) continue;
final Message messageObject = event.getMessage();
final String messageString = messageObject.getContentDisplay();
if (messageString.startsWith(prefix)) {
final DiscordCommandContext context = new DiscordCommandContext(bot, prefix, event);
bot.commandHandler.executeCommand(messageString.substring(prefix.length()), context, event);
return;
}
final boolean isForwarded = !messageObject.getMessageSnapshots().isEmpty();
Component output = Component.empty();
if (isForwarded) {
for (final MessageSnapshot snapshot : messageObject.getMessageSnapshots()) {
final List<Component> extraComponents = new ArrayList<>();
addExtraComponents(
extraComponents,
snapshot.getAttachments(),
snapshot.getEmbeds(),
snapshot.getStickers(),
bot
);
final String replacedMessageContent = replaceMessageContent(snapshot.getContentRaw());
Component messageComponent = Component.text(replacedMessageContent);
if (!extraComponents.isEmpty()) {
if (!replacedMessageContent.isBlank())
messageComponent = messageComponent.append(Component.space());
messageComponent = messageComponent
.append(
Component.join(
JoinConfiguration.spaces(),
extraComponents
)
);
}
output = Component
.translatable(
"[%s] %s %s",
this.messagePrefix,
Component
.translatable(
"%s forwarded",
Component.text(
messageObject.getMember() == null ?
messageObject.getAuthor().getName() :
messageObject.getMember().getEffectiveName()
).color(NamedTextColor.RED)
)
.color(NamedTextColor.GRAY),
messageComponent.color(NamedTextColor.GRAY)
)
.color(NamedTextColor.DARK_GRAY);
}
} else {
final Message reference = event.getMessage().getReferencedMessage();
if (reference != null) {
output = output
.append(
Component.empty()
.append(Component.text("Replying to ").color(NamedTextColor.GRAY))
.append(getMessageComponent(bot, reference))
.decorate(TextDecoration.ITALIC)
)
.append(Component.newline());
}
output = output.append(getMessageComponent(bot, event.getMessage()));
}
bot.chat.tellraw(output);
}
}
private Component getMessageComponent (
final Bot bot,
final Message message
) {
final List<Component> extraComponents = new ArrayList<>();
addExtraComponents(
extraComponents,
message.getAttachments(),
message.getEmbeds(),
message.getStickers(),
bot
);
final String username = message.getAuthor().getName();
final Member member = message.getMember();
final String displayName = member == null ? username : member.getEffectiveName();
final List<Role> roles = member == null ? Collections.emptyList() : member.getRoles();
Component rolesComponent = Component.empty();
if (!roles.isEmpty()) {
rolesComponent = rolesComponent
.append(Component.text("Roles:").color(NamedTextColor.GRAY))
.append(Component.newline());
final List<Component> rolesList = new ArrayList<>();
for (final Role role : roles) {
final Color color = role.getColor();
rolesList.add(
Component
.text(role.getName())
.color(
color == null ?
NamedTextColor.WHITE :
TextColor.color(
color.getRed(),
color.getGreen(),
color.getBlue()
)
)
);
}
rolesComponent = rolesComponent.append(Component.join(JoinConfiguration.newlines(), rolesList));
} else {
rolesComponent = rolesComponent.append(Component.text("No roles").color(NamedTextColor.GRAY));
}
Component nameComponent = Component
.text(displayName)
.clickEvent(ClickEvent.copyToClipboard(username))
.hoverEvent(
HoverEvent.showText(
Component.translatable(
"""
%s
%s
%s""",
Component.text(username).color(NamedTextColor.WHITE),
rolesComponent,
Component.text("Click here to copy the tag to your clipboard").color(NamedTextColor.GREEN)
).color(NamedTextColor.DARK_GRAY)
)
);
for (final Role role : roles) {
final Color color = role.getColor();
if (color == null) continue;
nameComponent = nameComponent.color(
TextColor.color(
color.getRed(),
color.getGreen(),
color.getBlue()
)
);
break;
}
if (nameComponent.color() == null) nameComponent = nameComponent.color(NamedTextColor.RED);
final String replacedMessageContent = replaceMessageContent(message.getContentDisplay());
Component actualMessage = ChatMessageUtilities.applyChatMessageStyling(replacedMessageContent);
if (!extraComponents.isEmpty()) {
if (!replacedMessageContent.isBlank()) actualMessage = actualMessage.append(Component.space());
actualMessage = actualMessage
.append(
Component.join(
JoinConfiguration.spaces(),
extraComponents
)
);
}
final Component messageComponent = Component.empty()
.color(NamedTextColor.GRAY)
.append(actualMessage)
.hoverEvent(
HoverEvent.showText(
Component
.text("Click here to copy the message to your clipboard")
.color(NamedTextColor.GREEN)
)
)
.clickEvent(
ClickEvent.copyToClipboard(
replacedMessageContent
)
);
return Component.translatable(
"[%s] %s %s",
messagePrefix,
nameComponent,
messageComponent
).color(NamedTextColor.DARK_GRAY);
}
private String replaceMessageContent (final String content) {
return ComponentUtilities
// replaces the ANSI codes (from the bot) with section signs corresponding to the color/style
.deserializeFromDiscordAnsi(content)
// replaces skull emoji
.replace("\uD83D\uDC80", "")
// replaces all ZWSP with nothing
.replace("\u200b", "");
}
private void addExtraComponents (
final List<Component> extraComponents,
final List<Message.Attachment> attachments,
final List<MessageEmbed> embeds,
final List<StickerItem> stickers,
final Bot bot
) {
addAttachmentsComponent(attachments, extraComponents);
addEmbedsComponent(embeds, extraComponents, bot);
addStickersComponent(stickers, extraComponents);
}
private void addAttachmentsComponent (final List<Message.Attachment> attachments, final List<Component> extraComponents) {
for (final Message.Attachment attachment : attachments) {
extraComponents.add(
Component
.text("[Attachment]")
.clickEvent(ClickEvent.openUrl(attachment.getUrl()))
.hoverEvent(
HoverEvent.showText(
Component
.text(attachment.getFileName())
.color(NamedTextColor.GREEN)
)
)
.color(NamedTextColor.GREEN)
);
}
}
private void addEmbedsComponent (final List<MessageEmbed> embeds, final List<Component> extraComponents, final Bot bot) {
for (final MessageEmbed embed : embeds) {
final Component hoverEvent = Component.translatable(
"""
Title: %s
%s""",
embed.getTitle() == null ?
Component.text("No title").color(NamedTextColor.GRAY) :
Component.text(embed.getTitle()).color(bot.colorPalette.string),
embed.getDescription() == null ?
Component.text("No description").color(NamedTextColor.GRAY) :
Component.text(embed.getDescription()).color(NamedTextColor.WHITE)
).color(NamedTextColor.GREEN);
extraComponents.add(
Component
.text("[Embed]")
.hoverEvent(HoverEvent.showText(hoverEvent))
.color(NamedTextColor.GREEN)
);
}
}
private void addStickersComponent (final List<StickerItem> stickers, final List<Component> extraComponents) {
for (final StickerItem sticker : stickers) {
extraComponents.add(
Component
.translatable(
"[%s]",
Component
.text(sticker.getName())
.hoverEvent(
HoverEvent.showText(
Component
.text(sticker.getId())
.color(NamedTextColor.GREEN)
)
)
.clickEvent(
ClickEvent.openUrl(
sticker.getIconUrl()
)
)
)
.color(NamedTextColor.GREEN)
);
}
}
}

View File

@@ -3,40 +3,30 @@ package me.chayapak1.chomens_bot.plugins;
import me.chayapak1.chomens_bot.Bot;
import me.chayapak1.chomens_bot.Configuration;
import me.chayapak1.chomens_bot.Main;
import me.chayapak1.chomens_bot.command.contexts.DiscordCommandContext;
import me.chayapak1.chomens_bot.util.ChatMessageUtilities;
import me.chayapak1.chomens_bot.discord.GuildMessageEventHandler;
import me.chayapak1.chomens_bot.util.CodeBlockUtilities;
import me.chayapak1.chomens_bot.util.ComponentUtilities;
import me.chayapak1.chomens_bot.util.LoggerUtilities;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.messages.MessageSnapshot;
import net.dv8tion.jda.api.entities.sticker.StickerItem;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.geysermc.mcprotocollib.network.event.session.ConnectedEvent;
import org.geysermc.mcprotocollib.network.event.session.DisconnectedEvent;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
// this is one of the classes which has >500 lines LMAO
public class DiscordPlugin extends ListenerAdapter {
public class DiscordPlugin {
public JDA jda;
public final Map<String, String> servers;
@@ -65,6 +55,7 @@ public class DiscordPlugin extends ListenerAdapter {
final JDABuilder builder = JDABuilder.createDefault(config.discord.token);
builder.enableIntents(GatewayIntent.MESSAGE_CONTENT);
builder.setEnableShutdownHook(false);
try {
jda = builder.build();
jda.awaitReady();
@@ -74,7 +65,7 @@ public class DiscordPlugin extends ListenerAdapter {
jda.getPresence().setPresence(Activity.playing(config.discord.statusMessage), false);
jda.addEventListener(this);
new GuildMessageEventHandler(jda, prefix, messagePrefix);
for (final Bot bot : Main.bots) {
final String channelId = servers.get(bot.getServerString(true));
@@ -149,329 +140,21 @@ public class DiscordPlugin extends ListenerAdapter {
}
}
@Override
public void onMessageReceived (@NotNull final MessageReceivedEvent event) {
for (final Bot bot : Main.bots) {
final String channelId = servers.get(bot.getServerString(true));
if (
!bot.loggedIn ||
!event.getChannel().getId().equals(channelId) ||
event.getAuthor().getId().equals(jda.getSelfUser().getId())
) continue;
final Message messageEvent = event.getMessage();
final String message = messageEvent.getContentDisplay();
if (message.startsWith(prefix)) {
final DiscordCommandContext context = new DiscordCommandContext(bot, prefix, event);
bot.commandHandler.executeCommand(message.substring(prefix.length()), context, event);
return;
}
final boolean isForwarded = !messageEvent.getMessageSnapshots().isEmpty();
Component output = Component.empty();
if (isForwarded) {
for (final MessageSnapshot snapshot : messageEvent.getMessageSnapshots()) {
final List<Component> extraComponents = new ArrayList<>();
addExtraComponents(
extraComponents,
snapshot.getAttachments(),
snapshot.getEmbeds(),
snapshot.getStickers(),
bot
);
Component messageComponent = Component.text(replaceMessageContent(snapshot.getContentRaw()));
if (!extraComponents.isEmpty()) {
messageComponent = messageComponent
.append(
Component.join(
JoinConfiguration.spaces(),
extraComponents
)
);
}
output = Component
.translatable(
"[%s] %s %s",
this.messagePrefix,
Component
.translatable(
"%s forwarded",
Component.text(
messageEvent.getMember() == null ?
messageEvent.getAuthor().getName() :
messageEvent.getMember().getEffectiveName()
).color(NamedTextColor.RED)
)
.color(NamedTextColor.GRAY),
messageComponent.color(NamedTextColor.GRAY)
)
.color(NamedTextColor.DARK_GRAY);
}
} else {
final Message reference = event.getMessage().getReferencedMessage();
if (reference != null) {
output = output
.append(
Component.empty()
.append(Component.text("Replying to ").color(NamedTextColor.GRAY))
.append(getMessageComponent(bot, reference))
.decorate(TextDecoration.ITALIC)
)
.append(Component.newline());
}
output = output.append(getMessageComponent(bot, event.getMessage()));
}
bot.chat.tellraw(output);
}
}
private Component getMessageComponent (
final Bot bot,
final Message message
) {
final List<Component> extraComponents = new ArrayList<>();
addExtraComponents(
extraComponents,
message.getAttachments(),
message.getEmbeds(),
message.getStickers(),
bot
);
final String username = message.getAuthor().getName();
final Member member = message.getMember();
final String displayName = member == null ? username : member.getEffectiveName();
final List<Role> roles = member == null ? Collections.emptyList() : member.getRoles();
Component rolesComponent = Component.empty();
if (!roles.isEmpty()) {
rolesComponent = rolesComponent
.append(Component.text("Roles:").color(NamedTextColor.GRAY))
.append(Component.newline());
final List<Component> rolesList = new ArrayList<>();
for (final Role role : roles) {
final Color color = role.getColor();
rolesList.add(
Component
.text(role.getName())
.color(
color == null ?
NamedTextColor.WHITE :
TextColor.color(
color.getRed(),
color.getGreen(),
color.getBlue()
)
)
);
}
rolesComponent = rolesComponent.append(Component.join(JoinConfiguration.newlines(), rolesList));
} else {
rolesComponent = rolesComponent.append(Component.text("No roles").color(NamedTextColor.GRAY));
}
Component nameComponent = Component
.text(displayName)
.clickEvent(ClickEvent.copyToClipboard(username))
.hoverEvent(
HoverEvent.showText(
Component.translatable(
"""
%s
%s
%s""",
Component.text(username).color(NamedTextColor.WHITE),
rolesComponent,
Component.text("Click here to copy the tag to your clipboard").color(NamedTextColor.GREEN)
).color(NamedTextColor.DARK_GRAY)
)
);
for (final Role role : roles) {
final Color color = role.getColor();
if (color == null) continue;
nameComponent = nameComponent.color(
TextColor.color(
color.getRed(),
color.getGreen(),
color.getBlue()
)
);
break;
}
if (nameComponent.color() == null) nameComponent = nameComponent.color(NamedTextColor.RED);
final String replacedMessageContent = replaceMessageContent(message.getContentDisplay());
Component actualMessage = ChatMessageUtilities.applyChatMessageStyling(replacedMessageContent);
if (!extraComponents.isEmpty()) {
if (!replacedMessageContent.isBlank()) actualMessage = actualMessage.append(Component.space());
actualMessage = actualMessage
.append(
Component.join(
JoinConfiguration.spaces(),
extraComponents
)
);
}
final Component messageComponent = Component.empty()
.color(NamedTextColor.GRAY)
.append(actualMessage)
.hoverEvent(
HoverEvent.showText(
Component
.text("Click here to copy the message to your clipboard")
.color(NamedTextColor.GREEN)
)
)
.clickEvent(
ClickEvent.copyToClipboard(
replacedMessageContent
)
);
return Component.translatable(
"[%s] %s %s",
messagePrefix,
nameComponent,
messageComponent
).color(NamedTextColor.DARK_GRAY);
}
private String replaceMessageContent (final String content) {
return ComponentUtilities
// replaces the ANSI codes (from the bot) with section signs corresponding to the color/style
.deserializeFromDiscordAnsi(content)
// replaces skull emoji
.replace("\uD83D\uDC80", "")
// replaces all ZWSP with nothing
.replace("\u200b", "");
}
private void addExtraComponents (
final List<Component> extraComponents,
final List<Message.Attachment> attachments,
final List<MessageEmbed> embeds,
final List<StickerItem> stickers,
final Bot bot
) {
addAttachmentsComponent(attachments, extraComponents);
addEmbedsComponent(embeds, extraComponents, bot);
addStickersComponent(stickers, extraComponents);
}
private void addAttachmentsComponent (final List<Message.Attachment> attachments, final List<Component> extraComponents) {
for (final Message.Attachment attachment : attachments) {
extraComponents.add(
Component
.text("[Attachment]")
.clickEvent(ClickEvent.openUrl(attachment.getUrl()))
.hoverEvent(
HoverEvent.showText(
Component
.text(attachment.getFileName())
.color(NamedTextColor.GREEN)
)
)
.color(NamedTextColor.GREEN)
);
}
}
private void addEmbedsComponent (final List<MessageEmbed> embeds, final List<Component> extraComponents, final Bot bot) {
for (final MessageEmbed embed : embeds) {
final Component hoverEvent = Component.translatable(
"""
Title: %s
%s""",
embed.getTitle() == null ?
Component.text("No title").color(NamedTextColor.GRAY) :
Component.text(embed.getTitle()).color(bot.colorPalette.string),
embed.getDescription() == null ?
Component.text("No description").color(NamedTextColor.GRAY) :
Component.text(embed.getDescription()).color(NamedTextColor.WHITE)
).color(NamedTextColor.GREEN);
extraComponents.add(
Component
.text("[Embed]")
.hoverEvent(HoverEvent.showText(hoverEvent))
.color(NamedTextColor.GREEN)
);
}
}
private void addStickersComponent (final List<StickerItem> stickers, final List<Component> extraComponents) {
for (final StickerItem sticker : stickers) {
extraComponents.add(
Component
.translatable(
"[%s]",
Component
.text(sticker.getName())
.hoverEvent(
HoverEvent.showText(
Component
.text(sticker.getId())
.color(NamedTextColor.GREEN)
)
)
.clickEvent(
ClickEvent.openUrl(
sticker.getIconUrl()
)
)
)
.color(NamedTextColor.GREEN)
);
}
}
// totallynotskidded™ from HBot (and changed a bit)
final Map<String, StringBuilder> logMessages = new HashMap<>();
final Map<String, Long> nextLogTimes = new HashMap<>();
final Map<String, Boolean> doneSendingInLogs = new HashMap<>();
// based from HBot (and modified quite a bit)
private final Map<String, StringBuilder> logMessages = new ConcurrentHashMap<>();
private final Map<String, Long> nextLogTimes = new ConcurrentHashMap<>();
private final Map<String, Boolean> doneSendingInLogs = new ConcurrentHashMap<>();
public void sendMessage (final String message, final String channelId) {
synchronized (logMessages) {
if (!logMessages.containsKey(channelId)) {
logMessages.put(channelId, new StringBuilder());
}
final StringBuilder logMessage = logMessages.get(channelId);
if (logMessage.length() < 2000) {
if (!logMessage.isEmpty()) {
logMessage.append('\n');
}
logMessage.append(message);
logMessages.putIfAbsent(channelId, new StringBuilder());
final StringBuilder logMessage = logMessages.get(channelId);
if (logMessage.length() < 2000) {
if (!logMessage.isEmpty()) {
logMessage.append('\n');
}
logMessage.append(message);
}
}
@@ -504,52 +187,50 @@ public class DiscordPlugin extends ListenerAdapter {
}
public void onDiscordTick (final String channelId) {
synchronized (logMessages) {
if (!logMessages.containsKey(channelId) || logMessages.get(channelId).isEmpty()) {
return;
}
if (!logMessages.containsKey(channelId) || logMessages.get(channelId).isEmpty()) {
return;
}
final long currentTime = System.currentTimeMillis();
if (!nextLogTimes.containsKey(channelId) || (currentTime >= nextLogTimes.get(channelId) && doneSendingInLogs.get(channelId))
|| currentTime - nextLogTimes.get(channelId) > 5000) {
if (!nextLogTimes.containsKey(channelId)
|| (currentTime >= nextLogTimes.get(channelId) && doneSendingInLogs.get(channelId))
|| currentTime - nextLogTimes.get(channelId) > 5000
) {
final long logDelay = 2000;
nextLogTimes.put(channelId, currentTime + logDelay);
String message;
synchronized (logMessages) {
final StringBuilder logMessage = logMessages.get(channelId);
final StringBuilder logMessage = logMessages.get(channelId);
final Matcher inviteMatcher = Message.INVITE_PATTERN.matcher(logMessage.toString());
final Matcher inviteMatcher = Message.INVITE_PATTERN.matcher(logMessage.toString());
final StringBuilder messageBuilder = new StringBuilder();
final StringBuilder messageBuilder = new StringBuilder();
while (inviteMatcher.find()) {
inviteMatcher.appendReplacement(
messageBuilder,
Matcher.quoteReplacement(
inviteMatcher.group()
// fixes discord.gg (and some more discord urls) showing invite
.replace(".", "\u200b.")
)
);
}
inviteMatcher.appendTail(messageBuilder);
final int maxLength = 2_000 - ("""
```ansi
```"""
).length(); // kinda sus
message = messageBuilder.substring(0, Math.min(messageBuilder.length(), maxLength));
logMessage.setLength(0);
while (inviteMatcher.find()) {
inviteMatcher.appendReplacement(
messageBuilder,
Matcher.quoteReplacement(
inviteMatcher.group()
// fixes discord.gg (and some more discord urls) showing invite
.replace(".", "\u200b.")
)
);
}
inviteMatcher.appendTail(messageBuilder);
final int maxLength = 2_000 - ("""
```ansi
```"""
).length(); // kinda sus
message = messageBuilder.substring(0, Math.min(messageBuilder.length(), maxLength));
logMessage.setLength(0);
if (message.trim().isBlank()) return;
sendMessageInstantly("```ansi\n" + message + "\n```", channelId);