From 2ed3af1d87993802d3b911f574b58f4534260579 Mon Sep 17 00:00:00 2001 From: Taah Date: Tue, 21 Jul 2026 16:50:05 -0700 Subject: [PATCH] Expose staff chat through the public API --- .../plex/api/event/StaffChatMessageEvent.java | 129 ++++++++++++++++++ .../dev/plex/api/player/PlexPlayerView.java | 14 ++ .../plex/api/impl/DefaultPlexPlayerView.java | 1 + .../dev/plex/command/impl/AdminChatCMD.java | 18 ++- .../dev/plex/listener/impl/ChatListener.java | 16 ++- 5 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 api/src/main/java/dev/plex/api/event/StaffChatMessageEvent.java diff --git a/api/src/main/java/dev/plex/api/event/StaffChatMessageEvent.java b/api/src/main/java/dev/plex/api/event/StaffChatMessageEvent.java new file mode 100644 index 0000000..645f6af --- /dev/null +++ b/api/src/main/java/dev/plex/api/event/StaffChatMessageEvent.java @@ -0,0 +1,129 @@ +package dev.plex.api.event; + +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Fired before Plex delivers a locally-originated staff-chat message. + * + *

The event covers both a player's toggled staff-chat input and the direct + * {@code /adminchat } command form. It is not fired when a staff-chat + * message is received from another server through Plex's Redis transport.

+ * + *

