From cf5b75200e3bb876bbbaa2a003ffbf036eb0855a Mon Sep 17 00:00:00 2001 From: BigVirusBoi Date: Thu, 17 Oct 2024 19:33:17 +0200 Subject: [PATCH] improvements --- README.md | 1 + .../bigvirusboi/whitelist/ProxyWhitelist.java | 9 +- .../whitelist/cache/MinecraftAPI.java | 10 +- .../whitelist/command/WhitelistCommand.java | 99 ++++++++++++---- .../whitelist/manager/Whitelist.java | 4 +- .../bigvirusboi/whitelist/util/TimeUtil.java | 106 ------------------ .../me/bigvirusboi/whitelist/util/Util.java | 41 +++++-- .../util/{ => time}/DurationParser.java | 60 ++-------- .../whitelist/util/time/DurationUnit.java | 55 +++++++++ .../whitelist/util/time/TimeUtil.java | 35 ++++++ 10 files changed, 221 insertions(+), 199 deletions(-) delete mode 100644 src/main/java/me/bigvirusboi/whitelist/util/TimeUtil.java rename src/main/java/me/bigvirusboi/whitelist/util/{ => time}/DurationParser.java (51%) create mode 100644 src/main/java/me/bigvirusboi/whitelist/util/time/DurationUnit.java create mode 100644 src/main/java/me/bigvirusboi/whitelist/util/time/TimeUtil.java diff --git a/README.md b/README.md index a50c62d..e21a239 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Advanced whitelist system for Velocity. \ - ***proxy.whitelist.disable* | /pwl disable** - Disable the whitelist - ***proxy.whitelist.enable* | /pwl enable** - Enable the whitelist - ***proxy.whitelist.get* | /pwl get \** - Get the whitelist status of a player +- ***proxy.whitelist* | /pwl help** - View the command help - ***proxy.whitelist.kick* | /pwl kick** - Kick everyone who is not whitelisted - ***proxy.whitelist.list* | /pwl list** - List all whitelisted players - ***proxy.whitelist.remove* | /pwl remove \** - Remove a player from the whitelist diff --git a/src/main/java/me/bigvirusboi/whitelist/ProxyWhitelist.java b/src/main/java/me/bigvirusboi/whitelist/ProxyWhitelist.java index a4fb13e..69a8a99 100644 --- a/src/main/java/me/bigvirusboi/whitelist/ProxyWhitelist.java +++ b/src/main/java/me/bigvirusboi/whitelist/ProxyWhitelist.java @@ -20,12 +20,10 @@ import java.nio.file.Path; import java.util.logging.Logger; -@Plugin(id = "whitelist", name = "Whitelist", version = BuildConstants.VERSION, - description = "Adds an advanced whitelist to Velocity", authors = {"BigVirusBoi"}) +@Plugin(id = "whitelist", name = "ProxyWhitelist", version = BuildConstants.VERSION, + description = "Advanced whitelist system for Velocity", authors = {"BigVirusBoi"}) public final class ProxyWhitelist { private final ProxyServer server; - private final Logger logger; - private final Path dataDirectory; private final PlayerCache cache; private final Whitelist whitelist; @@ -33,8 +31,6 @@ public final class ProxyWhitelist { @Inject public ProxyWhitelist(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { this.server = server; - this.logger = logger; - this.dataDirectory = dataDirectory; logger.info("ProxyWhitelist is loading"); this.cache = new PlayerCache(server); @@ -58,7 +54,6 @@ public void onProxyInitialization(ProxyInitializeEvent event) { } if (!whitelist.isWhitelisted(player)) { e.setResult(ResultedEvent.ComponentResult.denied(Component.text("§cYou are not whitelisted"))); - return; } }); diff --git a/src/main/java/me/bigvirusboi/whitelist/cache/MinecraftAPI.java b/src/main/java/me/bigvirusboi/whitelist/cache/MinecraftAPI.java index f3878f0..194da93 100644 --- a/src/main/java/me/bigvirusboi/whitelist/cache/MinecraftAPI.java +++ b/src/main/java/me/bigvirusboi/whitelist/cache/MinecraftAPI.java @@ -11,6 +11,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.util.UUID; +import java.util.logging.Logger; @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class MinecraftAPI { @@ -38,11 +39,16 @@ public static CachedPlayer getPlayer(String query) { String uuidString = jsonObject.get("id").getAsString(); UUID uuid = Util.parseUUID(uuidString); String name = jsonObject.get("name").getAsString(); - if (uuid == null || name == null) return null; + if (uuid == null || name == null) { + Logger.getLogger("MinecraftAPI").severe("Unable to GET player " + query + ": " + response.toString()); + return null; + } return new CachedPlayer(uuid, name); } - } catch (Exception ignored) { + } catch (Exception ex) { + Logger.getLogger("MinecraftAPI").severe("Unable to GET player " + query + ": " + ex); + ex.printStackTrace(); } return null; } diff --git a/src/main/java/me/bigvirusboi/whitelist/command/WhitelistCommand.java b/src/main/java/me/bigvirusboi/whitelist/command/WhitelistCommand.java index b43f714..bd7c9c9 100644 --- a/src/main/java/me/bigvirusboi/whitelist/command/WhitelistCommand.java +++ b/src/main/java/me/bigvirusboi/whitelist/command/WhitelistCommand.java @@ -8,13 +8,15 @@ import me.bigvirusboi.whitelist.cache.PlayerCache; import me.bigvirusboi.whitelist.manager.Whitelist; import me.bigvirusboi.whitelist.util.Constants; -import me.bigvirusboi.whitelist.util.DurationParser; +import me.bigvirusboi.whitelist.util.time.DurationParser; import me.bigvirusboi.whitelist.util.Permissions; -import me.bigvirusboi.whitelist.util.TimeUtil; +import me.bigvirusboi.whitelist.util.time.TimeUtil; import net.kyori.adventure.text.Component; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static me.bigvirusboi.whitelist.util.Constants.CMD; @@ -36,21 +38,13 @@ public void execute(Invocation invocation) { String[] args = invocation.arguments(); if (args.length == 0) { - source.sendMessage(Component.text("§6§lCommand usage§7 (Proxy Whitelist)")); - source.sendMessage(Component.text("§6 /" + CMD + "§e clear §8 - §7Clear the whitelist")); - source.sendMessage(Component.text("§6 /" + CMD + "§e disable§8 - §7Disable the whitelist")); - source.sendMessage(Component.text("§6 /" + CMD + "§e enable§8 - §7Enable the whitelist")); - source.sendMessage(Component.text("§6 /" + CMD + "§e get §8 - §7Get the whitelist status of a player")); - source.sendMessage(Component.text("§6 /" + CMD + "§e kick§8 - §7Kick everyone who is not whitelisted")); - source.sendMessage(Component.text("§6 /" + CMD + "§e list§8 - §7List all whitelisted players")); - source.sendMessage(Component.text("§6 /" + CMD + "§e remove §8 - §7Remove a player from the whitelist")); - source.sendMessage(Component.text("§6 /" + CMD + "§e set [duration]§8 - §7Add a player to the whitelist")); - source.sendMessage(Component.newline().append(Component.text( - "§7Running §6§lProxy Whitelist v" + BuildConstants.VERSION + "§7 by §e" + Constants.CREDITS))); + source.sendMessage(Component.text("§7Running §6§lProxy Whitelist §8(v" + BuildConstants.VERSION + ")§7 by §e" + Constants.CREDITS)); + source.sendMessage(Component.text("§7To view subcommands, use §e/" + CMD + " help")); return; } switch (args[0].toLowerCase()) { + case "help" -> handleHelp(source); case "clear" -> handleClear(source, args); case "disable" -> handleDisable(source); case "enable" -> handleEnable(source); @@ -65,23 +59,55 @@ public void execute(Invocation invocation) { @Override public List suggest(Invocation invocation) { + CommandSource source = invocation.source(); String[] args = invocation.arguments(); List arguments = new ArrayList<>(); if (args.length == 1) { - arguments.addAll(List.of("clear", "disable", "enable", "get", "kick", "list", "remove", "set")); + arguments.add("help"); + if (source.hasPermission(Permissions.CLEAR)) arguments.add("clear"); + if (source.hasPermission(Permissions.DISABLE)) arguments.add("disable"); + if (source.hasPermission(Permissions.ENABLE)) arguments.add("enable"); + if (source.hasPermission(Permissions.GET)) arguments.add("get"); + if (source.hasPermission(Permissions.KICK)) arguments.add("kick"); + if (source.hasPermission(Permissions.LIST)) arguments.add("list"); + if (source.hasPermission(Permissions.REMOVE)) arguments.add("remove"); + if (source.hasPermission(Permissions.SET)) arguments.add("set"); } else if (args.length == 2) { + Map> permittedArguments = new HashMap<>(); switch (args[0].toLowerCase()) { - case "clear" -> arguments.addAll(List.of("all", "expired")); - case "get", "set" -> arguments.addAll(proxy.getAllPlayers().stream().map(Player::getUsername).toList()); - case "remove" -> arguments.addAll(whitelist.getWhitelisted().keySet().stream().map(CachedPlayer::name).toList()); + case "clear" -> permittedArguments.put(Permissions.CLEAR, List.of("all", "expired")); + case "get" -> permittedArguments.put(Permissions.GET, whitelist.getWhitelisted().keySet().stream().map(CachedPlayer::name).toList()); + case "set" -> permittedArguments.put(Permissions.SET, proxy.getAllPlayers().stream().map(Player::getUsername).toList()); + case "remove" -> permittedArguments.put(Permissions.REMOVE, whitelist.getWhitelisted().keySet().stream().map(CachedPlayer::name).toList()); + } + + for (String permission : permittedArguments.keySet()) { + if (source.hasPermission(permission)) arguments.addAll(permittedArguments.get(permission)); } } return arguments; } + private void handleHelp(CommandSource source) { + source.sendMessage(Component.text("§6§lCommand usage")); + source.sendMessage(Component.text("§6 /" + CMD + "§e clear §8 - §7Clear the whitelist")); + source.sendMessage(Component.text("§6 /" + CMD + "§e disable§8 - §7Disable the whitelist")); + source.sendMessage(Component.text("§6 /" + CMD + "§e enable§8 - §7Enable the whitelist")); + source.sendMessage(Component.text("§6 /" + CMD + "§e get §8 - §7Get the whitelist status of a player")); + source.sendMessage(Component.text("§6 /" + CMD + "§e help§8 - §7View the command help")); + source.sendMessage(Component.text("§6 /" + CMD + "§e kick§8 - §7Kick everyone who is not whitelisted")); + source.sendMessage(Component.text("§6 /" + CMD + "§e list§8 - §7List all whitelisted players")); + source.sendMessage(Component.text("§6 /" + CMD + "§e remove §8 - §7Remove a player from the whitelist")); + source.sendMessage(Component.text("§6 /" + CMD + "§e set [duration]§8 - §7Add a player to the whitelist")); + } + private void handleClear(CommandSource source, String[] args) { + if (!source.hasPermission(Permissions.CLEAR)) { + sendPermission(source); + return; + } if (args.length == 1) { source.sendMessage(Component.text("§cUsage: /" + CMD + " clear ")); return; @@ -101,6 +127,10 @@ private void handleClear(CommandSource source, String[] args) { } private void handleDisable(CommandSource source) { + if (!source.hasPermission(Permissions.DISABLE)) { + sendPermission(source); + return; + } if (!whitelist.isEnabled()) { source.sendMessage(Component.text("§cWhitelist is already disabled")); return; @@ -112,6 +142,10 @@ private void handleDisable(CommandSource source) { } private void handleEnable(CommandSource source) { + if (!source.hasPermission(Permissions.ENABLE)) { + sendPermission(source); + return; + } if (whitelist.isEnabled()) { source.sendMessage(Component.text("§cWhitelist is already enabled")); return; @@ -123,6 +157,10 @@ private void handleEnable(CommandSource source) { } private void handleGet(CommandSource source, String[] args) { + if (!source.hasPermission(Permissions.GET)) { + sendPermission(source); + return; + } if (args.length == 1) { source.sendMessage(Component.text("§cUsage: /" + CMD + " get ")); return; @@ -142,23 +180,36 @@ private void handleGet(CommandSource source, String[] args) { } private void handleKick(CommandSource source) { - source.sendMessage(Component.text("§7Kicked all non whitelisted players")); + if (!source.hasPermission(Permissions.KICK)) { + sendPermission(source); + return; + } + + source.sendMessage(Component.text("§7Kicked all non-whitelisted players")); whitelist.kickNotWhitelisted(); } private void handleList(CommandSource source) { + if (!source.hasPermission(Permissions.LIST)) { + sendPermission(source); + return; + } if (whitelist.isEmpty()) { source.sendMessage(Component.text("§7The whitelist is empty")); return; } - source.sendMessage(Component.text("§6§lWhitelisted players §7(Proxy Whitelist)")); + source.sendMessage(Component.text("§6§lWhitelisted players")); for (CachedPlayer player : whitelist.getWhitelisted().keySet()) { source.sendMessage(Component.text("§8 - §7%s §e%s".formatted(player.name(), whitelist.getDurationFormatted(player)))); } } private void handleRemove(CommandSource source, String[] args) { + if (!source.hasPermission(Permissions.REMOVE)) { + sendPermission(source); + return; + } if (args.length == 1) { source.sendMessage(Component.text("§cUsage: /" + CMD + " remove ")); return; @@ -179,6 +230,10 @@ private void handleRemove(CommandSource source, String[] args) { } private void handleSet(CommandSource source, String[] args) { + if (!source.hasPermission(Permissions.SET)) { + sendPermission(source); + return; + } if (args.length == 1) { source.sendMessage(Component.text("§cUsage: /" + CMD + " set [duration]")); return; @@ -206,6 +261,10 @@ private void handleSet(CommandSource source, String[] args) { long expire = parser.isPermanent() ? -1 : System.currentTimeMillis() + parser.getMillis(); whitelist.set(player, expire); - source.sendMessage(Component.text("§7Set whitelist of §f%s§7 to §a%s§7!".formatted(player.name(), TimeUtil.formatToSeconds(parser.getMillis())))); + source.sendMessage(Component.text("§7Set whitelist of §f%s§7 to §a%s§7!".formatted(player.name(), TimeUtil.toFormattedString(parser.getMillis())))); + } + + private void sendPermission(CommandSource source) { + source.sendMessage(Component.text("§cYou do not have permission to use this command")); } } diff --git a/src/main/java/me/bigvirusboi/whitelist/manager/Whitelist.java b/src/main/java/me/bigvirusboi/whitelist/manager/Whitelist.java index 7a0ebca..43208d7 100644 --- a/src/main/java/me/bigvirusboi/whitelist/manager/Whitelist.java +++ b/src/main/java/me/bigvirusboi/whitelist/manager/Whitelist.java @@ -8,7 +8,7 @@ import me.bigvirusboi.whitelist.cache.CachedPlayer; import me.bigvirusboi.whitelist.cache.PlayerCache; import me.bigvirusboi.whitelist.util.Permissions; -import me.bigvirusboi.whitelist.util.TimeUtil; +import me.bigvirusboi.whitelist.util.time.TimeUtil; import me.bigvirusboi.whitelist.util.Util; import net.kyori.adventure.text.Component; @@ -49,7 +49,7 @@ public String getDurationFormatted(CachedPlayer player) { return "§cEXPIRED"; } - return "§a" + TimeUtil.formatToSeconds(expire - System.currentTimeMillis()); + return "§a" + TimeUtil.toFormattedString(expire - System.currentTimeMillis()); } public long getDuration(UUID uuid) { diff --git a/src/main/java/me/bigvirusboi/whitelist/util/TimeUtil.java b/src/main/java/me/bigvirusboi/whitelist/util/TimeUtil.java deleted file mode 100644 index b5f8b07..0000000 --- a/src/main/java/me/bigvirusboi/whitelist/util/TimeUtil.java +++ /dev/null @@ -1,106 +0,0 @@ -package me.bigvirusboi.whitelist.util; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class TimeUtil { - public static final long ONE_SECOND_MILLIS = 1000; - public static final long ONE_MINUTE_MILLIS = ONE_SECOND_MILLIS * 60; - public static final long ONE_HOUR_MILLIS = ONE_MINUTE_MILLIS * 60; - public static final long ONE_DAY_MILLIS = ONE_HOUR_MILLIS * 24; - - private static final TimeUnitInfo[] UNIT_INFO = new TimeUnitInfo[]{ - new TimeUnitInfo(TimeUnit.SECONDS, "s"), - new TimeUnitInfo(TimeUnit.MINUTES, "m"), - new TimeUnitInfo(TimeUnit.HOURS, "h"), - new TimeUnitInfo(TimeUnit.DAYS, "d") - }; - - private static final Map UNITS = new HashMap<>(); - static { - for (TimeUnitInfo info : UNIT_INFO) { - UNITS.put(info.unit, info.name); - } - } - - public static String toFormattedString(long time, TimeUnit unit) { - if (time < 1) { - return "0 " + UNITS.get(unit); - } - - StringBuilder buffer = new StringBuilder(); - long remaining = time; - - for (TimeUnitInfo unitInfo : UNIT_INFO) { - if (unitInfo.unit.ordinal() >= unit.ordinal()) { - long mod = remaining % getUnitDivisor(unitInfo.unit); - if (mod > 0) { - buffer.insert(0, mod + unitInfo.name + " "); - } - remaining /= getUnitDivisor(unitInfo.unit); - } - } - - return buffer.toString().trim(); - } - - private static long getUnitDivisor(TimeUnit unit) { - return switch (unit) { - case SECONDS, MINUTES -> 60; - case HOURS -> 24; - case DAYS -> 365; - default -> 0; - }; - } - - public static String millisToDurationString(long milliseconds, boolean prefix) { - long seconds = milliseconds / 1000; - if (seconds <= 0) { - return "now"; - } - - StringBuilder duration = new StringBuilder(); - if (prefix) { - duration.append("in "); - } - if (seconds < 60) { - duration.append(seconds == 1 ? "1 second" : seconds + " seconds"); - return duration.toString(); - } - - long minutes = seconds / 60; - if (minutes < 60) { - duration.append(minutes == 1 ? "1 minute" : minutes + " minutes"); - return duration.toString(); - } - - long hours = minutes / 60; - if (hours < 24) { - duration.append(hours == 1 ? "1 hour" : hours + " hours"); - return duration.toString(); - } - - long days = hours / 24; - duration.append(days == 1 ? "1 day" : days + " days"); - return duration.toString(); - } - - public static float daysSince(long time) { - return (float) (System.currentTimeMillis() - time) / ONE_DAY_MILLIS; - } - - public static String formatMillis(long millis) { - return toFormattedString(millis, TimeUnit.MILLISECONDS); - } - - public static String formatToSeconds(long millis) { - return toFormattedString(millis / 1000, TimeUnit.SECONDS); - } - - private record TimeUnitInfo(TimeUnit unit, String name) {} -} diff --git a/src/main/java/me/bigvirusboi/whitelist/util/Util.java b/src/main/java/me/bigvirusboi/whitelist/util/Util.java index dd3f557..1ac8b2c 100644 --- a/src/main/java/me/bigvirusboi/whitelist/util/Util.java +++ b/src/main/java/me/bigvirusboi/whitelist/util/Util.java @@ -11,13 +11,33 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class Util { - public static UUID parseUUID(String id) { - if (id == null) return null; + public static UUID parseUUID(String input) { + if (input == null) return null; try { - return UUID.fromString(id); - } catch (IllegalArgumentException ex) { - return null; + return UUID.fromString(input); + } catch (IllegalArgumentException ignored) {} + try { + return fromTrimmedUUID(input); + } catch (IllegalArgumentException ignored) {} + return null; + } + + public static UUID fromTrimmedUUID(String input) throws IllegalArgumentException { + if (input == null) + throw new IllegalArgumentException(); + + StringBuilder builder = new StringBuilder(input.trim()); + /* Backwards adding to avoid index adjustments */ + try { + builder.insert(20, "-"); + builder.insert(16, "-"); + builder.insert(12, "-"); + builder.insert(8, "-"); + } catch (StringIndexOutOfBoundsException e) { + throw new IllegalArgumentException(); } + + return UUID.fromString(builder.toString()); } public static void copyPartialMatches(String token, List originals, List collection) { @@ -35,17 +55,18 @@ public static boolean startsWithIgnoreCase(String string, String prefix) { return string.regionMatches(true, 0, prefix, 0, prefix.length()); } - public static File createFile(File file) { + public static void createFile(File file) { try { Files.createParentDirs(file); - } catch (IOException e) { - throw new RuntimeException(e); + } catch (IOException ex) { + throw new RuntimeException(ex); } if (!file.exists()) { try { file.createNewFile(); - } catch (IOException ignored) {} + } catch (IOException ex) { + throw new RuntimeException(ex); + } } - return file; } } diff --git a/src/main/java/me/bigvirusboi/whitelist/util/DurationParser.java b/src/main/java/me/bigvirusboi/whitelist/util/time/DurationParser.java similarity index 51% rename from src/main/java/me/bigvirusboi/whitelist/util/DurationParser.java rename to src/main/java/me/bigvirusboi/whitelist/util/time/DurationParser.java index 0f5cce9..05ddd53 100644 --- a/src/main/java/me/bigvirusboi/whitelist/util/DurationParser.java +++ b/src/main/java/me/bigvirusboi/whitelist/util/time/DurationParser.java @@ -1,4 +1,4 @@ -package me.bigvirusboi.whitelist.util; +package me.bigvirusboi.whitelist.util.time; import lombok.NoArgsConstructor; @@ -7,17 +7,17 @@ @NoArgsConstructor public class DurationParser { - private final List keys = new ArrayList<>(); + private final List keys = new ArrayList<>(); private long duration = 0; - private DurationParser(long duration) { - this.duration = duration; + public DurationParser(long millis) { + this.duration = millis; } public void parse(String input) { duration = 0; - keys.clear(); + StringBuilder numberBuilder = new StringBuilder(); StringBuilder unitBuilder = new StringBuilder(); @@ -26,7 +26,6 @@ public void parse(String input) { if (!unitBuilder.isEmpty()) { addKey(numberBuilder, unitBuilder); } - numberBuilder.append(ch); } else { unitBuilder.append(ch); @@ -37,10 +36,10 @@ public void parse(String input) { private void addKey(StringBuilder numberBuilder, StringBuilder unitBuilder) { if (!numberBuilder.isEmpty() && !unitBuilder.isEmpty()) { - int amount = Integer.parseInt(numberBuilder.toString()); - DurationUnit unit = DurationUnit.fromIdentifier(unitBuilder.toString()); + int amount = Integer.parseInt(numberBuilder.toString().trim()); + DurationUnit unit = DurationUnit.fromIdentifier(unitBuilder.toString().trim()); if (unit != null) { - keys.add(new DurationKey(amount, unit)); + keys.add(new DurationUnit.DurationKey(amount, unit)); } numberBuilder.setLength(0); unitBuilder.setLength(0); @@ -49,14 +48,9 @@ private void addKey(StringBuilder numberBuilder, StringBuilder unitBuilder) { public long getMillis() { if (duration != 0) return duration; - return keys.stream().mapToLong(key -> key.unit().toMillis(key.amount())).sum(); } - public long getSeconds() { - return getMillis() / 1000; - } - public boolean isValid() { return !keys.isEmpty(); } @@ -72,42 +66,4 @@ public static DurationParser ofMillis(long millis) { public static DurationParser permanent() { return ofMillis(-1); } - - public record DurationKey(int amount, DurationUnit unit) {} - - public enum DurationUnit { - SECONDS(1000, "s"), - MINUTES(SECONDS, 60, "m"), - HOURS(MINUTES, 60, "h"), - DAYS(HOURS, 24, "d"), - WEEKS(DAYS, 7, "w"), - MONTHS(DAYS, 30, "mo"), - YEARS(DAYS, 365, "y"); - - private final long multiplier; - private final String identifier; - - DurationUnit(long multiplier, String identifier) { - this.multiplier = multiplier; - this.identifier = identifier; - } - - DurationUnit(DurationUnit unit, long multiplier, String identifier) { - this.multiplier = unit.multiplier * multiplier; - this.identifier = identifier; - } - - public long toMillis(int amount) { - return amount * multiplier; - } - - public static DurationUnit fromIdentifier(String identifier) { - for (DurationUnit unit : values()) { - if (unit.identifier.equalsIgnoreCase(identifier)) { - return unit; - } - } - return null; - } - } } diff --git a/src/main/java/me/bigvirusboi/whitelist/util/time/DurationUnit.java b/src/main/java/me/bigvirusboi/whitelist/util/time/DurationUnit.java new file mode 100644 index 0000000..92bfb38 --- /dev/null +++ b/src/main/java/me/bigvirusboi/whitelist/util/time/DurationUnit.java @@ -0,0 +1,55 @@ +package me.bigvirusboi.whitelist.util.time; + +import lombok.Getter; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@Getter +public enum DurationUnit { + SECONDS(1000, "s"), + MINUTES(SECONDS, 60, "m"), + HOURS(MINUTES, 60, "h"), + DAYS(HOURS, 24, "d"), + WEEKS(DAYS, 7, "w"), + MONTHS(DAYS, 30, "mo"), + YEARS(DAYS, 365, "y"); + + private final long multiplier; + private final String identifier; + + DurationUnit(long multiplier, String identifier) { + this.multiplier = multiplier; + this.identifier = identifier; + } + + DurationUnit(DurationUnit unit, long multiplier, String identifier) { + this.multiplier = unit.multiplier * multiplier; + this.identifier = identifier; + } + + public long toMillis(int amount) { + return amount * multiplier; + } + + public static DurationUnit fromIdentifier(String identifier) { + for (DurationUnit unit : values()) { + if (unit.identifier.equalsIgnoreCase(identifier)) { + return unit; + } + } + return null; + } + + // Returns units in reverse order for formatting (from largest to smallest) + public static DurationUnit[] valuesReversed() { + DurationUnit[] values = values(); + List reversed = Arrays.asList(values); + Collections.reverse(reversed); + return reversed.toArray(new DurationUnit[0]); + } + + // Represents a unit and amount of time (e.g., 2h or 30m) + public record DurationKey(int amount, DurationUnit unit) {} +} diff --git a/src/main/java/me/bigvirusboi/whitelist/util/time/TimeUtil.java b/src/main/java/me/bigvirusboi/whitelist/util/time/TimeUtil.java new file mode 100644 index 0000000..3b7736b --- /dev/null +++ b/src/main/java/me/bigvirusboi/whitelist/util/time/TimeUtil.java @@ -0,0 +1,35 @@ +package me.bigvirusboi.whitelist.util.time; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class TimeUtil { + // Formats time from millis to a human-readable format + public static String toFormattedString(long millis) { + if (millis < 1000) { + return "0 s"; + } + + StringBuilder buffer = new StringBuilder(); + long remaining = millis; + + for (DurationUnit unit : DurationUnit.valuesReversed()) { + long unitMillis = unit.getMultiplier(); + long amount = remaining / unitMillis; + if (amount > 0) { + buffer.append(amount).append(unit.getIdentifier()).append(" "); + remaining %= unitMillis; + } + } + + return buffer.toString().trim(); + } + + // Parses a duration string like "2h 30m" into milliseconds + public static long parseDuration(String input) { + DurationParser parser = new DurationParser(); + parser.parse(input); + return parser.getMillis(); + } +}