Compare commits
26
Commits
v1.2.7
...
939ed09bdb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
939ed09bdb | ||
|
|
8417fd7b45 | ||
|
|
60e624e47e | ||
|
|
e8e3b4f9c0 | ||
|
|
7c1eb7cf24 | ||
|
|
ce9d01a47f | ||
|
|
b81b61187f | ||
|
|
5e109efc56 | ||
|
|
91e4d44170 | ||
|
|
e2a477e56f | ||
|
|
1ce1f98801 | ||
|
|
9865364919 | ||
|
|
fb99076f88 | ||
|
|
da26ec8a99 | ||
|
|
8b2dddf8d6 | ||
|
|
c57febbd0a | ||
|
|
78cf07636d | ||
|
|
3311f0889a | ||
|
|
c05b7ffef4 | ||
|
|
d8c2139df6 | ||
|
|
5c12776348 | ||
|
|
896c320dd7 | ||
|
|
d8cd699012 | ||
|
|
d550c69583 | ||
|
|
4c239b9b5b | ||
|
|
f82eb34588 |
No files matched your search
@@ -1,7 +1,7 @@
|
|||||||
# Minecraft bot stress tester
|
# Minecraft bot stress tester
|
||||||
🤖 A simple open source app written in Java used for stress testing Minecraft servers with bots (fake players).
|
🤖 A simple open source app written in Java used for stress testing Minecraft servers with bots (fake players).
|
||||||
💥 It can be also used to test plugins or minigames.
|
💥 It can be also used to test plugins or minigames.
|
||||||
✔️ The MC version of the bots is 1.19.1 (supports 1.19.2).
|
✔️ The MC version of the bots is 1.21.7 (supports server 1.21.8)
|
||||||
For older MC versions please look in the [releases](https://github.com/crpmax/mc-bots/releases " releases").
|
For older MC versions please look in the [releases](https://github.com/crpmax/mc-bots/releases " releases").
|
||||||
|
|
||||||
## 🆒 Features
|
## 🆒 Features
|
||||||
@@ -11,17 +11,17 @@ For older MC versions please look in the [releases](https://github.com/crpmax/mc
|
|||||||
- ✅ Set connection delay
|
- ✅ Set connection delay
|
||||||
- ✅ Set messages or commands on join
|
- ✅ Set messages or commands on join
|
||||||
- ✅ Generate random or real looking nicknames or load from file
|
- ✅ Generate random or real looking nicknames or load from file
|
||||||
- ✅ Online (premium) account support with login using Microsoft OAuth - only for migrated accounts
|
- ✅ Online (premium) account support with login using Microsoft
|
||||||
- ✅ Control all or selected bots
|
- ✅ Control all or selected bots
|
||||||
|
|
||||||
## 📖 Usage
|
## 📖 Usage
|
||||||
Minimal Java version: 8
|
Minimal Java version: 17
|
||||||
Use of pre-compiled jar from [releases](https://github.com/crpmax/mc-bots/releases " releases"):
|
Use of pre-compiled jar from [releases](https://github.com/crpmax/mc-bots/releases " releases"):
|
||||||
`java -jar mc-bots.jar -s <server address> [arguments]`
|
`java -jar mc-bots.jar -s <server address> [arguments]`
|
||||||
When running, you can write a chat message to the terminal to send it by all bots.
|
When running, you can write a chat message to the terminal to send it by all bots.
|
||||||
|
|
||||||
## 🧪 Example
|
## 🧪 Example
|
||||||
`java -jar mc-bots-1.2.6.jar -s 192.168.0.189:25565 -p BOT_ -d 4000 5000 -c 30 -r`
|
`java -jar mc-bots-1.2.17.jar -s 192.168.0.189:25565 -p BOT_ -d 4000 5000 -c 30 -r`
|
||||||
This will connect 30 bots to server at 192.168.0.189:25565 with delay 4000-5000 ms and will use real-looking nicknames prefixed with BOT_
|
This will connect 30 bots to server at 192.168.0.189:25565 with delay 4000-5000 ms and will use real-looking nicknames prefixed with BOT_
|
||||||
|
|
||||||
<img src="https://imgur.com/XWcckas.png" title="Connected bots" width="350px"/>
|
<img src="https://imgur.com/XWcckas.png" title="Connected bots" width="350px"/>
|
||||||
|
|||||||
+14
-9
@@ -3,10 +3,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group 'me.creepermaxcz.mc-bots'
|
group 'me.creepermaxcz.mc-bots'
|
||||||
version '1.2.7'
|
version '1.2.18'
|
||||||
|
|
||||||
sourceCompatibility = 1.8
|
|
||||||
targetCompatibility = 1.8
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@@ -16,11 +13,19 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.github.steveice10:mcprotocollib:1.19.4-1'
|
implementation 'org.geysermc.mcprotocollib:protocol:1.21.11-SNAPSHOT'
|
||||||
implementation 'commons-cli:commons-cli:1.5.0'
|
implementation 'net.kyori:adventure-text-serializer-gson:4.24.0'
|
||||||
implementation 'com.diogonunes:JColor:5.2.0'
|
implementation 'commons-cli:commons-cli:1.9.0'
|
||||||
implementation 'dnsjava:dnsjava:3.4.3'
|
implementation 'com.diogonunes:JColor:5.5.1'
|
||||||
implementation 'org.slf4j:slf4j-nop:1.7.32'
|
implementation 'dnsjava:dnsjava:3.5.3'
|
||||||
|
implementation 'org.slf4j:slf4j-nop:2.0.17'
|
||||||
|
implementation 'org.jline:jline:3.30.4'
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
@@ -1,25 +1,27 @@
|
|||||||
package me.creepermaxcz.mcbots;
|
package me.creepermaxcz.mcbots;
|
||||||
|
|
||||||
import com.github.steveice10.mc.auth.service.AuthenticationService;
|
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||||
import com.github.steveice10.mc.auth.service.SessionService;
|
import org.geysermc.mcprotocollib.auth.SessionService;
|
||||||
import com.github.steveice10.mc.protocol.MinecraftConstants;
|
import org.geysermc.mcprotocollib.network.ClientSession;
|
||||||
import com.github.steveice10.mc.protocol.MinecraftProtocol;
|
import org.geysermc.mcprotocollib.network.ProxyInfo;
|
||||||
import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException;
|
import org.geysermc.mcprotocollib.network.Session;
|
||||||
import com.github.steveice10.mc.protocol.data.game.ClientCommand;
|
import org.geysermc.mcprotocollib.network.event.session.DisconnectedEvent;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundLoginPacket;
|
import org.geysermc.mcprotocollib.network.event.session.SessionAdapter;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerCombatKillPacket;
|
import org.geysermc.mcprotocollib.network.factory.ClientNetworkSessionFactory;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerPositionPacket;
|
import org.geysermc.mcprotocollib.network.packet.Packet;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatCommandPacket;
|
import org.geysermc.mcprotocollib.network.session.ClientNetworkSession;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundChatPacket;
|
import org.geysermc.mcprotocollib.protocol.MinecraftConstants;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket;
|
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.ServerboundAcceptTeleportationPacket;
|
import org.geysermc.mcprotocollib.protocol.data.UnexpectedEncryptionException;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.*;
|
import org.geysermc.mcprotocollib.protocol.data.game.ClientCommand;
|
||||||
import com.github.steveice10.packetlib.ProxyInfo;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundLoginPacket;
|
||||||
import com.github.steveice10.packetlib.Session;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerCombatKillPacket;
|
||||||
import com.github.steveice10.packetlib.event.session.DisconnectedEvent;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerPositionPacket;
|
||||||
import com.github.steveice10.packetlib.event.session.SessionAdapter;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundChatCommandPacket;
|
||||||
import com.github.steveice10.packetlib.packet.Packet;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundChatPacket;
|
||||||
import com.github.steveice10.packetlib.tcp.TcpClientSession;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundAcceptTeleportationPacket;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.*;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -29,12 +31,10 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
public class Bot extends Thread {
|
public class Bot extends Thread {
|
||||||
|
|
||||||
MinecraftProtocol protocol = null;
|
|
||||||
|
|
||||||
private String nickname;
|
private String nickname;
|
||||||
private ProxyInfo proxy;
|
private ProxyInfo proxy;
|
||||||
private InetSocketAddress address;
|
private InetSocketAddress address;
|
||||||
private Session client;
|
private ClientSession client;
|
||||||
private boolean hasMainListener;
|
private boolean hasMainListener;
|
||||||
|
|
||||||
private double lastX, lastY, lastZ = -1;
|
private double lastX, lastY, lastZ = -1;
|
||||||
@@ -43,25 +43,18 @@ public class Bot extends Thread {
|
|||||||
|
|
||||||
private boolean manualDisconnecting = false;
|
private boolean manualDisconnecting = false;
|
||||||
|
|
||||||
public Bot(String nickname, InetSocketAddress address, ProxyInfo proxy) {
|
public Bot(MinecraftProtocol protocol, InetSocketAddress address, ProxyInfo proxy) {
|
||||||
this.nickname = nickname;
|
this.nickname = protocol.getProfile().getName();
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
|
|
||||||
Log.info("Creating bot", nickname);
|
Log.info("Creating bot", nickname);
|
||||||
protocol = new MinecraftProtocol(nickname);
|
|
||||||
client = new TcpClientSession(address.getHostString(), address.getPort(), protocol, proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bot(AuthenticationService authService, InetSocketAddress address, ProxyInfo proxy) {
|
client = ClientNetworkSessionFactory.factory()
|
||||||
this.nickname = authService.getUsername();
|
.setAddress(address.getHostString(), address.getPort())
|
||||||
this.address = address;
|
.setProtocol(protocol)
|
||||||
this.proxy = proxy;
|
.setProxy(proxy)
|
||||||
|
.create();
|
||||||
Log.info("Creating bot", nickname);
|
|
||||||
protocol = new MinecraftProtocol(authService.getSelectedProfile(), authService.getAccessToken());
|
|
||||||
|
|
||||||
client = new TcpClientSession(address.getHostString(), address.getPort(), protocol, proxy);
|
|
||||||
|
|
||||||
SessionService sessionService = new SessionService();
|
SessionService sessionService = new SessionService();
|
||||||
client.setFlag(MinecraftConstants.SESSION_SERVICE_KEY, sessionService);
|
client.setFlag(MinecraftConstants.SESSION_SERVICE_KEY, sessionService);
|
||||||
@@ -93,11 +86,11 @@ public class Bot extends Thread {
|
|||||||
else if (packet instanceof ClientboundPlayerPositionPacket) {
|
else if (packet instanceof ClientboundPlayerPositionPacket) {
|
||||||
ClientboundPlayerPositionPacket p = (ClientboundPlayerPositionPacket) packet;
|
ClientboundPlayerPositionPacket p = (ClientboundPlayerPositionPacket) packet;
|
||||||
|
|
||||||
lastX = p.getX();
|
lastX = p.getPosition().getX();
|
||||||
lastY = p.getY();
|
lastY = p.getPosition().getY();
|
||||||
lastZ = p.getZ();
|
lastZ = p.getPosition().getZ();
|
||||||
|
|
||||||
client.send(new ServerboundAcceptTeleportationPacket(p.getTeleportId()));
|
client.send(new ServerboundAcceptTeleportationPacket(p.getId()));
|
||||||
}
|
}
|
||||||
else if (packet instanceof ClientboundPlayerCombatKillPacket){
|
else if (packet instanceof ClientboundPlayerCombatKillPacket){
|
||||||
if (Main.autoRespawnDelay >= 0) {
|
if (Main.autoRespawnDelay >= 0) {
|
||||||
@@ -155,19 +148,10 @@ public class Bot extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void sendChat(String text) {
|
public void sendChat(String text) {
|
||||||
// timeStamp will provide when this message was sent by the user. If this value was not set or was set to 0,
|
|
||||||
// The server console will print out that the message was "expired". To avoid this, set timeStamp as now.
|
|
||||||
long timeStamp = Instant.now().toEpochMilli();
|
|
||||||
|
|
||||||
// Send command
|
// Send command
|
||||||
if (text.startsWith("/")) {
|
if (text.startsWith("/")) {
|
||||||
client.send(new ServerboundChatCommandPacket(
|
client.send(new ServerboundChatCommandPacket(
|
||||||
text.substring(1),
|
text.substring(1)
|
||||||
timeStamp,
|
|
||||||
0L,
|
|
||||||
Collections.emptyList(),
|
|
||||||
0,
|
|
||||||
new BitSet()
|
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// Send chat message
|
// Send chat message
|
||||||
@@ -181,7 +165,8 @@ public class Bot extends Thread {
|
|||||||
0L,
|
0L,
|
||||||
null,
|
null,
|
||||||
0,
|
0,
|
||||||
new BitSet()
|
new BitSet(),
|
||||||
|
0
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,7 +202,7 @@ public class Bot extends Thread {
|
|||||||
|
|
||||||
public void moveTo(double x, double y, double z)
|
public void moveTo(double x, double y, double z)
|
||||||
{
|
{
|
||||||
client.send(new ServerboundMovePlayerPosPacket(true, x, y, z));
|
client.send(new ServerboundMovePlayerPosPacket(true, false, x, y, z));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isConnected() {
|
public boolean isConnected() {
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
package me.creepermaxcz.mcbots;
|
package me.creepermaxcz.mcbots;
|
||||||
|
|
||||||
import com.github.steveice10.mc.auth.exception.request.AuthPendingException;
|
import net.lenni0451.commons.httpclient.HttpClient;
|
||||||
import com.github.steveice10.mc.auth.service.AuthenticationService;
|
import net.raphimc.minecraftauth.MinecraftAuth;
|
||||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
|
import net.raphimc.minecraftauth.java.JavaAuthManager;
|
||||||
import com.github.steveice10.mc.auth.util.HTTP;
|
import net.raphimc.minecraftauth.java.model.MinecraftProfile;
|
||||||
import com.github.steveice10.mc.protocol.data.status.ServerStatusInfo;
|
import net.raphimc.minecraftauth.msa.model.MsaDeviceCode;
|
||||||
import com.github.steveice10.packetlib.ProxyInfo;
|
import net.raphimc.minecraftauth.msa.service.impl.DeviceCodeMsaAuthService;
|
||||||
|
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||||
|
import org.geysermc.mcprotocollib.network.ProxyInfo;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.status.ServerStatusInfo;
|
||||||
import org.apache.commons.cli.*;
|
import org.apache.commons.cli.*;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.LineReaderBuilder;
|
||||||
|
import org.jline.reader.UserInterruptException;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.terminal.TerminalBuilder;
|
||||||
import org.xbill.DNS.Lookup;
|
import org.xbill.DNS.Lookup;
|
||||||
import org.xbill.DNS.Record;
|
import org.xbill.DNS.Record;
|
||||||
import org.xbill.DNS.SRVRecord;
|
import org.xbill.DNS.SRVRecord;
|
||||||
@@ -16,6 +25,7 @@ import java.io.*;
|
|||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
@@ -100,7 +110,7 @@ public class Main {
|
|||||||
try {
|
try {
|
||||||
proxyType = ProxyInfo.Type.valueOf(typeStr);
|
proxyType = ProxyInfo.Type.valueOf(typeStr);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Log.error("Inavlid proxy type, use SOCKS4 or SOCKS5.");
|
Log.error("Invalid proxy type, use SOCKS4 or SOCKS5.");
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,55 +274,27 @@ public class Main {
|
|||||||
Log.warn("There was an error retrieving server status information. The server may be offline or running on a different version.");
|
Log.warn("There was an error retrieving server status information. The server may be offline or running on a different version.");
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationService authService = null;
|
MinecraftProtocol protocol;
|
||||||
if (cmd.hasOption("o")) {
|
if (cmd.hasOption("o")) {
|
||||||
Log.warn("Online mode enabled. The bot count will be set to 1.");
|
Log.warn("Online mode enabled. The bot count will be set to 1.");
|
||||||
botCount = 1;
|
botCount = 1;
|
||||||
|
|
||||||
// Create request parameters map
|
HttpClient httpClient = MinecraftAuth.createHttpClient();
|
||||||
Map<String, String> params = new HashMap<>();
|
JavaAuthManager authManager = JavaAuthManager.create(httpClient)
|
||||||
params.put("client_id", CLIENT_ID);
|
.login(DeviceCodeMsaAuthService::new, (Consumer<MsaDeviceCode>) msaDeviceCode -> {
|
||||||
params.put("scope", "XboxLive.signin");
|
Log.info("Authorize your Microsoft account on " + msaDeviceCode.getDirectVerificationUri());
|
||||||
|
Log.info("Waiting for authorization.");
|
||||||
|
});
|
||||||
|
|
||||||
// Send request to Microsoft OAuth api
|
MinecraftProfile mcProfile = authManager.getMinecraftProfile().getUpToDate();
|
||||||
// https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code
|
Log.info("Logged in with username: " + mcProfile.getName());
|
||||||
URI endpointURI = URI.create("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode");
|
|
||||||
MsaAuthenticationService.MsCodeResponse response = HTTP.makeRequestForm(
|
|
||||||
Proxy.NO_PROXY, endpointURI, params, MsaAuthenticationService.MsCodeResponse.class
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
Log.info("Please go to " + response.verification_uri.toURL() + " to authenticate your account - Code: " + response.user_code);
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
Log.error("Error while trying to get the url of the authentication page");
|
|
||||||
}
|
|
||||||
|
|
||||||
authService = new MsaAuthenticationService(CLIENT_ID, response.device_code);
|
GameProfile gameProfile = new GameProfile(mcProfile.getId(), mcProfile.getName());
|
||||||
|
protocol = new MinecraftProtocol(gameProfile, authManager.getMinecraftToken().getUpToDate().getToken());
|
||||||
// Wait for user to login on microsoft page
|
} else {
|
||||||
int retryMax = 20;
|
protocol = null;
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
authService.login();
|
|
||||||
break;
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (e instanceof AuthPendingException) {
|
|
||||||
Log.info("Authentication is pending, waiting for user to authenticate...");
|
|
||||||
try {
|
|
||||||
Thread.sleep(5000);
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
}
|
|
||||||
if (retryMax == 0)
|
|
||||||
throw e;
|
|
||||||
retryMax--;
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationService finalAuthService = authService;
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
for (int i = 0; i < botCount; i++) {
|
for (int i = 0; i < botCount; i++) {
|
||||||
try {
|
try {
|
||||||
@@ -342,15 +324,15 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bot bot = null;
|
Bot bot = null;
|
||||||
if (finalAuthService != null) {
|
if (protocol != null) {
|
||||||
bot = new Bot(
|
bot = new Bot(
|
||||||
finalAuthService,
|
protocol,
|
||||||
inetAddr,
|
inetAddr,
|
||||||
proxyInfo
|
proxyInfo
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
bot = new Bot(
|
bot = new Bot(
|
||||||
nickGen.nextNick(),
|
new MinecraftProtocol(nickGen.nextNick()),
|
||||||
inetAddr,
|
inetAddr,
|
||||||
proxyInfo
|
proxyInfo
|
||||||
);
|
);
|
||||||
@@ -390,12 +372,22 @@ public class Main {
|
|||||||
}, 1000L, 500L);
|
}, 1000L, 500L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Terminal terminal = TerminalBuilder.builder().build();
|
||||||
|
LineReader lineReader = LineReaderBuilder.builder()
|
||||||
|
.terminal(terminal)
|
||||||
|
.parser(new org.jline.reader.impl.DefaultParser())
|
||||||
|
.build();
|
||||||
|
|
||||||
Scanner scanner = new Scanner(System.in);
|
|
||||||
prompt = "ALL";
|
prompt = "ALL";
|
||||||
while (scanner.hasNextLine()) {
|
|
||||||
String line = scanner.nextLine();
|
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
String line;
|
||||||
|
try {
|
||||||
|
line = lineReader.readLine();
|
||||||
|
} catch (UserInterruptException e) {
|
||||||
|
System.exit(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (line.isEmpty()) {
|
if (line.isEmpty()) {
|
||||||
System.out.print(getPrompt() + "> ");
|
System.out.print(getPrompt() + "> ");
|
||||||
continue;
|
continue;
|
||||||
@@ -456,16 +448,12 @@ public class Main {
|
|||||||
|
|
||||||
|
|
||||||
// List command
|
// List command
|
||||||
else if (commandName.equalsIgnoreCase("list") || commandName.equalsIgnoreCase("ls"))
|
else if (commandName.equalsIgnoreCase("list") || commandName.equalsIgnoreCase("ls")) {
|
||||||
{
|
|
||||||
Log.info("There are " + bots.size() + " bots connected:");
|
Log.info("There are " + bots.size() + " bots connected:");
|
||||||
for (Bot bot : bots) {
|
for (Bot bot : bots) {
|
||||||
Log.info(bot.getNickname(), bot.hasMainListener() ? "[MainListener]" : "");
|
Log.info(bot.getNickname(), bot.hasMainListener() ? "[MainListener]" : "");
|
||||||
}
|
}
|
||||||
}
|
} else if (commandName.equalsIgnoreCase("leave") || commandName.equalsIgnoreCase("exit")) {
|
||||||
|
|
||||||
else if (commandName.equalsIgnoreCase("leave") || commandName.equalsIgnoreCase("exit"))
|
|
||||||
{
|
|
||||||
int limit = -1;
|
int limit = -1;
|
||||||
if (split.length >= 2) {
|
if (split.length >= 2) {
|
||||||
try {
|
try {
|
||||||
@@ -496,17 +484,12 @@ public class Main {
|
|||||||
bot.disconnect();
|
bot.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
else {
|
|
||||||
Log.warn("Invalid command");
|
Log.warn("Invalid command");
|
||||||
}
|
}
|
||||||
}
|
} else if (controlledBots.size() > 0) {
|
||||||
|
|
||||||
else if (controlledBots.size() > 0) {
|
|
||||||
controlledBots.forEach(bot -> bot.sendChat(line));
|
controlledBots.forEach(bot -> bot.sendChat(line));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
bots.forEach(bot -> bot.sendChat(line));
|
bots.forEach(bot -> bot.sendChat(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package me.creepermaxcz.mcbots;
|
package me.creepermaxcz.mcbots;
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundPlayerChatPacket;
|
import org.geysermc.mcprotocollib.network.Session;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundSystemChatPacket;
|
import org.geysermc.mcprotocollib.network.event.session.*;
|
||||||
import com.github.steveice10.packetlib.Session;
|
import org.geysermc.mcprotocollib.network.packet.Packet;
|
||||||
import com.github.steveice10.packetlib.event.session.*;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundPlayerChatPacket;
|
||||||
import com.github.steveice10.packetlib.packet.Packet;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundSystemChatPacket;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.TextComponent;
|
import net.kyori.adventure.text.TextComponent;
|
||||||
import net.kyori.adventure.text.TranslatableComponent;
|
import net.kyori.adventure.text.TranslatableComponent;
|
||||||
@@ -31,9 +31,9 @@ public class MainListener implements SessionListener {
|
|||||||
// Sometimes the message's body gets null.
|
// Sometimes the message's body gets null.
|
||||||
// For example, some commands like /say makes the message content as null.
|
// For example, some commands like /say makes the message content as null.
|
||||||
// However, the message exists as in getMessagePlain(), thus can retrieve message using the method.
|
// However, the message exists as in getMessagePlain(), thus can retrieve message using the method.
|
||||||
if (message == null) { // When this message was null.
|
if (message == null) {
|
||||||
Log.chat(Utils.getFullText((TextComponent) sender, clientboundPlayerChatPacket.getContent(), Main.coloredChat));
|
Log.chat(Utils.getFullText((TextComponent) sender, Component.text(clientboundPlayerChatPacket.getContent()), Main.coloredChat));
|
||||||
} else { // When message exists.
|
} else {
|
||||||
Log.chat(Utils.getFullText((TextComponent) sender, (TextComponent) message, Main.coloredChat));
|
Log.chat(Utils.getFullText((TextComponent) sender, (TextComponent) message, Main.coloredChat));
|
||||||
}
|
}
|
||||||
chatPrintedOut = true;
|
chatPrintedOut = true;
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
package me.creepermaxcz.mcbots;
|
package me.creepermaxcz.mcbots;
|
||||||
|
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.MinecraftConstants;
|
import org.geysermc.mcprotocollib.network.ClientSession;
|
||||||
import com.github.steveice10.mc.protocol.MinecraftProtocol;
|
import org.geysermc.mcprotocollib.network.Session;
|
||||||
import com.github.steveice10.mc.protocol.data.status.ServerStatusInfo;
|
import org.geysermc.mcprotocollib.network.event.session.DisconnectedEvent;
|
||||||
import com.github.steveice10.mc.protocol.data.status.handler.ServerInfoHandler;
|
import org.geysermc.mcprotocollib.network.event.session.SessionAdapter;
|
||||||
import com.github.steveice10.mc.protocol.data.status.handler.ServerPingTimeHandler;
|
import org.geysermc.mcprotocollib.network.factory.ClientNetworkSessionFactory;
|
||||||
import com.github.steveice10.packetlib.Session;
|
import org.geysermc.mcprotocollib.protocol.MinecraftConstants;
|
||||||
import com.github.steveice10.packetlib.event.session.DisconnectedEvent;
|
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
|
||||||
import com.github.steveice10.packetlib.event.session.SessionAdapter;
|
import org.geysermc.mcprotocollib.protocol.data.status.ServerStatusInfo;
|
||||||
import com.github.steveice10.packetlib.tcp.TcpClientSession;
|
import org.geysermc.mcprotocollib.protocol.data.status.handler.ServerInfoHandler;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.data.status.handler.ServerPingTimeHandler;
|
||||||
|
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
public class ServerInfo {
|
public class ServerInfo {
|
||||||
|
|
||||||
private final Session client;
|
private final ClientSession client;
|
||||||
private ServerStatusInfo serverStatusInfo;
|
private ServerStatusInfo serverStatusInfo;
|
||||||
private long ping;
|
private long ping;
|
||||||
private boolean done;
|
private boolean done;
|
||||||
@@ -23,7 +25,11 @@ public class ServerInfo {
|
|||||||
public ServerInfo(InetSocketAddress address) {
|
public ServerInfo(InetSocketAddress address) {
|
||||||
|
|
||||||
MinecraftProtocol protocol = new MinecraftProtocol();
|
MinecraftProtocol protocol = new MinecraftProtocol();
|
||||||
client = new TcpClientSession(address.getHostString(), address.getPort(), protocol, null);
|
|
||||||
|
client = ClientNetworkSessionFactory.factory()
|
||||||
|
.setAddress(address.getHostString(), address.getPort())
|
||||||
|
.setProtocol(protocol)
|
||||||
|
.create();
|
||||||
|
|
||||||
client.setFlag(MinecraftConstants.SERVER_INFO_HANDLER_KEY, (ServerInfoHandler) (session, info) -> {
|
client.setFlag(MinecraftConstants.SERVER_INFO_HANDLER_KEY, (ServerInfoHandler) (session, info) -> {
|
||||||
this.serverStatusInfo = info;
|
this.serverStatusInfo = info;
|
||||||
|
|||||||
@@ -7,15 +7,16 @@ import com.google.gson.JsonParser;
|
|||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.TextComponent;
|
import net.kyori.adventure.text.TextComponent;
|
||||||
import net.kyori.adventure.text.TranslatableComponent;
|
import net.kyori.adventure.text.TranslatableComponent;
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
import net.kyori.adventure.text.TranslationArgument;
|
||||||
import net.kyori.adventure.text.format.Style;
|
import net.kyori.adventure.text.format.Style;
|
||||||
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
import net.kyori.adventure.text.format.TextDecoration;
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.w3c.dom.Text;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -28,6 +29,7 @@ public class Utils {
|
|||||||
public static String getFullText(TextComponent message, boolean colored) {
|
public static String getFullText(TextComponent message, boolean colored) {
|
||||||
if (message.children().size() > 0) {
|
if (message.children().size() > 0) {
|
||||||
StringBuilder text = new StringBuilder();
|
StringBuilder text = new StringBuilder();
|
||||||
|
text.append(parseColor(message, colored));
|
||||||
for (Component child : message.children()) {
|
for (Component child : message.children()) {
|
||||||
if (child instanceof TextComponent) {
|
if (child instanceof TextComponent) {
|
||||||
text.append(getFullText((TextComponent) child, colored));
|
text.append(getFullText((TextComponent) child, colored));
|
||||||
@@ -38,15 +40,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
return text.toString();
|
return text.toString();
|
||||||
} else {
|
} else {
|
||||||
String out = message.content();
|
return parseColor(message, colored);
|
||||||
if (colored) {
|
|
||||||
if (message.style().color() != null) {
|
|
||||||
List<Attribute> formats = new ArrayList<>(getFormats(message.style().decorations()));
|
|
||||||
formats.add(getColor((NamedTextColor) message.style().color()));
|
|
||||||
out = colorize(out, formats.toArray(new Attribute[0]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,36 +49,35 @@ public class Utils {
|
|||||||
* From 1.19.1 the protocol does not include sender's information in the message.
|
* From 1.19.1 the protocol does not include sender's information in the message.
|
||||||
* Thus, we need to merge two TextComponent (sender and message) and generate a text that consists of sent message.
|
* Thus, we need to merge two TextComponent (sender and message) and generate a text that consists of sent message.
|
||||||
* This means using old getFullText will just send the message. Which does NOT contain username who sent this message
|
* This means using old getFullText will just send the message. Which does NOT contain username who sent this message
|
||||||
* @param sender The TextComponent for sender.
|
*
|
||||||
|
* @param sender The TextComponent for sender.
|
||||||
* @param message The TextComponent for message.
|
* @param message The TextComponent for message.
|
||||||
* @param colored Whether if this output was colored or not.
|
* @param colored Whether if this output was colored or not.
|
||||||
* @return Returns String in "Username : Message" format.
|
* @return Returns String in "Username : Message" format.
|
||||||
*/
|
*/
|
||||||
public static String getFullText(@Nullable TextComponent sender, TextComponent message, boolean colored) {
|
public static String getFullText(@Nullable TextComponent sender, TextComponent message, boolean colored) {
|
||||||
if (colored) { // Check if we are using colors.
|
if (colored) {
|
||||||
Style messageStyle = message.style();
|
Style messageStyle = message.style();
|
||||||
StringBuilder outString = new StringBuilder();
|
StringBuilder outString = new StringBuilder();
|
||||||
|
|
||||||
if (sender != null) {
|
if (sender != null) {
|
||||||
Style senderStyle = sender.style();
|
Style senderStyle = sender.style();
|
||||||
if (senderStyle.color() != null) { // Generate sender text with color.
|
if (senderStyle.color() != null) { // Generate sender text with color.
|
||||||
NamedTextColor color = (NamedTextColor) senderStyle.color();
|
outString.append(colorizeText(sender.content(), senderStyle.color()));
|
||||||
outString.append(colorize(sender.content(), getColor(color)));
|
|
||||||
} else {
|
} else {
|
||||||
outString.append(sender.content());
|
outString.append(sender.content());
|
||||||
}
|
}
|
||||||
outString.append(" : "); // Add delimiter as : between sender and message.
|
outString.append(" : "); // Add delimiter as : between sender and message.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageStyle.color() != null) { // Generate message text with color.
|
if (messageStyle.color() != null && sender != null) { // Generate message text with color.
|
||||||
NamedTextColor color = (NamedTextColor) messageStyle.color();
|
outString.append(colorizeText(sender.content(), messageStyle.color()));
|
||||||
outString.append(colorize(message.content(), getColor(color)));
|
|
||||||
} else {
|
} else {
|
||||||
outString.append(message.content());
|
outString.append(message.content());
|
||||||
}
|
}
|
||||||
|
|
||||||
return outString.toString();
|
return outString.toString();
|
||||||
} else { // When color formatting was disabled.
|
} else {
|
||||||
if (sender == null)
|
if (sender == null)
|
||||||
return message.content();
|
return message.content();
|
||||||
else
|
else
|
||||||
@@ -92,83 +85,21 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static String parseColor(TextComponent message, boolean colored) {
|
||||||
* A static function that gets full text from text components.
|
String out = message.content();
|
||||||
* From 1.19.1 the protocol does not include sender's information in the message.
|
if (colored) {
|
||||||
* Sometimes the message as component is null. When this happens, this method will show message as String.
|
if (message.style().color() != null) {
|
||||||
* Since this is just a String being represented, this will not have formatting on String.
|
List<Attribute> formats = new ArrayList<>(getFormats(message.style().decorations()));
|
||||||
* @param sender The TextComponent for sender.
|
TextColor color = message.style().color();
|
||||||
* @param message The TextComponent for message.
|
formats.add(Attribute.TEXT_COLOR(color.red(), color.green(), color.blue()));
|
||||||
* @param colored Whether if this output was colored or not.
|
out = colorize(out, formats.toArray(new Attribute[0]));
|
||||||
* @return Returns String in "Username : Message" format.
|
|
||||||
*/
|
|
||||||
public static String getFullText(@Nullable TextComponent sender, String message, boolean colored) {
|
|
||||||
if (colored) { // Check if we are using colors.
|
|
||||||
StringBuilder outString = new StringBuilder();
|
|
||||||
|
|
||||||
if (sender != null) {
|
|
||||||
Style senderStyle = sender.style();
|
|
||||||
if (senderStyle.color() != null) { // Generate sender text with color.
|
|
||||||
NamedTextColor color = (NamedTextColor) senderStyle.color();
|
|
||||||
outString.append(colorize(sender.content(), getColor(color)));
|
|
||||||
} else {
|
|
||||||
outString.append(sender.content());
|
|
||||||
}
|
|
||||||
outString.append(" : "); // Add delimiter as : between sender and message.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
outString.append(message);
|
|
||||||
return outString.toString();
|
|
||||||
} else { // When color formatting was disabled.
|
|
||||||
if (sender == null)
|
|
||||||
return message;
|
|
||||||
else
|
|
||||||
return sender.content() + " : " + message;
|
|
||||||
}
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Attribute getColor(NamedTextColor color) {
|
private static String colorizeText(String text, TextColor color) {
|
||||||
switch (color.toString()) {
|
return colorize(text, Attribute.TEXT_COLOR(color.red(), color.green(), color.blue()));
|
||||||
case "red":
|
|
||||||
return Attribute.BRIGHT_RED_TEXT();
|
|
||||||
case "dark_red":
|
|
||||||
return Attribute.RED_TEXT();
|
|
||||||
|
|
||||||
case "blue":
|
|
||||||
return Attribute.BRIGHT_BLUE_TEXT();
|
|
||||||
case "dark_blue":
|
|
||||||
return Attribute.BLUE_TEXT();
|
|
||||||
|
|
||||||
case "white":
|
|
||||||
return Attribute.BRIGHT_WHITE_TEXT();
|
|
||||||
case "black":
|
|
||||||
return Attribute.BLACK_TEXT();
|
|
||||||
case "gray":
|
|
||||||
return Attribute.WHITE_TEXT();
|
|
||||||
case "dark_gray":
|
|
||||||
return Attribute.BRIGHT_BLACK_TEXT();
|
|
||||||
|
|
||||||
case "aqua":
|
|
||||||
return Attribute.BRIGHT_CYAN_TEXT();
|
|
||||||
case "dark_aqua":
|
|
||||||
return Attribute.CYAN_TEXT();
|
|
||||||
|
|
||||||
case "yellow":
|
|
||||||
return Attribute.BRIGHT_YELLOW_TEXT();
|
|
||||||
case "gold":
|
|
||||||
return Attribute.YELLOW_TEXT();
|
|
||||||
|
|
||||||
case "green":
|
|
||||||
return Attribute.BRIGHT_GREEN_TEXT();
|
|
||||||
case "dark_green":
|
|
||||||
return Attribute.GREEN_TEXT();
|
|
||||||
|
|
||||||
case "purple":
|
|
||||||
return Attribute.BRIGHT_MAGENTA_TEXT();
|
|
||||||
case "dark_purple":
|
|
||||||
return Attribute.MAGENTA_TEXT();
|
|
||||||
}
|
|
||||||
return Attribute.NONE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Attribute> getFormats(Map<TextDecoration, TextDecoration.State> decorations) {
|
public static List<Attribute> getFormats(Map<TextDecoration, TextDecoration.State> decorations) {
|
||||||
@@ -191,42 +122,53 @@ public class Utils {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String translate(TranslatableComponent message) {
|
public static String translate(TranslatableComponent message) {
|
||||||
if (lang == null) {
|
try {
|
||||||
try {
|
if (lang == null) {
|
||||||
InputStream resource = Utils.class.getResourceAsStream("/files/lang.json");
|
try {
|
||||||
lang = new JsonParser().parse(new InputStreamReader(resource)).getAsJsonObject();
|
InputStream resource = Utils.class.getResourceAsStream("/files/lang.json");
|
||||||
} catch (Exception e) {
|
if (resource == null) {
|
||||||
Log.crit(e);
|
throw new Exception("Failed to load lang resource.");
|
||||||
}
|
}
|
||||||
}
|
lang = new JsonParser().parse(new InputStreamReader(resource)).getAsJsonObject();
|
||||||
//Log.info(message.toString());
|
} catch (Exception e) {
|
||||||
|
Log.crit(e);
|
||||||
String key = message.key();
|
|
||||||
String base = key;
|
|
||||||
|
|
||||||
JsonElement value = lang.get(key);
|
|
||||||
if (value != null) {
|
|
||||||
base = value.getAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<String> placeholders = new ArrayList<>();
|
|
||||||
|
|
||||||
if (message.args().size() > 0) {
|
|
||||||
for (Component component : message.args()) {
|
|
||||||
if (component instanceof TranslatableComponent) {
|
|
||||||
placeholders.add(translate((TranslatableComponent) component));
|
|
||||||
}
|
|
||||||
else if (component instanceof TextComponent) {
|
|
||||||
placeholders.add(getFullText((TextComponent) component, false));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Log.info(Arrays.toString(placeholders.toArray()));
|
//Log.info(message.toString());
|
||||||
return String.format(base, placeholders.toArray());
|
|
||||||
} else {
|
String key = message.key();
|
||||||
return base;
|
String base = key;
|
||||||
|
|
||||||
|
JsonElement value = lang.get(key);
|
||||||
|
if (value != null) {
|
||||||
|
base = value.getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<String> placeholders = new ArrayList<>();
|
||||||
|
|
||||||
|
if (message.arguments().size() > 0) {
|
||||||
|
for (TranslationArgument arg : message.arguments()) {
|
||||||
|
Component component = arg.asComponent();
|
||||||
|
if (component instanceof TranslatableComponent) {
|
||||||
|
placeholders.add(translate((TranslatableComponent) component));
|
||||||
|
} else if (arg instanceof TextComponent) {
|
||||||
|
placeholders.add(getFullText((TextComponent) component, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Log.info(Arrays.toString(placeholders.toArray()));
|
||||||
|
try {
|
||||||
|
return String.format(base, placeholders.toArray());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.error("Error formatting '"+base+"' with placeholders " + Arrays.toString(placeholders.toArray()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+5698
-4494
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user