This event can be asynchronous. Listeners must check + * {@link #isAsynchronous()} before accessing APIs which require a server or + * region thread.

+ */ +public final class StaffChatMessageEvent extends Event implements Cancellable +{ + private static final HandlerList HANDLERS = new HandlerList(); + + private final CommandSender sender; + private final Source source; + private Component message; + private boolean cancelled; + + /** + * Creates a staff-chat message event. + * + * @param sender sender of the message + * @param message message to be delivered + * @param source input path which produced the message + * @param async whether the event is asynchronous + */ + public StaffChatMessageEvent( + @NotNull CommandSender sender, + @NotNull Component message, + @NotNull Source source, + boolean async) + { + super(async); + this.sender = sender; + this.message = message; + this.source = source; + } + + /** + * Returns the player or console which sent the message. + * + * @return message sender + */ + public @NotNull CommandSender getSender() + { + return sender; + } + + /** + * Returns the message which Plex will deliver. + * + * @return staff-chat message + */ + public @NotNull Component getMessage() + { + return message; + } + + /** + * Replaces the message Plex will deliver. + * + * @param message replacement message + */ + public void setMessage(@NotNull Component message) + { + this.message = message; + } + + /** + * Returns how the message entered staff chat. + * + * @return message source + */ + public @NotNull Source getSource() + { + return source; + } + + @Override + public boolean isCancelled() + { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) + { + this.cancelled = cancelled; + } + + @Override + public @NotNull HandlerList getHandlers() + { + return HANDLERS; + } + + /** + * Returns this event's handler list. + * + * @return handler list + */ + public static @NotNull HandlerList getHandlerList() + { + return HANDLERS; + } + + /** + * Identifies the Plex input path which produced a staff-chat message. + */ + public enum Source + { + /** A player spoke while staff-chat mode was enabled. */ + TOGGLED_CHAT, + + /** A sender used {@code /adminchat } or one of its aliases. */ + COMMAND + } +} diff --git a/api/src/main/java/dev/plex/api/player/PlexPlayerView.java b/api/src/main/java/dev/plex/api/player/PlexPlayerView.java index c2e12bc..4d48ece 100644 --- a/api/src/main/java/dev/plex/api/player/PlexPlayerView.java +++ b/api/src/main/java/dev/plex/api/player/PlexPlayerView.java @@ -59,6 +59,20 @@ public interface PlexPlayerView */ boolean lockedUp(); + /** + * Returns whether the player currently has staff-chat mode enabled. + * + *

When enabled, the player's normal chat input is routed to Plex staff + * chat and exposed through + * {@link dev.plex.api.event.StaffChatMessageEvent}.

+ * + * @return whether staff-chat mode is enabled + */ + default boolean staffChat() + { + return false; + } + /** * Returns the Bukkit player instance. * diff --git a/server/src/main/java/dev/plex/api/impl/DefaultPlexPlayerView.java b/server/src/main/java/dev/plex/api/impl/DefaultPlexPlayerView.java index 2afeb8f..4d9cccd 100644 --- a/server/src/main/java/dev/plex/api/impl/DefaultPlexPlayerView.java +++ b/server/src/main/java/dev/plex/api/impl/DefaultPlexPlayerView.java @@ -17,5 +17,6 @@ record DefaultPlexPlayerView(PlexPlayer player, PlayerNameResolver playerNameRes @Override public boolean frozen() { return player.isFrozen(); } @Override public boolean muted() { return player.isMuted(); } @Override public boolean lockedUp() { return player.isLockedUp(); } + @Override public boolean staffChat() { return player.isStaffChat(); } @Override public Player bukkitPlayer() { return player.getPlayer(); } } diff --git a/server/src/main/java/dev/plex/command/impl/AdminChatCMD.java b/server/src/main/java/dev/plex/command/impl/AdminChatCMD.java index 0cf560d..5284bb4 100644 --- a/server/src/main/java/dev/plex/command/impl/AdminChatCMD.java +++ b/server/src/main/java/dev/plex/command/impl/AdminChatCMD.java @@ -1,6 +1,7 @@ package dev.plex.command.impl; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import dev.plex.api.event.StaffChatMessageEvent; import dev.plex.command.ServerCommand; import dev.plex.command.ServerCommandContext; import dev.plex.hook.VaultHook; @@ -15,6 +16,7 @@ import io.papermc.paper.command.brigadier.CommandSourceStack; import net.kyori.adventure.text.Component; import org.apache.commons.lang3.StringUtils; +import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -68,8 +70,20 @@ protected Component execute(@NotNull ServerCommandContext context) } PlexLog.debug("admin chat prefix: {0}", prefix); String message = StringUtils.join(args, " "); - plugin.getServer().getConsoleSender().sendMessage(context.messageComponent("adminChatFormat", sender.getName(), prefix, message)); - MessageUtil.sendStaffChat(plugin, sender, SafeMiniMessage.mmDeserialize(message), PlexUtils.adminChat(sender.getName(), prefix, message).toArray(UUID[]::new)); + StaffChatMessageEvent staffChatEvent = new StaffChatMessageEvent( + sender, + SafeMiniMessage.mmDeserialize(message), + StaffChatMessageEvent.Source.COMMAND, + !Bukkit.isPrimaryThread()); + plugin.getServer().getPluginManager().callEvent(staffChatEvent); + if (staffChatEvent.isCancelled()) + { + return null; + } + Component eventMessage = staffChatEvent.getMessage(); + String serializedMessage = SafeMiniMessage.mmSerialize(eventMessage); + plugin.getServer().getConsoleSender().sendMessage(context.messageComponent("adminChatFormat", sender.getName(), prefix, serializedMessage)); + MessageUtil.sendStaffChat(plugin, sender, eventMessage, PlexUtils.adminChat(sender.getName(), prefix, serializedMessage).toArray(UUID[]::new)); return null; } diff --git a/server/src/main/java/dev/plex/listener/impl/ChatListener.java b/server/src/main/java/dev/plex/listener/impl/ChatListener.java index aadad7f..809d751 100644 --- a/server/src/main/java/dev/plex/listener/impl/ChatListener.java +++ b/server/src/main/java/dev/plex/listener/impl/ChatListener.java @@ -1,6 +1,7 @@ package dev.plex.listener.impl; import dev.plex.Plex; +import dev.plex.api.event.StaffChatMessageEvent; import dev.plex.hook.VaultHook; import dev.plex.listener.ServerListenerBase; import dev.plex.meta.PlayerMeta; @@ -50,9 +51,20 @@ public void onChat(AsyncChatEvent event) if (plexPlayer.isStaffChat()) { String prefix = PlexUtils.mmSerialize(VaultHook.getPrefix(event.getPlayer())); // Don't use PlexPlayer#getPrefix because that returns their custom set prefix and not their group's - MessageUtil.sendStaffChat(plugin, event.getPlayer(), event.message(), PlexUtils.adminChat(event.getPlayer().getName(), prefix, SafeMiniMessage.mmSerialize(event.message())).toArray(UUID[]::new)); - plugin.getServer().getConsoleSender().sendMessage(PlexUtils.messageComponent("adminChatFormat", event.getPlayer().getName(), prefix, SafeMiniMessage.mmSerialize(event.message().replaceText(URL_REPLACEMENT_CONFIG)))); + StaffChatMessageEvent staffChatEvent = new StaffChatMessageEvent( + event.getPlayer(), + event.message(), + StaffChatMessageEvent.Source.TOGGLED_CHAT, + event.isAsynchronous()); + plugin.getServer().getPluginManager().callEvent(staffChatEvent); event.setCancelled(true); + if (staffChatEvent.isCancelled()) + { + return; + } + Component message = staffChatEvent.getMessage(); + MessageUtil.sendStaffChat(plugin, event.getPlayer(), message, PlexUtils.adminChat(event.getPlayer().getName(), prefix, SafeMiniMessage.mmSerialize(message)).toArray(UUID[]::new)); + plugin.getServer().getConsoleSender().sendMessage(PlexUtils.messageComponent("adminChatFormat", event.getPlayer().getName(), prefix, SafeMiniMessage.mmSerialize(message.replaceText(URL_REPLACEMENT_CONFIG)))); return; } Component prefix = PlayerMeta.getPrefix(plexPlayer);