diff --git a/build.gradle b/build.gradle index d42e206..8f4e1ca 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group = 'com.springwater.easybot' -version = '2.1.1' +version = '2.1.2' repositories { mavenCentral() @@ -60,7 +60,7 @@ tasks.build.dependsOn(tasks.shadowJar) dependencies { compileOnly "org.spigotmc:spigot-api:1.13.2-R0.1-SNAPSHOT" - implementation 'com.springwater.easybot:easybot-bridge:1.4-SNAPSHOT' + implementation 'com.springwater.easybot:easybot-bridge:1.5' implementation 'org.javassist:javassist:3.28.0-GA' implementation 'bot.inker.acj:runtime:1.5' implementation 'org.reflections:reflections:0.10.2' @@ -75,6 +75,9 @@ dependencies { implementation("net.kyori:adventure-text-serializer-plain:4.17.0") implementation("org.glavo:rcon-java:2.0.2") + + compileOnly 'org.projectlombok:lombok:1.18.32' + annotationProcessor 'org.projectlombok:lombok:1.18.32' } diff --git a/libs/fakeplayer-0.3.19.jar b/libs/fakeplayer-0.3.19.jar new file mode 100644 index 0000000..60d5068 Binary files /dev/null and b/libs/fakeplayer-0.3.19.jar differ diff --git a/src/main/java/com/springwater/easybot/EasyBotImpl.java b/src/main/java/com/springwater/easybot/BridgeImpl.java similarity index 99% rename from src/main/java/com/springwater/easybot/EasyBotImpl.java rename to src/main/java/com/springwater/easybot/BridgeImpl.java index 1b494dd..9ef7ef3 100644 --- a/src/main/java/com/springwater/easybot/EasyBotImpl.java +++ b/src/main/java/com/springwater/easybot/BridgeImpl.java @@ -17,7 +17,7 @@ import java.util.logging.Logger; import java.util.stream.Collectors; -public class EasyBotImpl implements BridgeBehavior { +public class BridgeImpl implements BridgeBehavior { private final Logger logger = Logger.getLogger("EasyBotImpl"); @Override @@ -187,6 +187,7 @@ public void SyncToChatExtra(List segments, String text) { @Override public List getPlayerList() { return Bukkit.getOnlinePlayers().stream() + .filter(FakePlayerUtils::isNotFake) .map(x -> { PlayerInfo info = new PlayerInfo(); info.setPlayerName(GeyserUtils.getNameByPlayer(x)); diff --git a/src/main/java/com/springwater/easybot/Easybot.java b/src/main/java/com/springwater/easybot/Easybot.java index 8f19781..c92985a 100644 --- a/src/main/java/com/springwater/easybot/Easybot.java +++ b/src/main/java/com/springwater/easybot/Easybot.java @@ -12,6 +12,7 @@ import com.springwater.easybot.papi.OfflineStatisticExpansion; import com.springwater.easybot.task.TaskManager; import com.springwater.easybot.utils.BukkitUtils; +import com.springwater.easybot.utils.FakePlayerUtils; import com.springwater.easybot.utils.ItemsAdderUtils; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; @@ -57,7 +58,7 @@ public void onEnable() { ClientProfile.setDebugMode(getConfig().getBoolean("debug", false)); instance = this; - bridgeBehavior = new EasyBotImpl(); + bridgeBehavior = new BridgeImpl(); initHooks(); @@ -82,11 +83,11 @@ public void onEnable() { private void handleSkinsRestorerCompatibility() { - if(BukkitUtils.hasSkinsRestorer() && !BukkitUtils.placeholderApiInstalled()){ + if (BukkitUtils.hasSkinsRestorer() && !BukkitUtils.placeholderApiInstalled()) { getLogger().info("\u001B[31m※ 检测到SkinsRestorer插件,但未检测到PlaceholderApi插件,EasyBot获取SkinsRestorer的皮肤需要依赖PlaceholderApi!\u001B[0m"); } - if (BukkitUtils.hasSkinsRestorer() && BukkitUtils.placeholderApiInstalled()) { + if (BukkitUtils.hasSkinsRestorer() && BukkitUtils.placeholderApiInstalled()) { getLogger().info("\u001B[32m※ 检测到SkinsRestorer插件,玩家皮肤将通过该插件获取!\u001B[0m"); ClientProfile.setHasSkinsRestorer(true); } else if (BukkitUtils.hasPaperSkinApi()) { @@ -98,7 +99,7 @@ private void handleSkinsRestorerCompatibility() { } private void handleItemsAdderCompatibility() { - if(ItemsAdderUtils.isItemsAdderInstalled()){ + if (ItemsAdderUtils.isItemsAdderInstalled()) { getLogger().info("\u001B[32m※ 检测到ItemsAdder插件!\u001B[0m"); ClientProfile.setHasItemsAdder(true); Bukkit.getPluginManager().registerEvents(new ItemsAdderEvents(), this); @@ -238,6 +239,12 @@ private void initHooks() { getLogger().info("\u001B[32m※ 已注册离线变量,专用文档: \u001B[33mhttps://docs.hualib.com/offline-papi.html\u001B[0m"); } + getLogger().info("\u001B[32m[>]\u001B[0m 假人插件"); + if (FakePlayerUtils.isInstalled()) { + getLogger().info(" \u001B[32m[OK]\u001B[0m 已开启假人过滤"); + } else { + getLogger().info(" \u001B[32m[OK]\u001B[0m 已关闭假人过滤"); + } } private void uninstallPlaceholderApi() { diff --git a/src/main/java/com/springwater/easybot/event/BukkitSideMessageSyncEvents.java b/src/main/java/com/springwater/easybot/event/BukkitSideMessageSyncEvents.java index 8261e55..7f9714a 100644 --- a/src/main/java/com/springwater/easybot/event/BukkitSideMessageSyncEvents.java +++ b/src/main/java/com/springwater/easybot/event/BukkitSideMessageSyncEvents.java @@ -3,6 +3,7 @@ import com.springwater.easybot.Easybot; import com.springwater.easybot.bridge.packet.PlayerInfoWithRaw; import com.springwater.easybot.utils.BridgeUtils; +import com.springwater.easybot.utils.FakePlayerUtils; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -12,6 +13,7 @@ public class BukkitSideMessageSyncEvents implements Listener { @EventHandler(priority = EventPriority.LOWEST) public static void syncMessage(AsyncPlayerChatEvent event){ if(Easybot.instance.getConfig().getBoolean("skip_options.skip_chat")) return; + if(FakePlayerUtils.isFake(event.getPlayer())) return; if(!event.isCancelled()){ PlayerInfoWithRaw playerInfo = BridgeUtils.buildPlayerInfoFull(event.getPlayer()); new Thread(() -> Easybot.getClient().syncMessage(playerInfo, event.getMessage(), false), "EasyBotThread-SyncMessage(BukkitSide)").start(); diff --git a/src/main/java/com/springwater/easybot/event/PaperSideMessageSyncEvents.java b/src/main/java/com/springwater/easybot/event/PaperSideMessageSyncEvents.java index bfdfb51..db96e17 100644 --- a/src/main/java/com/springwater/easybot/event/PaperSideMessageSyncEvents.java +++ b/src/main/java/com/springwater/easybot/event/PaperSideMessageSyncEvents.java @@ -3,6 +3,7 @@ import com.springwater.easybot.Easybot; import com.springwater.easybot.bridge.packet.PlayerInfoWithRaw; import com.springwater.easybot.utils.BridgeUtils; +import com.springwater.easybot.utils.FakePlayerUtils; import io.papermc.paper.event.player.AsyncChatEvent; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.event.EventHandler; @@ -13,6 +14,7 @@ public class PaperSideMessageSyncEvents implements Listener { @EventHandler(priority = EventPriority.LOWEST) public static void syncMessage(AsyncChatEvent event){ if(Easybot.instance.getConfig().getBoolean("skip_options.skip_chat")) return; + if(FakePlayerUtils.isFake(event.getPlayer())) return; if(!event.isCancelled()){ PlayerInfoWithRaw playerInfo = BridgeUtils.buildPlayerInfoFull(event.getPlayer()); String message = PlainTextComponentSerializer.plainText().serialize(event.message()); diff --git a/src/main/java/com/springwater/easybot/event/PlayerChatMessageSyncEvents.java b/src/main/java/com/springwater/easybot/event/PlayerChatMessageSyncEvents.java index 5ffd904..0873b08 100644 --- a/src/main/java/com/springwater/easybot/event/PlayerChatMessageSyncEvents.java +++ b/src/main/java/com/springwater/easybot/event/PlayerChatMessageSyncEvents.java @@ -4,6 +4,7 @@ import com.springwater.easybot.Easybot; import com.springwater.easybot.bridge.packet.PlayerInfoWithRaw; import com.springwater.easybot.utils.BridgeUtils; +import com.springwater.easybot.utils.FakePlayerUtils; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -12,6 +13,7 @@ public class PlayerChatMessageSyncEvents implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public static void syncMessage(PlayerChannelChatEvent event){ if(Easybot.instance.getConfig().getBoolean("skip_options.skip_chat")) return; + if(FakePlayerUtils.isFake(event.getPlayer())) return; if(!event.isCancelled()){ PlayerInfoWithRaw playerInfo = BridgeUtils.buildPlayerInfoFull(event.getPlayer()); new Thread(() -> Easybot.getClient().syncMessage(playerInfo, event.getOriginalMessage(), false), "EasyBotThread-SyncMessage(PlayerChat)").start(); diff --git a/src/main/java/com/springwater/easybot/event/PlayerDeathSyncEvents.java b/src/main/java/com/springwater/easybot/event/PlayerDeathSyncEvents.java index c638f43..57a6c9b 100644 --- a/src/main/java/com/springwater/easybot/event/PlayerDeathSyncEvents.java +++ b/src/main/java/com/springwater/easybot/event/PlayerDeathSyncEvents.java @@ -3,6 +3,7 @@ import com.springwater.easybot.Easybot; import com.springwater.easybot.bridge.packet.PlayerInfoWithRaw; import com.springwater.easybot.utils.BridgeUtils; +import com.springwater.easybot.utils.FakePlayerUtils; import org.bukkit.block.Block; import org.bukkit.entity.Arrow; import org.bukkit.entity.Entity; @@ -39,6 +40,7 @@ public String getKiller(Player player) { @EventHandler(priority = EventPriority.LOWEST) public void onPlayerDeath(PlayerDeathEvent event) { if(Easybot.instance.getConfig().getBoolean("skip_options.skip_death")) return; + if(FakePlayerUtils.isFake(event.getPlayer())) return; PlayerInfoWithRaw playerInfo = BridgeUtils.buildPlayerInfoFull(event.getEntity()); String deathMessage = event.getDeathMessage(); if(deathMessage == null){ diff --git a/src/main/java/com/springwater/easybot/event/PlayerEvents.java b/src/main/java/com/springwater/easybot/event/PlayerEvents.java index ddd7524..4911d94 100644 --- a/src/main/java/com/springwater/easybot/event/PlayerEvents.java +++ b/src/main/java/com/springwater/easybot/event/PlayerEvents.java @@ -1,21 +1,18 @@ package com.springwater.easybot.event; -import com.google.errorprone.annotations.Var; import com.springwater.easybot.Easybot; import com.springwater.easybot.bridge.packet.PlayerLoginResultPacket; -import com.springwater.easybot.utils.BridgeUtils; +import com.springwater.easybot.utils.FakePlayerUtils; import com.springwater.easybot.utils.GeyserUtils; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerPreLoginEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerLoginEvent; public class PlayerEvents implements Listener { @EventHandler public void onPlayerLogin(AsyncPlayerPreLoginEvent event) { try { + if(FakePlayerUtils.isFake(event.getName())) return; String ip = event.getAddress().getHostAddress(); String name = GeyserUtils.getName(event.getUniqueId()); if (name == null) name = event.getPlayerProfile().getName(); diff --git a/src/main/java/com/springwater/easybot/event/PlayerJoinExitEvents.java b/src/main/java/com/springwater/easybot/event/PlayerJoinExitEvents.java index 63da00b..85776b9 100644 --- a/src/main/java/com/springwater/easybot/event/PlayerJoinExitEvents.java +++ b/src/main/java/com/springwater/easybot/event/PlayerJoinExitEvents.java @@ -3,6 +3,7 @@ import com.springwater.easybot.Easybot; import com.springwater.easybot.bridge.packet.PlayerInfoWithRaw; import com.springwater.easybot.utils.BridgeUtils; +import com.springwater.easybot.utils.FakePlayerUtils; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -13,6 +14,7 @@ public class PlayerJoinExitEvents implements Listener { @EventHandler(priority = EventPriority.LOWEST) public void onPlayerJoin(PlayerJoinEvent event) { if(Easybot.instance.getConfig().getBoolean("skip_options.skip_join")) return; + if(FakePlayerUtils.isFake(event.getPlayer())) return; PlayerInfoWithRaw playerInfo = BridgeUtils.buildPlayerInfoFull(event.getPlayer()); new Thread(() -> Easybot.getClient().syncEnterExit(playerInfo, true), "EasyBotThread-SyncPlayerJoinMessage").start(); } @@ -20,6 +22,7 @@ public void onPlayerJoin(PlayerJoinEvent event) { @EventHandler(priority = EventPriority.LOWEST) public void onPlayerQuit(PlayerQuitEvent event) { if(Easybot.instance.getConfig().getBoolean("skip_options.skip_quit")) return; + if(FakePlayerUtils.isFake(event.getPlayer())) return; PlayerInfoWithRaw playerInfo = BridgeUtils.buildPlayerInfoFull(event.getPlayer()); new Thread(() -> Easybot.getClient().syncEnterExit(playerInfo, false), "EasyBotThread-SyncPlayerExitMessage").start(); } diff --git a/src/main/java/com/springwater/easybot/event/RedisChatMessageSyncEvents.java b/src/main/java/com/springwater/easybot/event/RedisChatMessageSyncEvents.java index 214b57a..813cc32 100644 --- a/src/main/java/com/springwater/easybot/event/RedisChatMessageSyncEvents.java +++ b/src/main/java/com/springwater/easybot/event/RedisChatMessageSyncEvents.java @@ -3,6 +3,7 @@ import com.springwater.easybot.Easybot; import com.springwater.easybot.bridge.packet.PlayerInfoWithRaw; import com.springwater.easybot.utils.BridgeUtils; +import com.springwater.easybot.utils.FakePlayerUtils; import dev.unnm3d.redischat.api.events.AsyncRedisChatMessageEvent; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.entity.Player; @@ -17,6 +18,7 @@ public static void syncMessage(AsyncRedisChatMessageEvent event){ if(!event.isCancelled()){ if(event.getSender() instanceof Player){ Player player = (Player) event.getSender(); + if(FakePlayerUtils.isFake(player)) return; PlayerInfoWithRaw playerInfo = BridgeUtils.buildPlayerInfoFull(player); String message = PlainTextComponentSerializer.plainText().serialize(event.getContent()); new Thread(() -> Easybot.getClient().syncMessage(playerInfo, message, false), "EasyBotThread-SyncMessage(PlayerChat)").start(); diff --git a/src/main/java/com/springwater/easybot/rcon/NativeRcon.java b/src/main/java/com/springwater/easybot/rcon/NativeRcon.java index b1d30ee..2d7d269 100644 --- a/src/main/java/com/springwater/easybot/rcon/NativeRcon.java +++ b/src/main/java/com/springwater/easybot/rcon/NativeRcon.java @@ -8,34 +8,66 @@ public class NativeRcon { private Rcon rcon; + private String address; + private int port; + private String password; - public void start() throws AuthenticationException, IOException { - String address = Easybot.instance.getConfig().getString("adapter.native_rcon.address", "localhost"); - int port = Easybot.instance.getConfig().getInt("adapter.native_rcon.port", 25575); - String password = Easybot.instance.getConfig().getString("adapter.native_rcon.password", ""); - if(password.equals("")){ + public synchronized void start() { + this.address = Easybot.instance.getConfig().getString("adapter.native_rcon.address", "localhost"); + this.port = Easybot.instance.getConfig().getInt("adapter.native_rcon.port", 25575); + this.password = Easybot.instance.getConfig().getString("adapter.native_rcon.password", ""); + + if (this.password.isEmpty()) { Easybot.instance.getLogger().warning("Rcon密码为空,可能无法连接到服务器!"); } - rcon = new Rcon(address, port, password); + attemptConnection(); } - public String executeCommand(String command){ - if(rcon == null) return "Rcon不在线"; + private void attemptConnection() { + close(); + + try { + rcon = new Rcon(address, port, password); + Easybot.instance.getLogger().info("Rcon连接成功: " + address + ":" + port); + } catch (IOException | AuthenticationException e) { + Easybot.instance.getLogger().warning("Rcon连接建立失败: " + e.getMessage()); + rcon = null; + } + } + + public synchronized String executeCommand(String command) { + if (rcon == null) { + attemptConnection(); + if (rcon == null) { + return "Rcon离线,尝试重连失败"; + } + } try { return rcon.command(command); } catch (IOException e) { - Easybot.instance.getLogger().warning("NativeRCON执行失败: " + e); - return "NativeRCON执行失败,请查看服务器日志"; + Easybot.instance.getLogger().warning("Rcon连接丢失,正在尝试自动重连..."); + attemptConnection(); + if (rcon != null) { + try { + return rcon.command(command); + } catch (IOException ex) { + Easybot.instance.getLogger().warning("Rcon重连后执行仍失败: " + ex); + return "Rcon重连成功,但命令执行失败: " + ex.getMessage(); + } + } else { + return "Rcon连接断开,自动重连失败"; + } } } - public void close(){ - if(rcon == null) return; + public synchronized void close() { + if (rcon == null) return; try { rcon.close(); - rcon = null; } catch (IOException e) { + // 忽略关闭时的错误 + } finally { + rcon = null; } } - -} +} \ No newline at end of file diff --git a/src/main/java/com/springwater/easybot/utils/FakePlayerUtils.java b/src/main/java/com/springwater/easybot/utils/FakePlayerUtils.java new file mode 100644 index 0000000..dd87abb --- /dev/null +++ b/src/main/java/com/springwater/easybot/utils/FakePlayerUtils.java @@ -0,0 +1,56 @@ +package com.springwater.easybot.utils; +import io.github.hello09x.fakeplayer.core.Main; +import io.github.hello09x.fakeplayer.core.manager.FakeplayerList; +import io.github.hello09x.fakeplayer.core.manager.FakeplayerManager; +import lombok.SneakyThrows; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Field; +import java.util.Map; + +public class FakePlayerUtils { + private static boolean installed = false; + private static FakeplayerList list; + private static volatile Map cachedPlayersMap; + public static boolean isInstalled() { + try { + // 找 io.github.hello09x.fakeplayer.core.Main + Class.forName("io.github.hello09x.fakeplayer.core.Main"); + installed = true; + }catch (Exception ignored) { + installed = false; + } + return installed; + } + + @SneakyThrows + private static void init(){ + if(list != null) return; + list = Main.getInjector().getInstance(FakeplayerList.class); + // 解释一下为什么一定要通过这个字典获取 + // https://github.com/tanyaofei/minecraft-fakeplayer/issues/191 + Field field = list.getClass().getDeclaredField("playersByName"); + field.setAccessible(true); + //noinspection unchecked + cachedPlayersMap = (Map) field.get(list); + } + + public static boolean isNotFake(@NotNull Player player) { + if (!installed) return true; + init(); + return !cachedPlayersMap.containsKey(player.getName()); + } + + public static boolean isFake(@NotNull Player player) { + if (!installed) return false; + init(); + return cachedPlayersMap.containsKey(player.getName()); + } + + public static boolean isFake(@NotNull String name) { + if (!installed) return false; + init(); + return cachedPlayersMap.containsKey(name); + } +} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c78267b..5d65e96 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,7 +3,7 @@ version: '${version}' main: com.springwater.easybot.Easybot api-version: '1.13' load: STARTUP -softdepend: [Essentials,PlaceHolderAPI,PlayerChat,RedisChat,TrChat,Geyser-Spigot,SkinsRestorer,ItemsAdder] +softdepend: [Essentials,PlaceHolderAPI,PlayerChat,RedisChat,TrChat,Geyser-Spigot,SkinsRestorer,ItemsAdder,FakePlayer] folia-supported: true commands: