Chat parsing :D

This commit is contained in:
Chipmunk 2023-01-09 19:32:41 -05:00
parent 2f6dac6ae5
commit ef4ce27a4a
6 changed files with 235 additions and 7 deletions

View file

@ -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<String, Component> parameters;
}

View file

@ -0,0 +1,7 @@
package land.chipmunk.chipmunkbot.data.chat;
import net.kyori.adventure.text.Component;
public interface SystemChatParser {
PlayerMessage parse (Component message);
}

View file

@ -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<Listener> listeners = new ArrayList<>();
private List<SystemChatParser> 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) {}
}
}

View file

@ -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<MutablePlayerListEntry> list = new ArrayList<MutablePlayerListEntry>();
public List<MutablePlayerListEntry> 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());
}

View file

@ -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<Component> children = message.children();
if (!message.content().equals("") || !message.style().equals(empty) || children.size() < 3) return null;
final Map<String, Component> 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<Component> children, int start) {
return children.get(start).equals(SEPERATOR_COLON) && children.get(start + 1).equals(SEPERATOR_SPACE);
}
}

View file

@ -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<String, String> 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<Component> 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<String, Component> 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);
}
}