diff --git a/README.md b/README.md index 557d8e1..7994ac4 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Keklist includes many different features to make your whitelist experience as go - ~~MySQL~~ MariaDB support for syncing your whitelist across multiple servers - **Ingame GUI** - IP info command +- User info command (includes offline users) - Block users by continent and country - Proxy/VPN/Tor block mode - Require players to add server to their server list diff --git a/src/main/java/de/hdg/keklist/Keklist.java b/src/main/java/de/hdg/keklist/Keklist.java index 6f2b052..6453d18 100644 --- a/src/main/java/de/hdg/keklist/Keklist.java +++ b/src/main/java/de/hdg/keklist/Keklist.java @@ -33,7 +33,6 @@ import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; import net.luckperms.api.LuckPerms; import net.luckperms.api.context.ContextCalculator; -import net.luckperms.api.context.ContextManager; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.command.Command; @@ -62,7 +61,7 @@ public final class Keklist extends JavaPlugin { private @Getter @Nullable FloodgateApi floodgateApi = null; private static @Getter PlanHook planHook; private PlaceholderAPIExtension placeholders; - private LuckPerms luckPermsAPI; + private @Getter LuckPerms luckPermsAPI; private final List> registeredCalculators = new ArrayList<>(); private GeyserApi geyserApi; diff --git a/src/main/java/de/hdg/keklist/commands/KeklistCommand.java b/src/main/java/de/hdg/keklist/commands/KeklistCommand.java index 23db62c..de5262d 100644 --- a/src/main/java/de/hdg/keklist/commands/KeklistCommand.java +++ b/src/main/java/de/hdg/keklist/commands/KeklistCommand.java @@ -18,7 +18,9 @@ import java.io.File; import java.io.IOException; import java.sql.SQLException; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.List; public class KeklistCommand extends Command { @@ -211,7 +213,6 @@ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLab switch (type) { case IPv4, IPv6 -> { - new IpUtil(args[1]).getIpData().thenAccept(data -> sender.sendMessage(Keklist.getInstance().getMiniMessage().deserialize(Keklist.getTranslations().get("keklist.ip-info") .replace("%ip%", args[1]) @@ -254,7 +255,6 @@ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLab ip = target.getAddress().getAddress().getHostAddress(); } - sender.sendMessage(Keklist.getInstance().getMiniMessage().deserialize(Keklist.getTranslations().get("keklist.player-info") .replace("%name%", target.getName()) .replace("%uuid%", target.getUniqueId().toString()) @@ -274,6 +274,50 @@ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLab } catch (SQLException e) { throw new RuntimeException(e); } + } else { + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(args[1]); + + try { + boolean whitelisted = Keklist.getDatabase().onQuery("SELECT 1 FROM whitelist WHERE uuid = ?", offlinePlayer.getUniqueId().toString()).next(); + boolean blacklisted = Keklist.getDatabase().onQuery("SELECT 1 FROM blacklist WHERE uuid = ?", offlinePlayer.getUniqueId().toString()).next(); + + String latestIp = "unknown"; + int protocolId = -1; + String brand = "unknown"; + + if(Keklist.getDatabase().onQuery("SELECT 1 FROM lastSeen WHERE uuid = ?", offlinePlayer.getUniqueId().toString()).next()) { + latestIp = Keklist.getDatabase().onQuery("SELECT ip FROM lastSeen WHERE uuid = ?", offlinePlayer.getUniqueId().toString()).getString("ip"); + protocolId = Keklist.getDatabase().onQuery("SELECT protocolId FROM lastSeen WHERE uuid = ?", offlinePlayer.getUniqueId().toString()).getInt("protocolId"); + brand = Keklist.getDatabase().onQuery("SELECT brand FROM lastSeen WHERE uuid = ?", offlinePlayer.getUniqueId().toString()).getString("brand"); + } + + Location location = offlinePlayer.getLocation(); + + if(location == null) + location = new Location(Bukkit.getWorlds().getFirst(), 0, 100, 0); + + long lastSeen = offlinePlayer.getLastSeen(); + SimpleDateFormat sdf = new SimpleDateFormat(Keklist.getInstance().getConfig().getString("date-format")); + + sender.sendMessage(Keklist.getInstance().getMiniMessage().deserialize(Keklist.getTranslations().get("keklist.offline-player-info") + .replace("%name%", offlinePlayer.getName()) + .replace("%uuid%", offlinePlayer.getUniqueId().toString()) + .replace("%whitelisted%", whitelisted ? "" + Keklist.getTranslations().get("yes") : "" + Keklist.getTranslations().get("no")) + .replace("%blacklisted%", blacklisted ? "" + Keklist.getTranslations().get("yes") : "" + Keklist.getTranslations().get("no")) + .replace("%brand%", brand) + .replace("%version%", protocolId == -1 ? Keklist.getTranslations().get("unknown") : String.valueOf(protocolId)) + .replace("%x%", String.valueOf(location.getBlockX())) + .replace("%y%", String.valueOf(location.getBlockY())) + .replace("%z%", String.valueOf(location.getBlockZ())) + .replace("%ip%", latestIp) + .replace("%requested_by%", sender.getName()) + .replace("%last_seen%", sdf.format(new Date(lastSeen))) + )); + + } catch (SQLException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/main/java/de/hdg/keklist/database/DB.java b/src/main/java/de/hdg/keklist/database/DB.java index 05c8fd7..678c0be 100644 --- a/src/main/java/de/hdg/keklist/database/DB.java +++ b/src/main/java/de/hdg/keklist/database/DB.java @@ -155,6 +155,8 @@ private void createTables() { onUpdate("CREATE TABLE IF NOT EXISTS blacklist (uuid VARCHAR(36) PRIMARY KEY, name VARCHAR(16) UNIQUE, byPlayer VARCHAR(16), unix BIGINT(13), reason VARCHAR(1500) DEFAULT 'No reason given')"); onUpdate("CREATE TABLE IF NOT EXISTS blacklistIp (ip VARCHAR(39) PRIMARY KEY, byPlayer VARCHAR(16), unix BIGINT(13), reason VARCHAR(1500) DEFAULT 'No reason given')"); onUpdate("CREATE TABLE IF NOT EXISTS blacklistMotd (ip VARCHAR(39) PRIMARY KEY, byPlayer VARCHAR(16), unix BIGINT(13))"); + + onUpdate("CREATE TABLE IF NOT EXISTS lastSeen (uuid VARCHAR(36) PRIMARY KEY, ip VARCHAR(39) NOT NULL, protocolId INT(5) NOT NULL DEFAULT 'unknown', brand VARCHAR(1000) NOT NULL DEFAULT 'unknown', lastSeen BIGINT(13))"); } /** diff --git a/src/main/java/de/hdg/keklist/events/PreLoginKickEvent.java b/src/main/java/de/hdg/keklist/events/PreLoginKickEvent.java index 74d1a7b..e09f381 100644 --- a/src/main/java/de/hdg/keklist/events/PreLoginKickEvent.java +++ b/src/main/java/de/hdg/keklist/events/PreLoginKickEvent.java @@ -30,7 +30,17 @@ public class PreLoginKickEvent implements Listener { private final OkHttpClient client = new OkHttpClient(); @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) - public void onPreLogin(AsyncPlayerPreLoginEvent event) { + public void onPreLogin(@NotNull AsyncPlayerPreLoginEvent event) { + try (ResultSet rs = Keklist.getDatabase().onQuery("SELECT 1 FROM lastSeen WHERE uuid = ?", event.getUniqueId().toString())) { + if (!rs.next()) { + Keklist.getDatabase().onUpdate("INSERT INTO lastSeen (uuid, ip, lastSeen) VALUES (?, ?, ?)", event.getUniqueId().toString(), event.getAddress().getHostAddress(), System.currentTimeMillis()); + } else { + Keklist.getDatabase().onUpdate("UPDATE lastSeen SET ip = ?, lastSeen = ? WHERE uuid = ?", event.getAddress().getHostAddress(), System.currentTimeMillis(), event.getUniqueId().toString()); + } + } catch (Exception e) { + e.printStackTrace(); + } + if (!config.getList("blacklist.countries").isEmpty() || !config.getList("blacklist.continents").isEmpty() || !config.getBoolean("ip.proxy-allowed")) { @@ -240,7 +250,10 @@ public void onLogin(PlayerLoginEvent event) { } @EventHandler(priority = EventPriority.HIGHEST) - public void onJoin(PlayerJoinEvent event) { + public void onJoin(@NotNull PlayerJoinEvent event) { + // No need to check for the lastSeenIp table, because we already did this in the prelogin event + Keklist.getDatabase().onUpdate("UPDATE lastSeen SET protocolId = ?, brand = ? WHERE uuid = ?", event.getPlayer().getProtocolVersion(), (event.getPlayer().getClientBrandName() == null ? "unknown" : event.getPlayer().getClientBrandName()), event.getPlayer().getUniqueId().toString()); + if (config.getBoolean("blacklist.enabled")) { ResultSet rsUser = Keklist.getDatabase().onQuery("SELECT * FROM blacklist WHERE uuid = ?", event.getPlayer().getUniqueId().toString()); ResultSet rsIp = Keklist.getDatabase().onQuery("SELECT * FROM blacklistIp WHERE ip = ?", event.getPlayer().getAddress().getAddress().getHostAddress()); diff --git a/src/main/java/de/hdg/keklist/extentions/KekDataExtension.java b/src/main/java/de/hdg/keklist/extentions/KekDataExtension.java index a0df704..8239349 100644 --- a/src/main/java/de/hdg/keklist/extentions/KekDataExtension.java +++ b/src/main/java/de/hdg/keklist/extentions/KekDataExtension.java @@ -3,7 +3,9 @@ import com.djrapitops.plan.extension.CallEvents; import com.djrapitops.plan.extension.DataExtension; import com.djrapitops.plan.extension.annotation.BooleanProvider; +import com.djrapitops.plan.extension.annotation.Conditional; import com.djrapitops.plan.extension.annotation.PluginInfo; +import com.djrapitops.plan.extension.annotation.StringProvider; import com.djrapitops.plan.extension.icon.Color; import com.djrapitops.plan.extension.icon.Family; import de.hdg.keklist.Keklist; @@ -44,8 +46,28 @@ public CallEvents[] callExtensionMethodsOn() { ) public boolean isWhitelisted(UUID uuid) { try { - ResultSet rs = Keklist.getDatabase().onQuery("SELECT * FROM whitelist WHERE uuid = ?", uuid.toString()); - return rs.next(); + return Keklist.getDatabase().onQuery("SELECT 1 FROM whitelist WHERE uuid = ?", uuid.toString()).next(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @StringProvider( + text = "Whitelisted By", + description = "Who whitelisted the player?", + iconName = "user-lock", + iconColor = Color.LIGHT_GREEN, + priority = 95, + showInPlayerTable = false + ) + @Conditional("isWhitelisted") + public String whitelistedBy(UUID uuid) { + try { + ResultSet result = Keklist.getDatabase().onQuery("SELECT byPlayer FROM whitelist WHERE uuid = ?", uuid.toString()); + if (result.next()) { + return result.getString("byPlayer"); + } + return "Unknown"; } catch (SQLException e) { throw new RuntimeException(e); } @@ -62,10 +84,51 @@ public boolean isWhitelisted(UUID uuid) { ) public boolean isBlacklisted(UUID uuid) { try { - ResultSet rs = Keklist.getDatabase().onQuery("SELECT * FROM blacklist WHERE uuid = ?", uuid.toString()); - return rs.next(); + return Keklist.getDatabase().onQuery("SELECT 1 FROM blacklist WHERE uuid = ?", uuid.toString()).next(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @StringProvider( + text = "Blacklisted By", + description = "Who blacklisted the player?", + iconName = "user-lock", + iconColor = Color.BLUE, + priority = 85, + showInPlayerTable = false + ) + @Conditional("isBlacklisted") + public String blacklistedBy(UUID uuid) { + try { + ResultSet result = Keklist.getDatabase().onQuery("SELECT byPlayer FROM blacklist WHERE uuid = ?", uuid.toString()); + if (result.next()) { + return result.getString("byPlayer"); + } + return "Unknown"; + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @StringProvider( + text = "Last Seen IP", + description = "The last IP the player was seen with.", + iconName = "ethernet", + iconColor = Color.LIGHT_BLUE, + priority = 80, + showInPlayerTable = true + ) + public String lastSeenIp(UUID uuid) { + try { + ResultSet result = Keklist.getDatabase().onQuery("SELECT ip FROM lastSeen WHERE uuid = ?", uuid.toString()); + if (result.next()) { + return result.getString("ip"); + } + return "Unknown"; } catch (SQLException e) { throw new RuntimeException(e); } } + } diff --git a/src/main/resources/assets/lang/en-us.json b/src/main/resources/assets/lang/en-us.json index 572d1b3..9496e14 100644 --- a/src/main/resources/assets/lang/en-us.json +++ b/src/main/resources/assets/lang/en-us.json @@ -228,6 +228,7 @@ "keklist.manage.disabled": "This command is disabled due to security reasons! This can be enabled in the config.", "keklist.info": "\nKeklist Info \n\nWhitelist: %whitelist% \nWhitelisted: %whitelisted% \nBlacklist: %blacklist% \nBlacklisted: %blacklisted% \nDatabase: %database% \nVersion: %version% \n\nMade with <3 by SageSphinx63920 and jxnxsdev", "keklist.player-info": "\nPlayer Info \n\nName: %name% \nIp address: %ip% \nWhitelisted: %whitelisted% \nBlacklisted: %blacklisted% \nBrand: %brand% \nPing: %ping% \nProtocol Version: %version% \nGamemode: %gamemode% \nLocation: %x% %y% %z% \n\nSsh... also hover over the other fields!'>Hover too see additional data", + "keklist.offline-player-info": "\nOffline Player Info: \n\nName: %name% \nIp address: %ip% (last seen with this ip) \nWhitelisted: %whitelisted% \nBlacklisted: %blacklisted% \nBrand: %brand% (last seen with this brand) \nVersion: %version% (last seen with this version) \nLocation: %x% %y% %z% (last seen there) \nLast seen: %last_seen% \nSsh... also hover over the other fields!'>Hover too see additional data \n\nDisclaimer: This data may be not complete or wrong as it only gets the server's and our own cached data.", "keklist.ip-info": "\nIP Info: \n\nIP Address: | Using public IP if in local subnet'>%ip% \nPlayer: %player% \nCountry: Continent: %continent% \nCode: %continent_code%'>%country% | Code: %country_code% \nCity: Region: %region%'>%city% \nTimezone: %timezone% \ninternet service provider '>ISP: AS: %as%'>%org% \nMobile: %mobile% | Proxy/VPN: %proxy% | Hosting: %hosting% \n\nSsh... also hover over the other fields!'>Hover too see additional data", "keklist.info.unknown-type": "Can't find the information about %s",