diff --git a/src/main/java/dev/pgm/community/CommunityPermissions.java b/src/main/java/dev/pgm/community/CommunityPermissions.java index b9a4f61..9fa9223 100644 --- a/src/main/java/dev/pgm/community/CommunityPermissions.java +++ b/src/main/java/dev/pgm/community/CommunityPermissions.java @@ -27,6 +27,9 @@ public interface CommunityPermissions { String PUNISHMENT_BROADCASTS = ROOT + ".view-punishments"; // Access to view when punishments are broadcast silently + // Sign Logger + String SIGN_LOG_BROADCASTS = ROOT + ".view-sign-logs"; // Access to view when signs are placed + // Reports String REPORTS = ROOT + ".reports"; // Access to view report broadcast & report history String REPORT_BROADCASTS = REPORTS + ".view-broadcasts"; // Access to view report broadcasts diff --git a/src/main/java/dev/pgm/community/moderation/ModerationConfig.java b/src/main/java/dev/pgm/community/moderation/ModerationConfig.java index cded0ec..df38fcb 100644 --- a/src/main/java/dev/pgm/community/moderation/ModerationConfig.java +++ b/src/main/java/dev/pgm/community/moderation/ModerationConfig.java @@ -42,6 +42,8 @@ public class ModerationConfig extends FeatureConfigImpl { private static final String PLAYER_HOOK_KEY = TOOLS_KEY + ".player-hook"; private static final String LOOKUP_SIGN_KEY = TOOLS_KEY + ".lookup-sign"; + private static final String SIGN_LOGGER_KEY = KEY + ".sign-logger"; + // General options private boolean persist; private boolean broadcast; @@ -88,6 +90,9 @@ public class ModerationConfig extends FeatureConfigImpl { private int playerHookSlot; private int lookupSignSlot; + // Sign Logger + private boolean signLoggerEnabled; + /** * Config options related to {@link ModerationFeature} * @@ -251,6 +256,10 @@ public String getStaffBroadcastFormat() { return staffBroadcastFormat; } + public boolean isSignLoggerEnabled() { + return signLoggerEnabled; + } + @Override public void reload(Configuration config) { super.reload(config); @@ -299,5 +308,8 @@ public void reload(Configuration config) { this.modMenuSlot = config.getInt(getItemSlotKey(MOD_MENU_KEY)); this.playerHookSlot = config.getInt(getItemSlotKey(PLAYER_HOOK_KEY)); this.lookupSignSlot = config.getInt(getItemSlotKey(LOOKUP_SIGN_KEY)); + + // Sign Logger + this.signLoggerEnabled = config.getBoolean(getEnabledKey(SIGN_LOGGER_KEY)); } } diff --git a/src/main/java/dev/pgm/community/moderation/feature/ModerationFeatureBase.java b/src/main/java/dev/pgm/community/moderation/feature/ModerationFeatureBase.java index d26c537..679b55b 100644 --- a/src/main/java/dev/pgm/community/moderation/feature/ModerationFeatureBase.java +++ b/src/main/java/dev/pgm/community/moderation/feature/ModerationFeatureBase.java @@ -1,6 +1,7 @@ package dev.pgm.community.moderation.feature; import static net.kyori.adventure.text.Component.text; +import static tc.oc.pgm.util.player.PlayerComponent.player; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @@ -27,6 +28,7 @@ import dev.pgm.community.utils.Sounds; import java.time.Duration; import java.time.Instant; +import java.util.Arrays; import java.util.List; import java.util.Map.Entry; import java.util.Optional; @@ -37,9 +39,12 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -50,7 +55,6 @@ import org.bukkit.event.player.PlayerJoinEvent; import tc.oc.pgm.util.Audience; import tc.oc.pgm.util.named.NameStyle; -import tc.oc.pgm.util.player.PlayerComponent; public abstract class ModerationFeatureBase extends FeatureBase implements ModerationFeature { @@ -77,18 +81,16 @@ public ModerationFeatureBase( this.network = network; this.recents = Sets.newHashSet(); this.muteCache = CacheBuilder.newBuilder().build(); - this.banEvasionCache = - CacheBuilder.newBuilder() - .expireAfterWrite(config.getEvasionExpireMins(), TimeUnit.MINUTES) - .build(); + this.banEvasionCache = CacheBuilder.newBuilder() + .expireAfterWrite(config.getEvasionExpireMins(), TimeUnit.MINUTES) + .build(); this.observerBanCache = CacheBuilder.newBuilder().build(); this.pardonedPlayers = CacheBuilder.newBuilder().build(); if (config.getMatchBanDuration() != null) { - this.matchBan = - CacheBuilder.newBuilder() - .expireAfterWrite(config.getMatchBanDuration().getSeconds(), TimeUnit.SECONDS) - .build(); + this.matchBan = CacheBuilder.newBuilder() + .expireAfterWrite(config.getMatchBanDuration().getSeconds(), TimeUnit.SECONDS) + .build(); } if (config.isEnabled()) { @@ -132,19 +134,18 @@ public Punishment punish( boolean active, boolean silent) { Instant time = Instant.now(); - Punishment punishment = - Punishment.of( - UUID.randomUUID(), - target, - getSenderId(issuer.getSender()), - reason, - time.toEpochMilli(), - duration, - type, - active, - time.toEpochMilli(), - getSenderId(issuer.getSender()), - getModerationConfig().getService()); + Punishment punishment = Punishment.of( + UUID.randomUUID(), + target, + getSenderId(issuer.getSender()), + reason, + time.toEpochMilli(), + duration, + type, + active, + time.toEpochMilli(), + getSenderId(issuer.getSender()), + getModerationConfig().getService()); Bukkit.getPluginManager().callEvent(new PlayerPunishmentEvent(issuer, punishment, silent)); return punishment; } @@ -213,11 +214,10 @@ public void onPunishmentEvent(PlayerPunishmentEvent event) { && onlineTarget.get().hasPermission(CommunityPermissions.ADMIN)) { event .getSender() - .sendWarning( - text() - .append(PlayerComponent.player(onlineTarget.get(), NameStyle.FANCY)) - .append(text(" is exempt from punishment")) - .build()); + .sendWarning(text() + .append(player(onlineTarget.get(), NameStyle.FANCY)) + .append(text(" is exempt from punishment")) + .build()); return; } } else if (PunishmentType.WARN.equals(punishment.getType()) @@ -232,9 +232,8 @@ public void onPunishmentEvent(PlayerPunishmentEvent event) { punishment.punish(event.isSilent()); // Perform the actual punishment - sendUpdate( - new NetworkPunishment( - punishment, network.getNetworkId())); // Send out network punishment update + sendUpdate(new NetworkPunishment( + punishment, network.getNetworkId())); // Send out network punishment update switch (punishment.getType()) { // Cache known IPS of a recently banned player, so if they rejoin on an alt we can find them @@ -272,18 +271,14 @@ public void onPlayerJoinEvasionCheck(PlayerJoinEvent event) { boolean exclude = hasRecentPardon(event.getPlayer().getUniqueId()); if (banEvasion.isPresent() && !exclude) { - users - .renderUsername(banEvasion, NameStyle.FANCY) - .thenAcceptAsync( - bannedName -> { - if (!banEvasion.get().equals(event.getPlayer().getUniqueId())) { - BroadcastUtils.sendAdminChatMessage( - PunishmentFormats.formatBanEvasion( - event.getPlayer(), banEvasion.get(), bannedName), - Sounds.BAN_EVASION, - CommunityPermissions.UNBAN); - } - }); + users.renderUsername(banEvasion, NameStyle.FANCY).thenAcceptAsync(bannedName -> { + if (!banEvasion.get().equals(event.getPlayer().getUniqueId())) { + BroadcastUtils.sendAdminChatMessage( + PunishmentFormats.formatBanEvasion(event.getPlayer(), banEvasion.get(), bannedName), + Sounds.BAN_EVASION, + CommunityPermissions.UNBAN); + } + }); } } @@ -291,25 +286,28 @@ public void onPlayerJoinEvasionCheck(PlayerJoinEvent event) { @EventHandler(priority = EventPriority.HIGHEST) public void onAsyncPlayerChatEvent(AsyncPlayerChatEvent event) { // MUTES - getCachedMute(event.getPlayer().getUniqueId()) - .ifPresent( - mute -> { - event.setCancelled(true); - Audience.get(event.getPlayer()).sendWarning(mute.getChatMuteMessage()); - }); + getCachedMute(event.getPlayer().getUniqueId()).ifPresent(mute -> { + event.setCancelled(true); + Audience.get(event.getPlayer()).sendWarning(mute.getChatMuteMessage()); + }); } - // Clear sign text for muted players @EventHandler(priority = EventPriority.HIGHEST) public void onPlaceSign(SignChangeEvent event) { - getCachedMute(event.getPlayer().getUniqueId()) - .ifPresent( - mute -> { - for (int i = 0; i < 4; i++) { - event.setLine(i, " "); - } - Audience.get(event.getPlayer()).sendWarning(mute.getSignMuteMessage()); - }); + // Prevent muted players from using signs + Optional mute = getCachedMute(event.getPlayer().getUniqueId()); + + if (mute.isPresent()) { + for (int i = 0; i < 4; i++) { + event.setLine(i, " "); + } + Audience.get(event.getPlayer()).sendWarning(mute.get().getSignMuteMessage()); + + return; + } + + // Log sign text to file & chat when enabled + logSign(event.getPlayer(), event.getLines(), event.getBlock().getLocation()); } // BANS @@ -352,10 +350,9 @@ private UUID getSenderId(CommandSender sender) { } private Optional isBanEvasion(String address) { - Optional>> cached = - banEvasionCache.asMap().entrySet().stream() - .filter(s -> s.getValue().contains(address)) - .findAny(); + Optional>> cached = banEvasionCache.asMap().entrySet().stream() + .filter(s -> s.getValue().contains(address)) + .findAny(); return Optional.ofNullable(cached.isPresent() ? cached.get().getKey() : null); } @@ -367,36 +364,32 @@ private void banHover() { color = !color; NamedTextColor alertColor = color ? NamedTextColor.YELLOW : NamedTextColor.DARK_RED; Component warning = text(" \u26a0 ", alertColor); - Component banned = - text() - .append(warning) - .append(text("You have been banned", NamedTextColor.RED, TextDecoration.BOLD)) - .append(warning) - .build(); + Component banned = text() + .append(warning) + .append(text("You have been banned", NamedTextColor.RED, TextDecoration.BOLD)) + .append(warning) + .build(); this.observerBanCache.asMap().keySet().stream() .filter(id -> Bukkit.getPlayer(id) != null) .map(Bukkit::getPlayer) .map(Audience::get) - .forEach( - viewer -> { - viewer.sendActionBar(banned); - }); + .forEach(viewer -> { + viewer.sendActionBar(banned); + }); } private Audience getStaffAudience() { - List staff = - Bukkit.getOnlinePlayers().stream() - .filter(p -> p.hasPermission(CommunityPermissions.PUNISHMENT_BROADCASTS)) - .collect(Collectors.toList()); + List staff = Bukkit.getOnlinePlayers().stream() + .filter(p -> p.hasPermission(CommunityPermissions.PUNISHMENT_BROADCASTS)) + .collect(Collectors.toList()); return Audience.get(staff); } private Audience getGlobalAudience() { - List normal = - Bukkit.getOnlinePlayers().stream() - .filter(p -> !p.hasPermission(CommunityPermissions.PUNISHMENT_BROADCASTS)) - .collect(Collectors.toList()); + List normal = Bukkit.getOnlinePlayers().stream() + .filter(p -> !p.hasPermission(CommunityPermissions.PUNISHMENT_BROADCASTS)) + .collect(Collectors.toList()); return Audience.get(normal); } @@ -412,17 +405,45 @@ private void broadcastPunishment( if (global) { PunishmentFormats.formatBroadcast(punishment, server, getGlobalFormat(), users) - .thenAcceptAsync( - broadcast -> { - getGlobalAudience().sendMessage(broadcast); - }); + .thenAcceptAsync(broadcast -> { + getGlobalAudience().sendMessage(broadcast); + }); } PunishmentFormats.formatBroadcast(punishment, server, getStaffFormat(), users) - .thenAcceptAsync( - broadcast -> { - BroadcastUtils.sendAdminChatMessage( - broadcast, CommunityPermissions.PUNISHMENT_BROADCASTS); - }); + .thenAcceptAsync(broadcast -> { + BroadcastUtils.sendAdminChatMessage( + broadcast, CommunityPermissions.PUNISHMENT_BROADCASTS); + }); + } + + private void logSign(Player player, String[] lines, Location location) { + if (!getModerationConfig().isSignLoggerEnabled()) return; + + int totalChars = 0; + for (String line : lines) { + totalChars += line.length(); + } + + if (totalChars < 4) return; // Don't track signs with barely any text + + String locString = + String.format("%d %d %d", location.getBlockX(), location.getBlockY(), location.getBlockZ()); + + String oneLineSign = Arrays.stream(lines) + .map(line -> line.trim()) + .filter(line -> !line.isBlank() && !line.isEmpty()) + .collect(Collectors.joining(" ")); + + Component alert = text() + .append(player(player, NameStyle.FANCY)) + .append(text(" placed a sign: \"", NamedTextColor.GRAY)) + .append(text(oneLineSign, NamedTextColor.YELLOW)) + .append(text("\"", NamedTextColor.GRAY)) + .clickEvent(ClickEvent.runCommand("/tploc " + locString)) + .hoverEvent(HoverEvent.showText(text("Click to teleport to sign", NamedTextColor.GRAY))) + .build(); + + BroadcastUtils.sendAdminChatMessage(alert, CommunityPermissions.SIGN_LOG_BROADCASTS); } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 6f1f748..195a038 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -78,6 +78,10 @@ moderation: enabled: true public: true + # Sign Logging + sign-logger: + enabled: true + #Logins login-timeout: 30