Skip to content

Commit

Permalink
Add last seen database for offline user info
Browse files Browse the repository at this point in the history
  • Loading branch information
SageSphinx63920 committed Aug 20, 2024
1 parent 2910aef commit 8d514a2
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/de/hdg/keklist/Keklist.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<ContextCalculator<Player>> registeredCalculators = new ArrayList<>();
private GeyserApi geyserApi;

Expand Down
48 changes: 46 additions & 2 deletions src/main/java/de/hdg/keklist/commands/KeklistCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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())
Expand All @@ -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 ? "<green>" + Keklist.getTranslations().get("yes") : "<red>" + Keklist.getTranslations().get("no"))
.replace("%blacklisted%", blacklisted ? "<green>" + Keklist.getTranslations().get("yes") : "<red>" + 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);
}

}
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/de/hdg/keklist/database/DB.java
Original file line number Diff line number Diff line change
Expand Up @@ -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))");
}

/**
Expand Down
17 changes: 15 additions & 2 deletions src/main/java/de/hdg/keklist/events/PreLoginKickEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -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")) {
Expand Down Expand Up @@ -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());
Expand Down
71 changes: 67 additions & 4 deletions src/main/java/de/hdg/keklist/extentions/KekDataExtension.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
}

}
1 change: 1 addition & 0 deletions src/main/resources/assets/lang/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@
"keklist.manage.disabled": "<red>This command is disabled due to security reasons! <grey><i>This can be enabled in the config.",
"keklist.info": "\n<gray><b><u>Keklist Info</u></b> \n\n<yellow>Whitelist: %whitelist% \n<white>Whitelisted: <dark_green>%whitelisted% \n<yellow>Blacklist: %blacklist% \n<white>Blacklisted: <dark_green>%blacklisted% \n<blue>Database: <aqua>%database% \n<gold>Version: <white>%version% \n\n<gold>Made with <red><3 <gold>by SageSphinx63920 and jxnxsdev",
"keklist.player-info": "\n<grey><b><u>Player Info</u></b></grey> \n\n<hover:show_text:'%uuid%'><color:#26ffb3>Name</color>: <white><b>%name%</b></hover> \n<click:suggest_command:'/keklist info %ip% %name%'><hover:show_text:'Click to get information about ip'><color:#26ffb3>Ip address: <color:#91bbff>%ip%</color></hover></click> \n<yellow>Whitelisted: %whitelisted% \n<yellow>Blacklisted: %blacklisted% \n<color:#5cceff>Brand: <white>%brand% \n<color:#5cceff><hover:show_text:'Idle since: %idle% Seconds'>Ping: <green>%ping%</hover> \n<click:open_url:'https://wiki.vg/Protocol_version_numbers'><hover:show_text:'Click to get version'><color:#5cceff>Protocol Version: <white>%version%</hover></click> \n<color:#5cceff>Gamemode: <white>%gamemode% \n<color:#5cceff>Location: <hover:show_text:'Click to teleport'><white><click:run_command:'/tp %requested_by% %x% %y% %z%'>%x% %y% %z%</click></hover> \n\n<grey><i><hover:show_text:'<i>Ssh... also hover over the other fields!'>Hover too see additional data</hover>",
"keklist.offline-player-info": "\n<hover:show_text:'This is the cached/database data for this offline user!'><b><u><grey>Offline Player Info:</u></b></hover> \n\n<color:#26ffb3>Name: <white><b><hover:show_text:'%uuid%'>%name%</hover></b> \n<color:#26ffb3>Ip address: <click:suggest_command:'/keklist info %ip% %name%'><hover:show_text:'Click to get information about ip'><color:#6b81ff>%ip% <grey><i>(last seen with this ip)</i></color></hover></click> \n<yellow>Whitelisted: %whitelisted% \n<yellow>Blacklisted: %blacklisted% \n<color:#5cceff>Brand: <white>%brand% <grey><i>(last seen with this brand)</i> \n<click:open_url:'https://wiki.vg/Protocol_version_numbers'><hover:show_text:'Click to get version string'><color:#5cceff>Version: <white>%version% <grey><i>(last seen with this version)</i> \n<color:#5cceff>Location: <hover:show_text:'Click to teleport'><white><click:run_command:'/tp %requested_by% %x% %y% %z%'>%x% %y% %z% <grey><i>(last seen there)</i></click></hover> \n<dark_green>Last seen: <red>%last_seen%</red></dark_green> \n<grey><i><hover:show_text:'<i>Ssh... also hover over the other fields!'>Hover too see additional data</hover> \n\n<dark_grey><i>Disclaimer: This data may be not complete or wrong as it only gets the server's and our own cached data.",
"keklist.ip-info": "\n<hover:show_text:'Basic informations about the ip'><b><u><grey>IP Info:</u></b></hover> \n\n<color:#26ffb3>IP Address: <white><b><hover:show_text:'Click to see more informations <dark_grey>| <grey><i>Using public IP if in local subnet'><click:open_url:'https://ipinfo.io/%query%'>%ip%</click></hover></b> \n<color:#26ffb3>Player: <white>%player% \n<aqua>Country: <hover:show_text:'<dark_aqua>Continent: <white>%continent% \n<dark_aqua>Code: <white>%continent_code%'><white>%country% <dark_grey>| <grey>Code: %country_code% </hover> \n<aqua>City: <hover:show_text:'<dark_aqua>Region: <white>%region%'><white>%city%</hover> \n<aqua>Timezone: <white>%timezone% \n<color:#63a7ff><hover:show_text:'The <b><yellow>i<reset>nternet <b><yellow>s<reset>ervice <b><yellow>p<reset>rovider '>ISP</hover>: <hover:show_text:'<color:#8c73ff>AS: <white>%as%</color>'><white>%org%</hover> \n<yellow><hover:show_text:'Mobile (cellular) connection'>Mobile</hover>: %mobile% <dark_grey>| <gold><hover:show_text:'Detects Proxies, VPNs and Tor exit relays'>Proxy/VPN</hover>: %proxy% <dark_grey>| <color:#ff6f08><hover:show_text:'Hosting, colocated or data center'>Hosting</hover>: %hosting% \n\n<grey><i><hover:show_text:'<i>Ssh... also hover over the other fields!'>Hover too see additional data</hover>",
"keklist.info.unknown-type": "Can't find the information about %s",

Expand Down

0 comments on commit 8d514a2

Please sign in to comment.