Files
chomens-bot-java/src/main/java/me/chayapak1/chomens_bot/Bot.java
ChomeNS 21100ee7b1 refactor: use Executor.execute instead of submit
refactor: use mcprotocollib's packet handler executor to send the brand and the client information (even though it's for handling packets)
2025-05-31 17:30:23 +07:00

416 lines
17 KiB
Java

package me.chayapak1.chomens_bot;
import me.chayapak1.chomens_bot.data.color.ColorPalette;
import me.chayapak1.chomens_bot.data.listener.Listener;
import me.chayapak1.chomens_bot.plugins.*;
import me.chayapak1.chomens_bot.util.ComponentUtilities;
import me.chayapak1.chomens_bot.util.RandomStringUtilities;
import me.chayapak1.chomens_bot.util.UUIDUtilities;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import org.geysermc.mcprotocollib.auth.GameProfile;
import org.geysermc.mcprotocollib.network.BuiltinFlags;
import org.geysermc.mcprotocollib.network.ClientSession;
import org.geysermc.mcprotocollib.network.Session;
import org.geysermc.mcprotocollib.network.event.session.*;
import org.geysermc.mcprotocollib.network.factory.ClientNetworkSessionFactory;
import org.geysermc.mcprotocollib.network.packet.Packet;
import org.geysermc.mcprotocollib.protocol.MinecraftConstants;
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.HandPreference;
import org.geysermc.mcprotocollib.protocol.data.game.setting.ChatVisibility;
import org.geysermc.mcprotocollib.protocol.data.game.setting.ParticleStatus;
import org.geysermc.mcprotocollib.protocol.data.game.setting.SkinPart;
import org.geysermc.mcprotocollib.protocol.packet.common.clientbound.ClientboundCustomPayloadPacket;
import org.geysermc.mcprotocollib.protocol.packet.common.clientbound.ClientboundStoreCookiePacket;
import org.geysermc.mcprotocollib.protocol.packet.common.clientbound.ClientboundTransferPacket;
import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundClientInformationPacket;
import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundCustomPayloadPacket;
import org.geysermc.mcprotocollib.protocol.packet.cookie.clientbound.ClientboundCookieRequestPacket;
import org.geysermc.mcprotocollib.protocol.packet.cookie.serverbound.ServerboundCookieResponsePacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundLoginPacket;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundPlayerLoadedPacket;
import org.geysermc.mcprotocollib.protocol.packet.login.clientbound.ClientboundCustomQueryPacket;
import org.geysermc.mcprotocollib.protocol.packet.login.clientbound.ClientboundLoginCompressionPacket;
import org.geysermc.mcprotocollib.protocol.packet.login.clientbound.ClientboundLoginFinishedPacket;
import org.geysermc.mcprotocollib.protocol.packet.login.serverbound.ServerboundCustomQueryAnswerPacket;
import org.geysermc.mcprotocollib.protocol.packet.login.serverbound.ServerboundLoginAcknowledgedPacket;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Bot extends SessionAdapter {
public final String host;
public final int port;
public final Configuration.BotOption options;
public final Configuration config;
public final ColorPalette colorPalette;
public final List<Bot> bots;
public String username;
public GameProfile profile;
public ClientSession session;
private final Map<Key, byte[]> cookies = new HashMap<>();
private boolean isTransferring = false;
public boolean printDisconnectedCause = false;
public int connectAttempts = 0;
public boolean loggedIn = false;
public long loginTime;
public final ExecutorService executorService = Main.EXECUTOR_SERVICE;
public final ScheduledExecutorService executor = Main.EXECUTOR;
public final ListenerManagerPlugin listener;
public final LoggerPlugin logger;
public final TickPlugin tick;
public final ChatPlugin chat;
public final CommandSpyPlugin commandSpy;
public final PositionPlugin position;
public final ServerFeaturesPlugin serverFeatures;
public final SelfCarePlugin selfCare;
public final QueryPlugin query;
public final ExtrasMessengerPlugin extrasMessenger;
public final WorldPlugin world;
public final CorePlugin core;
public final TeamPlugin team;
public final PlayersPlugin players;
public final TabCompletePlugin tabComplete;
public final CommandHandlerPlugin commandHandler;
public final ChatCommandHandlerPlugin chatCommandHandler;
public final BossbarManagerPlugin bossbar;
public final MusicPlayerPlugin music;
public final TPSPlugin tps;
public final EvalPlugin eval;
public final TrustedPlugin trusted;
public final GrepLogPlugin grepLog;
public final BruhifyPlugin bruhify;
public final CloopPlugin cloop;
public final ExploitsPlugin exploits;
public final FilterManagerPlugin filterManager;
public final PlayerFilterPlugin playerFilter;
public final CommandSuggestionPlugin commandSuggestion;
public final MailPlugin mail;
public final PacketSnifferPlugin packetSniffer;
public final VoiceChatPlugin voiceChat;
public final BotSelectorBroadcasterPlugin selectorBroadcaster;
public final ChomeNSModIntegrationPlugin chomeNSMod;
public final AuthPlugin auth;
public final ScreensharePlugin screenshare = null;
public final ClearChatNameAnnouncerPlugin clearChatNameAnnouncer;
public final WhitelistPlugin whitelist;
public final PlayersDatabasePlugin playersDatabase;
public final IPFilterPlugin ipFilter;
public final RainbowArmorPlugin rainbowArmor;
public Bot (
final Configuration.BotOption botOption,
final List<Bot> bots,
final Configuration config
) {
this.host = botOption.host;
this.port = botOption.port;
this.options = botOption;
this.bots = bots;
this.config = config;
this.colorPalette = new ColorPalette(config.colorPalette);
this.listener = new ListenerManagerPlugin(this);
this.tick = new TickPlugin(this);
this.chat = new ChatPlugin(this);
this.commandSpy = new CommandSpyPlugin(this);
this.query = new QueryPlugin(this);
this.extrasMessenger = new ExtrasMessengerPlugin(this);
this.chomeNSMod = new ChomeNSModIntegrationPlugin(this);
this.commandSuggestion = new CommandSuggestionPlugin(this);
this.logger = new LoggerPlugin(this);
this.position = new PositionPlugin(this);
this.serverFeatures = new ServerFeaturesPlugin(this);
this.selfCare = new SelfCarePlugin(this);
this.world = new WorldPlugin(this);
this.core = new CorePlugin(this);
this.team = new TeamPlugin(this);
this.playersDatabase = new PlayersDatabasePlugin(this);
this.players = new PlayersPlugin(this);
this.tabComplete = new TabCompletePlugin(this);
this.commandHandler = new CommandHandlerPlugin(this);
this.chatCommandHandler = new ChatCommandHandlerPlugin(this);
this.bossbar = new BossbarManagerPlugin(this);
this.music = new MusicPlayerPlugin(this);
this.tps = new TPSPlugin(this);
this.eval = new EvalPlugin(this);
this.trusted = new TrustedPlugin(this);
this.grepLog = new GrepLogPlugin(this);
this.bruhify = new BruhifyPlugin(this);
this.cloop = new CloopPlugin(this);
this.exploits = new ExploitsPlugin(this);
this.filterManager = new FilterManagerPlugin(this);
this.playerFilter = new PlayerFilterPlugin(this);
this.mail = new MailPlugin(this);
this.packetSniffer = new PacketSnifferPlugin(this);
this.voiceChat = new VoiceChatPlugin(this);
this.selectorBroadcaster = new BotSelectorBroadcasterPlugin(this);
this.auth = new AuthPlugin(this);
// this.screenshare = new ScreensharePlugin(this);
this.clearChatNameAnnouncer = new ClearChatNameAnnouncerPlugin(this);
this.whitelist = new WhitelistPlugin(this);
this.ipFilter = new IPFilterPlugin(this);
this.rainbowArmor = new RainbowArmorPlugin(this);
}
protected void connect () {
reconnect();
}
private void reconnect () {
connectAttempts++;
this.listener.dispatch(Listener::onConnecting);
if (!isTransferring) {
username = options.username == null ?
RandomStringUtilities.generate(8) :
options.username;
}
final ClientNetworkSessionFactory factory = ClientNetworkSessionFactory.factory()
.setAddress(host, port)
.setProtocol(
new MinecraftProtocol(
new GameProfile(
UUIDUtilities.getOfflineUUID(username),
username
),
null
)
);
if (this.session != null) factory.setPacketHandlerExecutor(this.session.getPacketHandlerExecutor());
final ClientSession session = factory.create();
this.session = session;
// this is still needed since MinecraftProtocol will use this flag to set the
// handshake intention to transfer, it will be set back to false at
// login success
session.setFlag(BuiltinFlags.CLIENT_TRANSFERRING, isTransferring);
session.setFlag(MinecraftConstants.FOLLOW_TRANSFERS, false); // we have our own transfer handler
session.setFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, options.resolveSRV);
session.addListener(this);
session.connect(false);
}
@Override
public void packetReceived (final Session session, final Packet packet) {
this.listener.dispatch(listener -> listener.packetReceived(session, packet));
switch (packet) {
case final ClientboundLoginPacket t_packet -> packetReceived(t_packet);
case final ClientboundLoginFinishedPacket t_packet -> packetReceived(t_packet);
case final ClientboundCustomQueryPacket t_packet -> packetReceived(t_packet);
case final ClientboundCookieRequestPacket t_packet -> packetReceived(t_packet);
case final ClientboundTransferPacket t_packet -> packetReceived(t_packet);
case final ClientboundStoreCookiePacket t_packet -> packetReceived(t_packet);
case final ClientboundLoginCompressionPacket t_packet -> packetReceived(t_packet);
case final ClientboundCustomPayloadPacket t_packet -> packetReceived(t_packet);
default -> { }
}
}
private void packetReceived (final ClientboundLoginFinishedPacket packet) {
profile = packet.getProfile();
session.setFlag(BuiltinFlags.CLIENT_TRANSFERRING, false);
}
private void packetReceived (final ClientboundLoginPacket ignoredPacket) {
loggedIn = true;
loginTime = System.currentTimeMillis();
connectAttempts = 0;
this.listener.dispatch(listener -> listener.connected(new ConnectedEvent(session)));
session.send(ServerboundPlayerLoadedPacket.INSTANCE);
}
private void packetReceived (final ClientboundCustomQueryPacket packet) {
session.send(new ServerboundCustomQueryAnswerPacket(packet.getMessageId(), null));
}
private void packetReceived (final ClientboundCustomPayloadPacket packet) {
if (!packet.getChannel().asString().equals("minecraft:register")) return;
session.send(
new ServerboundCustomPayloadPacket(
Key.key("minecraft", "register"),
"\0".getBytes(StandardCharsets.UTF_8)
)
);
}
private void packetReceived (final ClientboundCookieRequestPacket packet) {
session.send(
new ServerboundCookieResponsePacket(
packet.getKey(),
cookies.get(packet.getKey())
)
);
}
private void packetReceived (final ClientboundStoreCookiePacket packet) {
cookies.put(packet.getKey(), packet.getPayload());
}
private void packetReceived (final ClientboundTransferPacket ignoredPacket) {
this.isTransferring = true;
session.disconnect(Component.translatable("disconnect.transfer"));
}
// MCProtocolLib devs forgot
// "Negative values will disable compression, meaning the packet format should remain in the uncompressed packet format."
// https://minecraft.wiki/w/Java_Edition_protocol#Set_Compression
// they actually implemented this, but at this commit:
// https://github.com/GeyserMC/MCProtocolLib/commit/f8460356db2b92fbf7cb506757fe8f87a011a1f7#diff-a9066adbcb6d5503f5edefe3ec95465cf755f1585e02b732a6fa907afe7c7177R67-L103
// they removed it, for some reason
private void packetReceived (final ClientboundLoginCompressionPacket packet) {
if (packet.getThreshold() < 0) {
session.setCompression(null);
}
}
@Override
public void packetSending (final PacketSendingEvent packetSendingEvent) {
this.listener.dispatch(listener -> listener.packetSending(packetSendingEvent));
}
@Override
public void packetSent (final Session session, final Packet packet) {
this.listener.dispatch(listener -> listener.packetSent(session, packet));
if (packet instanceof final ServerboundLoginAcknowledgedPacket t_packet) packetSent(t_packet);
}
private void packetSent (final ServerboundLoginAcknowledgedPacket ignoredPacket) {
// doesn't work without executing
session.getPacketHandlerExecutor().execute(() -> {
// for voicechat
session.send(new ServerboundCustomPayloadPacket(
Key.key("minecraft:brand"),
"\u0006fabric".getBytes(StandardCharsets.UTF_8)
));
// this enables all the skin parts (by default they are ALL DISABLED
// which is why most bots when they use someone's skin they are just
// kinda broken)
final List<SkinPart> skinParts = new ArrayList<>(Arrays.asList(SkinPart.VALUES));
// we also set other stuffs here
session.send(
new ServerboundClientInformationPacket(
ComponentUtilities.LANGUAGE.getOrDefault("language.code", "en_us"),
16,
ChatVisibility.FULL,
true,
skinParts,
HandPreference.RIGHT_HAND,
false,
false,
ParticleStatus.ALL
)
);
});
}
@Override
public void packetError (final PacketErrorEvent packetErrorEvent) {
this.listener.dispatch(listener -> listener.packetError(packetErrorEvent));
packetErrorEvent.setSuppress(true); // fixes the ohio sus exploit
}
@Override
public void disconnecting (final DisconnectingEvent disconnectingEvent) {
this.listener.dispatch(listener -> listener.disconnecting(disconnectingEvent));
}
@Override
public void disconnected (final DisconnectedEvent disconnectedEvent) {
loggedIn = false;
final Throwable cause = disconnectedEvent.getCause();
if (printDisconnectedCause && cause != null) logger.error(cause);
if (Main.stopping) return;
if (!isTransferring) cookies.clear();
int reconnectDelay = options.reconnectDelay;
if (isTransferring) {
// for now, it's going to transfer to the same server instead of
// other servers
reconnect(); // instantly reconnect
isTransferring = false;
} else {
final String stringMessage = ComponentUtilities.stringify(disconnectedEvent.getReason());
if (
stringMessage.equals("Wait 5 seconds before connecting, thanks! :)") ||
stringMessage.equals("You are logging in too fast, try again later.") ||
stringMessage.equals("Connection throttled! Please wait before reconnecting.")
) reconnectDelay = 1000 * (5 + 2); // 2 seconds extra delay just in case
executor.schedule(this::reconnect, reconnectDelay, TimeUnit.MILLISECONDS);
}
this.listener.dispatch(listener -> listener.disconnected(disconnectedEvent));
}
public String getServerString () { return getServerString(false); }
public String getServerString (final boolean bypassHidden) {
return options.hidden && !bypassHidden ?
options.serverName :
host + ":" + port;
}
public void stop () {
session.disconnect("Received stop signal");
Main.bots.remove(this);
}
@Override
public String toString () {
return "Bot{" +
"host='" + host + '\'' +
", port=" + port +
", username='" + username + '\'' +
", loggedIn=" + loggedIn +
'}';
}
}