diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..e65b78d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..2285b75 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..ad2ba02 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..fd62198 --- /dev/null +++ b/pom.xml @@ -0,0 +1,68 @@ + + 4.0.0 + land.chipmunk + chipmunkbot + jar + 1.0-SNAPSHOT + chipmunkbot + + + 1.8 + 1.8 + + + + + opencollab + https://repo.opencollab.dev/maven-releases/ + + + + + + com.github.steveice10 + mcprotocollib + 1.19.2-1 + + + + com.google.code.gson + gson + 2.9.0 + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + land.chipmunk.chipmunkbot.Main + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 + + + package + + shade + + + + + + + + diff --git a/src/main/java/land/chipmunk/chipmunkbot/Client.java b/src/main/java/land/chipmunk/chipmunkbot/Client.java new file mode 100644 index 0000000..d777b90 --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/Client.java @@ -0,0 +1,44 @@ +package land.chipmunk.chipmunkbot; + +import com.github.steveice10.mc.auth.service.AuthenticationService; +import com.github.steveice10.mc.protocol.MinecraftProtocol; +import com.github.steveice10.packetlib.ProxyInfo; +import com.github.steveice10.packetlib.Session; +import com.github.steveice10.packetlib.tcp.TcpClientSession; +import java.util.Map; +import java.util.HashMap; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +public class Client { + private Session session; + private Map plugins = new HashMap(); + + public Client (ClientOptions options) { + Session session = new TcpClientSession(options.host(), options.port(), options.protocol(), options.proxy()); + this.session = session; + + session.connect(); + } + + public Session session () { + return session; + } + + public Plugin getPlugin (String id) { return plugins.get(id); } + + public void loadPlugin (Class pluginClass) { + try { + Constructor constructor = pluginClass.getConstructor(Client.class); + Plugin plugin = constructor.newInstance(this); + plugins.put(plugin.id, plugin); + } catch (Exception ignored) { + } + } + + // TODO: Maybe also add unloading? + + public void inject (Injector injector) { + injector.inject(this); + } +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/ClientOptions.java b/src/main/java/land/chipmunk/chipmunkbot/ClientOptions.java new file mode 100644 index 0000000..8aed0b5 --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/ClientOptions.java @@ -0,0 +1,70 @@ +package land.chipmunk.chipmunkbot; + +import com.github.steveice10.mc.auth.service.AuthenticationService; +import com.github.steveice10.mc.protocol.MinecraftProtocol; +import com.github.steveice10.packetlib.ProxyInfo; +import com.github.steveice10.packetlib.Session; +import com.github.steveice10.packetlib.tcp.TcpClientSession; + +public class ClientOptions { + private String host; + private int port; + private MinecraftProtocol protocol; + private ProxyInfo proxy; + + public ClientOptions (String host, int port, MinecraftProtocol protocol, ProxyInfo proxy) { + this.host = host; + this.port = port; + this.protocol = protocol; + this.proxy = proxy; + } + + public ClientOptions () { // So it can easily be used as a builder + } + + public String host () { + return host; + } + + public ClientOptions host (String value) { + host = value; + return this; + } + + public int port () { + return port; + } + + public ClientOptions port (int value) { + port = value; + return this; + } + + public MinecraftProtocol protocol () { + return protocol; + } + + public ClientOptions protocol (MinecraftProtocol value) { + protocol = value; + return this; + } + + /* public ClientOptions profile (GameProfile profile) { + protocol(new MinecraftProtocol(profile)); + return this; + } */ + + public ClientOptions username (String username) { + protocol(new MinecraftProtocol(username)); + return this; + } + + public ProxyInfo proxy () { + return proxy; + } + + public ClientOptions proxy (ProxyInfo value) { + proxy = value; + return this; + } +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/Injector.java b/src/main/java/land/chipmunk/chipmunkbot/Injector.java new file mode 100644 index 0000000..4745e8c --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/Injector.java @@ -0,0 +1,5 @@ +package land.chipmunk.chipmunkbot; + +public interface Injector { + public void inject (Client client); +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/Main.java b/src/main/java/land/chipmunk/chipmunkbot/Main.java new file mode 100644 index 0000000..82e2a90 --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/Main.java @@ -0,0 +1,58 @@ +package land.chipmunk.chipmunkbot; + +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundLoginPacket; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatPacket; +import com.github.steveice10.packetlib.Session; +import com.github.steveice10.packetlib.event.session.SessionAdapter; +import com.github.steveice10.packetlib.packet.Packet; + +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.BufferedReader; +import java.io.File; + +import com.google.gson.JsonObject; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +import land.chipmunk.chipmunkbot.plugins.ChatPlugin; + +import com.google.gson.Gson; +import land.chipmunk.chipmunkbot.plugins.PlayerListPlugin; + +public class Main { + private static File CONFIG_FILE = new File("config.json"); + + private static JsonObject getConfig () throws Exception { + InputStream opt = new FileInputStream(CONFIG_FILE); + BufferedReader reader = new BufferedReader(new InputStreamReader(opt)); + + return JsonParser.parseReader(reader).getAsJsonObject(); + } + + public static ClientOptions parseClientOptions (JsonObject options) { + return new ClientOptions() + .host(options.has("host") ? options.get("host").getAsString() : "0.0.0.0") + .port(options.has("port") ? options.get("port").getAsInt() : 25565) + .username(options.has("username") ? options.get("username").getAsString() : "username"); + } + + public static void main (String[] arguments) { + System.out.println("ChipmunkBot is starting..."); + + JsonObject config = null; + try { + config = getConfig(); + } catch (Exception exception) { + exception.printStackTrace(); + System.exit(1); + } + + for (JsonElement element : config.get("bots").getAsJsonArray()) { + ClientOptions options = parseClientOptions(element.getAsJsonObject()); + Client client = new Client(options); // TODO: Maybe create a list of some sort + client.loadPlugin(PlayerListPlugin.class); + } + } +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/Plugin.java b/src/main/java/land/chipmunk/chipmunkbot/Plugin.java new file mode 100644 index 0000000..5c6ec54 --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/Plugin.java @@ -0,0 +1,11 @@ +package land.chipmunk.chipmunkbot; + +public class Plugin { + public String id; + public Client client; + + public Plugin (Client client, String id) { + this.client = client; + this.id = id; + } +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/chat/ChatMessage.java b/src/main/java/land/chipmunk/chipmunkbot/chat/ChatMessage.java new file mode 100644 index 0000000..b1b16fd --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/chat/ChatMessage.java @@ -0,0 +1,37 @@ +package land.chipmunk.chipmunkbot.chat; + +import net.kyori.adventure.text.Component; +import java.util.UUID; + +public class ChatMessage { + // * I do not really care about signatures, they were likely made for the sus chat reporting system anyway, but I still include the sender as it really helps with stuff such as commands. + private Component component; + private UUID sender; + + public ChatMessage (Component component, UUID sender) { + this.component = component; + this.sender = sender; + } + + public ChatMessage (Component component) { + this(component, new UUID(0, 0)); + } + + public Component component () { + return component; + } + + public ChatMessage component (Component value) { + component = value; + return this; + } + + public UUID sender () { + return sender; + } + + public ChatMessage sender (UUID value) { + sender = value; + return this; + } +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/chat/ChatParser.java b/src/main/java/land/chipmunk/chipmunkbot/chat/ChatParser.java new file mode 100644 index 0000000..e6c4a8d --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/chat/ChatParser.java @@ -0,0 +1,7 @@ +package land.chipmunk.chipmunkbot.chat; + +import net.kyori.adventure.text.Component; + +public interface ChatParser { + public PlayerMessage parseMessage(ChatMessage message); +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/chat/MessageType.java b/src/main/java/land/chipmunk/chipmunkbot/chat/MessageType.java new file mode 100644 index 0000000..7addf21 --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/chat/MessageType.java @@ -0,0 +1,8 @@ +package land.chipmunk.chipmunkbot.chat; + +public enum MessageType { + TEXT, + ANNOUNCEMENT, + EMOTE, + WHISPER +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/chat/MinecraftChatParser.java b/src/main/java/land/chipmunk/chipmunkbot/chat/MinecraftChatParser.java new file mode 100644 index 0000000..77b196f --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/chat/MinecraftChatParser.java @@ -0,0 +1,37 @@ +package land.chipmunk.chipmunkbot.chat; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslatableComponent; +import java.util.UUID; +import java.util.List; + +public class MinecraftChatParser implements ChatParser { + private MessageType getMessageType (TranslatableComponent component) { + String key = component.key(); + if (key.equals("chat.type.text")) return MessageType.TEXT; + if (key.equals("chat.type.announcement")) return MessageType.ANNOUNCEMENT; + if (key.equals("chat.type.emote")) return MessageType.EMOTE; + if (key.equals("chat.type.command")) return MessageType.WHISPER; + return null; + } + + public PlayerMessage parseMessage (ChatMessage message) { + Component component = message.component(); + if (component instanceof TranslatableComponent) return parseTranslatable((TranslatableComponent) component, message.sender()); + return null; + } + + public PlayerMessage parseTranslatable (TranslatableComponent component, UUID providedSender) { + TranslatableComponent translate = (TranslatableComponent) component; + MessageType type = getMessageType(translate); + if (type == null) return null; + + List args = component.args(); + + // Component displayName = args.get(0); + Component contents = args.get(1); + + return new PlayerMessage(null, contents, type); // TODO: Use player list + } +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/chat/PlayerMessage.java b/src/main/java/land/chipmunk/chipmunkbot/chat/PlayerMessage.java new file mode 100644 index 0000000..b19892a --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/chat/PlayerMessage.java @@ -0,0 +1,47 @@ +package land.chipmunk.chipmunkbot.chat; + +import net.kyori.adventure.text.Component; +import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; + +public class PlayerMessage { + private PlayerListEntry sender; + private Component contents; + private MessageType type; + + PlayerMessage (PlayerListEntry sender, Component contents, MessageType type) { + this.sender = sender; + this.contents = contents; + this.type = type; + } + + PlayerMessage (PlayerListEntry sender, Component contents) { + this(sender, contents, MessageType.TEXT); + } + + public PlayerListEntry sender () { + return sender; + } + + public PlayerMessage sender (PlayerListEntry value) { + sender = value; + return this; + } + + public Component contents () { + return contents; + } + + public PlayerMessage contents (Component value) { + contents = value; + return this; + } + + public MessageType type () { + return type; + } + + public PlayerMessage type (MessageType value) { + type = value; + return this; + } +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/data/MutablePlayerListEntry.java b/src/main/java/land/chipmunk/chipmunkbot/data/MutablePlayerListEntry.java new file mode 100644 index 0000000..4a577da --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/data/MutablePlayerListEntry.java @@ -0,0 +1,60 @@ +package land.chipmunk.chipmunkbot.data; + +import com.github.steveice10.mc.auth.data.GameProfile; +import com.github.steveice10.mc.protocol.data.game.PlayerListEntry; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import net.kyori.adventure.text.Component; + +import java.security.PublicKey; + +public class MutablePlayerListEntry { + private GameProfile profile; + private GameMode gamemode; + private int latency; + private Component displayName; + private long expiresAt; + private PublicKey publicKey; + private byte[] keySignature; + + public MutablePlayerListEntry (GameProfile profile, GameMode gamemode, int latency, Component displayName, long expiresAt, PublicKey publicKey, byte[] keySignature) { + this.profile = profile; + this.gamemode = gamemode; + this.latency = latency; + this.displayName = displayName; + this.expiresAt = expiresAt; + this.publicKey = publicKey; + this.keySignature = keySignature; + } + + public MutablePlayerListEntry (PlayerListEntry entry) { + this(entry.getProfile(), entry.getGameMode(), entry.getPing(), entry.getDisplayName(), entry.getExpiresAt(), entry.getPublicKey(), entry.getKeySignature()); + } + + public GameProfile profile () { return this.profile; } + + public void profile (GameProfile profile) { this.profile = profile; } + + public GameMode gamemode () { return this.gamemode; } + + public void gamemode (GameMode gamemode) { this.gamemode = gamemode; } + + public int latency () { return this.latency; } + + public void latency (int latency) { this.latency = latency; } + + public Component displayName () { return this.displayName; } + + public void displayName (Component displayName) { this.displayName = displayName; } + + public long expiresAt () { return this.expiresAt; } + + public void expiresAt (long expiresAt) { this.expiresAt = expiresAt; } + + public PublicKey publicKey () { return this.publicKey; } + + public void publicKey (PublicKey publicKey) { this.publicKey = publicKey; } + + public byte[] keySignature () { return this.keySignature; } + + public void keySignature (byte[] keySignature) { this.keySignature = keySignature; } +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/plugins/ChatPlugin.java b/src/main/java/land/chipmunk/chipmunkbot/plugins/ChatPlugin.java new file mode 100644 index 0000000..7d2d8a8 --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/plugins/ChatPlugin.java @@ -0,0 +1,38 @@ +package land.chipmunk.chipmunkbot.plugins; + +import land.chipmunk.chipmunkbot.Plugin; +import land.chipmunk.chipmunkbot.Client; +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; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatCommandPacket; +import com.github.steveice10.packetlib.packet.Packet; +import com.github.steveice10.packetlib.Session; +import com.github.steveice10.packetlib.event.session.SessionAdapter; +import java.util.BitSet; +import java.util.ArrayList; +import java.util.List; +import java.time.Instant; + +public class ChatPlugin extends Plugin { + public ChatPlugin (Client client) { + super(client, "chat"); + + /* client.session().addListener(new SessionAdapter () { + @Override + packetReceived (Session session, Packet packet) { + if (packet instanceof Server) + } + }); */ + } + + public void message (String message) { + final ServerboundChatPacket packet = new ServerboundChatPacket(message, Instant.now().toEpochMilli(), 0, new byte[0], false, new ArrayList<>(), null); + client.session().send(packet); + } + + public void command (String command) { + final ServerboundChatCommandPacket packet = new ServerboundChatCommandPacket(command, Instant.now().toEpochMilli(), 0, new ArrayList<>(), false, new ArrayList<>(), null); + client.session().send(packet); + } +} diff --git a/src/main/java/land/chipmunk/chipmunkbot/plugins/PlayerListPlugin.java b/src/main/java/land/chipmunk/chipmunkbot/plugins/PlayerListPlugin.java new file mode 100644 index 0000000..9a67f14 --- /dev/null +++ b/src/main/java/land/chipmunk/chipmunkbot/plugins/PlayerListPlugin.java @@ -0,0 +1,102 @@ +package land.chipmunk.chipmunkbot.plugins; + +import land.chipmunk.chipmunkbot.Plugin; +import land.chipmunk.chipmunkbot.Client; +import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerInfoPacket; +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.mc.protocol.data.game.PlayerListEntry; +import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction; +import land.chipmunk.chipmunkbot.data.MutablePlayerListEntry; + +import java.util.List; +import java.util.ArrayList; +import java.util.UUID; + +public class PlayerListPlugin extends Plugin { + public List list = new ArrayList(); + + public PlayerListPlugin (Client client) { + super(client, "player_list"); + + client.session().addListener(new SessionAdapter() { + @Override + public void packetReceived (Session session, Packet packet) { + if (packet instanceof ClientboundPlayerInfoPacket) { + ClientboundPlayerInfoPacket _packet = (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); + else if (action == PlayerListEntryAction.UPDATE_LATENCY) updateLatency(entry); + else if (action == PlayerListEntryAction.UPDATE_DISPLAY_NAME) updateDisplayName(entry); + else if (action == PlayerListEntryAction.REMOVE_PLAYER) removePlayer(entry); + } + } + } + }); + } + + public final MutablePlayerListEntry getEntry (UUID uuid) { + for (MutablePlayerListEntry candidate : list) { + if (candidate.profile().getId().equals(uuid)) { + return candidate; + } + } + + return null; + } + + public final MutablePlayerListEntry getEntry (String username) { + for (MutablePlayerListEntry candidate : list) { + if (candidate.profile().getName().equals(username)) { + return candidate; + } + } + + return null; + } + + private final MutablePlayerListEntry getEntry (PlayerListEntry other) { + return getEntry(other.getProfile().getId()); + } + + private void addPlayer (PlayerListEntry newEntry) { + final MutablePlayerListEntry duplicate = getEntry(newEntry); + if (duplicate != null) list.remove(duplicate); + + list.add(new MutablePlayerListEntry(newEntry)); + System.out.println("Added " + newEntry.getProfile().getName() + " to the player list."); + } + + private void updateGamemode (PlayerListEntry newEntry) { + final MutablePlayerListEntry target = getEntry(newEntry); + if (target == null) return; + + target.gamemode(newEntry.getGameMode()); + } + + private void updateLatency (PlayerListEntry newEntry) { + final MutablePlayerListEntry target = getEntry(newEntry); + if (target == null) return; + + target.latency(newEntry.getPing()); + } + + private void updateDisplayName (PlayerListEntry newEntry) { + final MutablePlayerListEntry target = getEntry(newEntry); + if (target == null) return; + + target.displayName(newEntry.getDisplayName()); + } + + private void removePlayer (PlayerListEntry newEntry) { + final MutablePlayerListEntry target = getEntry(newEntry); + if (target == null) return; + + list.remove(target); + } +}