Add support for local and global chats in Discord (#4684)

Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
This commit is contained in:
Sasha Sorokin 2023-05-01 03:30:11 +02:00 committed by GitHub
parent 84fd45bd09
commit e5b0c4c855
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 234 additions and 26 deletions

View File

@ -1,5 +1,6 @@
package net.essentialsx.api.v2.events.discord;
import net.essentialsx.api.v2.ChatType;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
@ -14,6 +15,7 @@ public class DiscordChatMessageEvent extends Event implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private final Player player;
private final ChatType chatType;
private String message;
private boolean cancelled = false;
@ -21,13 +23,14 @@ public class DiscordChatMessageEvent extends Event implements Cancellable {
* @param player The player which caused this event.
* @param message The message of this event.
*/
public DiscordChatMessageEvent(Player player, String message) {
public DiscordChatMessageEvent(Player player, String message, ChatType chatType) {
this.player = player;
this.message = message;
this.chatType = chatType;
}
/**
* The player which which caused this chat message.
* The player which caused this chat message.
* @return the player who caused the event.
*/
public Player getPlayer() {
@ -50,6 +53,14 @@ public class DiscordChatMessageEvent extends Event implements Cancellable {
this.message = message;
}
/**
* Type of chat of the original message.
* @return type of chat of the original message.
*/
public ChatType getChatType() {
return chatType;
}
@Override
public boolean isCancelled() {
return cancelled;

View File

@ -1,5 +1,6 @@
package net.essentialsx.api.v2.services.discord;
import net.essentialsx.api.v2.ChatType;
import net.essentialsx.api.v2.events.discord.DiscordChatMessageEvent;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
@ -20,7 +21,7 @@ public interface DiscordService {
void sendMessage(final MessageType type, final String message, final boolean allowGroupMentions);
/**
* Sends a chat messages to the {@link MessageType.DefaultTypes#CHAT default chat channel} with the same format
* Sends a chat message to the {@link MessageType.DefaultTypes#CHAT default chat channel} with the same format
* used for regular chat messages specified in the EssentialsX Discord configuration.
* <p>
* Note: Messages sent with this method will not fire a {@link DiscordChatMessageEvent}.
@ -29,6 +30,16 @@ public interface DiscordService {
*/
void sendChatMessage(final Player player, final String chatMessage);
/**
* Sends a chat message to the appropriate chat channel depending on the chat type with the format specified
* for that type in the EssentialsX Discord configuration.
* <p>
* Note: Messages sent with this method will not fire a {@link DiscordChatMessageEvent}.
* @param player The player who send the message.
* @param chatMessage The chat message the player has sent.
*/
void sendChatMessage(final ChatType chatType, final Player player, final String chatMessage);
/**
* Checks if a {@link MessageType} by the given key is already registered.
* @param key The {@link MessageType} key to check.

View File

@ -65,7 +65,10 @@ public final class MessageType {
public final static MessageType SERVER_STOP = new MessageType("server-stop", false);
public final static MessageType KICK = new MessageType("kick", false);
public final static MessageType MUTE = new MessageType("mute", false);
private final static MessageType[] VALUES = new MessageType[]{JOIN, FIRST_JOIN, LEAVE, CHAT, DEATH, AFK, ADVANCEMENT, ACTION, SERVER_START, SERVER_STOP, KICK, MUTE};
public final static MessageType LOCAL = new MessageType("local", true);
public final static MessageType QUESTION = new MessageType("question", true);
public final static MessageType SHOUT = new MessageType("shout", true);
private final static MessageType[] VALUES = new MessageType[]{JOIN, FIRST_JOIN, LEAVE, CHAT, DEATH, AFK, ADVANCEMENT, ACTION, SERVER_START, SERVER_STOP, KICK, MUTE, LOCAL, QUESTION, SHOUT};
/**
* Gets an array of all the default {@link MessageType MessageTypes}.

View File

@ -6,6 +6,7 @@ import com.earth2me.essentials.config.EssentialsConfiguration;
import com.earth2me.essentials.utils.FormatUtil;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Activity;
import net.essentialsx.api.v2.ChatType;
import org.apache.logging.log4j.Level;
import org.bukkit.entity.Player;
@ -107,6 +108,10 @@ public class DiscordSettings implements IConf {
return config.getBoolean("always-receive-primary", false);
}
public boolean isUseEssentialsEvents() {
return config.getBoolean("use-essentials-events", false);
}
public int getChatDiscordMaxLength() {
return config.getInt("chat.discord-max-length", 2000);
}
@ -215,17 +220,47 @@ public class DiscordSettings implements IConf {
}
public MessageFormat getMcToDiscordFormat(Player player) {
final String format = getFormatString("mc-to-discord");
return getMcToDiscordFormat(player, ChatType.UNKNOWN);
}
public MessageFormat getMcToDiscordFormat(Player player, ChatType chatType) {
final String format = getFormatString(getMcToDiscordFormatKey(chatType));
final String filled;
if (plugin.isPAPI() && format != null) {
filled = me.clip.placeholderapi.PlaceholderAPI.setPlaceholders(player, format);
} else {
filled = format;
}
return generateMessageFormat(filled, "{displayname}: {message}", false,
return generateMessageFormat(filled, getMcToDiscordDefaultFormat(chatType), false,
"username", "displayname", "message", "world", "prefix", "suffix");
}
private String getMcToDiscordFormatKey(ChatType chatType) {
switch (chatType) {
case LOCAL:
return "mc-to-discord-local";
case QUESTION:
return "mc-to-discord-question";
case SHOUT:
return "mc-to-discord-shout";
default:
return "mc-to-discord";
}
}
private String getMcToDiscordDefaultFormat(ChatType chatType) {
switch (chatType) {
case LOCAL:
return "**[Local]** {displayname}: {message}";
case QUESTION:
return "**[Question]** {displayname}: {message}";
case SHOUT:
return "**[Shout]** {displayname}: {message}";
default:
return "{displayname}: {message}";
}
}
public String getLegacyNameFormat() {
// For compatibility with old configs
if (isShowDisplayName()) {

View File

@ -24,6 +24,7 @@ public class EssentialsDiscord extends JavaPlugin implements IEssentialsModule {
private JDADiscordService jda;
private DiscordSettings settings;
private boolean isPAPI = false;
private boolean isEssentialsChat = false;
@Override
public void onEnable() {
@ -46,6 +47,8 @@ public class EssentialsDiscord extends JavaPlugin implements IEssentialsModule {
isPAPI = getServer().getPluginManager().getPlugin("PlaceholderAPI") != null;
isEssentialsChat = getServer().getPluginManager().getPlugin("EssentialsChat") != null;
settings = new DiscordSettings(this);
ess.addReloadListener(settings);
@ -79,6 +82,7 @@ public class EssentialsDiscord extends JavaPlugin implements IEssentialsModule {
public void onReload() {
if (jda != null && !jda.isInvalidStartup()) {
jda.updateListener();
jda.updatePresence();
jda.updatePrimaryChannel();
jda.updateConsoleRelay();
@ -102,6 +106,10 @@ public class EssentialsDiscord extends JavaPlugin implements IEssentialsModule {
return isPAPI;
}
public boolean isEssentialsChat() {
return isEssentialsChat;
}
@Override
public void onDisable() {
if (jda != null && !jda.isInvalidStartup()) {

View File

@ -23,6 +23,7 @@ import net.dv8tion.jda.api.utils.cache.CacheFlag;
import net.ess3.nms.refl.providers.AchievementListenerProvider;
import net.ess3.nms.refl.providers.AdvancementListenerProvider;
import net.ess3.provider.providers.PaperAdvancementListenerProvider;
import net.essentialsx.api.v2.ChatType;
import net.essentialsx.api.v2.events.discord.DiscordMessageEvent;
import net.essentialsx.api.v2.services.discord.DiscordService;
import net.essentialsx.api.v2.services.discord.InteractionController;
@ -40,12 +41,15 @@ import net.essentialsx.discord.interactions.commands.MessageCommand;
import net.essentialsx.discord.listeners.BukkitListener;
import net.essentialsx.discord.listeners.DiscordCommandDispatcher;
import net.essentialsx.discord.listeners.DiscordListener;
import net.essentialsx.discord.listeners.EssentialsChatListener;
import net.essentialsx.discord.listeners.BukkitChatListener;
import net.essentialsx.discord.util.ConsoleInjector;
import net.essentialsx.discord.util.DiscordUtil;
import net.essentialsx.discord.util.MessageUtil;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.ServicePriority;
import org.jetbrains.annotations.NotNull;
@ -83,6 +87,7 @@ public class JDADiscordService implements DiscordService, IEssentialsModule {
private ConsoleInjector injector;
private DiscordCommandDispatcher commandDispatcher;
private InteractionControllerImpl interactionController;
private Listener chatListener;
private boolean invalidStartup = false;
public JDADiscordService(EssentialsDiscord plugin) {
@ -214,6 +219,8 @@ public class JDADiscordService implements DiscordService, IEssentialsModule {
Bukkit.getPluginManager().registerEvents(new BukkitListener(this), plugin);
updateListener();
try {
if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_12_0_R01)) {
try {
@ -267,9 +274,14 @@ public class JDADiscordService implements DiscordService, IEssentialsModule {
@Override
public void sendChatMessage(final Player player, final String chatMessage) {
sendChatMessage(ChatType.UNKNOWN, player, chatMessage);
}
@Override
public void sendChatMessage(ChatType chatType, Player player, String chatMessage) {
final User user = getPlugin().getEss().getUser(player);
final String formattedMessage = MessageUtil.formatMessage(getSettings().getMcToDiscordFormat(player),
final String formattedMessage = MessageUtil.formatMessage(getSettings().getMcToDiscordFormat(player, chatType),
MessageUtil.sanitizeDiscordMarkdown(player.getName()),
MessageUtil.sanitizeDiscordMarkdown(player.getDisplayName()),
user.isAuthorized("essentials.discord.markdown") ? chatMessage : MessageUtil.sanitizeDiscordMarkdown(chatMessage),
@ -287,7 +299,20 @@ public class JDADiscordService implements DiscordService, IEssentialsModule {
FormatUtil.stripEssentialsFormat(getPlugin().getEss().getPermissionsHandler().getSuffix(player)),
guild.getMember(jda.getSelfUser()).getEffectiveName());
DiscordUtil.dispatchDiscordMessage(this, MessageType.DefaultTypes.CHAT, formattedMessage, user.isAuthorized("essentials.discord.ping"), avatarUrl, formattedName, player.getUniqueId());
DiscordUtil.dispatchDiscordMessage(this, chatTypeToMessageType(chatType), formattedMessage, user.isAuthorized("essentials.discord.ping"), avatarUrl, formattedName, player.getUniqueId());
}
private MessageType chatTypeToMessageType(ChatType chatType) {
switch (chatType) {
case SHOUT:
return MessageType.DefaultTypes.SHOUT;
case QUESTION:
return MessageType.DefaultTypes.QUESTION;
case LOCAL:
return MessageType.DefaultTypes.LOCAL;
default:
return MessageType.DefaultTypes.CHAT;
}
}
@Override
@ -318,6 +343,19 @@ public class JDADiscordService implements DiscordService, IEssentialsModule {
return message;
}
public void updateListener() {
if (chatListener != null) {
HandlerList.unregisterAll(chatListener);
chatListener = null;
}
chatListener = getSettings().isUseEssentialsEvents() && plugin.isEssentialsChat()
? new EssentialsChatListener(this)
: new BukkitChatListener(this);
Bukkit.getPluginManager().registerEvents(chatListener, plugin);
}
public void updatePresence() {
jda.getPresence().setPresence(plugin.getSettings().getStatus(), plugin.getSettings().getStatusActivity());
}

View File

@ -0,0 +1,34 @@
package net.essentialsx.discord.listeners;
import net.essentialsx.api.v2.ChatType;
import net.essentialsx.api.v2.events.discord.DiscordChatMessageEvent;
import net.essentialsx.discord.JDADiscordService;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
public class BukkitChatListener implements Listener {
private final JDADiscordService jda;
public BukkitChatListener(JDADiscordService jda) {
this.jda = jda;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onChat(AsyncPlayerChatEvent event) {
final Player player = event.getPlayer();
Bukkit.getScheduler().runTask(jda.getPlugin(), () -> {
final DiscordChatMessageEvent chatEvent = new DiscordChatMessageEvent(event.getPlayer(), event.getMessage(), ChatType.UNKNOWN);
chatEvent.setCancelled(!jda.getSettings().isShowAllChat() && !event.getRecipients().containsAll(Bukkit.getOnlinePlayers()));
Bukkit.getPluginManager().callEvent(chatEvent);
if (chatEvent.isCancelled()) {
return;
}
jda.sendChatMessage(player, chatEvent.getMessage());
});
}
}

View File

@ -11,13 +11,11 @@ import net.ess3.api.events.VanishStatusChangeEvent;
import net.ess3.provider.AbstractAchievementEvent;
import net.essentialsx.api.v2.events.AsyncUserDataLoadEvent;
import net.essentialsx.api.v2.events.UserActionEvent;
import net.essentialsx.api.v2.events.discord.DiscordChatMessageEvent;
import net.essentialsx.api.v2.events.discord.DiscordMessageEvent;
import net.essentialsx.api.v2.services.discord.MessageType;
import net.essentialsx.discord.JDADiscordService;
import net.essentialsx.discord.util.DiscordUtil;
import net.essentialsx.discord.util.MessageUtil;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameRule;
import org.bukkit.entity.Player;
@ -25,7 +23,6 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerKickEvent;
import org.bukkit.event.player.PlayerQuitEvent;
@ -81,21 +78,6 @@ public class BukkitListener implements Listener {
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onChat(AsyncPlayerChatEvent event) {
final Player player = event.getPlayer();
Bukkit.getScheduler().runTask(jda.getPlugin(), () -> {
final DiscordChatMessageEvent chatEvent = new DiscordChatMessageEvent(event.getPlayer(), event.getMessage());
chatEvent.setCancelled(!jda.getSettings().isShowAllChat() && !event.getRecipients().containsAll(Bukkit.getOnlinePlayers()));
Bukkit.getPluginManager().callEvent(chatEvent);
if (chatEvent.isCancelled()) {
return;
}
jda.sendChatMessage(player, chatEvent.getMessage());
});
}
@EventHandler(priority = EventPriority.MONITOR)
public void onJoin(AsyncUserDataLoadEvent event) {
// Delay join to let nickname load

View File

@ -0,0 +1,43 @@
package net.essentialsx.discord.listeners;
import net.essentialsx.api.v2.events.chat.ChatEvent;
import net.essentialsx.api.v2.events.chat.GlobalChatEvent;
import net.essentialsx.api.v2.events.chat.LocalChatEvent;
import net.essentialsx.api.v2.events.discord.DiscordChatMessageEvent;
import net.essentialsx.discord.JDADiscordService;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
public class EssentialsChatListener implements Listener {
private final JDADiscordService jda;
public EssentialsChatListener(JDADiscordService jda) {
this.jda = jda;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onLocalChat(LocalChatEvent event) {
processChatEvent(event);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onGlobalChat(GlobalChatEvent event) {
processChatEvent(event);
}
private void processChatEvent(ChatEvent event) {
final Player player = event.getPlayer();
Bukkit.getScheduler().runTask(jda.getPlugin(), () -> {
final DiscordChatMessageEvent chatEvent = new DiscordChatMessageEvent(event.getPlayer(), event.getMessage(), event.getChatType());
Bukkit.getPluginManager().callEvent(chatEvent);
if (!chatEvent.isCancelled()) {
jda.sendChatMessage(event.getChatType(), player, chatEvent.getMessage());
}
});
}
}

View File

@ -46,6 +46,10 @@ channels:
# players the essentials.discord.receive.primary permission.
always-receive-primary: false
# Whether to use Essentials Chat events instead of normal chat event.
# This allows you to filter chat by its type (local, question, shout).
use-essentials-events: false
# Chat relay settings
# General settings for chat relays between Minecraft and Discord.
# To configure the channel Minecraft chat is sent to, see the "message-types" section of the config.
@ -138,6 +142,15 @@ message-types:
kick: staff
# Message sent when a player's mute state is changed on the Minecraft server.
mute: staff
# Message sent when a player talks in local chat.
# use-essentials-events must be set to "true" for this to work.
local: none
# Message sent when a player asks a question in global chat.
# use-essentials-events must be set to "true" for this to work.
question: primary
# Message sent when a player talks in global chat.
# use-essentials-events must be set to "true" for this to work.
shout: primary
# Whether or not player messages should show their avatar as the profile picture in Discord.
# The bot will require the "Manage Webhooks" permission in the defined channels in order to use this feature.
@ -268,6 +281,36 @@ messages:
# - {botname}: Name of the Discord bot
# ... PlaceholderAPI placeholders are also supported here too!
mc-to-discord-name-format: "{botname}"
# This is the message that is used to relay minecraft local chat in Discord.
# The following placeholders can be used here:
# - {username}: The username of the player sending the message
# - {displayname}: The display name of the player sending the message (This would be their nickname)
# - {message}: The content of the message being sent
# - {world}: The name of the world the player sending the message is in
# - {prefix}: The prefix of the player sending the message
# - {suffix}: The suffix of the player sending the message
# ... PlaceholderAPI placeholders are also supported here too!
mc-to-discord-local: "**[Local]** {displayname}: {message}"
# This is the message that is used to relay questions from minecraft chat in Discord.
# The following placeholders can be used here:
# - {username}: The username of the player sending the message
# - {displayname}: The display name of the player sending the message (This would be their nickname)
# - {message}: The content of the message being sent
# - {world}: The name of the world the player sending the message is in
# - {prefix}: The prefix of the player sending the message
# - {suffix}: The suffix of the player sending the message
# ... PlaceholderAPI placeholders are also supported here too!
mc-to-discord-question: "**[Question]** {displayname}: {message}"
# This is the message that is used to relay minecraft global chat in Discord.
# The following placeholders can be used here:
# - {username}: The username of the player sending the message
# - {displayname}: The display name of the player sending the message (This would be their nickname)
# - {message}: The content of the message being sent
# - {world}: The name of the world the player sending the message is in
# - {prefix}: The prefix of the player sending the message
# - {suffix}: The suffix of the player sending the message
# ... PlaceholderAPI placeholders are also supported here too!
mc-to-discord-shout: "**[Shout]** {displayname}: {message}"
# This is the message sent to Discord when a player is temporarily muted in minecraft.
# The following placeholders can be used here:
# - {username}: The username of the player being muted