From 7e01f912e5ebfa38238d16ab0d0ac1708f3e8721 Mon Sep 17 00:00:00 2001 From: MD <1917406+mdcfe@users.noreply.github.com> Date: Sat, 30 Jul 2022 23:10:19 +0100 Subject: [PATCH] what --- .../chat/processing/AbstractChatHandler.java | 266 ++++++++++++++++++ .../chat/processing/ChatProcessingCache.java | 115 ++++++++ .../chat/processing/LegacyChatHandler.java | 5 + .../chat/processing/SignedChatHandler.java | 50 ++++ 4 files changed, 436 insertions(+) create mode 100644 EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/AbstractChatHandler.java create mode 100644 EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/ChatProcessingCache.java create mode 100644 EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/LegacyChatHandler.java create mode 100644 EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/SignedChatHandler.java diff --git a/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/AbstractChatHandler.java b/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/AbstractChatHandler.java new file mode 100644 index 000000000..4aee07787 --- /dev/null +++ b/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/AbstractChatHandler.java @@ -0,0 +1,266 @@ +package com.earth2me.essentials.chat.processing; + +import com.earth2me.essentials.ChargeException; +import com.earth2me.essentials.Essentials; +import com.earth2me.essentials.Trade; +import com.earth2me.essentials.User; +import com.earth2me.essentials.chat.EssentialsChat; +import com.earth2me.essentials.utils.FormatUtil; +import net.ess3.api.events.LocalChatSpyEvent; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.scoreboard.Team; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Set; +import java.util.logging.Level; + +import static com.earth2me.essentials.I18n.tl; + +public abstract class AbstractChatHandler { + + protected final Essentials ess; + protected final EssentialsChat essChat; + protected final Server server; + private final ChatProcessingCache cache; + + protected AbstractChatHandler(Essentials ess, EssentialsChat essChat) { + this.ess = ess; + this.essChat = essChat; + this.server = ess.getServer(); + this.cache = new ChatProcessingCache(); + } + + // The initial chat formatting logic, handled at LOWEST priority + protected void handleChatFormat(AsyncPlayerChatEvent event) { + if (isAborted(event)) { + return; + } + + final User user = ess.getUser(event.getPlayer()); + + if (user == null) { + event.setCancelled(true); + return; + } + + final ChatProcessingCache.IntermediateChat chat = new ChatProcessingCache.IntermediateChat(user, getChatType(user, event.getMessage()), event.getMessage()); + cache.setIntermediateChat(event.getPlayer(), chat); + + // This listener should apply the general chat formatting only...then return control back the event handler + event.setMessage(FormatUtil.formatMessage(user, "essentials.chat", event.getMessage())); + + if (ChatColor.stripColor(event.getMessage()).length() == 0) { + event.setCancelled(true); + return; + } + + final String group = user.getGroup(); + final String world = user.getWorld().getName(); + final String username = user.getName(); + final String nickname = user.getFormattedNickname(); + + final Player player = user.getBase(); + final String prefix = FormatUtil.replaceFormat(ess.getPermissionsHandler().getPrefix(player)); + final String suffix = FormatUtil.replaceFormat(ess.getPermissionsHandler().getSuffix(player)); + final Team team = player.getScoreboard().getPlayerTeam(player); + + String format = ess.getSettings().getChatFormat(group); + format = format.replace("{0}", group); + format = format.replace("{1}", ess.getSettings().getWorldAlias(world)); + format = format.replace("{2}", world.substring(0, 1).toUpperCase(Locale.ENGLISH)); + format = format.replace("{3}", team == null ? "" : team.getPrefix()); + format = format.replace("{4}", team == null ? "" : team.getSuffix()); + format = format.replace("{5}", team == null ? "" : team.getDisplayName()); + format = format.replace("{6}", prefix); + format = format.replace("{7}", suffix); + format = format.replace("{8}", username); + format = format.replace("{9}", nickname == null ? username : nickname); + synchronized (format) { + event.setFormat(format); + } + + chat.setModifiedMessage(String.format(format, player.getDisplayName(), event.getMessage())); + } + + // Local chat recipients logic, handled at NORMAL level + protected void handleChatRecipients(AsyncPlayerChatEvent event) { + if (isAborted(event)) { + return; + } + + // This file should handle detection of the local chat features; if local chat is enabled, we need to handle it here + long radius = ess.getSettings().getChatRadius(); + if (radius < 1) { + return; + } + radius *= radius; + + final ChatProcessingCache.IntermediateChat chatStore = cache.getIntermediateChat(event.getPlayer()); + final User user = chatStore.getUser(); + chatStore.setRadius(radius); + + if (event.getMessage().length() > 1) { + if (chatStore.getType().isEmpty()) { + if (!user.isAuthorized("essentials.chat.local")) { + user.sendMessage(tl("notAllowedToLocal")); + event.setCancelled(true); + return; + } + + if (user.isToggleShout() && event.getMessage().length() > 1 && event.getMessage().charAt(0) == ess.getSettings().getChatShout()) { + event.setMessage(event.getMessage().substring(1)); + } + + event.getRecipients().removeIf(player -> !ess.getUser(player).isAuthorized("essentials.chat.receive.local")); + } else { + final String permission = "essentials.chat." + chatStore.getType(); + + if (user.isAuthorized(permission)) { + if (event.getMessage().charAt(0) == ess.getSettings().getChatShout() || (event.getMessage().charAt(0) == ess.getSettings().getChatQuestion() && ess.getSettings().isChatQuestionEnabled())) { + event.setMessage(event.getMessage().substring(1)); + } + event.setFormat(tl(chatStore.getType() + "Format", event.getFormat())); + event.getRecipients().removeIf(player -> !ess.getUser(player).isAuthorized("essentials.chat.receive." + chatStore.getType())); + return; + } + + user.sendMessage(tl("notAllowedTo" + chatStore.getType().substring(0, 1).toUpperCase(Locale.ENGLISH) + chatStore.getType().substring(1))); + event.setCancelled(true); + return; + } + } + + final Location loc = user.getLocation(); + final World world = loc.getWorld(); + + // TODO: why here, why again in highest? who did this? why? pay for your crimes + if (!charge(event, chatStore)) { + return; + } + + final Set outList = event.getRecipients(); + final Set spyList = new HashSet<>(); + + try { + outList.add(event.getPlayer()); + } catch (final UnsupportedOperationException ex) { + if (ess.getSettings().isDebug()) { + essChat.getLogger().log(Level.INFO, "Plugin triggered custom chat event, local chat handling aborted.", ex); + } + return; + } + + final String format = event.getFormat(); + event.setFormat(tl("chatTypeLocal").concat(event.getFormat())); + + final Iterator it = outList.iterator(); + while (it.hasNext()) { + final Player onlinePlayer = it.next(); + final User onlineUser = ess.getUser(onlinePlayer); + if (!onlineUser.equals(user)) { + boolean abort = false; + final Location playerLoc = onlineUser.getLocation(); + if (playerLoc.getWorld() != world) { + abort = true; + } else { + final double delta = playerLoc.distanceSquared(loc); + if (delta > chatStore.getRadius()) { + abort = true; + } + } + if (abort) { + if (onlineUser.isAuthorized("essentials.chat.spy")) { + spyList.add(onlinePlayer); + } + it.remove(); + } + } + } + + if (outList.size() < 2) { + user.sendMessage(tl("localNoOne")); + } + + final LocalChatSpyEvent spyEvent = new LocalChatSpyEvent(event.isAsynchronous(), event.getPlayer(), format, event.getMessage(), spyList); + server.getPluginManager().callEvent(spyEvent); + + if (!spyEvent.isCancelled()) { + for (final Player onlinePlayer : spyEvent.getRecipients()) { + onlinePlayer.sendMessage(String.format(spyEvent.getFormat(), user.getDisplayName(), spyEvent.getMessage())); + } + } + } + + // Finalising the intermediate stages of chat processing, handled at HIGHEST level + protected void handleChatComplete(AsyncPlayerChatEvent event) { + final ChatProcessingCache.IntermediateChat intermediateChat = cache.clearIntermediateChat(event.getPlayer()); + if (isAborted(event) || intermediateChat == null) { + return; + } + + final ChatProcessingCache.ProcessedChat processed = new ChatProcessingCache.ProcessedChat(ess, intermediateChat); + cache.setProcessedChat(event.getPlayer(), processed); + } + + protected void handleChatSubmit(AsyncPlayerChatEvent event) { + if (isAborted(event)) { + return; + } + + // This file should handle charging the user for the action before returning control back + charge(event, cache.getProcessedChat(event.getPlayer())); + } + + boolean isAborted(final AsyncPlayerChatEvent event) { + return event.isCancelled(); + } + + String getChatType(final User user, final String message) { + if (message.length() == 0) { + //Ignore empty chat events generated by plugins + return ""; + } + + final char prefix = message.charAt(0); + if (prefix == ess.getSettings().getChatShout()) { + if (user.isToggleShout()) { + return ""; + } + return message.length() > 1 ? "shout" : ""; + } else if (ess.getSettings().isChatQuestionEnabled() && prefix == ess.getSettings().getChatQuestion()) { + return message.length() > 1 ? "question" : ""; + } else if (user.isToggleShout()) { + return message.length() > 1 ? "shout" : ""; + } else { + return ""; + } + } + + private void charge(final User user, final Trade charge) throws ChargeException { + charge.charge(user); + } + + boolean charge(final AsyncPlayerChatEvent event, final ChatProcessingCache.ProcessedChat chat) { + try { + charge(chat.getUser(), chat.getCharge()); + } catch (final ChargeException e) { + ess.showError(chat.getUser().getSource(), e, "\\ chat " + chat.getLongType()); + event.setCancelled(true); + return false; + } + return true; + } + + protected interface ChatListener extends Listener { + void onPlayerChat(AsyncPlayerChatEvent event); + } + +} diff --git a/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/ChatProcessingCache.java b/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/ChatProcessingCache.java new file mode 100644 index 000000000..f50d9f29d --- /dev/null +++ b/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/ChatProcessingCache.java @@ -0,0 +1,115 @@ +package com.earth2me.essentials.chat.processing; + +import com.earth2me.essentials.Trade; +import com.earth2me.essentials.User; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import net.ess3.api.IEssentials; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class ChatProcessingCache { + + private final Map intermediateChats = Collections.synchronizedMap(new HashMap<>()); + + private final Cache processedChats = CacheBuilder.newBuilder() + .expireAfterWrite(5, TimeUnit.MINUTES) + .build(); + + public IntermediateChat getIntermediateChat(Player player) { + return intermediateChats.get(player); + } + + public void setIntermediateChat(Player player, IntermediateChat intermediateChat) { + intermediateChats.put(player, intermediateChat); + } + + public IntermediateChat clearIntermediateChat(Player player) { + return intermediateChats.remove(player); + } + + public ProcessedChat getProcessedChat(Player player) { + return processedChats.getIfPresent(player); + } + + public void setProcessedChat(Player player, ProcessedChat chat) { + processedChats.put(player, chat); + } + + public static abstract class Chat { + private final User user; + private final String type; + private final String originalMessage; + + protected Chat(User user, String type, String originalMessage) { + this.user = user; + this.type = type; + this.originalMessage = originalMessage; + } + + User getUser() { + return user; + } + + String getType() { + return type; + } + + public String getOriginalMessage() { + return originalMessage; + } + + final String getLongType() { + return type.length() == 0 ? "chat" : "chat-" + type; + } + } + + public static class ProcessedChat extends Chat { + private final String message; + private final Trade charge; + + public ProcessedChat(final IEssentials ess, final IntermediateChat sourceChat) { + super(sourceChat.getUser(), sourceChat.getType(), sourceChat.getOriginalMessage()); + this.charge = new Trade(getLongType(), ess); + this.message = sourceChat.modifiedMessage; + } + + public String getMessage() { + return message; + } + + public Trade getCharge() { + return charge; + } + } + + public static class IntermediateChat extends Chat { + private String modifiedMessage; + private long radius; + + public IntermediateChat(final User user, final String type, final String originalMessage) { + super(user, type, originalMessage); + } + + long getRadius() { + return radius; + } + + void setRadius(final long radius) { + this.radius = radius; + } + + public String getModifiedMessage() { + return modifiedMessage; + } + + public void setModifiedMessage(String modifiedMessage) { + this.modifiedMessage = modifiedMessage; + } + } + +} diff --git a/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/LegacyChatHandler.java b/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/LegacyChatHandler.java new file mode 100644 index 000000000..8a2207e42 --- /dev/null +++ b/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/LegacyChatHandler.java @@ -0,0 +1,5 @@ +package com.earth2me.essentials.chat.processing; + +public class LegacyChatHandler { + // TODO +} diff --git a/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/SignedChatHandler.java b/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/SignedChatHandler.java new file mode 100644 index 000000000..9d1534834 --- /dev/null +++ b/EssentialsChat/src/main/java/com/earth2me/essentials/chat/processing/SignedChatHandler.java @@ -0,0 +1,50 @@ +package com.earth2me.essentials.chat.processing; + +import com.earth2me.essentials.Essentials; +import com.earth2me.essentials.chat.EssentialsChat; +import com.earth2me.essentials.utils.VersionUtil; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.AsyncPlayerChatPreviewEvent; +import org.bukkit.plugin.PluginManager; + +public class SignedChatHandler extends AbstractChatHandler { + + public SignedChatHandler(Essentials ess, EssentialsChat essChat) { + super(ess, essChat); + } + + boolean tryRegisterListeners() { + try { + final Class previewClass = Class.forName("org.bukkit.event.player.AsyncPlayerChatPreviewEvent"); + if (VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_19_1_R01) || !AsyncPlayerChatEvent.class.isAssignableFrom(previewClass)) { + return false; + } + } catch (ClassNotFoundException e) { + return false; + } + + final PluginManager pm = essChat.getServer().getPluginManager(); + pm.registerEvents(new Lowest(), essChat); + // TODO Normal + // TODO Highest (or Monitor?) + return true; + } + + private interface PreviewListener extends Listener { + void onPlayerChatPreview(AsyncPlayerChatPreviewEvent event); + } + + private class Lowest implements PreviewListener { + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerChatPreview(AsyncPlayerChatPreviewEvent event) { + // TODO + } + } + + // TODO preview on Normal, Highest + // TODO chat on ???? priority, how do we do this without exploding all over other plugins? + +}