diff --git a/src/main/java/de/shiewk/smoderation/paper/SModerationPaper.java b/src/main/java/de/shiewk/smoderation/paper/SModerationPaper.java index eefb3c4..2de25b8 100644 --- a/src/main/java/de/shiewk/smoderation/paper/SModerationPaper.java +++ b/src/main/java/de/shiewk/smoderation/paper/SModerationPaper.java @@ -5,7 +5,10 @@ import de.shiewk.smoderation.paper.command.*; import de.shiewk.smoderation.paper.input.ChatInput; import de.shiewk.smoderation.paper.input.ChatInputListener; import de.shiewk.smoderation.paper.listener.*; -import de.shiewk.smoderation.paper.storage.PunishmentContainer; +import de.shiewk.smoderation.paper.punishments.Ban; +import de.shiewk.smoderation.paper.punishments.Kick; +import de.shiewk.smoderation.paper.punishments.Mute; +import de.shiewk.smoderation.paper.punishments.PunishmentManager; import de.shiewk.smoderation.paper.translation.TranslatorManager; import de.shiewk.smoderation.paper.util.SchedulerUtil; import io.papermc.paper.command.brigadier.Commands; @@ -24,7 +27,6 @@ import org.bukkit.entity.Player; import org.bukkit.event.Listener; import org.bukkit.plugin.java.JavaPlugin; -import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; @@ -44,10 +46,8 @@ public final class SModerationPaper extends JavaPlugin { public static final TextColor INACTIVE_COLOR = NamedTextColor.GRAY; public static final Gson gson = new Gson(); - public static final PunishmentContainer container = new PunishmentContainer(); public static ComponentLogger LOGGER = null; public static SModerationPaper PLUGIN = null; - public static File SAVE_FILE = null; private static SkinTextureProvider textureProvider = null; private final TranslatorManager translatorManager = new TranslatorManager( @@ -60,6 +60,8 @@ public final class SModerationPaper extends JavaPlugin { } ); + private PunishmentManager punishmentManager; + public static FileConfiguration config() { return PLUGIN.getConfig(); } @@ -67,11 +69,16 @@ public final class SModerationPaper extends JavaPlugin { @Override public void onLoad() { LOGGER = getComponentLogger(); + LOGGER.info("Folia: {}", SchedulerUtil.isFolia ? "yes" : "no"); PLUGIN = this; - SAVE_FILE = new File(this.getDataFolder().getAbsolutePath() + "/container.gz"); LOGGER.info("Loading translations"); translatorManager.load(); updateConfig(); + + this.punishmentManager = new PunishmentManager(getDataPath().resolve("punishments.v2")); + this.punishmentManager.registerType("mute", new Mute.Factory()); + this.punishmentManager.registerType("ban", new Ban.Factory()); + this.punishmentManager.registerType("kick", new Kick.Factory()); } public boolean isFeatureEnabled(String feature){ @@ -80,9 +87,7 @@ public final class SModerationPaper extends JavaPlugin { @Override public void onEnable() { - LOGGER.info("Folia: {}", SchedulerUtil.isFolia ? "yes" : "no"); - - if (isFeatureEnabled("punishments")) listen(new PunishmentListener()); + if (isFeatureEnabled("punishments")) listen(new PunishmentListener(punishmentManager)); if (isFeatureEnabled("invsee")) listen(new InvSeeListener()); if (isFeatureEnabled("enderchestsee")) listen(new EnderchestSeeListener()); if (isFeatureEnabled("socialspy")) listen(new SocialSpyListener()); @@ -95,15 +100,15 @@ public final class SModerationPaper extends JavaPlugin { Commands commands = event.registrar(); if (isFeatureEnabled("punishments")){ - registerCommand(commands, new KickCommand()); - registerCommand(commands, new ModLogsCommand()); - registerCommand(commands, new UnmuteCommand()); - registerCommand(commands, new UnbanCommand()); - registerCommand(commands, new MuteCommand()); - registerCommand(commands, new BanCommand()); + registerCommand(commands, new KickCommand(punishmentManager)); + registerCommand(commands, new ModLogsCommand(punishmentManager)); + registerCommand(commands, new UnmuteCommand(punishmentManager)); + registerCommand(commands, new UnbanCommand(punishmentManager)); + registerCommand(commands, new MuteCommand(punishmentManager)); + registerCommand(commands, new BanCommand(punishmentManager)); if (isFeatureEnabled("smodmenu")){ - registerCommand(commands, new SModCommand()); + registerCommand(commands, new SModCommand(punishmentManager)); } } @@ -122,8 +127,6 @@ public final class SModerationPaper extends JavaPlugin { } SchedulerUtil.scheduleGlobalRepeating(PLUGIN, ChatInput::tickAll, 1, 1); - - container.load(SAVE_FILE); } private void listen(Listener listener) { @@ -140,7 +143,6 @@ public final class SModerationPaper extends JavaPlugin { @Override public void onDisable() { - SModerationPaper.container.save(SModerationPaper.SAVE_FILE); for (Player player : Bukkit.getOnlinePlayers()) { // in case players are still vanished when the server shuts down if (isVanished(player)){ diff --git a/src/main/java/de/shiewk/smoderation/paper/command/BanCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/BanCommand.java index c2434a1..7dfbcef 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/BanCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/BanCommand.java @@ -8,7 +8,9 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.SModerationPaper; import de.shiewk.smoderation.paper.command.argument.DurationArgument; import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument; +import de.shiewk.smoderation.paper.punishments.Ban; import de.shiewk.smoderation.paper.punishments.Punishment; +import de.shiewk.smoderation.paper.punishments.PunishmentManager; import de.shiewk.smoderation.paper.util.CommandUtil; import io.papermc.paper.command.brigadier.CommandSourceStack; import org.bukkit.Bukkit; @@ -24,6 +26,12 @@ import static io.papermc.paper.command.brigadier.Commands.literal; @SuppressWarnings("UnstableApiUsage") // Paper Brigadier API public final class BanCommand implements CommandProvider { + private final PunishmentManager punishmentManager; + + public BanCommand(PunishmentManager punishmentManager) { + this.punishmentManager = punishmentManager; + } + @Override public LiteralCommandNode getCommandNode() { return literal("ban") @@ -46,7 +54,7 @@ public final class BanCommand implements CommandProvider { UUID sender = CommandUtil.getSenderUUID(context.getSource()); UUID target = context.getArgument("player", UUID.class); long duration = context.getArgument("duration", Long.class); - executeBan(sender, target, duration, Punishment.DEFAULT_REASON); + executeBan(punishmentManager, sender, target, duration, Punishment.DEFAULT_REASON); return Command.SINGLE_SUCCESS; } @@ -55,17 +63,17 @@ public final class BanCommand implements CommandProvider { UUID target = context.getArgument("player", UUID.class); long duration = context.getArgument("duration", Long.class); String reason = StringArgumentType.getString(context, "reason"); - executeBan(sender, target, duration, reason); + executeBan(punishmentManager, sender, target, duration, reason); return Command.SINGLE_SUCCESS; } - public static void executeBan(UUID sender, UUID target, long duration, String reason) throws CommandSyntaxException { + public static void executeBan(PunishmentManager manager, UUID sender, UUID target, long duration, String reason) throws CommandSyntaxException { Player targetPlayer = Bukkit.getPlayer(target); if (duration == 0){ if (targetPlayer == null){ CommandUtil.errorTranslatable("smod.command.ban.fail.tooShort"); } else { - KickCommand.executeKick(sender, targetPlayer, reason); + KickCommand.executeKick(manager, sender, targetPlayer, reason); } return; } @@ -75,14 +83,18 @@ public final class BanCommand implements CommandProvider { if (targetPlayer != null && targetPlayer.hasPermission("smod.preventban")){ CommandUtil.errorTranslatable("smod.command.ban.fail.protect"); } else { - final Punishment punishment = Punishment.ban( + if (!manager.byTargetUUID(target, p -> p instanceof Ban ban && ban.isActive()).isEmpty()) { + CommandUtil.errorTranslatable("smod.command.ban.fail.alreadyBanned"); + } + Punishment punishment = new Ban( + Punishment.generateUUID(), System.currentTimeMillis(), - System.currentTimeMillis() + duration, sender, target, - reason + reason, + duration ); - Punishment.issue(punishment, SModerationPaper.container); + manager.tryIssue(punishment); } } } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/KickCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/KickCommand.java index caa11ad..232438b 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/KickCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/KickCommand.java @@ -6,7 +6,9 @@ import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.SModerationPaper; +import de.shiewk.smoderation.paper.punishments.Kick; import de.shiewk.smoderation.paper.punishments.Punishment; +import de.shiewk.smoderation.paper.punishments.PunishmentManager; import de.shiewk.smoderation.paper.util.CommandUtil; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; @@ -22,6 +24,12 @@ import static io.papermc.paper.command.brigadier.Commands.literal; @SuppressWarnings("UnstableApiUsage") // Paper Brigadier API public final class KickCommand implements CommandProvider { + private final PunishmentManager punishmentManager; + + public KickCommand(PunishmentManager punishmentManager) { + this.punishmentManager = punishmentManager; + } + @Override public LiteralCommandNode getCommandNode() { return literal("kick") @@ -39,7 +47,7 @@ public final class KickCommand implements CommandProvider { UUID sender = CommandUtil.getSenderUUID(context.getSource()); Player target = CommandUtil.getPlayerSingle(context, "player"); String reason = StringArgumentType.getString(context, "reason"); - executeKick(sender, target, reason); + executeKick(punishmentManager, sender, target, reason); return Command.SINGLE_SUCCESS; } @@ -49,24 +57,25 @@ public final class KickCommand implements CommandProvider { } UUID sender = CommandUtil.getSenderUUID(context.getSource()); Player target = CommandUtil.getPlayerSingle(context, "player"); - executeKick(sender, target, Punishment.DEFAULT_REASON); + executeKick(punishmentManager, sender, target, Punishment.DEFAULT_REASON); return Command.SINGLE_SUCCESS; } - public static void executeKick(UUID sender, Player target, String reason) throws CommandSyntaxException { + public static void executeKick(PunishmentManager manager, UUID sender, Player target, String reason) throws CommandSyntaxException { UUID targetId = target.getUniqueId(); if (sender.equals(targetId)) { CommandUtil.errorTranslatable("smod.command.kick.fail.self"); } else if (target.hasPermission("smod.preventkick")){ CommandUtil.errorTranslatable("smod.command.kick.fail.protect"); } - final Punishment punishment = Punishment.kick( + Punishment punishment = new Kick( + Punishment.generateUUID(), System.currentTimeMillis(), sender, targetId, reason ); - Punishment.issue(punishment, SModerationPaper.container); + manager.tryIssue(punishment); } @Override diff --git a/src/main/java/de/shiewk/smoderation/paper/command/ModLogsCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/ModLogsCommand.java index 4096027..69f8d49 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/ModLogsCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/ModLogsCommand.java @@ -5,6 +5,8 @@ import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument; import de.shiewk.smoderation.paper.punishments.Punishment; +import de.shiewk.smoderation.paper.punishments.PunishmentManager; +import de.shiewk.smoderation.paper.punishments.TimedPunishment; import de.shiewk.smoderation.paper.util.CommandUtil; import de.shiewk.smoderation.paper.util.PlayerUtil; import de.shiewk.smoderation.paper.util.TimeUtil; @@ -15,7 +17,6 @@ import java.util.Collection; import java.util.List; import java.util.UUID; -import static de.shiewk.smoderation.paper.SModerationPaper.container; import static io.papermc.paper.command.brigadier.Commands.argument; import static io.papermc.paper.command.brigadier.Commands.literal; import static net.kyori.adventure.text.Component.text; @@ -24,6 +25,12 @@ import static net.kyori.adventure.text.Component.translatable; @SuppressWarnings("UnstableApiUsage") // Paper Brigadier API public final class ModLogsCommand implements CommandProvider { + private final PunishmentManager punishmentManager; + + public ModLogsCommand(PunishmentManager punishmentManager) { + this.punishmentManager = punishmentManager; + } + @Override public LiteralCommandNode getCommandNode() { return literal("modlogs") @@ -39,13 +46,15 @@ public final class ModLogsCommand implements CommandProvider { UUID uuid = context.getArgument("player", UUID.class); String name = PlayerUtil.offlinePlayerName(uuid); sender.sendMessage(translatable("smod.command.modlogs.heading", text(name), text(uuid.toString()))); - final List punishments = container.findAll(p -> p.to.equals(uuid) && p.isActive()); + List punishments = punishmentManager.byTargetUUID(uuid); for (Punishment punishment : punishments) { - sender.sendMessage(translatable("smod.command.modlogs." + punishment.type.name().toLowerCase(), - TimeUtil.calendarTimestamp(punishment.until), - TimeUtil.formatTimeLong(punishment.until - System.currentTimeMillis()), - text(punishment.reason) - )); + if (punishment instanceof TimedPunishment timed && timed.isActive()){ + sender.sendMessage(translatable("smod.command.modlogs." + punishment.getType(), + TimeUtil.calendarTimestamp(timed.getExpiry()), + TimeUtil.formatTimeLong(timed.getExpiry() - System.currentTimeMillis()), + text(punishment.getReason()) + )); + } } if (punishments.isEmpty()){ sender.sendMessage(translatable("smod.command.modlogs.none")); diff --git a/src/main/java/de/shiewk/smoderation/paper/command/MuteCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/MuteCommand.java index 90ab1da..047ec88 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/MuteCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/MuteCommand.java @@ -8,7 +8,9 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.SModerationPaper; import de.shiewk.smoderation.paper.command.argument.DurationArgument; import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument; +import de.shiewk.smoderation.paper.punishments.Mute; import de.shiewk.smoderation.paper.punishments.Punishment; +import de.shiewk.smoderation.paper.punishments.PunishmentManager; import de.shiewk.smoderation.paper.util.CommandUtil; import io.papermc.paper.command.brigadier.CommandSourceStack; import org.bukkit.Bukkit; @@ -24,6 +26,12 @@ import static io.papermc.paper.command.brigadier.Commands.literal; @SuppressWarnings("UnstableApiUsage") // Paper Brigadier API public final class MuteCommand implements CommandProvider { + private final PunishmentManager punishmentManager; + + public MuteCommand(PunishmentManager punishmentManager) { + this.punishmentManager = punishmentManager; + } + @Override public LiteralCommandNode getCommandNode() { return literal("mute") @@ -46,7 +54,7 @@ public final class MuteCommand implements CommandProvider { UUID sender = CommandUtil.getSenderUUID(context.getSource()); UUID target = context.getArgument("player", UUID.class); long duration = context.getArgument("duration", Long.class); - executeMute(sender, target, duration, Punishment.DEFAULT_REASON); + executeMute(punishmentManager, sender, target, duration, Punishment.DEFAULT_REASON); return Command.SINGLE_SUCCESS; } @@ -55,11 +63,11 @@ public final class MuteCommand implements CommandProvider { UUID target = context.getArgument("player", UUID.class); long duration = context.getArgument("duration", Long.class); String reason = StringArgumentType.getString(context, "reason"); - executeMute(sender, target, duration, reason); + executeMute(punishmentManager, sender, target, duration, reason); return Command.SINGLE_SUCCESS; } - public static void executeMute(UUID sender, UUID target, long duration, String reason) throws CommandSyntaxException { + public static void executeMute(PunishmentManager manager, UUID sender, UUID target, long duration, String reason) throws CommandSyntaxException { if (duration == 0){ CommandUtil.errorTranslatable("smod.command.mute.fail.tooShort"); } @@ -70,14 +78,18 @@ public final class MuteCommand implements CommandProvider { if (targetPlayer != null && targetPlayer.hasPermission("smod.preventmute")){ CommandUtil.errorTranslatable("smod.command.mute.fail.protect"); } else { - final Punishment punishment = Punishment.mute( + if (!manager.byTargetUUID(target, p -> p instanceof Mute mute && mute.isActive()).isEmpty()) { + CommandUtil.errorTranslatable("smod.command.mute.fail.alreadyMuted"); + } + Punishment punishment = new Mute( + Punishment.generateUUID(), System.currentTimeMillis(), - System.currentTimeMillis() + duration, sender, target, - reason + reason, + duration ); - Punishment.issue(punishment, SModerationPaper.container); + manager.tryIssue(punishment); } } } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/SModCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/SModCommand.java index 9d347fe..76e4a11 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/SModCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/SModCommand.java @@ -4,8 +4,8 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.LiteralCommandNode; -import de.shiewk.smoderation.paper.SModerationPaper; import de.shiewk.smoderation.paper.inventory.SModMenu; +import de.shiewk.smoderation.paper.punishments.PunishmentManager; import de.shiewk.smoderation.paper.util.CommandUtil; import io.papermc.paper.command.brigadier.CommandSourceStack; import org.bukkit.entity.Player; @@ -18,6 +18,12 @@ import static io.papermc.paper.command.brigadier.Commands.literal; @SuppressWarnings("UnstableApiUsage") // Paper Brigadier API public final class SModCommand implements CommandProvider { + private final PunishmentManager punishmentManager; + + public SModCommand(PunishmentManager punishmentManager) { + this.punishmentManager = punishmentManager; + } + @Override public LiteralCommandNode getCommandNode() { return literal("smod") @@ -28,7 +34,7 @@ public final class SModCommand implements CommandProvider { private int openMenu(CommandContext context) throws CommandSyntaxException { Player player = CommandUtil.getExecutingPlayer(context.getSource()); - new SModMenu(player, SModerationPaper.container).open(); + new SModMenu(player, punishmentManager).open(); return Command.SINGLE_SUCCESS; } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/UnbanCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/UnbanCommand.java index 7aa0c9d..3cca9a9 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/UnbanCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/UnbanCommand.java @@ -4,10 +4,11 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.LiteralCommandNode; -import de.shiewk.smoderation.paper.SModerationPaper; import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument; +import de.shiewk.smoderation.paper.punishments.Ban; import de.shiewk.smoderation.paper.punishments.Punishment; -import de.shiewk.smoderation.paper.punishments.PunishmentType; +import de.shiewk.smoderation.paper.punishments.PunishmentManager; +import de.shiewk.smoderation.paper.punishments.TimedPunishment; import de.shiewk.smoderation.paper.util.CommandUtil; import io.papermc.paper.command.brigadier.CommandSourceStack; @@ -21,6 +22,12 @@ import static io.papermc.paper.command.brigadier.Commands.literal; @SuppressWarnings("UnstableApiUsage") // Paper Brigadier API public final class UnbanCommand implements CommandProvider { + private final PunishmentManager punishmentManager; + + public UnbanCommand(PunishmentManager punishmentManager) { + this.punishmentManager = punishmentManager; + } + @Override public LiteralCommandNode getCommandNode() { return literal("unban") @@ -34,13 +41,14 @@ public final class UnbanCommand implements CommandProvider { private int unbanPlayer(CommandContext context) throws CommandSyntaxException { UUID senderUUID = CommandUtil.getSenderUUID(context.getSource()); UUID target = context.getArgument("player", UUID.class); - final Punishment punishment = SModerationPaper.container.find( - p -> p.to.equals(target) && p.isActive() && p.type == PunishmentType.BAN + final List punishments = punishmentManager.byTargetUUID( + target, + p -> p instanceof Ban ban && ban.isActive() ); - if (punishment != null) { - punishment.undo(senderUUID); - punishment.broadcastUndo(SModerationPaper.container); - } else { + for (Punishment punishment : punishments) { + punishmentManager.cancel((TimedPunishment) punishment, senderUUID); + } + if (punishments.isEmpty()) { CommandUtil.errorTranslatable("smod.command.unban.fail.notBanned"); } return Command.SINGLE_SUCCESS; diff --git a/src/main/java/de/shiewk/smoderation/paper/command/UnmuteCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/UnmuteCommand.java index 8da6587..d8a936d 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/UnmuteCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/UnmuteCommand.java @@ -4,10 +4,11 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.LiteralCommandNode; -import de.shiewk.smoderation.paper.SModerationPaper; import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument; +import de.shiewk.smoderation.paper.punishments.Mute; import de.shiewk.smoderation.paper.punishments.Punishment; -import de.shiewk.smoderation.paper.punishments.PunishmentType; +import de.shiewk.smoderation.paper.punishments.PunishmentManager; +import de.shiewk.smoderation.paper.punishments.TimedPunishment; import de.shiewk.smoderation.paper.util.CommandUtil; import io.papermc.paper.command.brigadier.CommandSourceStack; @@ -21,6 +22,12 @@ import static io.papermc.paper.command.brigadier.Commands.literal; @SuppressWarnings("UnstableApiUsage") // Paper Brigadier API public final class UnmuteCommand implements CommandProvider { + private final PunishmentManager punishmentManager; + + public UnmuteCommand(PunishmentManager punishmentManager) { + this.punishmentManager = punishmentManager; + } + @Override public LiteralCommandNode getCommandNode() { return literal("unmute") @@ -34,14 +41,15 @@ public final class UnmuteCommand implements CommandProvider { private int unmutePlayer(CommandContext context) throws CommandSyntaxException { UUID senderUUID = CommandUtil.getSenderUUID(context.getSource()); UUID target = context.getArgument("player", UUID.class); - final Punishment punishment = SModerationPaper.container.find( - p -> p.to.equals(target) && p.isActive() && p.type == PunishmentType.MUTE + final List punishments = punishmentManager.byTargetUUID( + target, + p -> p instanceof Mute mute && mute.isActive() ); - if (punishment != null) { - punishment.undo(senderUUID); - punishment.broadcastUndo(SModerationPaper.container); - } else { - CommandUtil.errorTranslatable("smod.command.unmute.fail.notMuted"); + for (Punishment punishment : punishments) { + punishmentManager.cancel((TimedPunishment) punishment, senderUUID); + } + if (punishments.isEmpty()) { + CommandUtil.errorTranslatable("smod.command.unmute.fail.notBanned"); } return Command.SINGLE_SUCCESS; } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/VanishCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/VanishCommand.java index 2909a89..f3298d1 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/VanishCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/VanishCommand.java @@ -4,8 +4,8 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.LiteralCommandNode; -import de.shiewk.smoderation.paper.SModerationPaper; import de.shiewk.smoderation.paper.event.VanishToggleEvent; +import de.shiewk.smoderation.paper.punishments.Punishment; import de.shiewk.smoderation.paper.util.CommandUtil; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.argument.ArgumentTypes; @@ -92,7 +92,7 @@ public final class VanishCommand implements CommandProvider { } if (newStatus){ vanishedPlayers.add(player); - for (CommandSender sender : SModerationPaper.container.collectBroadcastTargets()) { + for (CommandSender sender : Punishment.getBroadcastTargets()) { sender.sendMessage(translatable("smod.command.vanish.broadcast.on", player.teamDisplayName())); } player.sendMessage(translatable("smod.command.vanish.toggle.on")); @@ -104,7 +104,7 @@ public final class VanishCommand implements CommandProvider { } } else { vanishedPlayers.remove(player); - for (CommandSender sender : container.collectBroadcastTargets()) { + for (CommandSender sender : Punishment.getBroadcastTargets()) { sender.sendMessage(translatable("smod.command.vanish.broadcast.off", player.teamDisplayName())); } player.sendMessage(translatable("smod.command.vanish.toggle.off")); diff --git a/src/main/java/de/shiewk/smoderation/paper/event/PunishmentIssueEvent.java b/src/main/java/de/shiewk/smoderation/paper/event/PunishmentIssueEvent.java index a00eeb4..8c277b6 100644 --- a/src/main/java/de/shiewk/smoderation/paper/event/PunishmentIssueEvent.java +++ b/src/main/java/de/shiewk/smoderation/paper/event/PunishmentIssueEvent.java @@ -1,7 +1,7 @@ package de.shiewk.smoderation.paper.event; import de.shiewk.smoderation.paper.punishments.Punishment; -import de.shiewk.smoderation.paper.storage.PunishmentContainer; +import de.shiewk.smoderation.paper.punishments.PunishmentManager; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; @@ -11,20 +11,20 @@ public class PunishmentIssueEvent extends Event implements Cancellable { private static final HandlerList handlerList = new HandlerList(); private final Punishment punishment; - private final PunishmentContainer container; + private final PunishmentManager manager; private boolean cancelled; - public PunishmentIssueEvent(Punishment punishment, PunishmentContainer container) { + public PunishmentIssueEvent(Punishment punishment, PunishmentManager manager) { this.punishment = punishment; - this.container = container; + this.manager = manager; } public Punishment getPunishment() { return punishment; } - public PunishmentContainer getContainer() { - return container; + public PunishmentManager getPunishmentManager() { + return manager; } @Override diff --git a/src/main/java/de/shiewk/smoderation/paper/inventory/SModMenu.java b/src/main/java/de/shiewk/smoderation/paper/inventory/SModMenu.java index 122afe5..42956b9 100644 --- a/src/main/java/de/shiewk/smoderation/paper/inventory/SModMenu.java +++ b/src/main/java/de/shiewk/smoderation/paper/inventory/SModMenu.java @@ -5,8 +5,8 @@ import com.destroystokyo.paper.profile.ProfileProperty; import de.shiewk.smoderation.paper.SkinTextureProvider; import de.shiewk.smoderation.paper.input.ChatInput; import de.shiewk.smoderation.paper.punishments.Punishment; -import de.shiewk.smoderation.paper.punishments.PunishmentType; -import de.shiewk.smoderation.paper.storage.PunishmentContainer; +import de.shiewk.smoderation.paper.punishments.PunishmentManager; +import de.shiewk.smoderation.paper.punishments.TimedPunishment; import de.shiewk.smoderation.paper.util.PlayerUtil; import de.shiewk.smoderation.paper.util.SchedulerUtil; import de.shiewk.smoderation.paper.util.TimeUtil; @@ -27,8 +27,10 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; import org.jetbrains.annotations.NotNull; +import java.io.IOException; import java.util.Comparator; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Predicate; @@ -41,8 +43,8 @@ import static net.kyori.adventure.text.Component.*; public class SModMenu extends PageableCustomInventory { public enum Filter { - ACTIVE(translatable("smod.menu.filter.active"), Punishment::isActive), - OLD(translatable("smod.menu.filter.expired"), p -> !p.isActive()), + ACTIVE(translatable("smod.menu.filter.active"), p -> p instanceof TimedPunishment timed && timed.isActive()), + OLD(translatable("smod.menu.filter.expired"), p -> !(p instanceof TimedPunishment timed && timed.isActive())), ALL(translatable("smod.menu.filter.all"), p -> true); public static final Material ICON = Material.HOPPER; @@ -56,10 +58,10 @@ public class SModMenu extends PageableCustomInventory { } public enum Sort { - EXPIRY(translatable("smod.menu.sort.expiry"), Comparator.comparingLong(p -> p.until)), - TIME(translatable("smod.menu.sort.time"), Comparator.comparingLong(p -> p.time)), - PLAYER_NAME(translatable("smod.menu.sort.playerName"), (p1, p2) -> String.CASE_INSENSITIVE_ORDER.compare(PlayerUtil.offlinePlayerName(p1.to), PlayerUtil.offlinePlayerName(p2.to))), - MODERATOR_NAME(translatable("smod.menu.sort.moderatorName"), (p1, p2) -> String.CASE_INSENSITIVE_ORDER.compare(PlayerUtil.offlinePlayerName(p1.by), PlayerUtil.offlinePlayerName(p2.by))); + EXPIRY(translatable("smod.menu.sort.expiry"), Comparator.comparingLong(p -> p instanceof TimedPunishment timed ? p.getTimestamp() + timed.getDuration() : p.getTimestamp())), + TIME(translatable("smod.menu.sort.time"), Comparator.comparingLong(Punishment::getTimestamp)), + PLAYER_NAME(translatable("smod.menu.sort.playerName"), (p1, p2) -> String.CASE_INSENSITIVE_ORDER.compare(PlayerUtil.offlinePlayerName(p1.getTargetID()), PlayerUtil.offlinePlayerName(p2.getTargetID()))), + MODERATOR_NAME(translatable("smod.menu.sort.moderatorName"), (p1, p2) -> String.CASE_INSENSITIVE_ORDER.compare(PlayerUtil.offlinePlayerName(p1.getIssuerID()), PlayerUtil.offlinePlayerName(p2.getIssuerID()))); public static final Material ICON = Material.COMPARATOR; @@ -74,7 +76,7 @@ public class SModMenu extends PageableCustomInventory { private final Inventory inventory; private final Player player; - private final PunishmentContainer container; + private final PunishmentManager punishmentManager; private final Int2ObjectArrayMap slotMap = new Int2ObjectArrayMap<>(45); private List punishments; private int sort = 0; @@ -83,10 +85,10 @@ public class SModMenu extends PageableCustomInventory { private int rfId = 0; private String searchQuery = null; - public SModMenu(Player player, PunishmentContainer container) { + public SModMenu(Player player, PunishmentManager punishmentManager) { super(45, 53); this.player = player; - this.container = container; + this.punishmentManager = punishmentManager; this.inventory = Bukkit.createInventory(this, 54, translatable("smod.menu")); reload(); } @@ -99,16 +101,21 @@ public class SModMenu extends PageableCustomInventory { return Filter.values()[filter]; } - public PunishmentType getType(){ - return type == -1 ? null : PunishmentType.values()[type]; + public String getType(){ + return type == -1 ? null : punishmentManager.getRegisteredTypes().get(type); } - private void reload(){ - this.punishments = container.copy().stream() - .filter(getFilter().filter) - .filter(p -> getType() == null || p.type == getType()) - .filter(p -> p.matchesSearchQuery(searchQuery)) - .sorted(getSort().comparator).toList(); + private void reload() { + try { + this.punishments = this.punishmentManager.getAll() + .stream() + .filter(getFilter().filter) + .filter(p -> getType() == null || Objects.equals(p.getType(), getType())) + .filter(p -> p.matchesSearchQuery(searchQuery)) + .sorted(getSort().comparator).toList(); + } catch (IOException e) { + this.punishments = List.of(); + } } public void promptSearchQuery(){ @@ -169,12 +176,12 @@ public class SModMenu extends PageableCustomInventory { player.playSound(player, Sound.UI_BUTTON_CLICK, 1f, backwards ? 0.8f : 2f); if (backwards){ if (type <= -1){ - type = PunishmentType.values().length-1; + type = punishmentManager.getRegisteredTypes().size()-1; } else { type--; } } else { - if (type >= PunishmentType.values().length-1){ + if (type >= punishmentManager.getRegisteredTypes().size()-1){ type = -1; } else { type++; @@ -209,19 +216,19 @@ public class SModMenu extends PageableCustomInventory { } private ItemStack createTypeItem(){ - final PunishmentType type = getType(); + final String type = getType(); final ItemStack stack = new ItemStack(Material.CHEST); - stack.setData(DataComponentTypes.ITEM_NAME, renderComponent(player, translatable("smod.menu.type", (type == null ? translatable("smod.menu.type.all") : type.name))).color(PRIMARY_COLOR)); + stack.setData(DataComponentTypes.ITEM_NAME, renderComponent(player, translatable("smod.menu.type", (type == null ? translatable("smod.menu.type.all") : translatable("smod.punishment.name." + type)))).color(PRIMARY_COLOR)); ItemLore.Builder loreBuilder = ItemLore.lore(); loreBuilder.addLine(empty()); - final Consumer addToLore = value -> { - final boolean selected = type == value; - Component typeText = renderComponent(player, applyFormatting(text((selected ? "\u00BB " : ""), selected ? SECONDARY_COLOR : INACTIVE_COLOR).append(value == null ? translatable("smod.menu.type.all") : value.name))); + final Consumer addToLore = value -> { + final boolean selected = Objects.equals(type, value); + Component typeText = renderComponent(player, applyFormatting(text((selected ? "\u00BB " : ""), selected ? SECONDARY_COLOR : INACTIVE_COLOR).append(value == null ? translatable("smod.menu.type.all") : translatable("smod.punishment.name." + value)))); loreBuilder.addLine(typeText); }; addToLore.accept(null); - for (PunishmentType value : PunishmentType.values()) { + for (String value : punishmentManager.getRegisteredTypes()) { addToLore.accept(value); } @@ -282,12 +289,12 @@ public class SModMenu extends PageableCustomInventory { private CompletableFuture createPunishmentItem(Punishment punishment){ SkinTextureProvider provider = getTextureProvider(); if (provider != null) { - return provider.textureProperty(punishment.to) + return provider.textureProperty(punishment.getTargetID()) .thenApply(texture -> { ItemStack stack = new ItemStack(Material.PLAYER_HEAD); stack.editMeta(meta -> { if (meta instanceof SkullMeta skullMeta){ - PlayerProfile profile = Bukkit.createProfile(punishment.to); + PlayerProfile profile = Bukkit.createProfile(punishment.getTargetID()); profile.setProperty(new ProfileProperty( "textures", texture @@ -303,9 +310,9 @@ public class SModMenu extends PageableCustomInventory { stack.editMeta(meta -> { if (meta instanceof SkullMeta skullMeta){ try { - skullMeta.setOwningPlayer(Bukkit.getOfflinePlayer(punishment.to)); + skullMeta.setOwningPlayer(Bukkit.getOfflinePlayer(punishment.getTargetID())); } catch (NullPointerException e) { - LOGGER.warn("Player {} has a punishment but was never on this server!", punishment.to); + LOGGER.warn("Player {} has a punishment but was never on this server!", punishment.getTargetID()); } } }); @@ -315,16 +322,16 @@ public class SModMenu extends PageableCustomInventory { } private void addPunishmentInfo(Punishment punishment, ItemStack stack) { - stack.setData(DataComponentTypes.CUSTOM_NAME, renderComponent(player, applyFormatting(punishment.type.name.color(NamedTextColor.RED).decorate(TextDecoration.BOLD)))); + stack.setData(DataComponentTypes.CUSTOM_NAME, renderComponent(player, applyFormatting(translatable("smod.punishment.name." + punishment.getType(), NamedTextColor.RED).decorate(TextDecoration.BOLD)))); ItemLore.Builder lore = ItemLore.lore(); - lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.player", text(PlayerUtil.offlinePlayerName(punishment.to)))))); - lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.punishedBy", text(PlayerUtil.offlinePlayerName(punishment.by)))))); - lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.timestamp", TimeUtil.calendarTimestamp(punishment.time))))); + lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.player", text(PlayerUtil.offlinePlayerName(punishment.getTargetID())))))); + lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.punishedBy", text(PlayerUtil.offlinePlayerName(punishment.getIssuerID())))))); + lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.timestamp", TimeUtil.calendarTimestamp(punishment.getTimestamp()))))); - if (punishment.type != PunishmentType.KICK){ - lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.duration", TimeUtil.formatTimeLong(punishment.until - punishment.time))))); - long remainingTime = punishment.until - System.currentTimeMillis(); + if (punishment instanceof TimedPunishment timed){ + lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.duration", TimeUtil.formatTimeLong(timed.getExpiry() - punishment.getTimestamp()))))); + long remainingTime = timed.getExpiry() - System.currentTimeMillis(); if (remainingTime > 0){ lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.expiry.future", TimeUtil.formatTimeLong(remainingTime))))); } else { @@ -332,14 +339,16 @@ public class SModMenu extends PageableCustomInventory { } } - lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.reason", text(punishment.reason))))); + lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.reason", text(punishment.getReason()))))); - if (punishment.wasUndone()){ - lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.undone", text(PlayerUtil.offlinePlayerName(punishment.undoneBy())))))); - } else if (punishment.isActive()) { - if ((punishment.type == PunishmentType.BAN && player.hasPermission("smod.unban")) || (punishment.type == PunishmentType.MUTE && player.hasPermission("smod.unmute"))){ - lore.addLine(empty()); - lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.click", NamedTextColor.GOLD)))); + if (punishment instanceof TimedPunishment timed){ + if (timed.wasCancelled()){ + lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.cancelled", text(PlayerUtil.offlinePlayerName(timed.getCancelledBy())))))); + } else if (timed.isActive()) { + if (player.hasPermission("smod.un" + punishment.getType())){ + lore.addLine(empty()); + lore.addLine(renderComponent(player, applyFormatting(translatable("smod.menu.info.click", NamedTextColor.GOLD)))); + } } } stack.setData(DataComponentTypes.LORE, lore); @@ -404,16 +413,13 @@ public class SModMenu extends PageableCustomInventory { cycleSort(event.isRightClick()); } else { Punishment punishment = slotMap.get(slot); - if (punishment != null){ - if (punishment.isActive()){ - if ((punishment.type == PunishmentType.BAN && player.hasPermission("smod.unban")) || (punishment.type == PunishmentType.MUTE && player.hasPermission("smod.unmute"))) { - new ConfirmationInventory(player, translatable("smod.menu.undoConfirmation"), () -> { - punishment.undo(player.getUniqueId()); - punishment.broadcastUndo(container); - player.playSound(player, Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 2f); - this.open(); - }, this::open).open(); - } + if (punishment instanceof TimedPunishment timed && timed.isActive()){ + if (player.hasPermission("smod.un" + punishment.getType())) { + new ConfirmationInventory(player, translatable("smod.menu.cancelConfirmation"), () -> { + punishmentManager.cancel(timed, player.getUniqueId()); + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 2f); + this.open(); + }, this::open).open(); } } } diff --git a/src/main/java/de/shiewk/smoderation/paper/listener/PunishmentListener.java b/src/main/java/de/shiewk/smoderation/paper/listener/PunishmentListener.java index 7768f5d..140f818 100644 --- a/src/main/java/de/shiewk/smoderation/paper/listener/PunishmentListener.java +++ b/src/main/java/de/shiewk/smoderation/paper/listener/PunishmentListener.java @@ -1,11 +1,10 @@ package de.shiewk.smoderation.paper.listener; import de.shiewk.smoderation.paper.SModerationPaper; -import de.shiewk.smoderation.paper.event.PunishmentIssueEvent; -import de.shiewk.smoderation.paper.inventory.CustomInventory; +import de.shiewk.smoderation.paper.punishments.Ban; +import de.shiewk.smoderation.paper.punishments.Mute; import de.shiewk.smoderation.paper.punishments.Punishment; -import de.shiewk.smoderation.paper.punishments.PunishmentType; -import de.shiewk.smoderation.paper.storage.PunishmentContainer; +import de.shiewk.smoderation.paper.punishments.PunishmentManager; import io.papermc.paper.event.player.AsyncChatEvent; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -14,7 +13,6 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.event.world.WorldSaveEvent; import java.util.List; @@ -23,42 +21,35 @@ import static net.kyori.adventure.text.Component.translatable; public class PunishmentListener implements Listener { + private final PunishmentManager punishmentManager; + + public PunishmentListener(PunishmentManager punishmentManager) { + this.punishmentManager = punishmentManager; + } + @EventHandler(priority = EventPriority.LOW) public void onPlayerLogin(AsyncPlayerPreLoginEvent event){ - // Have to use AsyncPlayerPreLoginEvent since PlayerLoginEvent is deprecated in newer versions - // I would use the new PlayerConnectionValidateLoginEvent but there is literally no API available - // there to get player's UUIDs - Punishment punishment = SModerationPaper.container.find(p -> - p.type == PunishmentType.BAN - && p.to.equals(event.getUniqueId()) - && p.isActive()); - if (punishment != null){ - event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_BANNED, punishment.playerMessage().colorIfAbsent(PRIMARY_COLOR)); + List list = punishmentManager.byTargetUUID(event.getUniqueId(), p -> p instanceof Ban ban && ban.isActive()); + if (!list.isEmpty()) { + event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_BANNED, list.getFirst().infoMessage()); } } @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onPlayerChat(AsyncChatEvent event){ final Player player = event.getPlayer(); - final Punishment punishment = SModerationPaper.container.find(p -> - p.type == PunishmentType.MUTE - && p.to.equals(player.getUniqueId()) - && p.isActive()); - if (punishment != null) { + List list = punishmentManager.byTargetUUID(player.getUniqueId(), p -> p instanceof Mute mute && mute.isActive()); + if (!list.isEmpty()) { event.setCancelled(true); - player.sendMessage(punishment.playerMessage().colorIfAbsent(PRIMARY_COLOR)); + player.sendMessage(list.getFirst().infoMessage().colorIfAbsent(PRIMARY_COLOR)); } } @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event){ Player player = event.getPlayer(); - final Punishment mute = SModerationPaper.container.find(p -> - p.type == PunishmentType.MUTE - && p.to.equals(player.getUniqueId()) - && p.isActive()); - - if (mute != null) { // Player is muted + List list = punishmentManager.byTargetUUID(player.getUniqueId(), p -> p instanceof Mute mute && mute.isActive()); + if (!list.isEmpty()) { // Player is muted List forbiddenCommands = SModerationPaper.config().getStringList("muted-forbidden-commands"); final String message = event.getMessage(); if (forbiddenCommands.stream().anyMatch(str -> @@ -73,29 +64,4 @@ public class PunishmentListener implements Listener { } - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onPunishmentIssue(PunishmentIssueEvent event){ - final Punishment punishment = event.getPunishment(); - final PunishmentContainer container = event.getContainer(); - final Punishment duplicate = container.find(p -> p.to.equals(punishment.to) && p.type == punishment.type && p.isActive()); - if (duplicate != null){ - container.remove(duplicate); - container.add(new Punishment(duplicate.type, duplicate.time, System.currentTimeMillis(), duplicate.by, duplicate.to, duplicate.reason)); - } - switch (punishment.type){ - case KICK, BAN -> { - final Player player = Bukkit.getPlayer(punishment.to); - if (player != null) { - player.kick(CustomInventory.renderComponent(player, punishment.playerMessage().colorIfAbsent(PRIMARY_COLOR))); - } - } - } - } - - @EventHandler - public void onWorldSave(WorldSaveEvent event){ - if (event.getWorld().equals(Bukkit.getServer().getWorlds().getFirst())){ - SModerationPaper.container.save(SModerationPaper.SAVE_FILE); - } - } } diff --git a/src/main/java/de/shiewk/smoderation/paper/punishments/Ban.java b/src/main/java/de/shiewk/smoderation/paper/punishments/Ban.java new file mode 100644 index 0000000..7c92a94 --- /dev/null +++ b/src/main/java/de/shiewk/smoderation/paper/punishments/Ban.java @@ -0,0 +1,46 @@ +package de.shiewk.smoderation.paper.punishments; + +import de.shiewk.smoderation.paper.inventory.CustomInventory; +import de.shiewk.smoderation.paper.util.SerializationHelper; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NonNull; + +import java.util.UUID; + +public class Ban extends TimedPunishment { + + public Ban(UUID id, long timestamp, UUID issuer, UUID target, String reason, long duration, UUID cancelledBy) { + super(id, "ban", timestamp, issuer, target, reason, duration, cancelledBy); + } + + public Ban(UUID id, long timestamp, UUID issuer, UUID target, String reason, long duration) { + this(id, timestamp, issuer, target, reason, duration, null); + } + + public static class Factory implements PunishmentFactory { + + @Override + public @NonNull Ban deserialize(SerializationHelper helper) { + return new Ban( + helper.getUUID("id"), + helper.getLong("timestamp"), + helper.getUUID("issuer"), + helper.getUUID("target"), + helper.getString("reason"), + helper.getLong("duration"), + helper.getUUID("cancelledBy", null) + ); + } + } + + @Override + public void processIssue() { + super.processIssue(); + final Player player = Bukkit.getPlayer(getTargetID()); + if (player != null) { + player.kick(CustomInventory.renderComponent(player, infoMessage())); + } + } + +} diff --git a/src/main/java/de/shiewk/smoderation/paper/punishments/Kick.java b/src/main/java/de/shiewk/smoderation/paper/punishments/Kick.java new file mode 100644 index 0000000..b7df728 --- /dev/null +++ b/src/main/java/de/shiewk/smoderation/paper/punishments/Kick.java @@ -0,0 +1,40 @@ +package de.shiewk.smoderation.paper.punishments; + +import de.shiewk.smoderation.paper.inventory.CustomInventory; +import de.shiewk.smoderation.paper.util.SerializationHelper; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jspecify.annotations.NonNull; + +import java.util.UUID; + +public class Kick extends Punishment { + + public Kick(UUID id, long timestamp, UUID issuer, UUID target, String reason) { + super(id, "kick", timestamp, issuer, target, reason); + } + + public static class Factory implements PunishmentFactory { + + @Override + public @NonNull Kick deserialize(SerializationHelper helper) { + return new Kick( + helper.getUUID("id"), + helper.getLong("timestamp"), + helper.getUUID("issuer"), + helper.getUUID("target"), + helper.getString("reason") + ); + } + + } + + @Override + public void processIssue() { + super.processIssue(); + final Player player = Bukkit.getPlayer(getTargetID()); + if (player != null) { + player.kick(CustomInventory.renderComponent(player, infoMessage())); + } + } +} diff --git a/src/main/java/de/shiewk/smoderation/paper/punishments/Mute.java b/src/main/java/de/shiewk/smoderation/paper/punishments/Mute.java new file mode 100644 index 0000000..e41f6f5 --- /dev/null +++ b/src/main/java/de/shiewk/smoderation/paper/punishments/Mute.java @@ -0,0 +1,34 @@ +package de.shiewk.smoderation.paper.punishments; + +import de.shiewk.smoderation.paper.util.SerializationHelper; +import org.jspecify.annotations.NonNull; + +import java.util.UUID; + +public class Mute extends TimedPunishment { + + public Mute(UUID id, long timestamp, UUID issuer, UUID target, String reason, long duration, UUID cancelledBy) { + super(id, "mute", timestamp, issuer, target, reason, duration, cancelledBy); + } + + public Mute(UUID id, long timestamp, UUID issuer, UUID target, String reason, long duration) { + this(id, timestamp, issuer, target, reason, duration, null); + } + + public static class Factory implements PunishmentFactory { + + @Override + public @NonNull Mute deserialize(SerializationHelper helper) { + return new Mute( + helper.getUUID("id"), + helper.getLong("timestamp"), + helper.getUUID("issuer"), + helper.getUUID("target"), + helper.getString("reason"), + helper.getLong("duration"), + helper.getUUID("cancelledBy", null) + ); + } + + } +} diff --git a/src/main/java/de/shiewk/smoderation/paper/punishments/Punishment.java b/src/main/java/de/shiewk/smoderation/paper/punishments/Punishment.java index 941b087..9207785 100644 --- a/src/main/java/de/shiewk/smoderation/paper/punishments/Punishment.java +++ b/src/main/java/de/shiewk/smoderation/paper/punishments/Punishment.java @@ -1,205 +1,123 @@ package de.shiewk.smoderation.paper.punishments; -import de.shiewk.smoderation.paper.event.PunishmentIssueEvent; -import de.shiewk.smoderation.paper.storage.PunishmentContainer; -import de.shiewk.smoderation.paper.util.ByteUtil; import de.shiewk.smoderation.paper.util.PlayerUtil; -import de.shiewk.smoderation.paper.util.TimeUtil; +import de.shiewk.smoderation.paper.util.SerializationHelper; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.util.List; +import java.util.Random; import java.util.UUID; -import static de.shiewk.smoderation.paper.SModerationPaper.*; import static net.kyori.adventure.text.Component.text; import static net.kyori.adventure.text.Component.translatable; -public class Punishment { +public abstract class Punishment { + public static final String DEFAULT_REASON = "No reason provided."; - public final PunishmentType type; - public final long time; - public final long until; - public final UUID by; - public final UUID to; - public final String reason; - private UUID undoneBy; - public Punishment(PunishmentType type, long time, long until, UUID by, UUID to, String reason) { - this(type, time, until, by, to, reason, null); - } + protected final UUID id; + protected final String type; + protected final long timestamp; + protected final UUID issuer; + protected final UUID target; + protected final String reason; - private Punishment(PunishmentType type, long time, long until, UUID by, UUID to, String reason, UUID undoneBy) { + protected Punishment(UUID id, String type, long timestamp, UUID issuer, UUID target, String reason) { + this.id = id; this.type = type; - this.time = time; - this.until = until; - this.by = by; - this.to = to; + this.timestamp = timestamp; + this.issuer = issuer; + this.target = target; this.reason = reason; - this.undoneBy = undoneBy; } - private static byte[] readStreamInternal(InputStream stream, int len) throws IOException { - final byte[] bytes = stream.readNBytes(len); - if (bytes.length != len){ - throw new EOFException("Stream has ended before enough bytes were read"); - } - return bytes; + public UUID getID() { + return id; } - public static Punishment load(InputStream in) throws IOException { - PunishmentType type = PunishmentType.values()[ByteUtil.bytesToInt(readStreamInternal(in, 4))]; - long time = ByteUtil.bytesToLong(readStreamInternal(in, 8)); - long until = ByteUtil.bytesToLong(readStreamInternal(in, 8)); - UUID by = ByteUtil.bytesToUuid(readStreamInternal(in, 16)); - UUID to = ByteUtil.bytesToUuid(readStreamInternal(in, 16)); - int reasonLen = ByteUtil.bytesToInt(readStreamInternal(in, 4)); - String reason = new String(readStreamInternal(in, reasonLen)); - UUID undoneBy = null; - boolean undone = in.read() == 1; - if (undone){ - undoneBy = ByteUtil.bytesToUuid(readStreamInternal(in, 16)); - } - return new Punishment(type, time, until, by, to, reason, undoneBy); + public String getType() { + return type; } - public boolean wasUndone(){ - return undoneBy != null; + public long getTimestamp() { + return timestamp; } - public UUID undoneBy() { - return undoneBy; + public UUID getIssuerID() { + return issuer; } - public void undo(UUID undoneBy){ - if (this.undoneBy != null){ - throw new IllegalArgumentException("This punishment was already undone."); - } - this.undoneBy = undoneBy; + public UUID getTargetID() { + return target; } - public boolean isActive(){ - return until > System.currentTimeMillis() && !wasUndone(); + public String getReason() { + return reason; } - public static Punishment mute(long time, long until, UUID by, UUID to, String reason){ - return new Punishment(PunishmentType.MUTE, time, until, by, to, reason); + public void addSerializableProperties(SerializationHelper helper){ + helper.putUUID("id", id); + helper.putString("type", type); + helper.putLong("timestamp", timestamp); + helper.putUUID("issuer", issuer); + helper.putUUID("target", target); + helper.putString("reason", reason); } - public static Punishment ban(long time, long until, UUID by, UUID to, String reason){ - return new Punishment(PunishmentType.BAN, time, until, by, to, reason); + public boolean matchesSearchQuery(String query){ + if (query == null) return true; + query = query.toLowerCase(); + return reason.toLowerCase().contains(query) + || issuer.toString().equalsIgnoreCase(query) + || target.toString().equalsIgnoreCase(query) + || PlayerUtil.offlinePlayerName(issuer).toLowerCase().contains(query) + || PlayerUtil.offlinePlayerName(target).toLowerCase().contains(query); } - public static Punishment kick(long time, UUID by, UUID to, String reason){ - return new Punishment(PunishmentType.KICK, time, time, by, to, reason); - } - - public void writeBytes(OutputStream stream) throws IOException { - stream.write(ByteUtil.intToBytes(type.ordinal())); - stream.write(ByteUtil.longToBytes(time)); - stream.write(ByteUtil.longToBytes(until)); - stream.write(ByteUtil.uuidToBytes(by)); - stream.write(ByteUtil.uuidToBytes(to)); - final byte[] reasonBytes = reason.getBytes(); - stream.write(ByteUtil.intToBytes(reasonBytes.length)); - stream.write(reasonBytes); - stream.write(wasUndone() ? 1 : 0); - if (wasUndone()){ - stream.write(ByteUtil.uuidToBytes(undoneBy)); - } - } - - private Component undoMessage(){ - String key = "smod.punishment.undo." + type.name().toLowerCase(); - return translatable(key, text(PlayerUtil.offlinePlayerName(to)), text(PlayerUtil.offlinePlayerName(undoneBy))); - } - - public void broadcastUndo(PunishmentContainer container){ - for (CommandSender sender : container.collectBroadcastTargets()) { - sender.sendMessage(undoMessage().colorIfAbsent(PRIMARY_COLOR)); - } - } - - @Override - public String toString() { - return "Punishment{" + - "type=" + type + - ", time=" + time + - ", until=" + until + - ", by=" + by + - ", to=" + to + - ", reason=" + reason + - '}'; - } - - public static void issue(Punishment punishment, PunishmentContainer container){ - final PunishmentIssueEvent event = new PunishmentIssueEvent(punishment, container); - Bukkit.getPluginManager().callEvent(event); - if (!event.isCancelled()){ - container.add(punishment); - punishment.firstIssue(container); - } - } - - private Component broadcastMessage(){ - String key = "smod.punishment.broadcast." + type.name().toLowerCase(); + public Component infoMessage(){ return translatable( - key, - text(PlayerUtil.offlinePlayerName(to)), - text(PlayerUtil.offlinePlayerName(by)), - TimeUtil.formatTimeLong(this.until - this.time), + "smod.punishment.playerMessage." + type, + text(PlayerUtil.offlinePlayerName(this.issuer)), text(reason) ); } - private void broadcastIssue(PunishmentContainer container){ - for (CommandSender sender : container.collectBroadcastTargets()) { - sender.sendMessage(broadcastMessage().colorIfAbsent(PRIMARY_COLOR)); - } - } - - private void firstIssue(PunishmentContainer container){ - switch (type) { - case MUTE, BAN -> { - final CommandSender sender = PlayerUtil.senderByUUID(to); - if (sender != null) { - sender.sendMessage(playerMessage().colorIfAbsent(PRIMARY_COLOR)); - } - } - } - broadcastIssue(container); - } - - public Component playerMessage(){ - String key = "smod.punishment.playerMessage." + type.name().toLowerCase(); + public Component adminMessage(){ return translatable( - key, - text(PlayerUtil.offlinePlayerName(this.by)), - text(reason), - TimeUtil.formatTimeLong(this.until - System.currentTimeMillis()) + "smod.punishment.broadcast." + type, + text(PlayerUtil.offlinePlayerName(target)), + text(PlayerUtil.offlinePlayerName(issuer)), + text(reason) ); } - public boolean matchesSearchQuery(String searchQuery) { - if (searchQuery == null) return true; - searchQuery = searchQuery.toLowerCase(); - return reason.toLowerCase().contains(searchQuery) - || by.toString().equalsIgnoreCase(searchQuery) - || to.toString().equalsIgnoreCase(searchQuery) - || getPlayerName().toLowerCase().contains(searchQuery) - || getModeratorName().toLowerCase().contains(searchQuery); - + public void processIssue() { + CommandSender sender = PlayerUtil.senderByUUID(target); + if (sender != null) { + sender.sendMessage(infoMessage()); + } + for (CommandSender target : getBroadcastTargets()) { + target.sendMessage(adminMessage()); + } } - private String getPlayerName() { - return PlayerUtil.offlinePlayerName(to); + public static List getBroadcastTargets() { + ObjectArrayList senders = new ObjectArrayList<>(); + senders.add(Bukkit.getConsoleSender()); + for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { + if (onlinePlayer.hasPermission("smod.notifications")){ + senders.add(onlinePlayer); + } + } + return List.copyOf(senders); } - private String getModeratorName() { - return PlayerUtil.offlinePlayerName(by); + public static UUID generateUUID() { + Random random = new Random(); + return new UUID(System.currentTimeMillis(), random.nextLong()); } } diff --git a/src/main/java/de/shiewk/smoderation/paper/punishments/PunishmentFactory.java b/src/main/java/de/shiewk/smoderation/paper/punishments/PunishmentFactory.java new file mode 100644 index 0000000..e9e270e --- /dev/null +++ b/src/main/java/de/shiewk/smoderation/paper/punishments/PunishmentFactory.java @@ -0,0 +1,10 @@ +package de.shiewk.smoderation.paper.punishments; + +import de.shiewk.smoderation.paper.util.SerializationHelper; +import org.jetbrains.annotations.NotNull; + +public interface PunishmentFactory { + + @NotNull T deserialize(SerializationHelper helper); + +} diff --git a/src/main/java/de/shiewk/smoderation/paper/punishments/PunishmentManager.java b/src/main/java/de/shiewk/smoderation/paper/punishments/PunishmentManager.java new file mode 100644 index 0000000..781182f --- /dev/null +++ b/src/main/java/de/shiewk/smoderation/paper/punishments/PunishmentManager.java @@ -0,0 +1,168 @@ +package de.shiewk.smoderation.paper.punishments; + +import com.google.gson.JsonObject; +import com.google.gson.Strictness; +import com.google.gson.stream.JsonReader; +import de.shiewk.smoderation.paper.SModerationPaper; +import de.shiewk.smoderation.paper.event.PunishmentIssueEvent; +import de.shiewk.smoderation.paper.util.PlayerUtil; +import de.shiewk.smoderation.paper.util.SerializationHelper; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import static de.shiewk.smoderation.paper.SModerationPaper.LOGGER; + +public final class PunishmentManager { + + private static final Logger log = LoggerFactory.getLogger(PunishmentManager.class); + private final Object2ObjectArrayMap> typeRegistry = new Object2ObjectArrayMap<>(1); + private final Object ioLock = new Object(); + private final Path dataDir; + + public PunishmentManager(Path dataDir) { + this.dataDir = dataDir; + } + + private Path getTargetFile(UUID targetUUID){ + return dataDir.resolve(targetUUID.toString().replace("-", "")); + } + + public boolean tryIssue(Punishment punishment) { + try { + PunishmentIssueEvent event = new PunishmentIssueEvent(punishment, this); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled()){ + this.appendToSave(punishment); + punishment.processIssue(); + return true; + } + return false; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public List byTargetUUID(UUID target) { + synchronized (ioLock) { + Path file = getTargetFile(target); + if (!Files.exists(file)) { + return List.of(); + } + try ( + BufferedReader reader = Files.newBufferedReader(file); + JsonReader json = new JsonReader(reader) + ) { + json.setStrictness(Strictness.LENIENT); + Object2ObjectArrayMap punishments = new Object2ObjectArrayMap<>(0); + while (json.hasNext()){ + JsonObject obj = SModerationPaper.gson.fromJson(json, JsonObject.class); + try { + SerializationHelper helper = new SerializationHelper(obj); + String type = helper.getString("type"); + PunishmentFactory factory = typeRegistry.get(type); + if (factory != null){ + Punishment punishment = factory.deserialize(helper); + if (!punishment.getTargetID().equals(target)){ + LOGGER.warn("Punishment saved in file for {} has incorrect target UUID {}", target, punishment.getTargetID()); + } else { + punishments.put(punishment.getID(), punishment); + } + } else { + LOGGER.warn("Unknown punishment type '{}'! Can not load.", type); + LOGGER.warn("Please check your configuration, or see file {} to remove corrupted data.", file); + LOGGER.warn(obj.toString()); + } + } catch (Exception e) { + LOGGER.warn("Could not deserialize punishment!", e); + LOGGER.warn("Please check file {} for corrupted data, or remove the corresponding line.", file); + LOGGER.warn(obj.toString()); + } + } + return List.copyOf(punishments.values()); + } catch (IOException e){ + throw new RuntimeException("Error while reading punishment file " + file, e); + } + } + } + + public List byTargetUUID(UUID target, Predicate filter) { + return byTargetUUID(target).stream().filter(filter).toList(); + } + + public void registerType(String type, PunishmentFactory factory){ + if (typeRegistry.containsKey(type)) { + throw new IllegalStateException("Punishment type already registered: " + type); + } + typeRegistry.put(type, factory); + } + + public List getRegisteredTypes(){ + return List.copyOf(typeRegistry.keySet()); + } + + private void appendToSave(Punishment punishment) throws IOException { + synchronized (ioLock) { + Path file = getTargetFile(punishment.getTargetID()); + if (!Files.exists(file)) { + Files.createDirectories(dataDir); + Files.createFile(file); + } + try (BufferedWriter writer = Files.newBufferedWriter(file, StandardOpenOption.APPEND)) { + JsonObject json = new JsonObject(); + punishment.addSerializableProperties(new SerializationHelper(json)); + SModerationPaper.gson.toJson(json, writer); + writer.append('\n'); + } + } + } + + public @NotNull List getAll() throws IOException { + ObjectArrayList punishments = new ObjectArrayList<>(); + synchronized (ioLock) { + try (Stream stream = Files.list(dataDir)) { + stream.forEach(file -> { + try { + String name = file.getFileName().toString(); + UUID targetUUID = PlayerUtil.uuidFromString(name); + punishments.addAll(byTargetUUID(targetUUID)); + } catch (Exception e) { + log.warn("Could not read punishment file {}", file, e); + } + }); + } + } + return List.copyOf(punishments); + } + + public List getAll(Predicate filter) throws IOException { + return getAll().stream().filter(filter).toList(); + } + + public void cancel(TimedPunishment punishment, UUID canceller) { + if (!punishment.isActive()){ + throw new IllegalStateException("This punishment is not active"); + } + punishment.cancel(canceller); + try { + appendToSave(punishment); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/de/shiewk/smoderation/paper/punishments/PunishmentType.java b/src/main/java/de/shiewk/smoderation/paper/punishments/PunishmentType.java deleted file mode 100644 index 968f7d4..0000000 --- a/src/main/java/de/shiewk/smoderation/paper/punishments/PunishmentType.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.shiewk.smoderation.paper.punishments; - -import net.kyori.adventure.text.Component; - -import static net.kyori.adventure.text.Component.translatable; - -public enum PunishmentType { - MUTE(translatable("smod.punishment.name.mute")), - KICK(translatable("smod.punishment.name.kick")), - BAN(translatable("smod.punishment.name.ban")); - - public final Component name; - - PunishmentType(Component name) { - this.name = name; - } -} diff --git a/src/main/java/de/shiewk/smoderation/paper/punishments/TimedPunishment.java b/src/main/java/de/shiewk/smoderation/paper/punishments/TimedPunishment.java new file mode 100644 index 0000000..ef26575 --- /dev/null +++ b/src/main/java/de/shiewk/smoderation/paper/punishments/TimedPunishment.java @@ -0,0 +1,98 @@ +package de.shiewk.smoderation.paper.punishments; + +import de.shiewk.smoderation.paper.util.PlayerUtil; +import de.shiewk.smoderation.paper.util.SerializationHelper; +import de.shiewk.smoderation.paper.util.TimeUtil; +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; + +import java.util.UUID; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.Component.translatable; + +public abstract class TimedPunishment extends Punishment { + + protected final long duration; + protected UUID cancelledBy; + + protected TimedPunishment(UUID id, String type, long timestamp, UUID issuer, UUID target, String reason, long duration, UUID cancelledBy) { + super(id, type, timestamp, issuer, target, reason); + this.duration = duration; + this.cancelledBy = cancelledBy; + } + + public long getDuration() { + return duration; + } + + public UUID getCancelledBy() { + return cancelledBy; + } + + public boolean wasCancelled(){ + return cancelledBy != null; + } + + public boolean isActive(){ + return !wasCancelled() && System.currentTimeMillis() < timestamp + duration; + } + + @Override + public void addSerializableProperties(SerializationHelper helper) { + super.addSerializableProperties(helper); + helper.putLong("duration", duration); + helper.putUUID("cancelledBy", cancelledBy); + } + + @Override + public boolean matchesSearchQuery(String query) { + if (super.matchesSearchQuery(query)) return true; + query = query.toLowerCase(); + return cancelledBy.toString().equalsIgnoreCase(query) + || PlayerUtil.offlinePlayerName(cancelledBy).toLowerCase().contains(query); + } + + @Override + public Component infoMessage(){ + return translatable( + "smod.punishment.playerMessage." + type, + text(PlayerUtil.offlinePlayerName(this.issuer)), + text(reason), + TimeUtil.formatTimeLong(this.timestamp + this.duration - System.currentTimeMillis()) + ); + } + + @Override + public Component adminMessage(){ + return translatable( + "smod.punishment.broadcast." + type, + text(PlayerUtil.offlinePlayerName(target)), + text(PlayerUtil.offlinePlayerName(issuer)), + text(reason), + TimeUtil.formatTimeLong(this.duration) + ); + } + + public Component cancelMessage(){ + return translatable( + "smod.punishment.cancel." + type, + text(PlayerUtil.offlinePlayerName(target)), + text(PlayerUtil.offlinePlayerName(cancelledBy)) + ); + } + + public long getExpiry() { + return getTimestamp() + getDuration(); + } + + protected void cancel(UUID canceller) { + if (this.cancelledBy != null){ + throw new IllegalArgumentException("This punishment was already cancelled."); + } + this.cancelledBy = canceller; + for (CommandSender sender : getBroadcastTargets()) { + sender.sendMessage(cancelMessage()); + } + } +} diff --git a/src/main/java/de/shiewk/smoderation/paper/storage/PunishmentContainer.java b/src/main/java/de/shiewk/smoderation/paper/storage/PunishmentContainer.java deleted file mode 100644 index b9abc32..0000000 --- a/src/main/java/de/shiewk/smoderation/paper/storage/PunishmentContainer.java +++ /dev/null @@ -1,124 +0,0 @@ -package de.shiewk.smoderation.paper.storage; - -import de.shiewk.smoderation.paper.SModerationPaper; -import de.shiewk.smoderation.paper.punishments.Punishment; -import net.kyori.adventure.text.logger.slf4j.ComponentLogger; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Predicate; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -public class PunishmentContainer { - - private final CopyOnWriteArrayList punishments = new CopyOnWriteArrayList<>(); - - public PunishmentContainer(){} - - public void add(Punishment punishment){ - punishments.add(punishment); - } - - public @Nullable Punishment remove(int index){ - return punishments.remove(index); - } - - public void remove(Punishment punishment){ - punishments.remove(punishment); - } - - public @Nullable Punishment find(Predicate predicate){ - for (Punishment punishment : punishments) { - if (predicate.test(punishment)){ - return punishment; - } - } - return null; - } - - public @NotNull List findAll(Predicate predicate){ - List found = new ArrayList<>(); - for (Punishment punishment : punishments) { - if (predicate.test(punishment)){ - found.add(punishment); - } - } - return found; - } - - public List collectBroadcastTargets(){ - ArrayList senders = new ArrayList<>(); - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - if (onlinePlayer.hasPermission("smod.notifications")){ - senders.add(onlinePlayer); - } - } - senders.add(Bukkit.getConsoleSender()); - return Collections.unmodifiableList(senders); - } - - public @Nullable Punishment findByTimestamp(long timestamp){ - return find(punishment -> punishment.time == timestamp); - } - - public ArrayList copy() { - return new ArrayList<>(punishments); - } - - public synchronized void load(File file){ - final ComponentLogger logger = SModerationPaper.LOGGER; - try { - logger.info("Loading from {}", file.getPath()); - if (!file.isFile()){ - logger.warn("The file does not exist."); - } else { - try (FileInputStream fin = new FileInputStream(file)){ - GZIPInputStream gzin = new GZIPInputStream(fin); - while (gzin.available() > 0){ - add(Punishment.load(gzin)); - } - } - logger.info("Successfully loaded {} items.", punishments.size()); - } - } catch (EOFException e) { - logger.error("The file was not correctly saved, {} items could be recovered!", this.punishments.size()); - } catch (IOException e){ - logger.error("An error occurred while loading: {}", e.toString()); - for (StackTraceElement stackTraceElement : e.getStackTrace()) { - logger.error(stackTraceElement.toString()); - } - } - } - - public synchronized void save(File file) { - final ComponentLogger logger = SModerationPaper.LOGGER; - try { - if (!file.isFile()){ - file.mkdirs(); - file.delete(); - file.createNewFile(); - } - try (FileOutputStream outputStream = new FileOutputStream(file)) { - GZIPOutputStream gzout = new GZIPOutputStream(outputStream); - for (Punishment punishment : copy()) { - punishment.writeBytes(gzout); - } - gzout.close(); - } - } catch (IOException e){ - logger.error("An error occurred while saving: {}", e.toString()); - for (StackTraceElement stackTraceElement : e.getStackTrace()) { - logger.error(stackTraceElement.toString()); - } - } - } -} diff --git a/src/main/java/de/shiewk/smoderation/paper/util/PlayerUtil.java b/src/main/java/de/shiewk/smoderation/paper/util/PlayerUtil.java index 7a971b0..24756d1 100644 --- a/src/main/java/de/shiewk/smoderation/paper/util/PlayerUtil.java +++ b/src/main/java/de/shiewk/smoderation/paper/util/PlayerUtil.java @@ -28,4 +28,12 @@ public final class PlayerUtil { return Bukkit.getPlayer(uid); } } + + public static UUID uuidFromString(String string) { + if (string.length() == 36) { + return UUID.fromString(string); + } else { + return UUID.fromString(string.replaceFirst("(.{8})(.{4})(.{4})(.{4})(.{12})", "$1-$2-$3-$4-$5")); + } + } } diff --git a/src/main/java/de/shiewk/smoderation/paper/util/ByteUtil.java b/src/main/java/de/shiewk/smoderation/paper/util/SModLegacy.java similarity index 77% rename from src/main/java/de/shiewk/smoderation/paper/util/ByteUtil.java rename to src/main/java/de/shiewk/smoderation/paper/util/SModLegacy.java index ac9cc80..8e0ffd8 100644 --- a/src/main/java/de/shiewk/smoderation/paper/util/ByteUtil.java +++ b/src/main/java/de/shiewk/smoderation/paper/util/SModLegacy.java @@ -3,20 +3,16 @@ package de.shiewk.smoderation.paper.util; import java.nio.ByteBuffer; import java.util.UUID; +public final class SModLegacy { + private SModLegacy() {} -/** - * Utility class for byte-based saving of integers, longs and UUIDs - */ -public final class ByteUtil { - private ByteUtil(){} - - public static byte[] longToBytes(long v){ + private static byte[] longToBytes(long v){ ByteBuffer buffer = ByteBuffer.allocate(8); buffer.putLong(v); return buffer.array(); } - public static long bytesToLong(byte[] i){ + private static long bytesToLong(byte[] i){ if (i.length != 8){ throw new IllegalArgumentException("length must be 8"); } @@ -25,7 +21,7 @@ public final class ByteUtil { return buffer.getLong(0); } - public static byte[] uuidToBytes(UUID uuid){ + private static byte[] uuidToBytes(UUID uuid){ byte[] l = longToBytes(uuid.getLeastSignificantBits()); byte[] m = longToBytes(uuid.getMostSignificantBits()); return new byte[]{ @@ -34,7 +30,7 @@ public final class ByteUtil { }; } - public static UUID bytesToUuid(byte[] i){ + private static UUID bytesToUuid(byte[] i){ if (i.length != 16){ throw new IllegalArgumentException("length must be 16, was " + i.length); } @@ -43,7 +39,7 @@ public final class ByteUtil { return new UUID(m, l); } - public static int bytesToInt(byte[] bytes) { + private static int bytesToInt(byte[] bytes) { if (bytes.length != 4){ throw new IllegalArgumentException("length must be 4"); } @@ -52,9 +48,10 @@ public final class ByteUtil { return buffer.getInt(0); } - public static byte[] intToBytes(int value) { + private static byte[] intToBytes(int value) { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.putInt(value); return buffer.array(); } + } diff --git a/src/main/java/de/shiewk/smoderation/paper/util/SerializationHelper.java b/src/main/java/de/shiewk/smoderation/paper/util/SerializationHelper.java new file mode 100644 index 0000000..c90573a --- /dev/null +++ b/src/main/java/de/shiewk/smoderation/paper/util/SerializationHelper.java @@ -0,0 +1,66 @@ +package de.shiewk.smoderation.paper.util; + +import com.google.gson.JsonObject; + +import java.util.UUID; + +public final class SerializationHelper { + + private final JsonObject json; + + public SerializationHelper(JsonObject json) { + this.json = json; + } + + public String getString(String key) { + try { + return json.get(key).getAsString(); + } catch (NullPointerException e) { + throw new IllegalStateException("Key " + key + " does not exist on this object"); + } catch (UnsupportedOperationException | IllegalStateException e) { + throw new IllegalStateException("Tried to get string " + key + ", but is " + json.get(key).getClass().getSimpleName()); + } + } + + public void putString(String key, String value) { + json.addProperty(key, value); + } + + public long getLong(String key) { + try { + return json.get(key).getAsLong(); + } catch (NullPointerException e) { + throw new IllegalStateException("Key " + key + " does not exist on this object"); + } catch (UnsupportedOperationException | IllegalStateException e) { + throw new IllegalStateException("Tried to get long " + key + ", but is " + json.get(key).getClass().getSimpleName()); + } catch (NumberFormatException e) { + throw new IllegalStateException("Tried to get long " + key + ", but is malformed: " + json.get(key).getAsString()); + } + } + + public void putLong(String key, long value) { + json.addProperty(key, value); + } + + public UUID getUUID(String key) { + try { + return PlayerUtil.uuidFromString(getString(key)); + } catch (IllegalArgumentException e) { + throw new IllegalStateException("UUID " + key + " malformed: " + getString(key)); + } + } + + public UUID getUUID(String key, UUID defaultValue) { + try { + return getUUID(key); + } catch (Exception ignored) { + return defaultValue; + } + } + + public void putUUID(String key, UUID value) { + if (value == null) return; + json.addProperty(key, value.toString().replace("-", "")); + } + +} diff --git a/src/main/resources/smoderation/translations/de_de.json b/src/main/resources/smoderation/translations/de_de.json index 55eafef..8f39028 100644 --- a/src/main/resources/smoderation/translations/de_de.json +++ b/src/main/resources/smoderation/translations/de_de.json @@ -1,9 +1,10 @@ { "smod.argument.duration.fail.invalid": "Ungültige Zeit ''", "smod.argument.duration.fail.pattern": "Bitte gib eine gültige Zeit an, z.B. '1d6h30min'", - "smod.argument.offlinePlayer.fail.notCached": "Dieser Spieler ist nicht gespeichert.", - "smod.argument.uuid.fail.notCached": "Dieser Spieler ist nicht gespeichert. Versuche, stattdessen seine UUID anzugeben.", + "smod.argument.offlinePlayer.fail.notCached": "Die Daten des Spielers sind nicht gespeichert.", + "smod.argument.uuid.fail.notCached": "Die Daten des Spielers sind nicht gespeichert. Versuche, stattdessen seine UUID anzugeben.", "smod.chatInput.remainingTime": " Sekunden", + "smod.command.ban.fail.alreadyBanned": "Dieser Spieler ist schon gebannt.", "smod.command.ban.fail.forceReason": "Bitte gib einen Grund an.", "smod.command.ban.fail.protect": "Dieser Spieler kann nicht gebannt werden.", "smod.command.ban.fail.self": "Du kannst dich nicht selbst bannen.", @@ -21,6 +22,7 @@ "smod.command.modlogs.heading": "Spieler ()", "smod.command.modlogs.mute": "- ist bis (in ) stummgeschaltet. Grund: ", "smod.command.modlogs.none": "- ist momentan nicht gebannt oder stummgeschaltet.", + "smod.command.mute.fail.alreadyMuted": "Dieser Spieler ist schon stummgeschaltet.", "smod.command.mute.fail.forceReason": "Bitte gib einen Grund an.", "smod.command.mute.fail.protect": "Dieser Spieler kann nicht stummgeschaltet werden.", "smod.command.mute.fail.self": "Du kannst dich nicht selbst stummschalten.", @@ -41,12 +43,16 @@ "smod.command.vanish.toggle.on": "Du bist jetzt unsichtbar.", "smod.confirm.no": "Nein", "smod.confirm.yes": "Ja", + "smod.inventory.next": "Nächste Seite (/)", + "smod.inventory.previous": "Vorherige Seite (/)", "smod.menu": "SMod Menü", + "smod.menu.cancelConfirmation": "Bist du sicher, dass du die Strafe aufheben willst?", "smod.menu.filter": "Filter: ", "smod.menu.filter.active": "Aktive Strafen", "smod.menu.filter.all": "Alle Strafen", "smod.menu.filter.expired": "Abgelaufene Strafen", "smod.menu.filter.switch": "\u00BB Klicke, um den Filter zu ändern", + "smod.menu.info.cancelled": "Aufgehoben von: ", "smod.menu.info.click": "\u00BB Klicke, um die Strafe aufzuheben", "smod.menu.info.duration": "Dauer: ", "smod.menu.info.expiry.future": "Läuft ab: In ", @@ -55,7 +61,6 @@ "smod.menu.info.punishedBy": "Bestraft von: ", "smod.menu.info.reason": "Grund: ", "smod.menu.info.timestamp": "Zeitpunkt: ", - "smod.menu.info.undone": "Aufgehoben von: ", "smod.menu.search": "Suchen", "smod.menu.search.current": "Aktueller Suchbegriff: ", "smod.menu.search.new": "\u00BB Klicke, um einen neuen Suchbegriff einzugeben", @@ -71,10 +76,11 @@ "smod.menu.type": "Typ: ", "smod.menu.type.all": "Alle", "smod.menu.type.switch": "\u00BB Klicke, um den Typ zu ändern", - "smod.menu.undoConfirmation": "Bist du sicher, dass du die Strafe aufheben willst?", - "smod.punishment.broadcast.ban": " wurde von für gebannt.Grund: ", - "smod.punishment.broadcast.kick": " wurde von gekickt.Grund: ", - "smod.punishment.broadcast.mute": " wurde von für stummgeschaltet.Grund: ", + "smod.punishment.broadcast.ban": " wurde von für gebannt.Grund: ", + "smod.punishment.broadcast.kick": " wurde von gekickt.Grund: ", + "smod.punishment.broadcast.mute": " wurde von für stummgeschaltet.Grund: ", + "smod.punishment.cancel.ban": " wurde von entbannt.", + "smod.punishment.cancel.mute": "s Stummschaltung wurde von aufgehoben.", "smod.punishment.name.ban": "Bann", "smod.punishment.name.kick": "Kick", "smod.punishment.name.mute": "Stummschaltung", @@ -82,8 +88,6 @@ "smod.punishment.playerMessage.kick": "Du wurdest von vom Server gekickt.Grund: ", "smod.punishment.playerMessage.mute": "Du wurdest von stummgeschaltet.Grund: Du kannst in wieder schreiben.", "smod.punishment.playerMessage.mute.chat": "Du kannst diesen Befehl nicht ausführen, während du stummgeschaltet bist.", - "smod.punishment.undo.ban": " wurde von entbannt.", - "smod.punishment.undo.mute": "s Stummschaltung wurde von aufgehoben.", "smod.socialspy.command": "[SocialSpy] : ", "smod.time.days": " Tage", "smod.time.hours": " Stunden", @@ -105,7 +109,5 @@ "smod.time.seconds": " Sekunden", "smod.time.timestamp": ". :: ", "smod.time.weeks": " Wochen", - "smod.time.years": " Jahre", - "smod.inventory.next": "Nächste Seite (/)", - "smod.inventory.previous": "Vorherige Seite (/)" + "smod.time.years": " Jahre" } \ No newline at end of file diff --git a/src/main/resources/smoderation/translations/en_us.json b/src/main/resources/smoderation/translations/en_us.json index 9b0ceed..9dead7b 100644 --- a/src/main/resources/smoderation/translations/en_us.json +++ b/src/main/resources/smoderation/translations/en_us.json @@ -1,9 +1,10 @@ { "smod.argument.duration.fail.invalid": "Invalid duration ''", "smod.argument.duration.fail.pattern": "Please provide a valid duration, e.g. '1d6h30min'", - "smod.argument.offlinePlayer.fail.notCached": "That player is not cached.", - "smod.argument.uuid.fail.notCached": "That player is not cached. Try providing an UUID instead.", + "smod.argument.offlinePlayer.fail.notCached": "That player's data is not saved.", + "smod.argument.uuid.fail.notCached": "That player's data is not saved. Try providing an UUID instead.", "smod.chatInput.remainingTime": " seconds", + "smod.command.ban.fail.alreadyBanned": "This player is already banned.", "smod.command.ban.fail.forceReason": "Please provide a reason.", "smod.command.ban.fail.protect": "This player can't be banned.", "smod.command.ban.fail.self": "You can't ban yourself.", @@ -21,6 +22,7 @@ "smod.command.modlogs.heading": "Player ()", "smod.command.modlogs.mute": "- is muted until (in ). Reason: ", "smod.command.modlogs.none": "- is not currently muted or banned.", + "smod.command.mute.fail.alreadyMuted": "This player is already muted.", "smod.command.mute.fail.forceReason": "Please provide a reason.", "smod.command.mute.fail.protect": "This player can't be muted.", "smod.command.mute.fail.self": "You can't mute yourself.", @@ -44,12 +46,14 @@ "smod.inventory.next": "Next page (/)", "smod.inventory.previous": "Previous page (/)", "smod.menu": "SMod Menu", + "smod.menu.cancelConfirmation": "Are you sure that you want to cancel this punishment?", "smod.menu.filter": "Filter: ", "smod.menu.filter.active": "Active punishments", "smod.menu.filter.all": "All punishments", "smod.menu.filter.expired": "Expired punishments", "smod.menu.filter.switch": "\u00BB Click to switch filter", - "smod.menu.info.click": "\u00BB Click to undo punishment", + "smod.menu.info.cancelled": "Cancelled by: ", + "smod.menu.info.click": "\u00BB Click to cancel punishment", "smod.menu.info.duration": "Duration: ", "smod.menu.info.expiry.future": "Expires: In ", "smod.menu.info.expiry.past": "Expired: ago", @@ -57,7 +61,6 @@ "smod.menu.info.punishedBy": "Punished by: ", "smod.menu.info.reason": "Reason: ", "smod.menu.info.timestamp": "Timestamp: ", - "smod.menu.info.undone": "Undone by: ", "smod.menu.search": "Search", "smod.menu.search.current": "Current search query: ", "smod.menu.search.new": "\u00BB Click to enter new search query", @@ -73,10 +76,11 @@ "smod.menu.type": "Type: ", "smod.menu.type.all": "All", "smod.menu.type.switch": "» Click to switch type", - "smod.menu.undoConfirmation": "Are you sure that you want to undo this punishment?", - "smod.punishment.broadcast.ban": " was banned by for .Reason: ", - "smod.punishment.broadcast.kick": " was kicked by .Reason: ", - "smod.punishment.broadcast.mute": " was muted by for .Reason: ", + "smod.punishment.broadcast.ban": " was banned by for .Reason: ", + "smod.punishment.broadcast.kick": " was kicked by .Reason: ", + "smod.punishment.broadcast.mute": " was muted by for .Reason: ", + "smod.punishment.cancel.ban": " was unbanned by .", + "smod.punishment.cancel.mute": " was unmuted by .", "smod.punishment.name.ban": "Ban", "smod.punishment.name.kick": "Kick", "smod.punishment.name.mute": "Mute", @@ -84,8 +88,6 @@ "smod.punishment.playerMessage.kick": "You have been kicked by .Reason: ", "smod.punishment.playerMessage.mute": "You have been muted by .Reason: Your mute expires in .", "smod.punishment.playerMessage.mute.chat": "You can't run this command while you are muted.", - "smod.punishment.undo.ban": " was unbanned by .", - "smod.punishment.undo.mute": " was unmuted by .", "smod.socialspy.command": "[SocialSpy] : ", "smod.time.days": " days", "smod.time.hours": " hours",