From e5b0c4c8557bdfe74fdb5e49ddf9a5dd824ce7e6 Mon Sep 17 00:00:00 2001 From: Sasha Sorokin <10401817+brawaru@users.noreply.github.com> Date: Mon, 1 May 2023 03:30:11 +0200 Subject: [PATCH] Add support for local and global chats in Discord (#4684) Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com> --- .../discord/DiscordChatMessageEvent.java | 15 ++++++- .../v2/services/discord/DiscordService.java | 13 +++++- .../api/v2/services/discord/MessageType.java | 5 ++- .../essentialsx/discord/DiscordSettings.java | 39 ++++++++++++++++- .../discord/EssentialsDiscord.java | 8 ++++ .../discord/JDADiscordService.java | 42 +++++++++++++++++- .../discord/listeners/BukkitChatListener.java | 34 +++++++++++++++ .../discord/listeners/BukkitListener.java | 18 -------- .../listeners/EssentialsChatListener.java | 43 +++++++++++++++++++ .../src/main/resources/config.yml | 43 +++++++++++++++++++ 10 files changed, 234 insertions(+), 26 deletions(-) create mode 100644 EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitChatListener.java create mode 100644 EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/EssentialsChatListener.java diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/events/discord/DiscordChatMessageEvent.java b/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/events/discord/DiscordChatMessageEvent.java index a0a86ace0..fd0c56aa1 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/events/discord/DiscordChatMessageEvent.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/events/discord/DiscordChatMessageEvent.java @@ -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; diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/services/discord/DiscordService.java b/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/services/discord/DiscordService.java index e6a7e5d49..21e55a0fd 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/services/discord/DiscordService.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/services/discord/DiscordService.java @@ -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. *

* 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. + *

+ * 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. diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/services/discord/MessageType.java b/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/services/discord/MessageType.java index 329f40bce..567bfbde5 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/services/discord/MessageType.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/services/discord/MessageType.java @@ -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}. diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/DiscordSettings.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/DiscordSettings.java index e95e3ffcd..d70302199 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/DiscordSettings.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/DiscordSettings.java @@ -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()) { diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java index ca288a580..e45e419ff 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java @@ -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()) { diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java index d45cd2617..71e35460e 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java @@ -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()); } diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitChatListener.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitChatListener.java new file mode 100644 index 000000000..2ae1c91e0 --- /dev/null +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitChatListener.java @@ -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()); + }); + } +} diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitListener.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitListener.java index e8a7e67ab..f869959f9 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitListener.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/BukkitListener.java @@ -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 diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/EssentialsChatListener.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/EssentialsChatListener.java new file mode 100644 index 000000000..debd6783d --- /dev/null +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/EssentialsChatListener.java @@ -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()); + } + }); + } +} diff --git a/EssentialsDiscord/src/main/resources/config.yml b/EssentialsDiscord/src/main/resources/config.yml index 145df92ff..553acc7c8 100644 --- a/EssentialsDiscord/src/main/resources/config.yml +++ b/EssentialsDiscord/src/main/resources/config.yml @@ -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