From ef4ce27a4a5498dc37fc4fe4c22e9e965a0d5395 Mon Sep 17 00:00:00 2001 From: Chip <65827213+ChipmunkMC@users.noreply.github.com> Date: Mon, 9 Jan 2023 19:32:41 -0500 Subject: [PATCH] Chat parsing :D --- .../chipmunkbot/data/chat/PlayerMessage.java | 16 ++++ .../data/chat/SystemChatParser.java | 7 ++ .../chipmunkbot/plugins/ChatPlugin.java | 57 +++++++++++++- .../chipmunkbot/plugins/PlayerListPlugin.java | 20 ++++- .../systemChat/KaboomChatParser.java | 75 +++++++++++++++++++ .../systemChat/MinecraftChatParser.java | 67 +++++++++++++++++ 6 files changed, 235 insertions(+), 7 deletions(-) create mode 100644 src/main/java/land/chipmunk/chipmunkbot/data/chat/PlayerMessage.java create mode 100644 src/main/java/land/chipmunk/chipmunkbot/data/chat/SystemChatParser.java create mode 100644 src/main/java/land/chipmunk/chipmunkbot/systemChat/KaboomChatParser.java create mode 100644 src/main/java/land/chipmunk/chipmunkbot/systemChat/MinecraftChatParser.java diff --git a/src/main/java/land/chipmunk/chipmunkbot/data/chat/PlayerMessage.java b/src/main/java/land/chipmunk/chipmunkbot/data/chat/PlayerMessage.java new file mode 100644 index 0000000..b01a4d0 --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/data/chat/PlayerMessage.java @@ -0,0 +1,16 @@ +package land.chipmunk.chipmunkbot.data.chat; + +import land.chipmunk.chipmunkbot.data.MutablePlayerListEntry; +import lombok.Data; +import lombok.AllArgsConstructor; +import net.kyori.adventure.text.Component; + +import java.util.Map; + +@Data +@AllArgsConstructor +public class PlayerMessage { + private String type; + private MutablePlayerListEntry sender; + private Map parameters; +} \ No newline at end of file diff --git a/src/main/java/land/chipmunk/chipmunkbot/data/chat/SystemChatParser.java b/src/main/java/land/chipmunk/chipmunkbot/data/chat/SystemChatParser.java new file mode 100644 index 0000000..79189e9 --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/data/chat/SystemChatParser.java @@ -0,0 +1,7 @@ +package land.chipmunk.chipmunkbot.data.chat; + +import net.kyori.adventure.text.Component; + +public interface SystemChatParser { + PlayerMessage parse (Component message); +} \ No newline at end of file diff --git a/src/main/java/land/chipmunk/chipmunkbot/plugins/ChatPlugin.java b/src/main/java/land/chipmunk/chipmunkbot/plugins/ChatPlugin.java index c6da7de..7da87ac 100644 --- a/src/main/java/land/chipmunk/chipmunkbot/plugins/ChatPlugin.java +++ b/src/main/java/land/chipmunk/chipmunkbot/plugins/ChatPlugin.java @@ -1,6 +1,8 @@ package land.chipmunk.chipmunkbot.plugins; -import land.chipmunk.chipmunkbot.Client; +import land.chipmunk.chipmunkbot.ChipmunkBot; +import land.chipmunk.chipmunkbot.data.chat.PlayerMessage; +import land.chipmunk.chipmunkbot.data.chat.SystemChatParser; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerChatPacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundSystemChatPacket; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatPacket; @@ -8,14 +10,29 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundCh import com.github.steveice10.packetlib.packet.Packet; import com.github.steveice10.packetlib.Session; import com.github.steveice10.packetlib.event.session.SessionAdapter; +import com.github.steveice10.packetlib.event.session.SessionListener; +import net.kyori.adventure.text.Component; import java.util.BitSet; import java.util.ArrayList; import java.util.List; import java.time.Instant; -public class ChatPlugin { - private Client client; - public ChatPlugin (Client client) { this.client = client; } +import land.chipmunk.chipmunkbot.systemChat.*; + +public class ChatPlugin extends SessionAdapter { + private final ChipmunkBot client; + private List listeners = new ArrayList<>(); + + private List systemChatParsers; + + public ChatPlugin (ChipmunkBot client) { + this.client = client; + client.session().addListener((SessionListener) this); + + systemChatParsers = new ArrayList<>(); + systemChatParsers.add(new MinecraftChatParser(client)); + systemChatParsers.add(new KaboomChatParser(client)); + } public void message (String message) { final ServerboundChatPacket packet = new ServerboundChatPacket(message, Instant.now().toEpochMilli(), 0, new byte[0], false, new ArrayList<>(), null); @@ -26,4 +43,36 @@ public class ChatPlugin { final ServerboundChatCommandPacket packet = new ServerboundChatCommandPacket(command, Instant.now().toEpochMilli(), 0, new ArrayList<>(), false, new ArrayList<>(), null); client.session().send(packet); } + + @Override + public void packetReceived (Session session, Packet packet) { + // TODO: Player chat + if (packet instanceof ClientboundSystemChatPacket) packetReceived(session, (ClientboundSystemChatPacket) packet); + } + + public void packetReceived (Session session, ClientboundSystemChatPacket packet) { + final Component component = packet.getContent(); + final boolean overlay = packet.isOverlay(); + + PlayerMessage playerMessage = null; + + for (SystemChatParser parser : systemChatParsers) { + playerMessage = parser.parse(component); + if (playerMessage != null) break; + } + + for (Listener listener : this.listeners) { + listener.systemMessageReceived(component, overlay); + if (playerMessage != null) listener.playerMessageReceived(playerMessage); + } + } + + public void addListener (Listener listener) { + listeners.add(listener); + } + + public class Listener { + public void systemMessageReceived (Component component, boolean overlay) {} + public void playerMessageReceived (PlayerMessage message) {} + } } diff --git a/src/main/java/land/chipmunk/chipmunkbot/plugins/PlayerListPlugin.java b/src/main/java/land/chipmunk/chipmunkbot/plugins/PlayerListPlugin.java index 0b5bb6f..4aeb22d 100644 --- a/src/main/java/land/chipmunk/chipmunkbot/plugins/PlayerListPlugin.java +++ b/src/main/java/land/chipmunk/chipmunkbot/plugins/PlayerListPlugin.java @@ -5,8 +5,10 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPl import com.github.steveice10.packetlib.packet.Packet; import com.github.steveice10.packetlib.Session; import com.github.steveice10.packetlib.event.session.SessionAdapter; +import com.github.steveice10.packetlib.event.session.SessionListener; import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction; +import net.kyori.adventure.text.Component; import land.chipmunk.chipmunkbot.data.MutablePlayerListEntry; import java.util.List; @@ -15,9 +17,12 @@ import java.util.UUID; public class PlayerListPlugin extends SessionAdapter { private Client client; - public List list = new ArrayList(); + public List list = new ArrayList<>(); - public PlayerListPlugin (Client client) { this.client = client; } + public PlayerListPlugin (Client client) { + this.client = client; + client.session().addListener((SessionListener) this); + } @Override public void packetReceived (Session session, Packet packet) { @@ -26,7 +31,6 @@ public class PlayerListPlugin extends SessionAdapter { public void packetReceived (Session session, ClientboundPlayerInfoPacket packet) { PlayerListEntryAction action = packet.getAction(); - for (PlayerListEntry entry : packet.getEntries()) { if (action == PlayerListEntryAction.ADD_PLAYER) addPlayer(entry); else if (action == PlayerListEntryAction.UPDATE_GAMEMODE) updateGamemode(entry); @@ -56,6 +60,16 @@ public class PlayerListPlugin extends SessionAdapter { return null; } + public final MutablePlayerListEntry getEntry (Component displayName) { + for (MutablePlayerListEntry candidate : list) { + if (candidate.displayName() != null && candidate.displayName().equals(displayName)) { + return candidate; + } + } + + return null; + } + private final MutablePlayerListEntry getEntry (PlayerListEntry other) { return getEntry(other.getProfile().getId()); } diff --git a/src/main/java/land/chipmunk/chipmunkbot/systemChat/KaboomChatParser.java b/src/main/java/land/chipmunk/chipmunkbot/systemChat/KaboomChatParser.java new file mode 100644 index 0000000..34f342a --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/systemChat/KaboomChatParser.java @@ -0,0 +1,75 @@ +package land.chipmunk.chipmunkbot.systemChat; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import land.chipmunk.chipmunkbot.ChipmunkBot; +import land.chipmunk.chipmunkbot.data.MutablePlayerListEntry; +import land.chipmunk.chipmunkbot.data.chat.PlayerMessage; +import land.chipmunk.chipmunkbot.data.chat.SystemChatParser; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.format.Style; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class KaboomChatParser implements SystemChatParser { + private final ChipmunkBot client; + + public KaboomChatParser (ChipmunkBot client) { + this.client = client; + } + + private static Style empty = Style.empty(); + private static Component SEPERATOR_COLON = Component.text(":"); + private static Component SEPERATOR_SPACE = Component.space(); + + @Override + public PlayerMessage parse (Component message) { + if (message instanceof TextComponent) return parse((TextComponent) message); + if (message instanceof TranslatableComponent) return parse((TranslatableComponent) message); + return null; + } + + public PlayerMessage parse (TranslatableComponent message) { + if (!message.key().equals("%") || message.args().size() != 1 || !message.style().equals(empty)) return parse(message.args().get(0)); + return null; + } + + public PlayerMessage parse (TextComponent message) { + List children = message.children(); + + if (!message.content().equals("") || !message.style().equals(empty) || children.size() < 3) return null; + + final Map parameters = new HashMap<>(); + + final Component prefix = children.get(0); + Component displayName = Component.empty(); + Component contents = Component.empty(); + + if (isSeperatorAt(children, 1)) { // Missing/blank display name + if (children.size() >= 2) contents = children.get(1); + } else if (isSeperatorAt(children, 2)) { + displayName = children.get(1); + if (children.size() >= 3) contents = children.get(2); + } else { + return null; + } + + MutablePlayerListEntry sender = client.playerList().getEntry(prefix.append(displayName)); + if (sender == null) sender = new MutablePlayerListEntry(new GameProfile(new UUID(0l, 0l), null), GameMode.SURVIVAL, 0, displayName, 0L, null, new byte[0]); + + parameters.put("sender", displayName); + parameters.put("prefix", prefix); + parameters.put("contents", contents); + + return new PlayerMessage("minecraft:chat", sender, parameters); + } + + private boolean isSeperatorAt (List children, int start) { + return children.get(start).equals(SEPERATOR_COLON) && children.get(start + 1).equals(SEPERATOR_SPACE); + } +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/systemChat/MinecraftChatParser.java b/src/main/java/land/chipmunk/chipmunkbot/systemChat/MinecraftChatParser.java new file mode 100644 index 0000000..6a887e5 --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/systemChat/MinecraftChatParser.java @@ -0,0 +1,67 @@ +package land.chipmunk.chipmunkbot.systemChat; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import land.chipmunk.chipmunkbot.ChipmunkBot; +import land.chipmunk.chipmunkbot.data.MutablePlayerListEntry; +import land.chipmunk.chipmunkbot.data.chat.PlayerMessage; +import land.chipmunk.chipmunkbot.data.chat.SystemChatParser; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.event.HoverEvent; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class MinecraftChatParser implements SystemChatParser { + // ? Is such a mapping necessary? + private static final Map typeMap = new HashMap<>(); + static { + typeMap.put("chat.type.text", "minecraft:chat"); + typeMap.put("chat.type.announcement", "minecraft:say_command"); + typeMap.put("chat.type.command", "minecraft:msg_command"); + typeMap.put("chat.type.team.text", "minecraft:team_msg_command"); + typeMap.put("chat.type.emote", "minecraft:emote_command"); + } + + private final ChipmunkBot client; + + public MinecraftChatParser (ChipmunkBot client) { + this.client = client; + } + + @Override + public PlayerMessage parse (Component message) { + if (message instanceof TranslatableComponent) return parse((TranslatableComponent) message); + return null; + } + + public PlayerMessage parse (TranslatableComponent message) { + final List args = message.args(); + final String key = message.key(); + if (args.size() < 2 || !typeMap.containsKey(key)) return null; + + final String type = typeMap.get(key); + + final Map parameters = new HashMap<>(); + + final Component senderComponent = args.get(0); + final Component contents = args.get(1); + + // Find the sender and attempt to map it to a player + final HoverEvent hoverEvent = senderComponent.hoverEvent(); + if (hoverEvent == null || !hoverEvent.action().equals(HoverEvent.Action.SHOW_ENTITY)) return null; + HoverEvent.ShowEntity entityInfo = (HoverEvent.ShowEntity) hoverEvent.value(); + final UUID senderUUID = entityInfo.id(); + + MutablePlayerListEntry sender = client.playerList().getEntry(senderUUID); + if (sender == null) sender = new MutablePlayerListEntry(new GameProfile(senderUUID, null), GameMode.SURVIVAL, 0, entityInfo.name(), 0L, null, new byte[0]); + + parameters.put("sender", senderComponent); + parameters.put("contents", contents); + + return new PlayerMessage(type, sender, parameters); + } +}