From 422bbcfdf445234a5d107a2db181f9ccf1919204 Mon Sep 17 00:00:00 2001 From: Tomasz Kuraczyk Date: Mon, 16 Mar 2026 21:54:09 +0100 Subject: [PATCH 1/4] Sketch of proper KickPlayerAfterBan system --- build.gradle | 1 + .../java/net/kuraczyk/banhammer/cmds/Ban.java | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/build.gradle b/build.gradle index 4d36b5c..ac0085b 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,7 @@ dependencies { implementation 'org.postgresql:postgresql:42.7.7' implementation 'org.flywaydb:flyway-core:10.7.2' implementation 'org.flywaydb:flyway-database-postgresql:10.7.2' + implementation 'com.google.code.gson:gson:2.11.0' } shadowJar { diff --git a/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java b/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java index 7ce7d2e..1e6a8a6 100644 --- a/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java +++ b/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java @@ -17,6 +17,9 @@ import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; +import com.google.gson.Gson; + + public class Ban implements CommandExecutor { BanHammer plugin; @@ -309,6 +312,21 @@ public class Ban implements CommandExecutor { } } + private void kickPlayer(PlayerBan b){ + final Gson gson = new Gson(); + try { + Connection conn = plugin.db.getConnection(); + + String preparedJson = gson.toJson(b); + PreparedStatement ps = conn.prepareStatement("SELECT pg_notify('ban_jobs', ?)"); + ps.setString(1, preparedJson); + ps.execute(); + } + catch (SQLException e) { + plugin.getLogger().severe(e.toString()); + } + } + private String resolveBanMessagePlaceholders(String s, String player, String scope, String reason, String operator) { return s.replace("{player}", player) .replace("{scope}", scope) From a7a77b9cc08095bd262212dfe4c4ca9961c30eac Mon Sep 17 00:00:00 2001 From: Tomasz Date: Tue, 17 Mar 2026 17:58:41 +0100 Subject: [PATCH 2/4] sync --- .../java/net/kuraczyk/banhammer/cmds/Ban.java | 16 +++++++++--- .../net/kuraczyk/banhammer/cmds/BanIP.java | 25 +++++++++++++++++++ .../banhammer/workers/BanJobWorker.java | 24 ++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/main/java/net/kuraczyk/banhammer/workers/BanJobWorker.java diff --git a/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java b/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java index 1e6a8a6..d8ac176 100644 --- a/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java +++ b/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java @@ -1,7 +1,10 @@ package net.kuraczyk.banhammer.cmds; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import io.papermc.paper.configuration.type.fallback.FallbackValue; import net.kuraczyk.banhammer.BanHammer; +import net.kuraczyk.banhammer.utils.IPBan; import net.kuraczyk.banhammer.utils.PlayerBan; import net.kuraczyk.banhammer.utils.TextUtils; import net.kyori.adventure.text.Component; @@ -312,14 +315,21 @@ public class Ban implements CommandExecutor { } } - private void kickPlayer(PlayerBan b){ + private void createPlayerKickJob(IPBan b, String scope){ final Gson gson = new Gson(); try { Connection conn = plugin.db.getConnection(); - String preparedJson = gson.toJson(b); + JsonElement banObject = gson.toJsonTree(b); + JsonObject wrapper = new JsonObject(); + wrapper.addProperty("ban_type", "player"); + wrapper.addProperty("ban_scope", scope); + + wrapper.add("ban_data", banObject); + String json = gson.toJson(wrapper); + PreparedStatement ps = conn.prepareStatement("SELECT pg_notify('ban_jobs', ?)"); - ps.setString(1, preparedJson); + ps.setString(1, json); ps.execute(); } catch (SQLException e) { diff --git a/src/main/java/net/kuraczyk/banhammer/cmds/BanIP.java b/src/main/java/net/kuraczyk/banhammer/cmds/BanIP.java index 8a6991d..169e3b1 100644 --- a/src/main/java/net/kuraczyk/banhammer/cmds/BanIP.java +++ b/src/main/java/net/kuraczyk/banhammer/cmds/BanIP.java @@ -18,6 +18,10 @@ import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.Gson; + public class BanIP implements CommandExecutor { BanHammer plugin; @@ -325,6 +329,27 @@ public class BanIP implements CommandExecutor { } } + private void createIPKickJob(IPBan b, String scope){ + final Gson gson = new Gson(); + try { + Connection conn = plugin.db.getConnection(); + + JsonElement banObject = gson.toJsonTree(b); + JsonObject wrapper = new JsonObject(); + wrapper.addProperty("ban_type", "ip"); + wrapper.addProperty("ban_scope", scope); + wrapper.add("ban_data", banObject); + String json = gson.toJson(wrapper); + + PreparedStatement ps = conn.prepareStatement("SELECT pg_notify('ban_jobs', ?)"); + ps.setString(1, json); + ps.execute(); + } + catch (SQLException e) { + plugin.getLogger().severe(e.toString()); + } + } + private String resolveIpBanMessagePlaceholders(String s, String ipAddr, String scope, String reason, String operator) { return s.replace("{ipAddr}", ipAddr) .replace("{scope}", scope) diff --git a/src/main/java/net/kuraczyk/banhammer/workers/BanJobWorker.java b/src/main/java/net/kuraczyk/banhammer/workers/BanJobWorker.java new file mode 100644 index 0000000..48566c0 --- /dev/null +++ b/src/main/java/net/kuraczyk/banhammer/workers/BanJobWorker.java @@ -0,0 +1,24 @@ +package net.kuraczyk.banhammer.workers; + +import net.kuraczyk.banhammer.BanHammer; + +public class BanJobWorker { + + BanHammer plugin; + + public void BanJobWorker(BanHammer p) { + plugin=p; + banJobListener(); + } + + private void banJobListener() { + + } + + private boolean banJobExecutor() { + + } + + + +} From 18cf76ae7fde2b859b7fd540243392f3080632f6 Mon Sep 17 00:00:00 2001 From: Tomasz Kuraczyk Date: Tue, 17 Mar 2026 20:32:20 +0100 Subject: [PATCH 3/4] Fix to critical issue #6 --- .../net/kuraczyk/banhammer/BanHammer.java | 3 + .../java/net/kuraczyk/banhammer/cmds/Ban.java | 23 ++-- .../net/kuraczyk/banhammer/cmds/BanIP.java | 22 +--- .../net/kuraczyk/banhammer/utils/IPBan.java | 20 ++++ .../kuraczyk/banhammer/utils/PlayerBan.java | 25 ++++ .../banhammer/workers/BanJobWorker.java | 110 +++++++++++++++++- 6 files changed, 165 insertions(+), 38 deletions(-) diff --git a/src/main/java/net/kuraczyk/banhammer/BanHammer.java b/src/main/java/net/kuraczyk/banhammer/BanHammer.java index 5675a7c..921ea89 100644 --- a/src/main/java/net/kuraczyk/banhammer/BanHammer.java +++ b/src/main/java/net/kuraczyk/banhammer/BanHammer.java @@ -1,5 +1,6 @@ package net.kuraczyk.banhammer; +import net.kuraczyk.banhammer.workers.BanJobWorker; import org.bukkit.ChatColor; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -57,6 +58,8 @@ public final class BanHammer extends JavaPlugin { new BHConfig(this); getServer().getPluginManager().registerEvents(new PlayerPreLogin(this), this); + + new BanJobWorker(this); } @Override diff --git a/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java b/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java index d8ac176..4bf22dc 100644 --- a/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java +++ b/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java @@ -106,7 +106,7 @@ public class Ban implements CommandExecutor { stmt.setString(2, scope); ResultSet rs = stmt.executeQuery(); if(rs.next()){ - p.sendMessage(TextUtils.colorString(addPrefix(plugin.translations.getString("command.ban.alreadyBannedInScopeError")))); + p.sendMessage(TextUtils.colorString(resolveBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.ban.alreadyBannedInScopeError")), args[0], scope, reason, p.getName()))); return false; } } catch (SQLException e){ @@ -119,9 +119,7 @@ public class Ban implements CommandExecutor { if(b != null) { if(banEndDate == null) { - if(target!=null) - if(target.isOnline()) - if(Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanPerm"), b))); + createPlayerKickJob(b, scope); p.sendMessage(TextUtils.colorString(resolveBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.ban.successfulBanMessagePerm")), args[0], scope, reason, p.getName()))); String bcMess = plugin.translations.getString("command.ban.banPublicAnnouncementPerm"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveBanMessagePlaceholders(bcMess, args[0], scope, reason, p.getName()))); @@ -137,9 +135,7 @@ public class Ban implements CommandExecutor { long timeLeftSeconds = TimeUnit.MILLISECONDS.toSeconds(timeLeft); String parsedBanDuration = TextUtils.parsePolishDate(timeLeftDays, timeLeftHours, timeLeftMinutes, timeLeftSeconds); - if(target!=null) - if(target.isOnline()) - if(Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanTemp"), b))); + createPlayerKickJob(b, scope); p.sendMessage(TextUtils.colorString(resolveBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.ban.successfulBanMessageTemp")), args[0], scope, reason, p.getName(), parsedBanDuration))); String bcMess = plugin.translations.getString("command.ban.banPublicAnnouncementTemp"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveBanMessagePlaceholders(bcMess, args[0], scope, reason, p.getName(), parsedBanDuration))); @@ -220,7 +216,7 @@ public class Ban implements CommandExecutor { stmt.setString(2, scope); ResultSet rs = stmt.executeQuery(); if(rs.next()){ - Bukkit.getConsoleSender().sendMessage(TextUtils.colorString(addPrefix(plugin.translations.getString("command.ban.alreadyBannedInScopeError")))); + Bukkit.getConsoleSender().sendMessage(TextUtils.colorString(resolveBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.ban.alreadyBannedInScopeError")), args[0], scope, reason, "CONSOLE"))); return false; } } catch (SQLException e){ @@ -233,9 +229,7 @@ public class Ban implements CommandExecutor { if(b != null) { if(banEndDate == null) { - if(target!=null) - if(target.isOnline()) - if(Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanPerm"), b))); + createPlayerKickJob(b, scope); Bukkit.getConsoleSender().sendMessage(TextUtils.colorString(resolveBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.ban.successfulBanMessagePerm")), args[0], scope, reason, "CONSOLE"))); String bcMess = plugin.translations.getString("command.ban.banPublicAnnouncementPerm"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveBanMessagePlaceholders(bcMess, args[0], scope, reason, "CONSOLE"))); @@ -251,9 +245,7 @@ public class Ban implements CommandExecutor { long timeLeftSeconds = TimeUnit.MILLISECONDS.toSeconds(timeLeft); String parsedBanDuration = TextUtils.parsePolishDate(timeLeftDays, timeLeftHours, timeLeftMinutes, timeLeftSeconds); - if(target!=null) - if(target.isOnline()) - if(Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanTemp"), b))); + createPlayerKickJob(b, scope); Bukkit.getConsoleSender().sendMessage(TextUtils.colorString(resolveBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.ban.successfulBanMessageTemp")), args[0], scope, reason, "CONSOLE", parsedBanDuration))); String bcMess = plugin.translations.getString("command.ban.banPublicAnnouncementTemp"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveBanMessagePlaceholders(bcMess, args[0], scope, reason, "CONSOLE", parsedBanDuration))); @@ -315,7 +307,7 @@ public class Ban implements CommandExecutor { } } - private void createPlayerKickJob(IPBan b, String scope){ + private void createPlayerKickJob(PlayerBan b, String scope){ final Gson gson = new Gson(); try { Connection conn = plugin.db.getConnection(); @@ -324,7 +316,6 @@ public class Ban implements CommandExecutor { JsonObject wrapper = new JsonObject(); wrapper.addProperty("ban_type", "player"); wrapper.addProperty("ban_scope", scope); - wrapper.add("ban_data", banObject); String json = gson.toJson(wrapper); diff --git a/src/main/java/net/kuraczyk/banhammer/cmds/BanIP.java b/src/main/java/net/kuraczyk/banhammer/cmds/BanIP.java index 169e3b1..3a7c5cd 100644 --- a/src/main/java/net/kuraczyk/banhammer/cmds/BanIP.java +++ b/src/main/java/net/kuraczyk/banhammer/cmds/BanIP.java @@ -123,10 +123,7 @@ public class BanIP implements CommandExecutor { if(b != null) { if(banEndDate == null) { - for(Player target : Bukkit.getOnlinePlayers()) { - if (target.getAddress().getAddress().equals(addr)) - if(Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanPerm"), b))); - } + createIPKickJob(b, scope); p.sendMessage(TextUtils.colorString(resolveIpBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.banip.successfulBanMessagePerm")), args[0], scope, reason, p.getName()))); String bcMess = plugin.translations.getString("command.banip.banPublicAnnouncementPerm"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveIpBanMessagePlaceholders(bcMess, args[0], scope, reason, p.getName()))); @@ -142,11 +139,7 @@ public class BanIP implements CommandExecutor { long timeLeftSeconds = TimeUnit.MILLISECONDS.toSeconds(timeLeft); String parsedBanDuration = TextUtils.parsePolishDate(timeLeftDays, timeLeftHours, timeLeftMinutes, timeLeftSeconds); - for(Player target : Bukkit.getOnlinePlayers()) { - if (target.getAddress().getAddress().equals(addr)) - if (Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) - target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanTemp"), b))); - } + createIPKickJob(b, scope); p.sendMessage(TextUtils.colorString(resolveIpBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.banip.successfulBanMessageTemp")), args[0], scope, reason, p.getName(), parsedBanDuration))); String bcMess = plugin.translations.getString("command.banip.banPublicAnnouncementTemp"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveIpBanMessagePlaceholders(bcMess, args[0], scope, reason, p.getName(), parsedBanDuration))); @@ -245,10 +238,7 @@ public class BanIP implements CommandExecutor { if(b != null) { if(banEndDate == null) { - for(Player target : Bukkit.getOnlinePlayers()) { - if (target.getAddress().getAddress().equals(addr)) - if(Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanPerm"), b))); - } + createIPKickJob(b, scope); Bukkit.getConsoleSender().sendMessage(TextUtils.colorString(resolveIpBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.banip.successfulBanMessagePerm")), args[0], scope, reason, "CONSOLE"))); String bcMess = plugin.translations.getString("command.banip.banPublicAnnouncementPerm"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveIpBanMessagePlaceholders(bcMess, args[0], scope, reason, "CONSOLE"))); @@ -264,11 +254,7 @@ public class BanIP implements CommandExecutor { long timeLeftSeconds = TimeUnit.MILLISECONDS.toSeconds(timeLeft); String parsedBanDuration = TextUtils.parsePolishDate(timeLeftDays, timeLeftHours, timeLeftMinutes, timeLeftSeconds); - for(Player target : Bukkit.getOnlinePlayers()) { - if (target.getAddress().getAddress().equals(addr)) - if (Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) - target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanTemp"), b))); - } + createIPKickJob(b, scope); Bukkit.getConsoleSender().sendMessage(TextUtils.colorString(resolveIpBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.banip.successfulBanMessageTemp")), args[0], scope, reason, "CONSOLE", parsedBanDuration))); String bcMess = plugin.translations.getString("command.banip.banPublicAnnouncementTemp"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveIpBanMessagePlaceholders(bcMess, args[0], scope, reason, "CONSOLE", parsedBanDuration))); diff --git a/src/main/java/net/kuraczyk/banhammer/utils/IPBan.java b/src/main/java/net/kuraczyk/banhammer/utils/IPBan.java index adb6d68..f412d3e 100644 --- a/src/main/java/net/kuraczyk/banhammer/utils/IPBan.java +++ b/src/main/java/net/kuraczyk/banhammer/utils/IPBan.java @@ -17,4 +17,24 @@ public class IPBan { this.operator = operator; this.banId = banId; } + + public InetAddress getIp() { + return ip; + } + + public String getReason() { + return reason; + } + + public String getTimeLeft() { + return timeLeft; + } + + public String getOperator() { + return operator; + } + + public UUID getBanId() { + return banId; + } } diff --git a/src/main/java/net/kuraczyk/banhammer/utils/PlayerBan.java b/src/main/java/net/kuraczyk/banhammer/utils/PlayerBan.java index 7172850..88060b5 100644 --- a/src/main/java/net/kuraczyk/banhammer/utils/PlayerBan.java +++ b/src/main/java/net/kuraczyk/banhammer/utils/PlayerBan.java @@ -2,6 +2,7 @@ package net.kuraczyk.banhammer.utils; import org.bukkit.entity.Player; +import java.net.InetAddress; import java.sql.Timestamp; import java.util.UUID; @@ -22,4 +23,28 @@ public class PlayerBan { this.banId = banId; } + public String getPlayerName() { + return playerName; + } + + public UUID getPlayerUUID() { + return uuid; + } + + public String getReason() { + return reason; + } + + public String getTimeLeft() { + return timeLeft; + } + + public String getOperator() { + return operator; + } + + public UUID getBanId() { + return banId; + } + } \ No newline at end of file diff --git a/src/main/java/net/kuraczyk/banhammer/workers/BanJobWorker.java b/src/main/java/net/kuraczyk/banhammer/workers/BanJobWorker.java index 48566c0..2a68639 100644 --- a/src/main/java/net/kuraczyk/banhammer/workers/BanJobWorker.java +++ b/src/main/java/net/kuraczyk/banhammer/workers/BanJobWorker.java @@ -1,24 +1,126 @@ package net.kuraczyk.banhammer.workers; import net.kuraczyk.banhammer.BanHammer; +import net.kuraczyk.banhammer.utils.IPBan; +import net.kuraczyk.banhammer.utils.PlayerBan; +import net.kuraczyk.banhammer.utils.TextUtils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.postgresql.PGConnection; +import org.postgresql.PGNotification; + +import javax.swing.plaf.nimbus.State; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Objects; + +import com.google.gson.*; public class BanJobWorker { BanHammer plugin; - public void BanJobWorker(BanHammer p) { + public BanJobWorker(BanHammer p) { plugin=p; banJobListener(); } private void banJobListener() { + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { + try { + Connection conn = plugin.db.getConnection(); + PGConnection pgConn = conn.unwrap(PGConnection.class); + try (Statement stmt = conn.createStatement()) { + stmt.execute("LISTEN ban_jobs"); + } + + while (!conn.isClosed()){ + try (Statement ping = conn.createStatement()) { + ping.execute("SELECT 1"); + } + + PGNotification[] notifications = pgConn.getNotifications(); + + if(notifications!=null){ + for(PGNotification notif : notifications){ + JsonObject unwrappedJson = JsonParser.parseString(notif.getParameter()).getAsJsonObject(); + String banType = unwrappedJson.get("ban_type").getAsString(); + String banScope = unwrappedJson.get("ban_scope").getAsString(); + JsonElement banData = unwrappedJson.get("ban_data"); + + if(banScope.equals("*") || banScope.equals(plugin.config.getString("serverID"))){ + if(banJobExecutor(banType, banData)) plugin.getLogger().info(String.format("[BanHammer] Job %d has been executed successfully!", notif.getPID())); + else plugin.getLogger().severe("[BanHammer] Error occured while executing ban job!"); + } + + } + } + } + + } catch (SQLException e) { + plugin.getLogger().severe(e.toString()); + } + }); } - private boolean banJobExecutor() { - + private boolean banJobExecutor(String type, JsonElement banData) { + final Gson gson = new Gson(); + switch(type){ + case "player" -> { + PlayerBan ban = gson.fromJson(banData, PlayerBan.class); + handlePlayerBanJob(ban); + } + case "ip" -> { + IPBan ban = gson.fromJson(banData, IPBan.class); + handleIPBanJob(ban); + } + default -> { + plugin.getLogger().severe("[BanHammer] (BanJobExecutorException) Unexpected ban type provided."); + return false; + } + } + return true; } + private boolean handlePlayerBanJob(PlayerBan b) { + Player target = Bukkit.getPlayer(b.getPlayerName()); + if(target!=null) + if(target.isOnline()) { + if(b.getTimeLeft()!=null){ + plugin.getServer().getScheduler().runTask(plugin, () -> { + target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanTemp"), b))); + }); + return true; + } + plugin.getServer().getScheduler().runTask(plugin, () -> { + target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanPerm"), b))); + }); + return true; + } + return false; + } -} + private boolean handleIPBanJob(IPBan b) { + for(Player target : Bukkit.getOnlinePlayers()) { + if (target.getAddress().getAddress().equals(b.getIp())) { + if (b.getTimeLeft() != null) { + plugin.getServer().getScheduler().runTask(plugin, () -> { + target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanTemp"), b))); + }); + return true; + } + plugin.getServer().getScheduler().runTask(plugin, () -> { + target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanPerm"), b))); + }); + return true; + } + } + + return false; + } + +} \ No newline at end of file From 508d27b896427152d4646abb53056504edc349d0 Mon Sep 17 00:00:00 2001 From: Tomasz Kuraczyk Date: Tue, 17 Mar 2026 19:36:19 +0000 Subject: [PATCH 4/4] Merge critical issue fixes to stable --- build.gradle | 1 + .../net/kuraczyk/banhammer/BanHammer.java | 3 + .../java/net/kuraczyk/banhammer/cmds/Ban.java | 47 +++++-- .../net/kuraczyk/banhammer/cmds/BanIP.java | 47 ++++--- .../net/kuraczyk/banhammer/utils/IPBan.java | 20 +++ .../kuraczyk/banhammer/utils/PlayerBan.java | 25 ++++ .../banhammer/workers/BanJobWorker.java | 126 ++++++++++++++++++ 7 files changed, 237 insertions(+), 32 deletions(-) create mode 100644 src/main/java/net/kuraczyk/banhammer/workers/BanJobWorker.java diff --git a/build.gradle b/build.gradle index 4d36b5c..ac0085b 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,7 @@ dependencies { implementation 'org.postgresql:postgresql:42.7.7' implementation 'org.flywaydb:flyway-core:10.7.2' implementation 'org.flywaydb:flyway-database-postgresql:10.7.2' + implementation 'com.google.code.gson:gson:2.11.0' } shadowJar { diff --git a/src/main/java/net/kuraczyk/banhammer/BanHammer.java b/src/main/java/net/kuraczyk/banhammer/BanHammer.java index 5675a7c..921ea89 100644 --- a/src/main/java/net/kuraczyk/banhammer/BanHammer.java +++ b/src/main/java/net/kuraczyk/banhammer/BanHammer.java @@ -1,5 +1,6 @@ package net.kuraczyk.banhammer; +import net.kuraczyk.banhammer.workers.BanJobWorker; import org.bukkit.ChatColor; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -57,6 +58,8 @@ public final class BanHammer extends JavaPlugin { new BHConfig(this); getServer().getPluginManager().registerEvents(new PlayerPreLogin(this), this); + + new BanJobWorker(this); } @Override diff --git a/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java b/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java index 7ce7d2e..4bf22dc 100644 --- a/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java +++ b/src/main/java/net/kuraczyk/banhammer/cmds/Ban.java @@ -1,7 +1,10 @@ package net.kuraczyk.banhammer.cmds; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import io.papermc.paper.configuration.type.fallback.FallbackValue; import net.kuraczyk.banhammer.BanHammer; +import net.kuraczyk.banhammer.utils.IPBan; import net.kuraczyk.banhammer.utils.PlayerBan; import net.kuraczyk.banhammer.utils.TextUtils; import net.kyori.adventure.text.Component; @@ -17,6 +20,9 @@ import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; +import com.google.gson.Gson; + + public class Ban implements CommandExecutor { BanHammer plugin; @@ -100,7 +106,7 @@ public class Ban implements CommandExecutor { stmt.setString(2, scope); ResultSet rs = stmt.executeQuery(); if(rs.next()){ - p.sendMessage(TextUtils.colorString(addPrefix(plugin.translations.getString("command.ban.alreadyBannedInScopeError")))); + p.sendMessage(TextUtils.colorString(resolveBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.ban.alreadyBannedInScopeError")), args[0], scope, reason, p.getName()))); return false; } } catch (SQLException e){ @@ -113,9 +119,7 @@ public class Ban implements CommandExecutor { if(b != null) { if(banEndDate == null) { - if(target!=null) - if(target.isOnline()) - if(Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanPerm"), b))); + createPlayerKickJob(b, scope); p.sendMessage(TextUtils.colorString(resolveBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.ban.successfulBanMessagePerm")), args[0], scope, reason, p.getName()))); String bcMess = plugin.translations.getString("command.ban.banPublicAnnouncementPerm"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveBanMessagePlaceholders(bcMess, args[0], scope, reason, p.getName()))); @@ -131,9 +135,7 @@ public class Ban implements CommandExecutor { long timeLeftSeconds = TimeUnit.MILLISECONDS.toSeconds(timeLeft); String parsedBanDuration = TextUtils.parsePolishDate(timeLeftDays, timeLeftHours, timeLeftMinutes, timeLeftSeconds); - if(target!=null) - if(target.isOnline()) - if(Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanTemp"), b))); + createPlayerKickJob(b, scope); p.sendMessage(TextUtils.colorString(resolveBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.ban.successfulBanMessageTemp")), args[0], scope, reason, p.getName(), parsedBanDuration))); String bcMess = plugin.translations.getString("command.ban.banPublicAnnouncementTemp"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveBanMessagePlaceholders(bcMess, args[0], scope, reason, p.getName(), parsedBanDuration))); @@ -214,7 +216,7 @@ public class Ban implements CommandExecutor { stmt.setString(2, scope); ResultSet rs = stmt.executeQuery(); if(rs.next()){ - Bukkit.getConsoleSender().sendMessage(TextUtils.colorString(addPrefix(plugin.translations.getString("command.ban.alreadyBannedInScopeError")))); + Bukkit.getConsoleSender().sendMessage(TextUtils.colorString(resolveBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.ban.alreadyBannedInScopeError")), args[0], scope, reason, "CONSOLE"))); return false; } } catch (SQLException e){ @@ -227,9 +229,7 @@ public class Ban implements CommandExecutor { if(b != null) { if(banEndDate == null) { - if(target!=null) - if(target.isOnline()) - if(Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanPerm"), b))); + createPlayerKickJob(b, scope); Bukkit.getConsoleSender().sendMessage(TextUtils.colorString(resolveBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.ban.successfulBanMessagePerm")), args[0], scope, reason, "CONSOLE"))); String bcMess = plugin.translations.getString("command.ban.banPublicAnnouncementPerm"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveBanMessagePlaceholders(bcMess, args[0], scope, reason, "CONSOLE"))); @@ -245,9 +245,7 @@ public class Ban implements CommandExecutor { long timeLeftSeconds = TimeUnit.MILLISECONDS.toSeconds(timeLeft); String parsedBanDuration = TextUtils.parsePolishDate(timeLeftDays, timeLeftHours, timeLeftMinutes, timeLeftSeconds); - if(target!=null) - if(target.isOnline()) - if(Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanTemp"), b))); + createPlayerKickJob(b, scope); Bukkit.getConsoleSender().sendMessage(TextUtils.colorString(resolveBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.ban.successfulBanMessageTemp")), args[0], scope, reason, "CONSOLE", parsedBanDuration))); String bcMess = plugin.translations.getString("command.ban.banPublicAnnouncementTemp"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveBanMessagePlaceholders(bcMess, args[0], scope, reason, "CONSOLE", parsedBanDuration))); @@ -309,6 +307,27 @@ public class Ban implements CommandExecutor { } } + private void createPlayerKickJob(PlayerBan b, String scope){ + final Gson gson = new Gson(); + try { + Connection conn = plugin.db.getConnection(); + + JsonElement banObject = gson.toJsonTree(b); + JsonObject wrapper = new JsonObject(); + wrapper.addProperty("ban_type", "player"); + wrapper.addProperty("ban_scope", scope); + wrapper.add("ban_data", banObject); + String json = gson.toJson(wrapper); + + PreparedStatement ps = conn.prepareStatement("SELECT pg_notify('ban_jobs', ?)"); + ps.setString(1, json); + ps.execute(); + } + catch (SQLException e) { + plugin.getLogger().severe(e.toString()); + } + } + private String resolveBanMessagePlaceholders(String s, String player, String scope, String reason, String operator) { return s.replace("{player}", player) .replace("{scope}", scope) diff --git a/src/main/java/net/kuraczyk/banhammer/cmds/BanIP.java b/src/main/java/net/kuraczyk/banhammer/cmds/BanIP.java index 8a6991d..3a7c5cd 100644 --- a/src/main/java/net/kuraczyk/banhammer/cmds/BanIP.java +++ b/src/main/java/net/kuraczyk/banhammer/cmds/BanIP.java @@ -18,6 +18,10 @@ import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.Gson; + public class BanIP implements CommandExecutor { BanHammer plugin; @@ -119,10 +123,7 @@ public class BanIP implements CommandExecutor { if(b != null) { if(banEndDate == null) { - for(Player target : Bukkit.getOnlinePlayers()) { - if (target.getAddress().getAddress().equals(addr)) - if(Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanPerm"), b))); - } + createIPKickJob(b, scope); p.sendMessage(TextUtils.colorString(resolveIpBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.banip.successfulBanMessagePerm")), args[0], scope, reason, p.getName()))); String bcMess = plugin.translations.getString("command.banip.banPublicAnnouncementPerm"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveIpBanMessagePlaceholders(bcMess, args[0], scope, reason, p.getName()))); @@ -138,11 +139,7 @@ public class BanIP implements CommandExecutor { long timeLeftSeconds = TimeUnit.MILLISECONDS.toSeconds(timeLeft); String parsedBanDuration = TextUtils.parsePolishDate(timeLeftDays, timeLeftHours, timeLeftMinutes, timeLeftSeconds); - for(Player target : Bukkit.getOnlinePlayers()) { - if (target.getAddress().getAddress().equals(addr)) - if (Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) - target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanTemp"), b))); - } + createIPKickJob(b, scope); p.sendMessage(TextUtils.colorString(resolveIpBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.banip.successfulBanMessageTemp")), args[0], scope, reason, p.getName(), parsedBanDuration))); String bcMess = plugin.translations.getString("command.banip.banPublicAnnouncementTemp"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveIpBanMessagePlaceholders(bcMess, args[0], scope, reason, p.getName(), parsedBanDuration))); @@ -241,10 +238,7 @@ public class BanIP implements CommandExecutor { if(b != null) { if(banEndDate == null) { - for(Player target : Bukkit.getOnlinePlayers()) { - if (target.getAddress().getAddress().equals(addr)) - if(Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanPerm"), b))); - } + createIPKickJob(b, scope); Bukkit.getConsoleSender().sendMessage(TextUtils.colorString(resolveIpBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.banip.successfulBanMessagePerm")), args[0], scope, reason, "CONSOLE"))); String bcMess = plugin.translations.getString("command.banip.banPublicAnnouncementPerm"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveIpBanMessagePlaceholders(bcMess, args[0], scope, reason, "CONSOLE"))); @@ -260,11 +254,7 @@ public class BanIP implements CommandExecutor { long timeLeftSeconds = TimeUnit.MILLISECONDS.toSeconds(timeLeft); String parsedBanDuration = TextUtils.parsePolishDate(timeLeftDays, timeLeftHours, timeLeftMinutes, timeLeftSeconds); - for(Player target : Bukkit.getOnlinePlayers()) { - if (target.getAddress().getAddress().equals(addr)) - if (Objects.equals(plugin.config.getString("serverID"), scope) || Objects.equals(scope, "*")) - target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanTemp"), b))); - } + createIPKickJob(b, scope); Bukkit.getConsoleSender().sendMessage(TextUtils.colorString(resolveIpBanMessagePlaceholders(addPrefix(plugin.translations.getString("command.banip.successfulBanMessageTemp")), args[0], scope, reason, "CONSOLE", parsedBanDuration))); String bcMess = plugin.translations.getString("command.banip.banPublicAnnouncementTemp"); if(bcMess!=null) Bukkit.broadcast(TextUtils.colorString(resolveIpBanMessagePlaceholders(bcMess, args[0], scope, reason, "CONSOLE", parsedBanDuration))); @@ -325,6 +315,27 @@ public class BanIP implements CommandExecutor { } } + private void createIPKickJob(IPBan b, String scope){ + final Gson gson = new Gson(); + try { + Connection conn = plugin.db.getConnection(); + + JsonElement banObject = gson.toJsonTree(b); + JsonObject wrapper = new JsonObject(); + wrapper.addProperty("ban_type", "ip"); + wrapper.addProperty("ban_scope", scope); + wrapper.add("ban_data", banObject); + String json = gson.toJson(wrapper); + + PreparedStatement ps = conn.prepareStatement("SELECT pg_notify('ban_jobs', ?)"); + ps.setString(1, json); + ps.execute(); + } + catch (SQLException e) { + plugin.getLogger().severe(e.toString()); + } + } + private String resolveIpBanMessagePlaceholders(String s, String ipAddr, String scope, String reason, String operator) { return s.replace("{ipAddr}", ipAddr) .replace("{scope}", scope) diff --git a/src/main/java/net/kuraczyk/banhammer/utils/IPBan.java b/src/main/java/net/kuraczyk/banhammer/utils/IPBan.java index adb6d68..f412d3e 100644 --- a/src/main/java/net/kuraczyk/banhammer/utils/IPBan.java +++ b/src/main/java/net/kuraczyk/banhammer/utils/IPBan.java @@ -17,4 +17,24 @@ public class IPBan { this.operator = operator; this.banId = banId; } + + public InetAddress getIp() { + return ip; + } + + public String getReason() { + return reason; + } + + public String getTimeLeft() { + return timeLeft; + } + + public String getOperator() { + return operator; + } + + public UUID getBanId() { + return banId; + } } diff --git a/src/main/java/net/kuraczyk/banhammer/utils/PlayerBan.java b/src/main/java/net/kuraczyk/banhammer/utils/PlayerBan.java index 7172850..88060b5 100644 --- a/src/main/java/net/kuraczyk/banhammer/utils/PlayerBan.java +++ b/src/main/java/net/kuraczyk/banhammer/utils/PlayerBan.java @@ -2,6 +2,7 @@ package net.kuraczyk.banhammer.utils; import org.bukkit.entity.Player; +import java.net.InetAddress; import java.sql.Timestamp; import java.util.UUID; @@ -22,4 +23,28 @@ public class PlayerBan { this.banId = banId; } + public String getPlayerName() { + return playerName; + } + + public UUID getPlayerUUID() { + return uuid; + } + + public String getReason() { + return reason; + } + + public String getTimeLeft() { + return timeLeft; + } + + public String getOperator() { + return operator; + } + + public UUID getBanId() { + return banId; + } + } \ No newline at end of file diff --git a/src/main/java/net/kuraczyk/banhammer/workers/BanJobWorker.java b/src/main/java/net/kuraczyk/banhammer/workers/BanJobWorker.java new file mode 100644 index 0000000..2a68639 --- /dev/null +++ b/src/main/java/net/kuraczyk/banhammer/workers/BanJobWorker.java @@ -0,0 +1,126 @@ +package net.kuraczyk.banhammer.workers; + +import net.kuraczyk.banhammer.BanHammer; +import net.kuraczyk.banhammer.utils.IPBan; +import net.kuraczyk.banhammer.utils.PlayerBan; +import net.kuraczyk.banhammer.utils.TextUtils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.postgresql.PGConnection; +import org.postgresql.PGNotification; + +import javax.swing.plaf.nimbus.State; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Objects; + +import com.google.gson.*; + +public class BanJobWorker { + + BanHammer plugin; + + public BanJobWorker(BanHammer p) { + plugin=p; + banJobListener(); + } + + private void banJobListener() { + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { + try { + Connection conn = plugin.db.getConnection(); + PGConnection pgConn = conn.unwrap(PGConnection.class); + + try (Statement stmt = conn.createStatement()) { + stmt.execute("LISTEN ban_jobs"); + } + + while (!conn.isClosed()){ + try (Statement ping = conn.createStatement()) { + ping.execute("SELECT 1"); + } + + PGNotification[] notifications = pgConn.getNotifications(); + + if(notifications!=null){ + for(PGNotification notif : notifications){ + JsonObject unwrappedJson = JsonParser.parseString(notif.getParameter()).getAsJsonObject(); + String banType = unwrappedJson.get("ban_type").getAsString(); + String banScope = unwrappedJson.get("ban_scope").getAsString(); + JsonElement banData = unwrappedJson.get("ban_data"); + + if(banScope.equals("*") || banScope.equals(plugin.config.getString("serverID"))){ + if(banJobExecutor(banType, banData)) plugin.getLogger().info(String.format("[BanHammer] Job %d has been executed successfully!", notif.getPID())); + else plugin.getLogger().severe("[BanHammer] Error occured while executing ban job!"); + } + + } + } + } + + } catch (SQLException e) { + plugin.getLogger().severe(e.toString()); + } + }); + } + + private boolean banJobExecutor(String type, JsonElement banData) { + final Gson gson = new Gson(); + switch(type){ + case "player" -> { + PlayerBan ban = gson.fromJson(banData, PlayerBan.class); + handlePlayerBanJob(ban); + } + case "ip" -> { + IPBan ban = gson.fromJson(banData, IPBan.class); + handleIPBanJob(ban); + } + default -> { + plugin.getLogger().severe("[BanHammer] (BanJobExecutorException) Unexpected ban type provided."); + return false; + } + } + return true; + } + + private boolean handlePlayerBanJob(PlayerBan b) { + Player target = Bukkit.getPlayer(b.getPlayerName()); + if(target!=null) + if(target.isOnline()) { + if(b.getTimeLeft()!=null){ + plugin.getServer().getScheduler().runTask(plugin, () -> { + target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanTemp"), b))); + }); + return true; + } + plugin.getServer().getScheduler().runTask(plugin, () -> { + target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanPerm"), b))); + }); + return true; + } + + return false; + } + + private boolean handleIPBanJob(IPBan b) { + for(Player target : Bukkit.getOnlinePlayers()) { + if (target.getAddress().getAddress().equals(b.getIp())) { + if (b.getTimeLeft() != null) { + plugin.getServer().getScheduler().runTask(plugin, () -> { + target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanTemp"), b))); + }); + return true; + } + plugin.getServer().getScheduler().runTask(plugin, () -> { + target.kick(TextUtils.colorString(TextUtils.resolvePlaceholders(plugin.translations.getString("banKickMessage.playerBanPerm"), b))); + }); + return true; + } + } + + return false; + } + +} \ No newline at end of file