Compare commits

...

4 commits

Author SHA1 Message Date
Tomasz Kuraczyk
18cf76ae7f Fix to critical issue #6 2026-03-17 20:32:20 +01:00
7ffd52d4f9 Merge branch 'purpur' into 'dev'
Rename "purpur" branch to "dev" branch

See merge request tkuraczyk/banhammer!1
2026-03-17 17:41:55 +00:00
a7a77b9cc0 sync 2026-03-17 17:58:41 +01:00
422bbcfdf4 Sketch of proper KickPlayerAfterBan system 2026-03-16 21:54:09 +01:00
7 changed files with 237 additions and 32 deletions

View file

@ -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 {

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}