From 5e602349ca2c7501b39fae3b41e58badd731c5fc Mon Sep 17 00:00:00 2001 From: Jonathan Hiles <7577851+axieum@users.noreply.github.com> Date: Fri, 11 Nov 2022 23:49:57 +1000 Subject: [PATCH 1/7] feat: add `Patbox/TextPlaceholderAPI` and replace string templates [wip] --- build.gradle | 2 + minecord-api/build.gradle | 3 +- minecord-api/gradle.properties | 1 + .../minecord/api/util/PlaceholdersExt.java | 204 ++++++++++++++++++ .../src/main/resources/fabric.mod.json | 3 +- minecord-chat/build.gradle | 3 - .../discord/MessageReactionListener.java | 85 +++++--- .../discord/MessageReceivedListener.java | 145 +++++++------ .../discord/MessageUpdateListener.java | 64 +++--- .../minecraft/EntityDeathCallback.java | 50 +++-- .../minecraft/PlayerAdvancementCallback.java | 44 ++-- .../minecraft/PlayerChangeWorldCallback.java | 62 +++--- .../minecraft/PlayerConnectionCallback.java | 90 ++++---- .../minecraft/PlayerDeathCallback.java | 67 +++--- .../minecraft/ServerLifecycleCallback.java | 105 +++++---- .../minecraft/ServerMessageCallback.java | 134 ++++++------ .../src/main/resources/fabric.mod.json | 3 +- .../cmds/callback/DiscordCommandListener.java | 34 ++- .../cmds/command/discord/CustomCommand.java | 37 ++-- .../cmds/command/discord/UptimeCommand.java | 23 +- .../src/main/resources/fabric.mod.json | 3 +- .../presence/category/PresenceSupplier.java | 17 +- .../event/PresencePlaceholderCallback.java | 41 ---- .../impl/presence/PresenceUpdateTask.java | 76 +++---- .../impl/presence/config/PresenceConfig.java | 10 +- .../src/main/resources/fabric.mod.json | 3 +- 26 files changed, 799 insertions(+), 510 deletions(-) create mode 100644 minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java delete mode 100644 minecord-presence/src/main/java/me/axieum/mcmod/minecord/api/presence/event/PresencePlaceholderCallback.java diff --git a/build.gradle b/build.gradle index 67126b2..42a2de2 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ allprojects { modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" // Mods + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" // Code Quality compileOnly "org.jetbrains:annotations:${project.jetbrains_annotations_version}" @@ -95,6 +96,7 @@ allprojects { mavenCentral() maven { name 'dv8tion'; url 'https://m2.dv8tion.net/releases' } maven { name 'Fabric'; url 'https://maven.fabricmc.net/' } + maven { name 'Nucleoid'; url 'https://maven.nucleoid.xyz/' } maven { name 'Shedaniel'; url 'https://maven.shedaniel.me/' } } diff --git a/minecord-api/build.gradle b/minecord-api/build.gradle index 5ff1bf5..7ae6929 100644 --- a/minecord-api/build.gradle +++ b/minecord-api/build.gradle @@ -1,10 +1,9 @@ // Declare dependencies dependencies { - api include(fabricApi.module("fabric-api-base", project.fabric_version)) - modApi include(fabricApi.module("fabric-lifecycle-events-v1", project.fabric_version)) modApi include("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}") { exclude module: 'modmenu' } + modApi include("eu.pb4:placeholder-api:${project.placeholder_api_version}") implementation shade("com.vdurmont:emoji-java:${project.emoji_java_version}") implementation shade("net.dv8tion:JDA:${project.jda_version}") { exclude module: 'opus-java' // exclude audio diff --git a/minecord-api/gradle.properties b/minecord-api/gradle.properties index adc7372..9fe8578 100644 --- a/minecord-api/gradle.properties +++ b/minecord-api/gradle.properties @@ -11,3 +11,4 @@ cloth_config_version = 8.2.88 emoji_java_version = 5.1.1 jda_version = 5.0.0-alpha.21 log4j_version = 2.19.0 +placeholder_api_version = 2.0.0-beta.7+1.19 diff --git a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java new file mode 100644 index 0000000..7deb56d --- /dev/null +++ b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java @@ -0,0 +1,204 @@ +package me.axieum.mcmod.minecord.api.util; + +import java.time.Duration; +import java.util.Map; +import java.util.function.Supplier; +import java.util.regex.Pattern; + +import eu.pb4.placeholders.api.ParserContext; +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; +import eu.pb4.placeholders.api.PlaceholderResult; +import eu.pb4.placeholders.api.Placeholders; +import eu.pb4.placeholders.api.Placeholders.PlaceholderGetter; +import eu.pb4.placeholders.api.TextParserUtils; +import eu.pb4.placeholders.api.node.EmptyNode; +import eu.pb4.placeholders.api.node.TextNode; +import eu.pb4.placeholders.api.node.parent.ParentNode; +import eu.pb4.placeholders.api.parsers.TextParserV1; +import org.apache.commons.lang3.time.DurationFormatUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.text.Text; + +import me.axieum.mcmod.minecord.api.Minecord; + +/** + * Extension methods for interfacing with the {@link eu.pb4.placeholders.api.Placeholders Placeholder API}. + */ +public final class PlaceholdersExt +{ + /** The placeholder pattern used. */ + public static final Pattern PLACEHOLDER_PATTERN = Placeholders.PREDEFINED_PLACEHOLDER_PATTERN; + + private PlaceholdersExt() {} + + /* + * Placeholder contexts. + */ + + /** + * Returns the Minecraft server captured by Minecord placeholder context if it exists. + * + * @return Minecraft server placeholder context if present + */ + public static @Nullable PlaceholderContext getMinecordServerContext() + { + return Minecord.getInstance().getMinecraft().map(PlaceholderContext::of).orElse(null); + } + + /* + * Text-output placeholder parsers. + */ + + public static Text parseText(@NotNull String str, @NotNull Map placeholders) + { + return parseText(str, null, placeholders); + } + + public static Text parseText( + @NotNull String str, @Nullable PlaceholderContext context, @NotNull Map placeholders + ) + { + return parseText(str, context, placeholders::get); + } + + public static Text parseText( + @NotNull String str, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter + ) + { + return parseText(parseNode(str), context, placeholderGetter); + } + + public static Text parseText(@NotNull Text text, @NotNull Map placeholders) + { + return parseText(text, null, placeholders); + } + + public static Text parseText( + @NotNull Text text, @Nullable PlaceholderContext context, @NotNull Map placeholders + ) + { + return parseText(text, context, placeholders::get); + } + + public static Text parseText( + @NotNull Text text, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter + ) + { + return parseText(parseNode(text), context, placeholderGetter); + } + + public static Text parseText( + @NotNull TextNode node, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter + ) + { + return context != null + ? Placeholders.parseText(node, context, PLACEHOLDER_PATTERN, placeholderGetter) + : Placeholders.parseNodes(node, PLACEHOLDER_PATTERN, placeholderGetter).toText(ParserContext.of(), true); + } + + public static @NotNull TextNode parseNode(@Nullable Text text) + { + return text != null ? new ParentNode( + TextParserV1.DEFAULT.parseNodes(TextNode.convert(text)) + ) : EmptyNode.INSTANCE; + } + + /* + * String-output placeholder parsers. + */ + + public static String parseString(@NotNull Text text, @NotNull Map placeholders) + { + return parseString(text, null, placeholders); + } + + public static String parseString( + @NotNull Text text, @Nullable PlaceholderContext context, @NotNull Map placeholders + ) + { + return parseString(text, context, placeholders::get); + } + + public static String parseString( + @NotNull Text text, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter + ) + { + return parseString(parseNode(text), context, placeholderGetter); + } + + public static String parseString(@NotNull String str, @NotNull Map placeholders) + { + return parseString(str, null, placeholders); + } + + public static String parseString( + @NotNull String str, @Nullable PlaceholderContext context, @NotNull Map placeholders + ) + { + return parseString(str, context, placeholders::get); + } + + public static String parseString( + @NotNull String str, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter + ) + { + return parseString(parseNode(str), context, placeholderGetter); + } + + public static String parseString( + @NotNull TextNode node, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter + ) + { + return parseText(node, context, placeholderGetter).getString(); + } + + public static @NotNull TextNode parseNode(@Nullable String string) + { + return string != null && !string.isEmpty() ? Placeholders.parseNodes( + TextParserUtils.formatNodes(string) + ) : EmptyNode.INSTANCE; + } + + /* + * Placeholder handlers. + */ + + /** + * Returns a {@link String} placeholder handler. + * + * @param string string + * @return string placeholder handler + */ + public static PlaceholderHandler string(final String string) + { + return (ctx, arg) -> PlaceholderResult.value(string); + } + + /** + * Returns a {@link Duration} placeholder handler. + * + * @param duration duration instance + * @return duration placeholder handler + */ + public static PlaceholderHandler duration(final Duration duration) + { + return (ctx, arg) -> PlaceholderResult.value( + arg != null ? DurationFormatUtils.formatDuration(duration.toMillis(), arg) + : DurationFormatUtils.formatDurationWords(duration.toMillis(), true, true) + ); + } + + /** + * Returns a lazy {@link Duration} placeholder handler. + * + * @param duration duration supplier + * @return lazy duration placeholder handler + */ + public static PlaceholderHandler duration(final Supplier duration) + { + return (ctx, arg) -> duration(duration.get()).onPlaceholderRequest(ctx, arg); + } +} diff --git a/minecord-api/src/main/resources/fabric.mod.json b/minecord-api/src/main/resources/fabric.mod.json index ec6ec60..e3736e9 100644 --- a/minecord-api/src/main/resources/fabric.mod.json +++ b/minecord-api/src/main/resources/fabric.mod.json @@ -37,6 +37,7 @@ "java": ">=17", "minecraft": "~1.19.1", "fabricloader": ">=0.11.3", - "fabric-lifecycle-events-v1": "*" + "fabric-lifecycle-events-v1": "*", + "placeholder-api": "*" } } diff --git a/minecord-chat/build.gradle b/minecord-chat/build.gradle index e90f638..e6a9da9 100644 --- a/minecord-chat/build.gradle +++ b/minecord-chat/build.gradle @@ -3,7 +3,4 @@ dependencies { implementation project(path: ':minecord-api', configuration: 'namedElements') implementation project(path: ':minecord-api', configuration: 'shadow') implementation shade("io.github.java-diff-utils:java-diff-utils:${project.java_diff_utils_version}") - modApi include(fabricApi.module("fabric-entity-events-v1", project.fabric_version)) - modApi include(fabricApi.module("fabric-message-api-v1", project.fabric_version)) - modApi include(fabricApi.module("fabric-networking-api-v1", project.fabric_version)) } diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java index 8ead0d6..35971ec 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java @@ -1,12 +1,17 @@ package me.axieum.mcmod.minecord.impl.chat.callback.discord; +import java.util.Map; + +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; import net.dv8tion.jda.api.events.message.react.GenericMessageReactionEvent; import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.Nullable; -import me.axieum.mcmod.minecord.api.chat.event.ChatPlaceholderEvents; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.impl.chat.util.MinecraftDispatcher; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.LOGGER; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.getConfig; @@ -32,43 +37,61 @@ public void onGenericMessageReaction(GenericMessageReactionEvent event) final String emote = ":" + event.getEmoji().getName() + ":"; /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The issuer's tag (i.e. username#discriminator), e.g. Axieum#1001 - st.add("issuer_tag", event.getUser().getAsTag()); - // The issuer's username, e.g. Axieum - st.add("issuer_username", event.getUser().getName()); - // The issuer's username discriminator, e.g. 1001 - st.add("issuer_discriminator", event.getUser().getDiscriminator()); - // The issuer's nickname or username - st.add("issuer", event.getMember() != null ? event.getMember().getEffectiveName() - : event.getUser().getName()); - // The author's tag (i.e. username#discriminator), e.g. Axieum#1001 - st.add("author_tag", event.getUser().getAsTag()); - // The author's username, e.g. Axieum - st.add("author_username", event.getUser().getName()); - // The author's username discriminator, e.g. 1001 - st.add("author_discriminator", event.getUser().getDiscriminator()); - // The author's nickname or username - st.add("author", event.getMember() != null ? event.getMember().getEffectiveName() - : event.getUser().getName()); - // The emote used to react - st.add("emote", emote); - - ChatPlaceholderEvents.Discord.REACTION.invoker().onReactionPlaceholder(st, event); + final @Nullable PlaceholderContext ctx = PlaceholdersExt.getMinecordServerContext(); + final Map placeholders = Map.of( + // The issuer's tag (i.e. username#discriminator), e.g. Axieum#1001 + "issuer_tag", string(event.getUser().getAsTag()), + // The issuer's username, e.g. Axieum + "issuer_username", string(event.getUser().getName()), + // The issuer's username discriminator, e.g. 1001 + "issuer_discriminator", string(event.getUser().getDiscriminator()), + // The issuer's nickname or username + "issuer", string( + event.getMember() != null ? event.getMember().getEffectiveName() : event.getUser().getName() + ), + // The author's tag (i.e. username#discriminator), e.g. Axieum#1001 + "author_tag", string(event.getUser().getAsTag()), + // The author's username, e.g. Axieum + "author_username", string(event.getUser().getName()), + // The author's username discriminator, e.g. 1001 + "author_discriminator", string(event.getUser().getDiscriminator()), + // The author's nickname or username + "author", string( + event.getMember() != null ? event.getMember().getEffectiveName() : event.getUser().getName() + ), + // The emote used to react + "emote", string(emote) + ); /* * Dispatch the message. */ - MinecraftDispatcher.json(entry -> st.format(isAdded ? entry.minecraft.react : entry.minecraft.unreact), - entry -> (isAdded ? entry.minecraft.react : entry.minecraft.unreact) != null && entry.id == channelId); + // A user reacted to a recent message + if (isAdded) { + MinecraftDispatcher.dispatch( + entry -> PlaceholdersExt.parseText(entry.minecraft.react, ctx, placeholders), + entry -> entry.minecraft.react != null && entry.id == channelId + ); + LOGGER.info(PlaceholdersExt.parseString( + "@${issuer_tag} reacted with ${emote} to ${author_tag}'s message", ctx, placeholders::get + )); - LOGGER.info(st.format(isAdded ? "@${issuer_tag} reacted with ${emote} to ${author_tag}'s message" - : "@${issuer_tag} removed their reaction of ${emote} from ${author_tag}'s message")); + // A user removed their reaction from a recent message + } else { + MinecraftDispatcher.dispatch( + entry -> PlaceholdersExt.parseText(entry.minecraft.unreact, ctx, placeholders), + entry -> entry.minecraft.unreact != null && entry.id == channelId + ); + LOGGER.info(PlaceholdersExt.parseString( + "@${issuer_tag} removed their reaction of ${emote} from ${author_tag}'s message", + ctx, + placeholders::get + )); + } }); } } diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java index e68aa70..23e45a5 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java @@ -1,14 +1,19 @@ package me.axieum.mcmod.minecord.impl.chat.callback.discord; +import java.util.HashMap; +import java.util.Map; + +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import org.jetbrains.annotations.Nullable; -import me.axieum.mcmod.minecord.api.chat.event.ChatPlaceholderEvents; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.MinecraftDispatcher; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.LOGGER; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.getConfig; @@ -44,40 +49,47 @@ public void onText(MessageReceivedEvent event) final @Nullable Message replyMessage = event.getMessage().getReferencedMessage(); /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The author's tag (i.e. username#discriminator), e.g. Axieum#1001 - st.add("tag", event.getAuthor().getAsTag()); - // The author's username, e.g. Axieum - st.add("username", event.getAuthor().getName()); - // The author's username discriminator, e.g. 1001 - st.add("discriminator", event.getAuthor().getDiscriminator()); - // The author's nickname or username - st.add("author", event.getMember() != null ? event.getMember().getEffectiveName() - : event.getAuthor().getName()); - // The formatted message contents - st.add("message", StringUtils.discordToMinecraft(event.getMessage().getContentDisplay())); - // The raw message contents - st.add("raw", event.getMessage().getContentRaw()); + final @Nullable PlaceholderContext ctx = PlaceholdersExt.getMinecordServerContext(); + final Map placeholders = new HashMap<>(Map.of( + // The author's tag (i.e. username#discriminator), e.g. Axieum#1001 + "tag", string(event.getAuthor().getAsTag()), + // The author's username, e.g. Axieum + "username", string(event.getAuthor().getName()), + // The author's username discriminator, e.g. 1001 + "discriminator", string(event.getAuthor().getDiscriminator()), + // The author's nickname or username + "author", string( + event.getMember() != null ? event.getMember().getEffectiveName() : event.getAuthor().getName() + ), + // The formatted message contents + "message", string(StringUtils.discordToMinecraft(event.getMessage().getContentDisplay())), + // The raw message contents + "raw", string(event.getMessage().getContentRaw()) + )); // The message is in reply to another if (replyMessage != null) { - // The replied message author's tag (i.e. username#discriminator), e.g. Axieum#1001 - st.add("reply_tag", replyMessage.getAuthor().getAsTag()); - // The replied message author's username, e.g. Axieum - st.add("reply_username", replyMessage.getAuthor().getName()); - // The replied message author's username discriminator, e.g. 1001 - st.add("reply_discriminator", replyMessage.getAuthor().getDiscriminator()); - // The replied message author's nickname or username - st.add("reply_author", replyMessage.getMember() != null ? replyMessage.getMember().getEffectiveName() - : replyMessage.getAuthor().getName()); - // The replied message formatted message contents - st.add("reply_message", StringUtils.discordToMinecraft(replyMessage.getContentDisplay())); - // The replied message raw message contents - st.add("reply_raw", replyMessage.getContentRaw()); + placeholders.putAll(Map.of( + // The replied message author's tag (i.e. username#discriminator), e.g. Axieum#1001 + "reply_tag", string(replyMessage.getAuthor().getAsTag()), + // The replied message author's username, e.g. Axieum + "reply_username", string(replyMessage.getAuthor().getName()), + // The replied message author's username discriminator, e.g. 1001 + "reply_discriminator", string(replyMessage.getAuthor().getDiscriminator()), + // The replied message author's nickname or username + "reply_author", string( + replyMessage.getMember() != null + ? replyMessage.getMember().getEffectiveName() + : replyMessage.getAuthor().getName() + ), + // The replied message formatted message contents + "reply_message", string(StringUtils.discordToMinecraft(replyMessage.getContentDisplay())), + // The replied message raw message contents + "reply_raw", string(replyMessage.getContentRaw()) + )); } /* @@ -86,17 +98,19 @@ public void onText(MessageReceivedEvent event) // The message is a standalone message if (replyMessage == null) { - ChatPlaceholderEvents.Discord.MESSAGE_RECEIVED.invoker().onMessageReceivedPlaceholder(st, event); - MinecraftDispatcher.json(entry -> st.format(entry.minecraft.chat), - entry -> entry.minecraft.chat != null && entry.id == channelId); - LOGGER.info(st.format("@${tag} > ${raw}")); + MinecraftDispatcher.dispatch( + entry -> PlaceholdersExt.parseText(entry.minecraft.chat, ctx, placeholders), + entry -> entry.minecraft.chat != null && entry.id == channelId + ); + LOGGER.info(PlaceholdersExt.parseString("@${tag} > ${raw}", ctx, placeholders)); // The message is in reply to another } else { - ChatPlaceholderEvents.Discord.REPLY_RECEIVED.invoker().onReplyReceivedPlaceholder(st, event); - MinecraftDispatcher.json(entry -> st.format(entry.minecraft.reply), - entry -> entry.minecraft.reply != null && entry.id == channelId); - LOGGER.info(st.format("@${tag} (in reply to @${reply_tag}) > ${raw}")); + MinecraftDispatcher.dispatch( + entry -> PlaceholdersExt.parseText(entry.minecraft.reply, ctx, placeholders), + entry -> entry.minecraft.reply != null && entry.id == channelId + ); + LOGGER.info(PlaceholdersExt.parseString("@${tag} (in reply to @${reply_tag}) > ${raw}", ctx, placeholders)); } } @@ -111,40 +125,39 @@ public void onAttachment(MessageReceivedEvent event, Message.Attachment attachme final long channelId = event.getChannel().getIdLong(); /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The author's tag (i.e. username#discriminator), e.g. Axieum#1001 - st.add("tag", event.getAuthor().getAsTag()); - // The author's username, e.g. Axieum - st.add("username", event.getAuthor().getName()); - // The author's username discriminator, e.g. 1001 - st.add("discriminator", event.getAuthor().getDiscriminator()); - // The author's nickname or username - st.add("author", event.getMember() != null ? event.getMember().getEffectiveName() - : event.getAuthor().getName()); - // The link to the file to download - st.add("url", attachment.getUrl()); - // The file name that was uploaded - st.add("name", attachment.getFileName()); - // The file extension/type - st.add("ext", attachment.getFileExtension()); - // The file size for humans - st.add("size", StringUtils.bytesToHuman(attachment.getSize())); - - ChatPlaceholderEvents.Discord.ATTACHMENT_RECEIVED.invoker().onAttachmentReceivedPlaceholder( - st, event, attachment + final @Nullable PlaceholderContext ctx = PlaceholdersExt.getMinecordServerContext(); + final Map placeholders = Map.of( + // The author's tag (i.e. username#discriminator), e.g. Axieum#1001 + "tag", string(event.getAuthor().getAsTag()), + // The author's username, e.g. Axieum + "username", string(event.getAuthor().getName()), + // The author's username discriminator, e.g. 1001 + "discriminator", string(event.getAuthor().getDiscriminator()), + // The author's nickname or username + "author", string( + event.getMember() != null ? event.getMember().getEffectiveName() : event.getAuthor().getName() + ), + // The link to the file to download + "url", string(attachment.getUrl()), + // The file name that was uploaded + "name", string(attachment.getFileName()), + // The file extension/type + "ext", string(attachment.getFileExtension()), + // The file size for humans + "size", string(StringUtils.bytesToHuman(attachment.getSize())) ); /* * Dispatch the message. */ - MinecraftDispatcher.json(entry -> st.format(entry.minecraft.attachment), - entry -> entry.minecraft.attachment != null && entry.id == channelId); - - LOGGER.info(st.format("@${tag} attached ${name} (${size})")); + MinecraftDispatcher.dispatch( + entry -> PlaceholdersExt.parseText(entry.minecraft.attachment, ctx, placeholders), + entry -> entry.minecraft.attachment != null && entry.id == channelId + ); + LOGGER.info(PlaceholdersExt.parseString("@${tag} attached ${name} (${size})", ctx, placeholders)); } } diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java index 6c487ec..9a50aeb 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java @@ -7,18 +7,21 @@ import com.github.difflib.text.DiffRow; import com.github.difflib.text.DiffRowGenerator; +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.events.message.MessageDeleteEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageUpdateEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.jetbrains.annotations.Nullable; import net.minecraft.util.Formatting; -import me.axieum.mcmod.minecord.api.chat.event.ChatPlaceholderEvents; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.MinecraftDispatcher; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.LOGGER; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.getConfig; @@ -55,43 +58,42 @@ public void onMessageUpdate(MessageUpdateEvent event) final long channelId = event.getChannel().getIdLong(); /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The author's tag (i.e. username#discriminator), e.g. Axieum#1001 - st.add("tag", event.getAuthor().getAsTag()); - // The author's username, e.g. Axieum - st.add("username", event.getAuthor().getName()); - // The author's username discriminator, e.g. 1001 - st.add("discriminator", event.getAuthor().getDiscriminator()); - // The author's nickname or username - st.add("author", event.getMember() != null ? event.getMember().getEffectiveName() - : event.getAuthor().getName()); - // The old formatted message contents - st.add("original", StringUtils.discordToMinecraft(original)); - // The old raw message contents - st.add("original_raw", context.getContentRaw()); - // The new formatted message contents - st.add("message", StringUtils.discordToMinecraft(event.getMessage().getContentDisplay())); - // The new raw message contents - st.add("raw", event.getMessage().getContentRaw()); - // The difference between the original and new message - st.add("diff", StringUtils.discordToMinecraft(diffs.get(0).getOldLine())); - - ChatPlaceholderEvents.Discord.MESSAGE_UPDATED.invoker().onMessageUpdatedPlaceholder( - st, event, context, diffs + final @Nullable PlaceholderContext ctx = PlaceholdersExt.getMinecordServerContext(); + final Map placeholders = Map.of( + // The author's tag (i.e. username#discriminator), e.g. Axieum#1001 + "tag", string(event.getAuthor().getAsTag()), + // The author's username, e.g. Axieum + "username", string(event.getAuthor().getName()), + // The author's username discriminator, e.g. 1001 + "discriminator", string(event.getAuthor().getDiscriminator()), + // The author's nickname or username + "author", string( + event.getMember() != null ? event.getMember().getEffectiveName() : event.getAuthor().getName() + ), + // The old formatted message contents + "original", string(StringUtils.discordToMinecraft(original)), + // The old raw message contents + "original_raw", string(context.getContentRaw()), + // The new formatted message contents + "message", string(StringUtils.discordToMinecraft(event.getMessage().getContentDisplay())), + // The new raw message contents + "raw", string(event.getMessage().getContentRaw()), + // The difference between the original and new message + "diff", string(StringUtils.discordToMinecraft(diffs.get(0).getOldLine())) ); /* * Dispatch the message. */ - MinecraftDispatcher.json(entry -> st.format(entry.minecraft.edit), - entry -> entry.minecraft.edit != null && entry.id == channelId); - - LOGGER.info(st.format("@${tag} > ${raw}")); + MinecraftDispatcher.dispatch( + entry -> PlaceholdersExt.parseText(entry.minecraft.edit, ctx, placeholders), + entry -> entry.minecraft.edit != null && entry.id == channelId + ); + LOGGER.info(PlaceholdersExt.parseString("@${tag} > ${raw}", ctx, placeholders)); } // Update the message cache diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java index 4d48714..0131252 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java @@ -1,16 +1,20 @@ package me.axieum.mcmod.minecord.impl.chat.callback.minecraft; import java.awt.Color; +import java.util.Map; + +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.damage.DamageSource; import me.axieum.mcmod.minecord.api.Minecord; -import me.axieum.mcmod.minecord.api.chat.event.ChatPlaceholderEvents; import me.axieum.mcmod.minecord.api.chat.event.minecraft.EntityDeathEvents; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; /** * A listener for when a Minecraft entity dies. @@ -27,35 +31,35 @@ public void onEntityDeath(LivingEntity entity, DamageSource source) final String entityName = entity.getDisplayName().getString(); /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The entity's display name - st.add("name", entityName); - // The reason for the entity's death - st.add( - "cause", source.getDeathMessage(entity).getString().replaceFirst(entityName, "").trim() + final PlaceholderContext ctx = PlaceholderContext.of(entity); + final Map placeholders = Map.of( + // The entity's display name + "name", string(entityName), + // The reason for the entity's death + "cause", string(source.getDeathMessage(entity).getString().replaceFirst(entityName, "").trim()), + // The name of the world the entity died in + "world", string(StringUtils.getWorldName(entity.world)), + // The X coordinate of where the entity died + "x", string(String.valueOf((int) entity.prevX)), + // The Y coordinate of where the entity died + "y", string(String.valueOf((int) entity.prevY)), + // The Z coordinate of where the entity died + "z", string(String.valueOf((int) entity.prevZ)) ); - // The name of the world the entity died in - st.add("world", StringUtils.getWorldName(entity.world)); - // The X coordinate of where the entity died - st.add("x", String.valueOf((int) entity.prevX)); - // The Y coordinate of where the entity died - st.add("y", String.valueOf((int) entity.prevY)); - // The Z coordinate of where the entity died - st.add("z", String.valueOf((int) entity.prevZ)); - - ChatPlaceholderEvents.Minecraft.ENTITY_DEATH.invoker().onEntityDeathPlaceholder(st, entity, source); /* * Dispatch the message. */ - DiscordDispatcher.embed((embed, entry) -> - embed.setColor(Color.RED).setDescription(st.format(entry.discord.grief)), - entry -> entry.discord.grief != null && entry.hasWorld(entity.world)); + DiscordDispatcher.embed( + (embed, entry) -> embed.setColor(Color.RED).setDescription( + PlaceholdersExt.parseString(entry.discord.grief, ctx, placeholders) + ), + entry -> entry.discord.grief != null && entry.hasWorld(entity.world) + ); }); } } diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerAdvancementCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerAdvancementCallback.java index 3606b2b..08b240a 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerAdvancementCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerAdvancementCallback.java @@ -1,15 +1,20 @@ package me.axieum.mcmod.minecord.impl.chat.callback.minecraft; +import java.util.Map; + +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; + import net.minecraft.advancement.Advancement; import net.minecraft.advancement.AdvancementDisplay; import net.minecraft.server.network.ServerPlayerEntity; import me.axieum.mcmod.minecord.api.Minecord; -import me.axieum.mcmod.minecord.api.chat.event.ChatPlaceholderEvents; import me.axieum.mcmod.minecord.api.chat.event.minecraft.GrantCriterionCallback; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; /** * A listener for when a Minecraft player is granted an advancement. @@ -25,32 +30,31 @@ public void onGrantCriterion(ServerPlayerEntity player, Advancement advancement, if (info == null || !info.shouldAnnounceToChat()) return; /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The player's username - st.add("username", player.getName().getString()); - // The player's display name - st.add("player", player.getDisplayName().getString()); - // The type of advancement - st.add("type", StringUtils.getAdvancementTypeName(info.getFrame())); - // The title of the advancement - st.add("title", info.getTitle().getString()); - // A description of the advancement - st.add("description", info.getDescription().getString()); - - ChatPlaceholderEvents.Minecraft.PLAYER_ADVANCEMENT.invoker().onPlayerAdvancementPlaceholder( - st, player, advancement, criterion + final PlaceholderContext ctx = PlaceholderContext.of(player); + final Map placeholders = Map.of( + // The player's username + "username", string(player.getName().getString()), + // The player's display name + "player", string(player.getDisplayName().getString()), + // The type of advancement + "type", string(StringUtils.getAdvancementTypeName(info.getFrame())), + // The title of the advancement + "title", string(info.getTitle().getString()), + // A description of the advancement + "description", string(info.getDescription().getString()) ); /* * Dispatch the message. */ - DiscordDispatcher.embed((embed, entry) -> - embed.setDescription(st.format(entry.discord.advancement)), + DiscordDispatcher.embed( + (embed, entry) -> embed.setDescription( + PlaceholdersExt.parseString(entry.discord.advancement, ctx, placeholders) + ), entry -> entry.discord.advancement != null && entry.hasWorld(player.world)); }); } diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java index 5c1e587..40ae7d8 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java @@ -1,15 +1,20 @@ package me.axieum.mcmod.minecord.impl.chat.callback.minecraft; +import java.util.Map; + +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; + import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents; import me.axieum.mcmod.minecord.api.Minecord; -import me.axieum.mcmod.minecord.api.chat.event.ChatPlaceholderEvents; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; /** * A listener for when a Minecraft player changes world. @@ -21,34 +26,31 @@ public void afterChangeWorld(ServerPlayerEntity player, ServerWorld origin, Serv { Minecord.getInstance().getJDA().ifPresent(jda -> { /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The player's username - st.add("username", player.getName().getString()); - // The player's display name - st.add("player", player.getDisplayName().getString()); - // The name of the world the player entered - st.add("world", StringUtils.getWorldName(dest)); - // The X coordinate of where the player entered - st.add("x", String.valueOf(player.getBlockX())); - // The Y coordinate of where the player entered - st.add("y", String.valueOf(player.getBlockY())); - // The Z coordinate of where the player entered - st.add("z", String.valueOf(player.getBlockZ())); - // The name of the world the player left - st.add("origin", StringUtils.getWorldName(origin)); - // The X coordinate of where the player left - st.add("origin_x", String.valueOf((int) player.prevX)); - // The Y coordinate of where the player left - st.add("origin_y", String.valueOf((int) player.prevY)); - // The Z coordinate of where the player left - st.add("origin_z", String.valueOf((int) player.prevZ)); - - ChatPlaceholderEvents.Minecraft.PLAYER_CHANGE_WORLD.invoker().onPlayerChangeWorldPlaceholder( - st, player, origin, dest + final PlaceholderContext ctx = PlaceholderContext.of(player); + final Map placeholders = Map.of( + // The player's username + "username", string(player.getName().getString()), + // The player's display name + "player", string(player.getDisplayName().getString()), + // The name of the world the player entered + "world", string(StringUtils.getWorldName(dest)), + // The X coordinate of where the player entered + "x", string(String.valueOf(player.getBlockX())), + // The Y coordinate of where the player entered + "y", string(String.valueOf(player.getBlockY())), + // The Z coordinate of where the player entered + "z", string(String.valueOf(player.getBlockZ())), + // The name of the world the player left + "origin", string(StringUtils.getWorldName(origin)), + // The X coordinate of where the player left + "origin_x", string(String.valueOf((int) player.prevX)), + // The Y coordinate of where the player left + "origin_y", string(String.valueOf((int) player.prevY)), + // The Z coordinate of where the player left + "origin_z", string(String.valueOf((int) player.prevZ)) ); /* @@ -56,7 +58,9 @@ public void afterChangeWorld(ServerPlayerEntity player, ServerWorld origin, Serv */ DiscordDispatcher.embedWithAvatar( - (embed, entry) -> embed.setDescription(st.format(entry.discord.teleport)), + (embed, entry) -> embed.setDescription( + PlaceholdersExt.parseString(entry.discord.teleport, ctx, placeholders) + ), entry -> entry.discord.teleport != null && entry.hasWorld(dest), player.getUuidAsString() ); diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerConnectionCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerConnectionCallback.java index ec99ffb..c7d378b 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerConnectionCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerConnectionCallback.java @@ -1,5 +1,11 @@ package me.axieum.mcmod.minecord.impl.chat.callback.minecraft; +import java.util.Map; + +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; +import org.jetbrains.annotations.Nullable; + import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; @@ -9,10 +15,10 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents.Join; import me.axieum.mcmod.minecord.api.Minecord; -import me.axieum.mcmod.minecord.api.chat.event.ChatPlaceholderEvents; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; /** * A listener for when a Minecraft player joins or leaves. @@ -23,34 +29,36 @@ public class PlayerConnectionCallback implements Join, Disconnect public void onPlayReady(ServerPlayNetworkHandler handler, PacketSender sender, MinecraftServer server) { Minecord.getInstance().getJDA().ifPresent(jda -> { - /* - * Prepare a message template. - */ - - final StringTemplate st = new StringTemplate(); final ServerPlayerEntity player = handler.player; - // The player's username - st.add("username", player.getName().getString()); - // The player's display name - st.add("player", player.getDisplayName().getString()); - // The name of the world the player logged into - st.add("world", StringUtils.getWorldName(player.world)); - // The X coordinate of where the player logged into - st.add("x", String.valueOf(player.getBlockX())); - // The Y coordinate of where the player logged into - st.add("y", String.valueOf(player.getBlockY())); - // The Z coordinate of where the player logged into - st.add("z", String.valueOf(player.getBlockZ())); + /* + * Prepare the message placeholders. + */ - ChatPlaceholderEvents.Minecraft.PLAYER_CONNECT.invoker().onPlayerConnectPlaceholder(st, player); + final @Nullable PlaceholderContext ctx = PlaceholderContext.of(player); + final Map placeholders = Map.of( + // The player's username + "username", string(player.getName().getString()), + // The player's display name + "player", string(player.getDisplayName().getString()), + // The name of the world the player logged into + "world", string(StringUtils.getWorldName(player.world)), + // The X coordinate of where the player logged into + "x", string(String.valueOf(player.getBlockX())), + // The Y coordinate of where the player logged into + "y", string(String.valueOf(player.getBlockY())), + // The Z coordinate of where the player logged into + "z", string(String.valueOf(player.getBlockZ())) + ); /* * Dispatch the message. */ DiscordDispatcher.embedWithAvatar( - (embed, entry) -> embed.setDescription(st.format(entry.discord.join)), + (embed, entry) -> embed.setDescription( + PlaceholdersExt.parseString(entry.discord.join, ctx, placeholders) + ), entry -> entry.discord.join != null && entry.hasWorld(player.world), player.getUuidAsString() ); @@ -61,34 +69,36 @@ public void onPlayReady(ServerPlayNetworkHandler handler, PacketSender sender, M public void onPlayDisconnect(ServerPlayNetworkHandler handler, MinecraftServer server) { Minecord.getInstance().getJDA().ifPresent(jda -> { - /* - * Prepare a message template. - */ - - final StringTemplate st = new StringTemplate(); final ServerPlayerEntity player = handler.player; - // The player's username - st.add("username", player.getName().getString()); - // The player's display name - st.add("player", player.getDisplayName().getString()); - // The name of the world the player logged out - st.add("world", StringUtils.getWorldName(player.world)); - // The X coordinate of where the player logged out - st.add("x", String.valueOf(player.getBlockX())); - // The Y coordinate of where the player logged out - st.add("y", String.valueOf(player.getBlockY())); - // The Z coordinate of where the player logged out - st.add("z", String.valueOf(player.getBlockZ())); + /* + * Prepare the message placeholders. + */ - ChatPlaceholderEvents.Minecraft.PLAYER_DISCONNECT.invoker().onPlayerDisconnectPlaceholder(st, player); + final @Nullable PlaceholderContext ctx = PlaceholderContext.of(player); + final Map placeholders = Map.of( + // The player's username + "username", string(player.getName().getString()), + // The player's display name + "player", string(player.getDisplayName().getString()), + // The name of the world the player logged out + "world", string(StringUtils.getWorldName(player.world)), + // The X coordinate of where the player logged out + "x", string(String.valueOf(player.getBlockX())), + // The Y coordinate of where the player logged out + "y", string(String.valueOf(player.getBlockY())), + // The Z coordinate of where the player logged out + "z", string(String.valueOf(player.getBlockZ())) + ); /* * Dispatch the message. */ DiscordDispatcher.embedWithAvatar( - (embed, entry) -> embed.setDescription(st.format(entry.discord.leave)), + (embed, entry) -> embed.setDescription( + PlaceholdersExt.parseString(entry.discord.leave, ctx, placeholders) + ), entry -> entry.discord.leave != null && entry.hasWorld(player.world), player.getUuidAsString() ); diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java index 2659453..92a3e62 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java @@ -1,17 +1,23 @@ package me.axieum.mcmod.minecord.impl.chat.callback.minecraft; import java.time.Duration; +import java.util.Map; + +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; +import org.jetbrains.annotations.Nullable; import net.minecraft.entity.damage.DamageSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.stat.Stats; import me.axieum.mcmod.minecord.api.Minecord; -import me.axieum.mcmod.minecord.api.chat.event.ChatPlaceholderEvents; import me.axieum.mcmod.minecord.api.chat.event.minecraft.EntityDeathEvents; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.duration; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; /** * A listener for when a Minecraft player dies. @@ -25,44 +31,43 @@ public void onPlayerDeath(ServerPlayerEntity player, DamageSource source) final String playerName = player.getDisplayName().getString(); /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The player's username - st.add("username", player.getName().getString()); - // The player's display name - st.add("player", playerName); - // The reason for the player's death - st.add( - "cause", source.getDeathMessage(player).getString().replaceFirst(playerName, "").trim() + final @Nullable PlaceholderContext ctx = PlaceholderContext.of(player); + final Map placeholders = Map.of( + // The player's username + "username", string(player.getName().getString()), + // The player's display name + "player", string(playerName), + // The reason for the player's death + "cause", string(source.getDeathMessage(player).getString().replaceFirst(playerName, "").trim()), + // The name of the world the player died in + "world", string(StringUtils.getWorldName(player.world)), + // The X coordinate of where the player died + "x", string(String.valueOf((int) player.prevX)), + // The Y coordinate of where the player died + "y", string(String.valueOf((int) player.prevY)), + // The Z coordinate of where the player died + "z", string(String.valueOf((int) player.prevZ)), + // The total time for which the player was alive for + "lifespan", duration(Duration.ofMinutes( + player.getStatHandler().getStat(Stats.CUSTOM.getOrCreateStat(Stats.TIME_SINCE_DEATH)) + )), + // The player's total score before they died + "score", string(String.valueOf(player.getScore())), + // The player's number of experience levels before they died + "exp", string(String.valueOf(player.experienceLevel)) ); - // The name of the world the player died in - st.add("world", StringUtils.getWorldName(player.world)); - // The X coordinate of where the player died - st.add("x", String.valueOf((int) player.prevX)); - // The Y coordinate of where the player died - st.add("y", String.valueOf((int) player.prevY)); - // The Z coordinate of where the player died - st.add("z", String.valueOf((int) player.prevZ)); - // The total time for which the player was alive for - st.add("lifespan", Duration.ofMinutes( - player.getStatHandler().getStat(Stats.CUSTOM.getOrCreateStat(Stats.TIME_SINCE_DEATH)) - )); - // The player's total score before they died - st.add("score", String.valueOf(player.getScore())); - // The player's number of experience levels before they died - st.add("exp", String.valueOf(player.experienceLevel)); - - ChatPlaceholderEvents.Minecraft.PLAYER_DEATH.invoker().onPlayerDeathPlaceholder(st, player, source); /* * Dispatch the message. */ DiscordDispatcher.embedWithAvatar( - (embed, entry) -> embed.setDescription(st.format(entry.discord.death)), + (embed, entry) -> embed.setDescription( + PlaceholdersExt.parseString(entry.discord.death, ctx, placeholders) + ), entry -> entry.discord.death != null && entry.hasWorld(player.world), player.getUuidAsString() ); diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerLifecycleCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerLifecycleCallback.java index a7adf8b..36598fb 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerLifecycleCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerLifecycleCallback.java @@ -4,8 +4,13 @@ import java.io.File; import java.lang.management.ManagementFactory; import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; import net.dv8tion.jda.api.utils.FileUpload; import org.jetbrains.annotations.Nullable; @@ -17,10 +22,11 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents.ServerStopping; import me.axieum.mcmod.minecord.api.Minecord; -import me.axieum.mcmod.minecord.api.chat.event.ChatPlaceholderEvents; import me.axieum.mcmod.minecord.api.event.ServerShutdownCallback; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.duration; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; /** * A listener for when the Minecraft server starts, stops or crashes. @@ -32,20 +38,22 @@ public void onServerStarting(MinecraftServer server) { Minecord.getInstance().getJDA().ifPresent(jda -> { /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - ChatPlaceholderEvents.Minecraft.SERVER_STARTING.invoker().onServerStartingPlaceholder(st, server); + final PlaceholderContext ctx = PlaceholderContext.of(server); + final Map placeholders = Collections.emptyMap(); /* * Dispatch the message. */ - DiscordDispatcher.embed((embed, entry) -> - embed.setDescription(st.format(entry.discord.starting)), - entry -> entry.discord.starting != null); + DiscordDispatcher.embed( + (embed, entry) -> embed.setDescription( + PlaceholdersExt.parseString(entry.discord.starting, ctx, placeholders) + ), + entry -> entry.discord.starting != null + ); }); } @@ -54,23 +62,25 @@ public void onServerStarted(MinecraftServer server) { Minecord.getInstance().getJDA().ifPresent(jda -> { /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The time taken for the server to start - st.add("uptime", Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime())); - - ChatPlaceholderEvents.Minecraft.SERVER_STARTED.invoker().onServerStartedPlaceholder(st, server); + final PlaceholderContext ctx = PlaceholderContext.of(server); + final Map placeholders = Map.of( + // The time taken for the server to start + "uptime", duration(Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime())) + ); /* * Dispatch the message. */ - DiscordDispatcher.embed((embed, entry) -> - embed.setColor(Color.GREEN).setDescription(st.format(entry.discord.started)), - entry -> entry.discord.started != null); + DiscordDispatcher.embed( + (embed, entry) -> embed.setColor(Color.GREEN).setDescription( + PlaceholdersExt.parseString(entry.discord.started, ctx, placeholders) + ), + entry -> entry.discord.started != null + ); }); } @@ -79,23 +89,25 @@ public void onServerStopping(MinecraftServer server) { Minecord.getInstance().getJDA().ifPresent(jda -> { /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The total time for which the server has been online for - st.add("uptime", Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime())); - - ChatPlaceholderEvents.Minecraft.SERVER_STOPPING.invoker().onServerStoppingPlaceholder(st, server); + final PlaceholderContext ctx = PlaceholderContext.of(server); + final Map placeholders = Map.of( + // The total time for which the server has been online for + "uptime", duration(Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime())) + ); /* * Dispatch the message. */ - DiscordDispatcher.embed((embed, entry) -> - embed.setDescription(st.format(entry.discord.stopping)), - entry -> entry.discord.stopping != null); + DiscordDispatcher.embed( + (embed, entry) -> embed.setDescription( + PlaceholdersExt.parseString(entry.discord.stopping, ctx, placeholders) + ), + entry -> entry.discord.stopping != null + ); }); } @@ -104,19 +116,16 @@ public void onServerShutdown(MinecraftServer server, @Nullable CrashReport crash { Minecord.getInstance().getJDA().ifPresent(jda -> { /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The total time for which the server has been online for - st.add("uptime", Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime())); + final PlaceholderContext ctx = PlaceholderContext.of(server); + final Map placeholders = new HashMap<>(Map.of( + // The total time for which the server has been online for + "uptime", duration(Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime())) + )); // The reason for the server stopping, if crashed - if (crashReport != null) st.add("reason", crashReport.getMessage()); - - ChatPlaceholderEvents.Minecraft.SERVER_SHUTDOWN.invoker().onServerShutdownPlaceholder( - st, server, crashReport - ); + if (crashReport != null) placeholders.put("reason", string(crashReport.getMessage())); /* * Dispatch the message. @@ -124,9 +133,12 @@ public void onServerShutdown(MinecraftServer server, @Nullable CrashReport crash // The server stopped normally if (crashReport == null) { - DiscordDispatcher.embed((embed, entry) -> - embed.setColor(Color.RED).setDescription(st.format(entry.discord.stopped)), - entry -> entry.discord.stopped != null); + DiscordDispatcher.embed( + (embed, entry) -> embed.setColor(Color.RED).setDescription( + PlaceholdersExt.parseString(entry.discord.stopped, ctx, placeholders) + ), + entry -> entry.discord.stopped != null + ); // The server stopped due to an error } else { @@ -134,15 +146,18 @@ public void onServerShutdown(MinecraftServer server, @Nullable CrashReport crash final Optional file = Optional.ofNullable(crashReport.getFile()).filter(File::exists); // Dispatch the message - DiscordDispatcher.embed((embed, entry) -> - embed.setColor(Color.ORANGE).setDescription(st.format(entry.discord.crashed)), + DiscordDispatcher.embed( + (embed, entry) -> embed.setColor(Color.ORANGE).setDescription( + PlaceholdersExt.parseString(entry.discord.crashed, ctx, placeholders) + ), (action, entry) -> { // Conditionally attach the crash report if required if (entry.discord.uploadCrashReport) file.map(FileUpload::fromData).ifPresent(action::addFiles); action.queue(); }, - entry -> entry.discord.crashed != null); + entry -> entry.discord.crashed != null + ); } }); } diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java index d243e28..dc61568 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java @@ -1,5 +1,10 @@ package me.axieum.mcmod.minecord.impl.chat.callback.minecraft; +import java.util.HashMap; +import java.util.Map; + +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; import org.jetbrains.annotations.Nullable; import net.minecraft.network.message.MessageType; @@ -12,11 +17,11 @@ import net.fabricmc.fabric.api.message.v1.ServerMessageEvents.CommandMessage; import me.axieum.mcmod.minecord.api.Minecord; -import me.axieum.mcmod.minecord.api.chat.event.ChatPlaceholderEvents; import me.axieum.mcmod.minecord.api.chat.event.minecraft.TellRawMessageCallback; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; /** * A listener for a when a Minecraft player sends a message. @@ -30,29 +35,29 @@ public void onChatMessage( { Minecord.getInstance().getJDA().ifPresent(jda -> { /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The player's username - st.add("username", player.getName().getString()); - // The player's display name - st.add("player", player.getDisplayName().getString()); - // The name of the world the player logged into - st.add("world", StringUtils.getWorldName(player.world)); - // The formatted message contents - st.add("message", StringUtils.minecraftToDiscord(message.getContent().getString())); - - ChatPlaceholderEvents.Minecraft.PLAYER_CHAT.invoker().onPlayerChatPlaceholder(st, player, message, params); + final PlaceholderContext ctx = PlaceholderContext.of(player); + final Map placeholders = Map.of( + // The player's username + "username", string(player.getName().getString()), + // The player's display name + "player", string(player.getDisplayName().getString()), + // The name of the world the player logged into + "world", string(StringUtils.getWorldName(player.world)), + // The formatted message contents + "message", string(StringUtils.minecraftToDiscord(message.getContent().getString())) + ); /* * Dispatch the message. */ - DiscordDispatcher.dispatch((embed, entry) -> - embed.setContent(st.format(entry.discord.chat)), - entry -> entry.discord.chat != null && entry.hasWorld(player.world)); + DiscordDispatcher.dispatch( + (embed, entry) -> embed.setContent(PlaceholdersExt.parseString(entry.discord.chat, ctx, placeholders)), + entry -> entry.discord.chat != null && entry.hasWorld(player.world) + ); }); } @@ -86,31 +91,33 @@ public void onEmoteCommandMessage(SignedMessage message, ServerCommandSource sou final @Nullable ServerPlayerEntity player = source.getPlayer(); /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The player's username - st.add("username", player != null ? player.getName().getString() : null); - // The player's display name - st.add("player", player != null ? player.getDisplayName().getString() : null); - // The name of the world the player logged into - st.add("world", player != null ? StringUtils.getWorldName(source.getWorld()) : null); - // The formatted message contents - st.add("action", StringUtils.minecraftToDiscord(message.getContent().getString())); - - ChatPlaceholderEvents.Minecraft.EMOTE_COMMAND.invoker().onEmoteCommandPlaceholder( - st, source, message, params - ); + final PlaceholderContext ctx = PlaceholderContext.of(source); + final Map placeholders = new HashMap<>(Map.of( + // The formatted message contents + "action", string(StringUtils.minecraftToDiscord(message.getContent().getString())) + )); + if (player != null) { + placeholders.putAll(Map.of( + // The player's username + "username", string(player.getName().getString()), + // The player's display name + "player", string(player.getDisplayName().getString()), + // The name of the world the player logged into + "world", string(StringUtils.getWorldName(source.getWorld())) + )); + } /* * Dispatch the message. */ - DiscordDispatcher.dispatch((embed, entry) -> - embed.setContent(st.format(entry.discord.emote)), - entry -> entry.discord.emote != null && (player == null || entry.hasWorld(source.getWorld()))); + DiscordDispatcher.dispatch( + (embed, entry) -> embed.setContent(PlaceholdersExt.parseString(entry.discord.emote, ctx, placeholders)), + entry -> entry.discord.emote != null && (player == null || entry.hasWorld(source.getWorld())) + ); }); } @@ -129,29 +136,33 @@ public void onSayCommandMessage(SignedMessage message, ServerCommandSource sourc final @Nullable ServerPlayerEntity player = source.getPlayer(); /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The player's username - st.add("username", player != null ? player.getName().getString() : null); - // The player's display name - st.add("player", player != null ? player.getDisplayName().getString() : null); - // The name of the world the player logged into - st.add("world", player != null ? StringUtils.getWorldName(source.getWorld()) : null); - // The formatted message contents - st.add("message", StringUtils.minecraftToDiscord(message.getContent().getString())); - - ChatPlaceholderEvents.Minecraft.SAY_COMMAND.invoker().onSayCommandPlaceholder(st, source, message, params); + final @Nullable PlaceholderContext ctx = player != null ? PlaceholderContext.of(player) : null; + final Map placeholders = new HashMap<>(Map.of( + // The formatted message contents + "message", string(StringUtils.minecraftToDiscord(message.getContent().getString())) + )); + if (player != null) { + placeholders.putAll(Map.of( + // The player's username + "username", string(player.getName().getString()), + // The player's display name + "player", string(player.getDisplayName().getString()), + // The name of the world the player logged into + "world", string(StringUtils.getWorldName(source.getWorld())) + )); + } /* * Dispatch the message. */ - DiscordDispatcher.dispatch((embed, entry) -> - embed.setContent(st.format(entry.discord.say)), - entry -> entry.discord.say != null && (player == null || entry.hasWorld(source.getWorld()))); + DiscordDispatcher.dispatch( + (embed, entry) -> embed.setContent(PlaceholdersExt.parseString(entry.discord.say, ctx, placeholders)), + entry -> entry.discord.say != null && (player == null || entry.hasWorld(source.getWorld())) + ); }); } @@ -160,22 +171,23 @@ public void onTellRawCommandMessage(Text message, ServerCommandSource source) { Minecord.getInstance().getJDA().ifPresent(jda -> { /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The formatted message contents - st.add("message", StringUtils.minecraftToDiscord(message.getString())); - - ChatPlaceholderEvents.Minecraft.TELLRAW_COMMAND.invoker().onTellRawCommandPlaceholder(st, source, message); + final PlaceholderContext ctx = PlaceholderContext.of(source); + final Map placeholders = Map.of( + // The formatted message contents + "message", string(StringUtils.minecraftToDiscord(message.getString())) + ); /* * Dispatch the message. */ - DiscordDispatcher.dispatch((embed, entry) -> - embed.setContent(st.format(entry.discord.tellraw)), + DiscordDispatcher.dispatch( + (embed, entry) -> embed.setContent( + PlaceholdersExt.parseString(entry.discord.tellraw, ctx, placeholders) + ), entry -> entry.discord.tellraw != null); }); } diff --git a/minecord-chat/src/main/resources/fabric.mod.json b/minecord-chat/src/main/resources/fabric.mod.json index 664172a..6b37807 100644 --- a/minecord-chat/src/main/resources/fabric.mod.json +++ b/minecord-chat/src/main/resources/fabric.mod.json @@ -41,6 +41,7 @@ "fabric-lifecycle-events-v1": "*", "fabric-message-api-v1": "*", "fabric-networking-api-v1": "*", - "minecord-api": "*" + "minecord-api": "*", + "placeholder-api": "*" } } diff --git a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/callback/DiscordCommandListener.java b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/callback/DiscordCommandListener.java index acfa16d..8503388 100644 --- a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/callback/DiscordCommandListener.java +++ b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/callback/DiscordCommandListener.java @@ -1,7 +1,11 @@ package me.axieum.mcmod.minecord.impl.cmds.callback; import java.time.Duration; +import java.util.Collections; +import java.util.Map; +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderResult; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.session.ReadyEvent; @@ -13,7 +17,7 @@ import me.axieum.mcmod.minecord.api.Minecord; import me.axieum.mcmod.minecord.api.cmds.MinecordCommands; import me.axieum.mcmod.minecord.api.cmds.event.MinecordCommandEvents; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import static me.axieum.mcmod.minecord.impl.cmds.MinecordCommandsImpl.LOGGER; import static me.axieum.mcmod.minecord.impl.cmds.MinecordCommandsImpl.getConfig; @@ -46,14 +50,19 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) event.deferReply(isEphemeral).queue(); event.getHook().setEphemeral(isEphemeral); + // Fetch the Minecraft server instance + final @Nullable MinecraftServer server = Minecord.getInstance().getMinecraft().orElse(null); + final @Nullable PlaceholderContext pCtx = server != null ? PlaceholderContext.of(server) : null; + // Attempt to run the command try { // Check whether Minecraft is required, and hence whether the server has started - final @Nullable MinecraftServer server = Minecord.getInstance().getMinecraft().orElse(null); if (command.requiresMinecraft() && (server == null || server.getTickTime() == 0)) { LOGGER.warn("@{} used '{}' but the server is not yet ready!", username, raw); event.getHook().sendMessageEmbeds( - new EmbedBuilder().setColor(0xff8800).setDescription(getConfig().messages.unavailable).build() + new EmbedBuilder().setColor(0xff8800).setDescription( + PlaceholdersExt.parseString(getConfig().messages.unavailable, pCtx, Collections.emptyMap()) + ).build() ).queue(); return; } @@ -66,10 +75,14 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) LOGGER.warn("@{} used '{}' but must wait another {} seconds!", username, raw, remaining); event.getHook().setEphemeral(true).sendMessageEmbeds( new EmbedBuilder().setColor(0xff8800).setDescription( - new StringTemplate() - .add("cooldown", Duration.ofSeconds(command.getCooldown())) - .add("remaining", Duration.ofSeconds(remaining)) - .format(getConfig().messages.cooldown) + PlaceholdersExt.parseString( + getConfig().messages.cooldown, pCtx, Map.of( + // The total cooldown before the command can be used again + "cooldown", PlaceholdersExt.duration(Duration.ofSeconds(command.getCooldown())), + // The remaining time before the command can be used again + "remaining", PlaceholdersExt.duration(Duration.ofSeconds(remaining)) + ) + ) ).build() ).queue(); return; @@ -95,7 +108,12 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) LOGGER.error("@{} failed to use '{}'", username, raw, e); event.getHook().sendMessageEmbeds( new EmbedBuilder().setColor(0xff0000).setDescription( - new StringTemplate().add("reason", e.getMessage()).format(getConfig().messages.failed) + PlaceholdersExt.parseString( + getConfig().messages.failed, pCtx, Map.of( + // The reason for the command failing + "reason", (ctx, arg) -> PlaceholderResult.value(e.getMessage()) + ) + ) ).build() ).queue(); } finally { diff --git a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/CustomCommand.java b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/CustomCommand.java index 95c6f51..79fe37e 100644 --- a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/CustomCommand.java +++ b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/CustomCommand.java @@ -2,12 +2,16 @@ import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; +import eu.pb4.placeholders.api.PlaceholderResult; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.exceptions.ParsingException; @@ -36,7 +40,7 @@ import me.axieum.mcmod.minecord.api.Minecord; import me.axieum.mcmod.minecord.api.cmds.command.MinecordCommand; import me.axieum.mcmod.minecord.api.cmds.event.MinecordCommandEvents; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.impl.cmds.config.CommandConfig; import static me.axieum.mcmod.minecord.impl.cmds.MinecordCommandsImpl.LOGGER; import static me.axieum.mcmod.minecord.impl.cmds.MinecordCommandsImpl.getConfig; @@ -85,7 +89,7 @@ public void execute(@NotNull SlashCommandInteractionEvent event, @Nullable Minec // Prepare the Minecraft command final String origCommand; try { - origCommand = prepareCommand(config.command, event.getOptions()); + origCommand = prepareCommand(config.command, event.getOptions(), server); } catch (ParsingException | IllegalArgumentException e) { throw new Exception("Unable to prepare Minecraft command!", e); } @@ -168,28 +172,27 @@ public void execute(@NotNull SlashCommandInteractionEvent event, @Nullable Minec * * @param command Minecraft command template using {n} for the nth argument, and {} for all * @param options Discord command options to substitute into the template + * @param server optional Minecraft server for placeholder context * @return a prepared Minecraft command * @throws ParsingException if an invalid command option type is encountered */ - private static String prepareCommand(@NotNull String command, List options) throws ParsingException + private static String prepareCommand( + @NotNull String command, + List options, + @Nullable MinecraftServer server + ) throws ParsingException { if (command.length() == 0) return command; - // Prepare a new string template - final StringTemplate st = new StringTemplate(); - - // Add all options to the template - for (OptionMapping option : options) { - switch (option.getType()) { - case BOOLEAN -> st.add(option.getName(), option.getAsBoolean()); - case NUMBER -> st.add(option.getName(), option.getAsDouble()); - case INTEGER -> st.add(option.getName(), option.getAsLong()); - default -> st.add(option.getName(), option.getAsString()); - } - } + // Prepare new command placeholders + final @Nullable PlaceholderContext ctx = server != null ? PlaceholderContext.of(server) : null; + final HashMap placeholders = new HashMap<>(options.size()); + options.forEach(option -> + placeholders.put(option.getName(), (c, a) -> PlaceholderResult.value(option.getAsString())) + ); - // Apply the template to the given command - String result = st.transform(String::trim).format(command); + // Parse the placeholders in the given command + String result = PlaceholdersExt.parseString(command, ctx, placeholders).trim(); // Strip any leading '/' if present, and return return result.length() > 0 && result.charAt(0) == '/' ? result.substring(1) : result; diff --git a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/UptimeCommand.java b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/UptimeCommand.java index 088c6f6..24b6248 100644 --- a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/UptimeCommand.java +++ b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/UptimeCommand.java @@ -2,7 +2,11 @@ import java.lang.management.ManagementFactory; import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.Map; +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; @@ -13,7 +17,7 @@ import me.axieum.mcmod.minecord.api.cmds.command.MinecordCommand; import me.axieum.mcmod.minecord.api.cmds.event.MinecordCommandEvents; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.impl.cmds.config.CommandConfig; import static me.axieum.mcmod.minecord.impl.cmds.MinecordCommandsImpl.getConfig; @@ -22,9 +26,13 @@ */ public class UptimeCommand extends MinecordCommand { - /** A reusable string template for all uptime commands. */ - public static final StringTemplate TEMPLATE = new StringTemplate() - .add("uptime", () -> Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime())); + /** Reusable placeholders for all uptime commands. */ + public static final Map PLACEHOLDERS = Map.of( + // The total process uptime (to the nearest minute) + "uptime", PlaceholdersExt.duration(() -> + Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime()).truncatedTo(ChronoUnit.MINUTES) + ) + ); /** * Constructs a new uptime command. @@ -46,8 +54,13 @@ public UptimeCommand(CommandConfig.BaseCommandSchema config) @Override public void execute(@NotNull SlashCommandInteractionEvent event, @Nullable MinecraftServer server) { + // Prepare the message placeholders + final @Nullable PlaceholderContext ctx = server != null ? PlaceholderContext.of(server) : null; + // Prepare an embed to be sent to the user - EmbedBuilder embed = new EmbedBuilder().setDescription(TEMPLATE.format(getConfig().builtin.uptime.message)); + EmbedBuilder embed = new EmbedBuilder().setDescription( + PlaceholdersExt.parseString(getConfig().builtin.uptime.message, ctx, PLACEHOLDERS) + ); // Fire an event to allow the embed to be mutated embed = MinecordCommandEvents.Builtin.UPTIME.invoker().onUptimeCommand(this, event, server, embed); diff --git a/minecord-cmds/src/main/resources/fabric.mod.json b/minecord-cmds/src/main/resources/fabric.mod.json index 47e9e3c..0a04250 100644 --- a/minecord-cmds/src/main/resources/fabric.mod.json +++ b/minecord-cmds/src/main/resources/fabric.mod.json @@ -32,6 +32,7 @@ "minecraft": "~1.19.1", "fabricloader": ">=0.11.3", "fabric-lifecycle-events-v1": "*", - "minecord-api": "*" + "minecord-api": "*", + "placeholder-api": "*" } } diff --git a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/api/presence/category/PresenceSupplier.java b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/api/presence/category/PresenceSupplier.java index 69e5dff..cf8c8ad 100644 --- a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/api/presence/category/PresenceSupplier.java +++ b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/api/presence/category/PresenceSupplier.java @@ -1,12 +1,11 @@ package me.axieum.mcmod.minecord.api.presence.category; import java.util.Optional; +import java.util.function.Function; import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.entities.Activity; -import me.axieum.mcmod.minecord.api.util.StringTemplate; - /** * A Minecord presence supplier. */ @@ -35,10 +34,20 @@ default Optional getStatus() /** * Returns the game activity for the bot. * - * @param template string template with placeholders * @return game activity, or empty if not changing */ - default Optional getActivity(StringTemplate template) + default Optional getActivity() + { + return getActivity(Function.identity()); + } + + /** + * Returns the game activity for the bot after modifying the name. + * + * @param nameMutator mutator for the activity name + * @return game activity, or empty if not changing + */ + default Optional getActivity(Function nameMutator) { return Optional.empty(); } diff --git a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/api/presence/event/PresencePlaceholderCallback.java b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/api/presence/event/PresencePlaceholderCallback.java deleted file mode 100644 index d97cf8e..0000000 --- a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/api/presence/event/PresencePlaceholderCallback.java +++ /dev/null @@ -1,41 +0,0 @@ -package me.axieum.mcmod.minecord.api.presence.event; - -import net.dv8tion.jda.api.JDA; -import org.jetbrains.annotations.Nullable; - -import net.minecraft.server.MinecraftServer; - -import net.fabricmc.fabric.api.event.Event; -import net.fabricmc.fabric.api.event.EventFactory; - -import me.axieum.mcmod.minecord.api.presence.category.PresenceCategory; -import me.axieum.mcmod.minecord.api.util.StringTemplate; - -/** - * A callback for when providing placeholder values to Discord bot presence messages. - */ -@FunctionalInterface -public interface PresencePlaceholderCallback -{ - /** - * Called when providing placeholder values to Discord bot presence messages. - */ - Event EVENT = - EventFactory.createArrayBacked(PresencePlaceholderCallback.class, callbacks -> (st, stage, jda, server) -> { - for (PresencePlaceholderCallback callback : callbacks) { - callback.onPresencePlaceholder(st, stage, jda, server); - } - }); - - /** - * Called when providing placeholder values to Discord bot presence messages. - * - * @param template mutable string template - * @param category Minecord presence category - * @param jda JDA client - * @param server Minecraft server, if present - */ - void onPresencePlaceholder( - StringTemplate template, PresenceCategory category, JDA jda, @Nullable MinecraftServer server - ); -} diff --git a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/PresenceUpdateTask.java b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/PresenceUpdateTask.java index 54814b7..589ef6f 100644 --- a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/PresenceUpdateTask.java +++ b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/PresenceUpdateTask.java @@ -3,9 +3,13 @@ import java.lang.management.ManagementFactory; import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.TimerTask; +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.PlaceholderHandler; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.entities.Activity; @@ -13,13 +17,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import net.minecraft.server.MinecraftServer; - import me.axieum.mcmod.minecord.api.Minecord; import me.axieum.mcmod.minecord.api.presence.category.PresenceCategory; import me.axieum.mcmod.minecord.api.presence.category.PresenceSupplier; -import me.axieum.mcmod.minecord.api.presence.event.PresencePlaceholderCallback; -import me.axieum.mcmod.minecord.api.util.StringTemplate; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import static me.axieum.mcmod.minecord.impl.presence.MinecordPresenceImpl.LOGGER; /** @@ -51,49 +52,29 @@ public void run() final PresenceSupplier supplier = category.getPresenceSupplier(); /* - * Prepare a message template. + * Prepare the message placeholders. */ - final StringTemplate st = new StringTemplate(); - - // The total process uptime (to the nearest minute) - st.add("uptime", Duration.ofMillis( - ManagementFactory.getRuntimeMXBean().getUptime() - ).truncatedTo(ChronoUnit.MINUTES)); - - // The Minecraft server, if present - final @Nullable MinecraftServer server = Minecord.getInstance().getMinecraft().orElse(null); - if (server != null) { - // The server version - st.add("version", server.getVersion()); - // The server IP address - st.add("ip", server.getServerIp()); - // The server port - st.add("port", server.getServerPort()); - // The server message-of-the-day (MOTD) - st.add("motd", server.getServerMotd()); - // The world difficulty - st.add("difficulty", server.getSaveProperties().getDifficulty().getName()); - // The server player counts, if loaded - if (server.getPlayerManager() != null) { - // The server max player count - st.add("max_players", server.getMaxPlayerCount()); - // The server current player count - st.add("player_count", server.getCurrentPlayerCount()); - } - } - - PresencePlaceholderCallback.EVENT.invoker().onPresencePlaceholder(st, category, jda, server); + final Optional ctx = Minecord.getInstance().getMinecraft().map(PlaceholderContext::of); + final Map placeholders = Map.of( + // The total process uptime (to the nearest minute) + "uptime", PlaceholdersExt.duration(() -> + Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime()).truncatedTo(ChronoUnit.MINUTES) + ) + ); /* * Build the various presence components. */ final Presence current = jda.getPresence(); + final @Nullable Activity currentActivity = current.getActivity(); final Boolean idle = supplier.isIdle().orElse(current.isIdle()); - final OnlineStatus status = supplier.getStatus().orElse(current.getStatus()); - final Activity activity = supplier.getActivity(st).orElse(current.getActivity()); + final @Nullable OnlineStatus status = supplier.getStatus().orElse(current.getStatus()); + final @Nullable Activity activity = supplier.getActivity( + name -> PlaceholdersExt.parseString(name, ctx.orElse(null), placeholders) + ).orElse(currentActivity); /* * Update the bot. @@ -103,14 +84,21 @@ public void run() if ( idle != current.isIdle() || status != current.getStatus() - || activity.getType() != current.getActivity().getType() - || !Objects.equals(activity.getName(), current.getActivity().getName()) - || !Objects.equals(activity.getUrl(), current.getActivity().getUrl()) + || activity != null && currentActivity != null + && ( + activity.getType() != currentActivity.getType() + || !Objects.equals(activity.getName(), currentActivity.getName()) + || !Objects.equals(activity.getUrl(), currentActivity.getUrl()) + ) ) { - LOGGER.debug( - "Updating the Discord bot presence to: [idle={}] [status={}] {}... {} ({})", - idle, status, activity.getType().name(), activity.getName(), activity.getUrl() - ); + if (activity != null) { + LOGGER.debug( + "Updating the Discord bot presence to: [idle={}] [status={}] {}... {} ({})", + idle, status, activity.getType().name(), activity.getName(), activity.getUrl() + ); + } else { + LOGGER.debug("Updating the Discord bot presence to: [idle={}] [status={}]", idle, status); + } current.setPresence(status, activity, idle); } else { LOGGER.debug("Skipping Discord bot presence update as no change was detected!"); diff --git a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/config/PresenceConfig.java b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/config/PresenceConfig.java index f4c8017..27c54d4 100644 --- a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/config/PresenceConfig.java +++ b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/config/PresenceConfig.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.function.Function; import me.shedaniel.autoconfig.AutoConfig; import me.shedaniel.autoconfig.ConfigData; @@ -22,7 +23,6 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import me.axieum.mcmod.minecord.api.presence.category.PresenceSupplier; -import me.axieum.mcmod.minecord.api.util.StringTemplate; import me.axieum.mcmod.minecord.impl.presence.MinecordPresenceImpl; import static me.axieum.mcmod.minecord.impl.presence.MinecordPresenceImpl.LOGGER; @@ -186,11 +186,11 @@ public Optional isIdle() } @Override - public Optional getActivity(StringTemplate template) + public Optional getActivity(Function nameMutator) { - return activity != null ? Optional.of( - Activity.of(activity.type, template.format(activity.name), activity.url) - ) : Optional.empty(); + return Optional.ofNullable(activity).map(activity -> + Activity.of(activity.type, nameMutator.apply(activity.name), activity.url) + ); } }; } diff --git a/minecord-presence/src/main/resources/fabric.mod.json b/minecord-presence/src/main/resources/fabric.mod.json index 1078e38..6abb15d 100644 --- a/minecord-presence/src/main/resources/fabric.mod.json +++ b/minecord-presence/src/main/resources/fabric.mod.json @@ -32,6 +32,7 @@ "minecraft": "~1.19.1", "fabricloader": ">=0.11.3", "fabric-lifecycle-events-v1": "*", - "minecord-api": "*" + "minecord-api": "*", + "placeholder-api": "*" } } From d2fc0c32d8a2b79d624c36e380a6be96ae08540b Mon Sep 17 00:00:00 2001 From: Jonathan Hiles Date: Sat, 1 Apr 2023 21:05:54 +1000 Subject: [PATCH 2/7] deps: upgrade all dependencies --- build.gradle | 6 +++--- gradle.properties | 20 +++++++++---------- minecord-api/gradle.properties | 8 ++++---- .../src/main/resources/fabric.mod.json | 4 ++-- .../src/main/resources/fabric.mod.json | 4 ++-- .../src/main/resources/fabric.mod.json | 4 ++-- .../src/main/resources/fabric.mod.json | 4 ++-- src/main/resources/fabric.mod.json | 4 ++-- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/build.gradle b/build.gradle index 42a2de2..ca8e41c 100644 --- a/build.gradle +++ b/build.gradle @@ -2,10 +2,10 @@ plugins { id 'java' id 'checkstyle' id 'maven-publish' - id 'com.modrinth.minotaur' version '2.3.1' + id 'com.modrinth.minotaur' version '2.7.5' id 'com.matthewprenger.cursegradle' version '1.4.0' id 'com.github.johnrengelman.shadow' version '7.1.2' - id 'fabric-loom' version '1.0-SNAPSHOT' + id 'fabric-loom' version '1.1-SNAPSHOT' } allprojects { @@ -30,7 +30,7 @@ allprojects { modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" // Mods - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + // modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" // Code Quality compileOnly "org.jetbrains:annotations:${project.jetbrains_annotations_version}" diff --git a/gradle.properties b/gradle.properties index d54cfbb..893a9e2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,19 +7,19 @@ mod_version = 1.1.0-beta.3 ## {x-release-please-end} # Fabric -minecraft_version = 1.19.2 -loader_version = 0.14.9 -yarn_mappings = 1.19.2+build.20 -fabric_version = 0.62.0+1.19.2 +minecraft_version = 1.19.4 +loader_version = 0.14.18 +yarn_mappings = 1.19.4+build.1 +fabric_version = 0.76.0+1.19.4 # Dependencies -checkstyle_version = 10.3.4 -jetbrains_annotations_version = 23.0.0 -junit_jupiter_version = 5.9.1 +checkstyle_version = 10.9.3 +jetbrains_annotations_version = 24.0.1 +junit_jupiter_version = 5.9.2 # CurseForge cf_project_id = 502254 -cf_game_versions = Fabric, Java 17, 1.19.1, 1.19.2 +cf_game_versions = Fabric, Java 17, 1.19.1, 1.19.2, 1.19.3, 1.19.4 cf_relations_required = fabric-api cf_relations_optional = cf_relations_embedded = cloth-config @@ -28,7 +28,7 @@ cf_relations_incompatible = # Modrinth mr_project_id = DoVQa3oa -mr_game_versions = 1.19.1, 1.19.2 +mr_game_versions = 1.19.1, 1.19.2, 1.19.3, 1.19.4 mr_relations_required = P7dR8mSH mr_relations_optional = mr_relations_incompatible = @@ -38,4 +38,4 @@ maven_group = me.axieum.mcmod.minecord github_repo = axieum/minecord # Other -org.gradle.jvmargs=-Xmx2G +org.gradle.jvmargs=-Xmx3G diff --git a/minecord-api/gradle.properties b/minecord-api/gradle.properties index 9fe8578..06d1104 100644 --- a/minecord-api/gradle.properties +++ b/minecord-api/gradle.properties @@ -7,8 +7,8 @@ mod_version = 1.1.0-beta.3 ## {x-release-please-end} # Dependencies -cloth_config_version = 8.2.88 +cloth_config_version = 10.0.96 emoji_java_version = 5.1.1 -jda_version = 5.0.0-alpha.21 -log4j_version = 2.19.0 -placeholder_api_version = 2.0.0-beta.7+1.19 +jda_version = 5.0.0-beta.6 +log4j_version = 2.20.0 +placeholder_api_version = 2.0.0-rc.1+1.19.3 diff --git a/minecord-api/src/main/resources/fabric.mod.json b/minecord-api/src/main/resources/fabric.mod.json index e3736e9..8f081bc 100644 --- a/minecord-api/src/main/resources/fabric.mod.json +++ b/minecord-api/src/main/resources/fabric.mod.json @@ -35,8 +35,8 @@ ], "depends": { "java": ">=17", - "minecraft": "~1.19.1", - "fabricloader": ">=0.11.3", + "minecraft": "~1.19.4", + "fabricloader": ">=0.14.17", "fabric-lifecycle-events-v1": "*", "placeholder-api": "*" } diff --git a/minecord-chat/src/main/resources/fabric.mod.json b/minecord-chat/src/main/resources/fabric.mod.json index 6b37807..19fd7dd 100644 --- a/minecord-chat/src/main/resources/fabric.mod.json +++ b/minecord-chat/src/main/resources/fabric.mod.json @@ -35,8 +35,8 @@ ], "depends": { "java": ">=17", - "minecraft": "~1.19.1", - "fabricloader": ">=0.11.3", + "minecraft": "~1.19.4", + "fabricloader": ">=0.14.17", "fabric-entity-events-v1": "*", "fabric-lifecycle-events-v1": "*", "fabric-message-api-v1": "*", diff --git a/minecord-cmds/src/main/resources/fabric.mod.json b/minecord-cmds/src/main/resources/fabric.mod.json index 0a04250..73a7e52 100644 --- a/minecord-cmds/src/main/resources/fabric.mod.json +++ b/minecord-cmds/src/main/resources/fabric.mod.json @@ -29,8 +29,8 @@ }, "depends": { "java": ">=17", - "minecraft": "~1.19.1", - "fabricloader": ">=0.11.3", + "minecraft": "~1.19.4", + "fabricloader": ">=0.14.17", "fabric-lifecycle-events-v1": "*", "minecord-api": "*", "placeholder-api": "*" diff --git a/minecord-presence/src/main/resources/fabric.mod.json b/minecord-presence/src/main/resources/fabric.mod.json index 6abb15d..7125a6e 100644 --- a/minecord-presence/src/main/resources/fabric.mod.json +++ b/minecord-presence/src/main/resources/fabric.mod.json @@ -29,8 +29,8 @@ }, "depends": { "java": ">=17", - "minecraft": "~1.19.1", - "fabricloader": ">=0.11.3", + "minecraft": "~1.19.4", + "fabricloader": ">=0.14.17", "fabric-lifecycle-events-v1": "*", "minecord-api": "*", "placeholder-api": "*" diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 7647d25..4aeee0a 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -24,8 +24,8 @@ "environment": "server", "depends": { "java": ">=17", - "minecraft": "~1.19.1", - "fabricloader": ">=0.11.3" + "minecraft": "~1.19.4", + "fabricloader": ">=0.14.18" }, "recommends": { "minecord-api": "*", From b6770f6c04b31342e50d332072fb4049c25bf564 Mon Sep 17 00:00:00 2001 From: Jonathan Hiles Date: Mon, 29 May 2023 20:53:19 +1000 Subject: [PATCH 3/7] build: fix shadow-jar distribution --- build.gradle | 6 +++--- gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 19 ++++++++++++------- gradlew.bat | 1 + minecord-api/build.gradle | 2 ++ minecord-api/gradle.properties | 4 ++-- minecord-chat/build.gradle | 5 ++++- minecord-cmds/build.gradle | 2 +- minecord-presence/build.gradle | 2 +- 9 files changed, 28 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index ca8e41c..4b74d32 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'com.modrinth.minotaur' version '2.7.5' id 'com.matthewprenger.cursegradle' version '1.4.0' id 'com.github.johnrengelman.shadow' version '7.1.2' - id 'fabric-loom' version '1.1-SNAPSHOT' + id 'fabric-loom' version '1.2-SNAPSHOT' } allprojects { @@ -30,7 +30,7 @@ allprojects { modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" // Mods - // modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" +// modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" // Code Quality compileOnly "org.jetbrains:annotations:${project.jetbrains_annotations_version}" @@ -164,7 +164,7 @@ allprojects { dependencies { subprojects.each { implementation project(path: ":${it.name}", configuration: 'namedElements') - implementation project(path: ":${it.name}", configuration: 'shadow') + implementation project(path: ":${it.name}", configuration: 'shade') include project("${it.name}:") } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661..37aef8d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index a69d9cb..aeb74cb 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -143,12 +140,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,6 +194,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/gradlew.bat b/gradlew.bat index f127cfd..93e3f59 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% diff --git a/minecord-api/build.gradle b/minecord-api/build.gradle index 7ae6929..56091bd 100644 --- a/minecord-api/build.gradle +++ b/minecord-api/build.gradle @@ -1,5 +1,7 @@ // Declare dependencies dependencies { + modApi include(fabricApi.module("fabric-api-base", project.fabric_version)) + modApi include(fabricApi.module("fabric-lifecycle-events-v1", project.fabric_version)) modApi include("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}") { exclude module: 'modmenu' } diff --git a/minecord-api/gradle.properties b/minecord-api/gradle.properties index 06d1104..ffbd6e9 100644 --- a/minecord-api/gradle.properties +++ b/minecord-api/gradle.properties @@ -9,6 +9,6 @@ mod_version = 1.1.0-beta.3 # Dependencies cloth_config_version = 10.0.96 emoji_java_version = 5.1.1 -jda_version = 5.0.0-beta.6 +jda_version = 5.0.0-beta.9 log4j_version = 2.20.0 -placeholder_api_version = 2.0.0-rc.1+1.19.3 +placeholder_api_version = 2.1.0+1.19.4 diff --git a/minecord-chat/build.gradle b/minecord-chat/build.gradle index e6a9da9..e9598c2 100644 --- a/minecord-chat/build.gradle +++ b/minecord-chat/build.gradle @@ -1,6 +1,9 @@ // Declare dependencies dependencies { implementation project(path: ':minecord-api', configuration: 'namedElements') - implementation project(path: ':minecord-api', configuration: 'shadow') + implementation project(path: ':minecord-api', configuration: 'shade') + modApi include(fabricApi.module("fabric-entity-events-v1", project.fabric_version)) + modApi include(fabricApi.module("fabric-message-api-v1", project.fabric_version)) + modApi include(fabricApi.module("fabric-networking-api-v1", project.fabric_version)) implementation shade("io.github.java-diff-utils:java-diff-utils:${project.java_diff_utils_version}") } diff --git a/minecord-cmds/build.gradle b/minecord-cmds/build.gradle index b2ac044..c8bc210 100644 --- a/minecord-cmds/build.gradle +++ b/minecord-cmds/build.gradle @@ -1,5 +1,5 @@ // Declare dependencies dependencies { implementation project(path: ':minecord-api', configuration: 'namedElements') - implementation project(path: ':minecord-api', configuration: 'shadow') + implementation project(path: ':minecord-api', configuration: 'shade') } diff --git a/minecord-presence/build.gradle b/minecord-presence/build.gradle index b2ac044..c8bc210 100644 --- a/minecord-presence/build.gradle +++ b/minecord-presence/build.gradle @@ -1,5 +1,5 @@ // Declare dependencies dependencies { implementation project(path: ':minecord-api', configuration: 'namedElements') - implementation project(path: ':minecord-api', configuration: 'shadow') + implementation project(path: ':minecord-api', configuration: 'shade') } From 02ecc8b191eccfa870d048e1b662c1f0965c67eb Mon Sep 17 00:00:00 2001 From: Jonathan Hiles Date: Sun, 4 Jun 2023 01:07:37 +1000 Subject: [PATCH 4/7] feat: apply placeholder api everywhere feat(chat): separate advancement config into task, goal and challenge --- .../minecord/api/util/PlaceholdersExt.java | 51 +++- .../mcmod/minecord/api/util/StringUtils.java | 71 +---- .../minecord/impl/config/I18nConfig.java | 8 - .../minecord/api/util/StringUtilsTests.java | 154 +---------- .../discord/MessageReactionListener.java | 12 +- .../discord/MessageReceivedListener.java | 5 +- .../discord/MessageUpdateListener.java | 8 +- .../minecraft/EntityDeathCallback.java | 6 +- .../minecraft/PlayerAdvancementCallback.java | 37 ++- .../minecraft/PlayerChangeWorldCallback.java | 16 +- .../minecraft/PlayerConnectionCallback.java | 33 +-- .../minecraft/PlayerDeathCallback.java | 13 - .../minecraft/ServerMessageCallback.java | 29 +-- .../minecord/impl/chat/config/ChatConfig.java | 244 ++++++++++++------ .../cmds/callback/DiscordCommandListener.java | 9 +- .../cmds/command/discord/CustomCommand.java | 14 +- .../cmds/command/discord/UptimeCommand.java | 3 +- .../impl/cmds/config/CommandConfig.java | 34 ++- .../impl/presence/PresenceUpdateTask.java | 3 +- .../impl/presence/config/PresenceConfig.java | 17 +- 20 files changed, 316 insertions(+), 451 deletions(-) diff --git a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java index 7deb56d..1317596 100644 --- a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java +++ b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java @@ -11,10 +11,12 @@ import eu.pb4.placeholders.api.PlaceholderResult; import eu.pb4.placeholders.api.Placeholders; import eu.pb4.placeholders.api.Placeholders.PlaceholderGetter; -import eu.pb4.placeholders.api.TextParserUtils; import eu.pb4.placeholders.api.node.EmptyNode; import eu.pb4.placeholders.api.node.TextNode; import eu.pb4.placeholders.api.node.parent.ParentNode; +import eu.pb4.placeholders.api.parsers.MarkdownLiteParserV1; +import eu.pb4.placeholders.api.parsers.NodeParser; +import eu.pb4.placeholders.api.parsers.PatternPlaceholderParser; import eu.pb4.placeholders.api.parsers.TextParserV1; import org.apache.commons.lang3.time.DurationFormatUtils; import org.jetbrains.annotations.NotNull; @@ -31,6 +33,13 @@ public final class PlaceholdersExt { /** The placeholder pattern used. */ public static final Pattern PLACEHOLDER_PATTERN = Placeholders.PREDEFINED_PLACEHOLDER_PATTERN; + /** The placeholder node parser. */ + public static final NodeParser NODE_PARSER = NodeParser.merge( + TextParserV1.DEFAULT, + PatternPlaceholderParser.of( + PLACEHOLDER_PATTERN, PlaceholderContext.KEY, Placeholders.DEFAULT_PLACEHOLDER_GETTER + ) + ); private PlaceholdersExt() {} @@ -101,9 +110,7 @@ public static Text parseText( public static @NotNull TextNode parseNode(@Nullable Text text) { - return text != null ? new ParentNode( - TextParserV1.DEFAULT.parseNodes(TextNode.convert(text)) - ) : EmptyNode.INSTANCE; + return text != null ? new ParentNode(NODE_PARSER.parseNodes(TextNode.convert(text))) : EmptyNode.INSTANCE; } /* @@ -148,6 +155,15 @@ public static String parseString( return parseString(parseNode(str), context, placeholderGetter); } + public static String parseString( + @NotNull TextNode node, + @Nullable PlaceholderContext context, + @NotNull Map placeholders + ) + { + return parseText(node, context, placeholders::get).getString(); + } + public static String parseString( @NotNull TextNode node, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter ) @@ -157,9 +173,7 @@ public static String parseString( public static @NotNull TextNode parseNode(@Nullable String string) { - return string != null && !string.isEmpty() ? Placeholders.parseNodes( - TextParserUtils.formatNodes(string) - ) : EmptyNode.INSTANCE; + return string != null && !string.isEmpty() ? NODE_PARSER.parseNode(string) : EmptyNode.INSTANCE; } /* @@ -177,6 +191,29 @@ public static PlaceholderHandler string(final String string) return (ctx, arg) -> PlaceholderResult.value(string); } + /** + * Returns a {@link Text} placeholder handler. + * + * @param text Minecraft text + * @return text placeholder handler + */ + public static PlaceholderHandler text(final Text text) + { + return (ctx, arg) -> PlaceholderResult.value(text); + } + + /** + * Returns a {@link Text} markdown placeholder handler. + * + * @param markdown markdown string + * @return markdown placeholder handler + */ + public static PlaceholderHandler markdown(final String markdown) + { + final Text markdownText = MarkdownLiteParserV1.ALL.parseText(markdown, ParserContext.of()); + return (ctx, arg) -> PlaceholderResult.value(markdownText); + } + /** * Returns a {@link Duration} placeholder handler. * diff --git a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/StringUtils.java b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/StringUtils.java index 1f13714..ed8f167 100644 --- a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/StringUtils.java +++ b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/StringUtils.java @@ -8,10 +8,8 @@ import java.util.regex.MatchResult; import java.util.regex.Pattern; -import com.vdurmont.emoji.EmojiParser; import net.dv8tion.jda.api.entities.IMentionable; -import net.minecraft.advancement.AdvancementFrame; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.world.World; @@ -27,7 +25,7 @@ public final class StringUtils /** Mapping of Minecraft world identifiers to their human-readable names. */ public static final HashMap WORLD_NAMES = new HashMap<>(3); /** String templates for translating between Minecraft and Discord formatted strings. */ - public static StringTemplate discordMinecraftST, minecraftDiscordST; + public static StringTemplate minecraftDiscordST; private StringUtils() {} @@ -48,46 +46,16 @@ public static String bytesToHuman(long bytes) return String.format("%.1f %cB", bytes / 1000.0, ci.current()); } - static { - final Pattern bold = Pattern.compile("\\*\\*(.+?)\\*\\*"); - final Pattern underline = Pattern.compile("__(.+?)__"); - final Pattern italics = Pattern.compile("_(.+?)_"); - final Pattern italics2 = Pattern.compile("\\*(.+?)\\*"); - final Pattern strike = Pattern.compile("~~(.+?)~~"); - final Pattern spoilers = Pattern.compile("\\|\\|(.+?)\\|\\|"); - final Pattern code = Pattern.compile("(?s)```(\\w+?)\\n(.*?)```"); - final Pattern code2 = Pattern.compile("(?s)```(.*?)```"); - final Pattern code3 = Pattern.compile("(?s)`(.*?)`"); - discordMinecraftST = new StringTemplate() - // Translate bold - .transform(s -> bold.matcher(s).replaceAll("\u00A7l$1\u00A7r")) - // Translate underline - .transform(s -> underline.matcher(s).replaceAll("\u00A7n$1\u00A7r")) - // Translate italics - .transform(s -> italics.matcher(s).replaceAll("\u00A7o$1\u00A7r")) - .transform(s -> italics2.matcher(s).replaceAll("\u00A7o$1\u00A7r")) - // Translate strikethrough - .transform(s -> strike.matcher(s).replaceAll("\u00A7m$1\u00A7r")) - // Obfuscate spoilers - .transform(s -> spoilers.matcher(s).replaceAll("\u00A7k$1\u00A7r")) - // Darken code blocks - .transform(s -> code.matcher(s).replaceAll("($1) \u00A77$2\u00A7r")) - .transform(s -> code2.matcher(s).replaceAll("\u00A77$1\u00A7r")) - .transform(s -> code3.matcher(s).replaceAll("\u00A77$1\u00A7r")) - // Translate emojis from unicode - .transform(EmojiParser::parseToAliases); - } - static { final Pattern breaks = Pattern.compile("(?s)\\n+"); - final Pattern bold = Pattern.compile("(?<=[\u00A7]l)(.+?)(?=\\s?[\u00A7]r|$)"); - final Pattern underline = Pattern.compile("(?<=[\u00A7]n)(.+?)(?=\\s?[\u00A7]r|$)"); - final Pattern italics = Pattern.compile("(?<=[\u00A7]o)(.+?)(?=\\s?[\u00A7]r|$)"); - final Pattern strike = Pattern.compile("(?<=[\u00A7]m)(.+?)(?=\\s?[\u00A7]r|$)"); - final Pattern spoilers = Pattern.compile("(?<=[\u00A7]k)(.+?)(?=\\s?[\u00A7]r|$)"); + final Pattern bold = Pattern.compile("(?<=§l)(.+?)(?=\\s?§r|$)"); + final Pattern underline = Pattern.compile("(?<=§n)(.+?)(?=\\s?§r|$)"); + final Pattern italics = Pattern.compile("(?<=§o)(.+?)(?=\\s?§r|$)"); + final Pattern strike = Pattern.compile("(?<=§m)(.+?)(?=\\s?§r|$)"); + final Pattern spoilers = Pattern.compile("(?<=§k)(.+?)(?=\\s?§r|$)"); final Pattern mention = Pattern.compile("@(\\w+?)#(\\d{4})"); final Pattern mention2 = Pattern.compile("@((?!everyone|here)\\w+)(?!#\\d{4})\\b"); - final Pattern channel = Pattern.compile("#([^\\s]+)"); + final Pattern channel = Pattern.compile("#(\\S+)"); final Function resolveMention = m -> Minecord.getInstance().getJDA() .flatMap(jda -> Optional.ofNullable(jda.getUserByTag(m.group(1), m.group(2)))) @@ -132,19 +100,6 @@ public static String bytesToHuman(long bytes) .transform(Formatting::strip); } - /** - * Translates a Discord flavoured markdown string to a - * Minecraft-formatted string. - * - * @param contents Discord flavoured markdown string - * @return Minecraft-formatted string - */ - public static String discordToMinecraft(final String contents) - { - // Apply the appropriate string template against the given contents and return - return discordMinecraftST.format(contents); - } - /** * Translates a Minecraft-formatted string to Discord flavoured markdown. * @@ -199,16 +154,4 @@ public static String deriveWorldName(final Identifier identifier) return new String(chars); }); } - - /** - * Attempts to retrieve the advancement type name from the config files - * first, otherwise uses it symbol name. - * - * @param type Minecraft advancement frame/type - * @return name of the advancement type - */ - public static String getAdvancementTypeName(final AdvancementFrame type) - { - return getConfig().i18n.advancementTypes.getOrDefault(type.getId(), type.getId()); - } } diff --git a/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/config/I18nConfig.java b/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/config/I18nConfig.java index fb8434d..8b792fe 100644 --- a/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/config/I18nConfig.java +++ b/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/config/I18nConfig.java @@ -28,12 +28,4 @@ public class I18nConfig implements ConfigData Map.entry("minecraft:the_nether", "Nether"), Map.entry("minecraft:the_end", "The End") )); - - /** A mapping of advancement types to their respective names. */ - @Comment("A mapping of advancement types to their respective names") - public Map advancementTypes = new HashMap<>(Map.ofEntries( - Map.entry("task", "task"), - Map.entry("challenge", "challenge"), - Map.entry("goal", "goal") - )); } diff --git a/minecord-api/src/test/java/me/axieum/mcmod/minecord/api/util/StringUtilsTests.java b/minecord-api/src/test/java/me/axieum/mcmod/minecord/api/util/StringUtilsTests.java index 6e93afc..2b4a4b4 100644 --- a/minecord-api/src/test/java/me/axieum/mcmod/minecord/api/util/StringUtilsTests.java +++ b/minecord-api/src/test/java/me/axieum/mcmod/minecord/api/util/StringUtilsTests.java @@ -33,162 +33,10 @@ public void bytesToHuman() assertEquals("1.4 EB", StringUtils.bytesToHuman(1427965224842685628L)); } - @Nested - @DisplayName("Translate Discord flavoured markdown to Minecraft-formatted text") - public class DiscordToMinecraft - { - @Test - @DisplayName("Translate bold text") - public void bold() - { - assertEquals( - "This is \u00A7lbold text\u00A7r!", - StringUtils.discordToMinecraft("This is **bold text**!") - ); - } - - @Test - @DisplayName("Translate underlined text") - public void underline() - { - assertEquals( - "This is \u00A7nunderlined text\u00A7r!", - StringUtils.discordToMinecraft("This is __underlined text__!") - ); - } - - @Test - @DisplayName("Translate italicised text") - public void italics() - { - assertEquals( - "This is \u00A7oitalicised text\u00A7r!", - StringUtils.discordToMinecraft("This is _italicised text_!") - ); - assertEquals( - "This is \u00A7oitalicised text\u00A7r!", - StringUtils.discordToMinecraft("This is *italicised text*!") - ); - } - - @Test - @DisplayName("Translate strikethrough text") - public void strikethrough() - { - assertEquals( - "This is \u00A7mstrikethrough text\u00A7r!", - StringUtils.discordToMinecraft("This is ~~strikethrough text~~!") - ); - } - - @Test - @DisplayName("Obfuscate spoilers") - public void spoilers() - { - assertEquals( - "This is \u00A7ksecret text\u00A7r!", - StringUtils.discordToMinecraft("This is ||secret text||!") - ); - } - - @Test - @DisplayName("Darken code blocks") - public void code() - { - final String json = """ - { - "enabled": true, - "command": "whitelist" - }"""; - assertEquals( - "This is (json) \u00A77" + json + "\u00A7r text!", - StringUtils.discordToMinecraft("This is ```json\n" + json + "``` text!") - ); - assertEquals( - "This is \u00A77" + json + "\u00A7r text!", - StringUtils.discordToMinecraft("This is ```" + json + "``` text!") - ); - assertEquals( - "This is \u00A77inline code\u00A7r!", - StringUtils.discordToMinecraft("This is `inline code`!") - ); - } - - @Test - @DisplayName("Translate emojis from unicode formatted text") - public void emojis() - { - assertEquals( - "This is a smiley :slightly_smiling: face!", - StringUtils.discordToMinecraft("This is a smiley \uD83D\uDE42 face!") - ); - } - } - @Nested @DisplayName("Translate Minecraft-formatted text to Discord flavoured markdown") public class MinecraftToDiscord { - @Test - @DisplayName("Collapse line breaks") - public void collapseLineBreaks() - { - assertEquals( - "There are no line breaks in this text!", - StringUtils.minecraftToDiscord("There are no line\nbreaks in\n\nthis text!") - ); - } - - @Test - @DisplayName("Translate bold text") - public void bold() - { - assertEquals( - "This is **bold text**!", - StringUtils.minecraftToDiscord("This is \u00A7lbold text\u00A7r!") - ); - } - - @Test - @DisplayName("Translate underline text") - public void underline() - { - assertEquals( - "This is __underlined text__!", - StringUtils.minecraftToDiscord("This is \u00A7nunderlined text\u00A7r!") - ); - } - - @Test - @DisplayName("Translate italics text") - public void italics() - { - assertEquals( - "This is _italicised text_!", - StringUtils.minecraftToDiscord("This is \u00A7oitalicised text\u00A7r!") - ); - } - - @Test - @DisplayName("Translate strikethrough text") - public void strikethrough() - { - assertEquals( - "This is ~~strikethrough text~~!", - StringUtils.minecraftToDiscord("This is \u00A7mstrikethrough text\u00A7r!") - ); - } - - @Test - @DisplayName("Obfuscate spoilers") - public void spoilers() - { - assertEquals( - "This is ||secret text||!", - StringUtils.minecraftToDiscord("This is \u00A7ksecret text\u00A7r!") - ); - } - @Test @DisplayName("Suppress @everyone and @here mentions") public void suppressGlobalMentions() @@ -209,7 +57,7 @@ public void stripFormatting() { assertEquals( "This is green text!", - StringUtils.minecraftToDiscord("This is \u00A7agreen\u00A7r text!") + StringUtils.minecraftToDiscord("This is §agreen§r text!") ); } } diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java index 35971ec..b7a43b0 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java @@ -53,14 +53,14 @@ public void onGenericMessageReaction(GenericMessageReactionEvent event) event.getMember() != null ? event.getMember().getEffectiveName() : event.getUser().getName() ), // The author's tag (i.e. username#discriminator), e.g. Axieum#1001 - "author_tag", string(event.getUser().getAsTag()), + "author_tag", string(context.getAuthor().getAsTag()), // The author's username, e.g. Axieum - "author_username", string(event.getUser().getName()), + "author_username", string(context.getAuthor().getName()), // The author's username discriminator, e.g. 1001 - "author_discriminator", string(event.getUser().getDiscriminator()), + "author_discriminator", string(context.getAuthor().getDiscriminator()), // The author's nickname or username "author", string( - event.getMember() != null ? event.getMember().getEffectiveName() : event.getUser().getName() + context.getMember() != null ? context.getMember().getEffectiveName() : context.getAuthor().getName() ), // The emote used to react "emote", string(emote) @@ -77,7 +77,7 @@ public void onGenericMessageReaction(GenericMessageReactionEvent event) entry -> entry.minecraft.react != null && entry.id == channelId ); LOGGER.info(PlaceholdersExt.parseString( - "@${issuer_tag} reacted with ${emote} to ${author_tag}'s message", ctx, placeholders::get + "@${issuer_tag} reacted with ${emote} to ${author_tag}'s message", ctx, placeholders )); // A user removed their reaction from a recent message @@ -89,7 +89,7 @@ public void onGenericMessageReaction(GenericMessageReactionEvent event) LOGGER.info(PlaceholdersExt.parseString( "@${issuer_tag} removed their reaction of ${emote} from ${author_tag}'s message", ctx, - placeholders::get + placeholders )); } }); diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java index 23e45a5..4a10ad3 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java @@ -13,6 +13,7 @@ import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.MinecraftDispatcher; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.markdown; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.LOGGER; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.getConfig; @@ -65,7 +66,7 @@ public void onText(MessageReceivedEvent event) event.getMember() != null ? event.getMember().getEffectiveName() : event.getAuthor().getName() ), // The formatted message contents - "message", string(StringUtils.discordToMinecraft(event.getMessage().getContentDisplay())), + "message", markdown(event.getMessage().getContentDisplay()), // The raw message contents "raw", string(event.getMessage().getContentRaw()) )); @@ -86,7 +87,7 @@ public void onText(MessageReceivedEvent event) : replyMessage.getAuthor().getName() ), // The replied message formatted message contents - "reply_message", string(StringUtils.discordToMinecraft(replyMessage.getContentDisplay())), + "reply_message", markdown(replyMessage.getContentDisplay()), // The replied message raw message contents "reply_raw", string(replyMessage.getContentRaw()) )); diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java index 9a50aeb..28a1bf1 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java @@ -19,8 +19,8 @@ import net.minecraft.util.Formatting; import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; -import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.MinecraftDispatcher; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.markdown; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.LOGGER; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.getConfig; @@ -74,15 +74,15 @@ public void onMessageUpdate(MessageUpdateEvent event) event.getMember() != null ? event.getMember().getEffectiveName() : event.getAuthor().getName() ), // The old formatted message contents - "original", string(StringUtils.discordToMinecraft(original)), + "original", markdown(original), // The old raw message contents "original_raw", string(context.getContentRaw()), // The new formatted message contents - "message", string(StringUtils.discordToMinecraft(event.getMessage().getContentDisplay())), + "message", markdown(event.getMessage().getContentDisplay()), // The new raw message contents "raw", string(event.getMessage().getContentRaw()), // The difference between the original and new message - "diff", string(StringUtils.discordToMinecraft(diffs.get(0).getOldLine())) + "diff", markdown(diffs.get(0).getOldLine()) ); /* diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java index 0131252..2506628 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java @@ -43,11 +43,11 @@ public void onEntityDeath(LivingEntity entity, DamageSource source) // The name of the world the entity died in "world", string(StringUtils.getWorldName(entity.world)), // The X coordinate of where the entity died - "x", string(String.valueOf((int) entity.prevX)), + "pos_x", string(String.valueOf((int) entity.prevX)), // The Y coordinate of where the entity died - "y", string(String.valueOf((int) entity.prevY)), + "pos_y", string(String.valueOf((int) entity.prevY)), // The Z coordinate of where the entity died - "z", string(String.valueOf((int) entity.prevZ)) + "pos_z", string(String.valueOf((int) entity.prevZ)) ); /* diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerAdvancementCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerAdvancementCallback.java index 08b240a..25f83c2 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerAdvancementCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerAdvancementCallback.java @@ -12,7 +12,6 @@ import me.axieum.mcmod.minecord.api.Minecord; import me.axieum.mcmod.minecord.api.chat.event.minecraft.GrantCriterionCallback; import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; -import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; @@ -35,12 +34,6 @@ public void onGrantCriterion(ServerPlayerEntity player, Advancement advancement, final PlaceholderContext ctx = PlaceholderContext.of(player); final Map placeholders = Map.of( - // The player's username - "username", string(player.getName().getString()), - // The player's display name - "player", string(player.getDisplayName().getString()), - // The type of advancement - "type", string(StringUtils.getAdvancementTypeName(info.getFrame())), // The title of the advancement "title", string(info.getTitle().getString()), // A description of the advancement @@ -51,11 +44,31 @@ public void onGrantCriterion(ServerPlayerEntity player, Advancement advancement, * Dispatch the message. */ - DiscordDispatcher.embed( - (embed, entry) -> embed.setDescription( - PlaceholdersExt.parseString(entry.discord.advancement, ctx, placeholders) - ), - entry -> entry.discord.advancement != null && entry.hasWorld(player.world)); + switch (info.getFrame()) { + // A player reached an advancement goal + case GOAL -> DiscordDispatcher.embed( + (embed, entry) -> embed.setDescription( + PlaceholdersExt.parseString(entry.discord.advancementGoal, ctx, placeholders) + ), + entry -> entry.discord.advancementGoal != null && entry.hasWorld(player.world) + ); + + // A player completed an advancement challenge + case CHALLENGE -> DiscordDispatcher.embed( + (embed, entry) -> embed.setDescription( + PlaceholdersExt.parseString(entry.discord.advancementChallenge, ctx, placeholders) + ), + entry -> entry.discord.advancementChallenge != null && entry.hasWorld(player.world) + ); + + // A player unlocked an advancement task + default -> DiscordDispatcher.embed( + (embed, entry) -> embed.setDescription( + PlaceholdersExt.parseString(entry.discord.advancementTask, ctx, placeholders) + ), + entry -> entry.discord.advancementTask != null && entry.hasWorld(player.world) + ); + } }); } } diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java index 40ae7d8..efcb9dc 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java @@ -31,26 +31,22 @@ public void afterChangeWorld(ServerPlayerEntity player, ServerWorld origin, Serv final PlaceholderContext ctx = PlaceholderContext.of(player); final Map placeholders = Map.of( - // The player's username - "username", string(player.getName().getString()), - // The player's display name - "player", string(player.getDisplayName().getString()), // The name of the world the player entered "world", string(StringUtils.getWorldName(dest)), // The X coordinate of where the player entered - "x", string(String.valueOf(player.getBlockX())), + "pos_x", string(String.valueOf(player.getBlockX())), // The Y coordinate of where the player entered - "y", string(String.valueOf(player.getBlockY())), + "pos_y", string(String.valueOf(player.getBlockY())), // The Z coordinate of where the player entered - "z", string(String.valueOf(player.getBlockZ())), + "pos_z", string(String.valueOf(player.getBlockZ())), // The name of the world the player left "origin", string(StringUtils.getWorldName(origin)), // The X coordinate of where the player left - "origin_x", string(String.valueOf((int) player.prevX)), + "origin_pos_x", string(String.valueOf((int) player.prevX)), // The Y coordinate of where the player left - "origin_y", string(String.valueOf((int) player.prevY)), + "origin_pos_y", string(String.valueOf((int) player.prevY)), // The Z coordinate of where the player left - "origin_z", string(String.valueOf((int) player.prevZ)) + "origin_pos_z", string(String.valueOf((int) player.prevZ)) ); /* diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerConnectionCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerConnectionCallback.java index c7d378b..5ea053e 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerConnectionCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerConnectionCallback.java @@ -1,5 +1,6 @@ package me.axieum.mcmod.minecord.impl.chat.callback.minecraft; +import java.util.Collections; import java.util.Map; import eu.pb4.placeholders.api.PlaceholderContext; @@ -16,9 +17,7 @@ import me.axieum.mcmod.minecord.api.Minecord; import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; -import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; -import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; /** * A listener for when a Minecraft player joins or leaves. @@ -36,20 +35,7 @@ public void onPlayReady(ServerPlayNetworkHandler handler, PacketSender sender, M */ final @Nullable PlaceholderContext ctx = PlaceholderContext.of(player); - final Map placeholders = Map.of( - // The player's username - "username", string(player.getName().getString()), - // The player's display name - "player", string(player.getDisplayName().getString()), - // The name of the world the player logged into - "world", string(StringUtils.getWorldName(player.world)), - // The X coordinate of where the player logged into - "x", string(String.valueOf(player.getBlockX())), - // The Y coordinate of where the player logged into - "y", string(String.valueOf(player.getBlockY())), - // The Z coordinate of where the player logged into - "z", string(String.valueOf(player.getBlockZ())) - ); + final Map placeholders = Collections.emptyMap(); /* * Dispatch the message. @@ -76,20 +62,7 @@ public void onPlayDisconnect(ServerPlayNetworkHandler handler, MinecraftServer s */ final @Nullable PlaceholderContext ctx = PlaceholderContext.of(player); - final Map placeholders = Map.of( - // The player's username - "username", string(player.getName().getString()), - // The player's display name - "player", string(player.getDisplayName().getString()), - // The name of the world the player logged out - "world", string(StringUtils.getWorldName(player.world)), - // The X coordinate of where the player logged out - "x", string(String.valueOf(player.getBlockX())), - // The Y coordinate of where the player logged out - "y", string(String.valueOf(player.getBlockY())), - // The Z coordinate of where the player logged out - "z", string(String.valueOf(player.getBlockZ())) - ); + final Map placeholders = Collections.emptyMap(); /* * Dispatch the message. diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java index 92a3e62..d46651d 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java @@ -14,7 +14,6 @@ import me.axieum.mcmod.minecord.api.Minecord; import me.axieum.mcmod.minecord.api.chat.event.minecraft.EntityDeathEvents; import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; -import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.duration; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; @@ -36,20 +35,8 @@ public void onPlayerDeath(ServerPlayerEntity player, DamageSource source) final @Nullable PlaceholderContext ctx = PlaceholderContext.of(player); final Map placeholders = Map.of( - // The player's username - "username", string(player.getName().getString()), - // The player's display name - "player", string(playerName), // The reason for the player's death "cause", string(source.getDeathMessage(player).getString().replaceFirst(playerName, "").trim()), - // The name of the world the player died in - "world", string(StringUtils.getWorldName(player.world)), - // The X coordinate of where the player died - "x", string(String.valueOf((int) player.prevX)), - // The Y coordinate of where the player died - "y", string(String.valueOf((int) player.prevY)), - // The Z coordinate of where the player died - "z", string(String.valueOf((int) player.prevZ)), // The total time for which the player was alive for "lifespan", duration(Duration.ofMinutes( player.getStatHandler().getStat(Stats.CUSTOM.getOrCreateStat(Stats.TIME_SINCE_DEATH)) diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java index dc61568..820586e 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java @@ -40,12 +40,6 @@ public void onChatMessage( final PlaceholderContext ctx = PlaceholderContext.of(player); final Map placeholders = Map.of( - // The player's username - "username", string(player.getName().getString()), - // The player's display name - "player", string(player.getDisplayName().getString()), - // The name of the world the player logged into - "world", string(StringUtils.getWorldName(player.world)), // The formatted message contents "message", string(StringUtils.minecraftToDiscord(message.getContent().getString())) ); @@ -99,16 +93,6 @@ public void onEmoteCommandMessage(SignedMessage message, ServerCommandSource sou // The formatted message contents "action", string(StringUtils.minecraftToDiscord(message.getContent().getString())) )); - if (player != null) { - placeholders.putAll(Map.of( - // The player's username - "username", string(player.getName().getString()), - // The player's display name - "player", string(player.getDisplayName().getString()), - // The name of the world the player logged into - "world", string(StringUtils.getWorldName(source.getWorld())) - )); - } /* * Dispatch the message. @@ -144,16 +128,6 @@ public void onSayCommandMessage(SignedMessage message, ServerCommandSource sourc // The formatted message contents "message", string(StringUtils.minecraftToDiscord(message.getContent().getString())) )); - if (player != null) { - placeholders.putAll(Map.of( - // The player's username - "username", string(player.getName().getString()), - // The player's display name - "player", string(player.getDisplayName().getString()), - // The name of the world the player logged into - "world", string(StringUtils.getWorldName(source.getWorld())) - )); - } /* * Dispatch the message. @@ -188,7 +162,8 @@ public void onTellRawCommandMessage(Text message, ServerCommandSource source) (embed, entry) -> embed.setContent( PlaceholdersExt.parseString(entry.discord.tellraw, ctx, placeholders) ), - entry -> entry.discord.tellraw != null); + entry -> entry.discord.tellraw != null + ); }); } } diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/config/ChatConfig.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/config/ChatConfig.java index 1453669..33364f9 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/config/ChatConfig.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/config/ChatConfig.java @@ -56,43 +56,51 @@ public static class DiscordSchema /** * A player sent an in-game chat message. * - *

Usages: {@code ${username}}, {@code ${player}}, {@code ${message}} and {@code ${world}}. + *

    + *
  • {@code ${message}} — the formatted message contents
  • + *
*/ @Comment(""" A player sent an in-game chat message - Usages: ${username}, ${player}, ${message} and ${world}""") - public String chat = "`${world}` **${player}** > ${message}"; + Usages: ${message}""") + public String chat = "`${world:name}` **${player:name_unformatted}** > ${message}"; /** * A player sent an in-game message via the {@code /me} command. * *

Note: there is no player or world if sent from a command block or console! * - *

Usages: {@code ${username}}, {@code ${player}}, {@code ${action}} and {@code ${world}}. + *

    + *
  • {@code ${action}} — the formatted action
  • + *
*/ @Comment(""" A player sent an in-game message via the '/me' command Note: there is no player or world if sent from a command block or console! - Usages: ${username}, ${player}, ${action} and ${world}""") - public String emote = "`${world:-∞}` **${player:-Server}** _${action}_"; + Usages: ${action}""") + public String emote = "`${world:name}` **${player:name_unformatted}** _${action}_"; /** * An admin broadcast an in-game message via the {@code /say} command. * *

Note: there is no player or world if sent from a command block or console! * - *

Usages: {@code ${username}}, {@code ${player}}, {@code ${message}} and {@code ${world}}. + *

    + *
  • {@code ${message}} — the formatted message contents
  • + *
*/ @Comment(""" An admin broadcast an in-game message via the '/say' command Note: there is no player or world if sent from a command block or console! - Usages: ${username}, ${player}, ${message} and ${world}""") - public String say = "**[${player:-Server}]** ${message}"; + Usages: ${message}""") + public String say = "**[${player:name_unformatted}]** ${message}"; /** * An admin broadcast an in-game message to all players via the {@code /tellraw @a} command. * - *

Usages: {@code ${message}}. + *

    + *
  • {@code ${message}} — the formatted message contents
  • + *
*/ @Comment(""" An admin broadcast an in-game message to all players via the '/tellraw @a' command @@ -102,66 +110,100 @@ public static class DiscordSchema /** * A player had died. * - *

Usages: {@code ${username}}, {@code ${player}}, {@code ${cause}}, {@code ${world}}, {@code ${x}}, - * {@code ${y}}, {@code ${z}}, {@code ${score}} and {@code ${exp}}. + *

    + *
  • {@code ${cause}} — the reason for the player's death
  • + *
  • {@code ${exp}} — the player's number of experience levels before they died
  • + *
  • {@code ${lifespan [format]}} — the total time for which the player was alive for
  • + *
  • {@code ${score}} — the player's total score before they died
  • + *
*/ @Comment(""" A player had died - Usages: ${username}, ${player}, ${cause}, ${world}, ${x}, ${y}, ${z}, ${score} and ${exp}""") - public String death = "**${player}** ${cause}! :skull:\n_${world} | ${x}, ${y}, ${z}_"; + Usages: ${cause}, ${exp}, ${lifespan [format]} and ${score}""") + public String death = "**${player:name_unformatted}** ${cause}! :skull:\n_${world:name} at ${player:pos_x}, ${player:pos_y}, ${player:pos_z}_"; /** * A named animal/monster (with name tag) had died. * - *

Usages: {@code ${name}}, {@code ${cause}}, {@code ${world}}, {@code ${x}}, {@code ${y}} and - * {@code ${z}}. + *

    + *
  • {@code ${cause}} — the reason for the entity's death
  • + *
  • {@code ${name}} — the entity's display name
  • + *
  • {@code ${pos_x}} — the X coordinate of where the entity died
  • + *
  • {@code ${pos_y}} — the Y coordinate of where the entity died
  • + *
  • {@code ${pos_z}} — the Z coordinate of where the entity died
  • + *
  • {@code ${world}} — the name of the world the entity died in
  • + *
*/ @Comment(""" A named animal/monster (with name tag) had died - Usages: ${name}, ${cause}, ${world}, ${x}, ${y} and ${z}""") - public String grief = "**${name}** ${cause}! :coffin:"; + Usages: ${cause}, ${name}, ${pos_x}, ${pos_y}, ${pos_z} and ${world}""") + public String grief = "**${name}** ${cause}! :coffin:\n_${world} at ${pos_x}, ${pos_y}, ${pos_z}_"; /** - * A player unlocked an advancement. + * A player unlocked an advancement task. * - *

Usages: {@code ${username}}, {@code ${player}}, {@code ${type}}, {@code ${title}}, and - * {@code ${description}}. + *

    + *
  • {@code ${title}} — the title of the advancement (task)
  • + *
  • {@code ${description}} — the description of the advancement (task)
  • + *
*/ @Comment(""" - A player unlocked an advancement - Usages: ${username}, ${player}, ${type}, ${title}, and ${description}""") - public String advancement = "**${player}** completed the ${type} **${title}**! :clap:\n_${description}_"; + A player unlocked an advancement task + Usages: ${title} and ${description}""") + public String advancementTask = "**${player:name_unformatted}** has made the advancement **${title}**! :clap:\n_${description}_"; /** - * A player teleported to another dimension. + * A player reached an advancement goal. * - *

Usages: {@code ${username}}, {@code ${player}}, {@code ${world}}, {@code ${x}}, {@code ${y}}, - * {@code ${z}}, {@code ${origin}}, {@code ${origin_x}}, {@code ${origin_y}} and {@code ${origin_z}}. + *

    + *
  • {@code ${title}} — the title of the advancement
  • + *
  • {@code ${description}} — the description of the advancement
  • + *
*/ @Comment(""" - A player teleported to another dimension - Usages: ${username}, ${player}, ${world}, ${x}, ${y}, ${z}, ${origin}, ${origin_x}, ${origin_y} and ${origin_z}""") - public String teleport = "**${player}** entered ${world}. :cyclone:"; + A player reached an advancement goal + Usages: ${title} and ${description}""") + public String advancementGoal = "**${player:name_unformatted}** has reached the goal **${title}**! :clap:\n_${description}_"; /** - * A player joined the game. + * A player completed an advancement challenge. * - *

Usages: {@code ${username}}, {@code ${player}} and {@code ${world}}. + *

    + *
  • {@code ${title}} — the title of the advancement
  • + *
  • {@code ${description}} — the description of the advancement
  • + *
*/ @Comment(""" - A player joined the game - Usages: ${username}, ${player} and ${world}""") - public String join = "**${player}** joined!"; + A player completed an advancement challenge + Usages: ${title} and ${description}""") + public String advancementChallenge = "**${player:name_unformatted}** has completed the challenge **${title}**! :trophy:\n_${description}_"; /** - * A player left the game. + * A player teleported to another dimension. * - *

Usages: {@code ${username}}, {@code ${player}}, {@code ${world}} and {@code ${playtime}}. + *

    + *
  • {@code ${world}} — the name of the world the player entered
  • + *
  • {@code ${pos_x}} — the X coordinate of where the player entered
  • + *
  • {@code ${pos_y}} — the Y coordinate of where the player entered
  • + *
  • {@code ${pos_z}} — the Z coordinate of where the player entered
  • + *
  • {@code ${origin}} — the name of the world the player left
  • + *
  • {@code ${origin_pos_x}} — the X coordinate of where the player left
  • + *
  • {@code ${origin_pos_y}} — the Y coordinate of where the player left
  • + *
  • {@code ${origin_pos_z}} — the Z coordinate of where the player left
  • + *
*/ @Comment(""" - A player left the game - Usages: ${username}, ${player}, ${world} and ${playtime}""") - public String leave = "**${player}** left!"; + A player teleported to another dimension + Usages: ${world}, ${pos_x}, ${pos_y}, ${pos_z}, ${origin}, ${origin_pos_x}, ${origin_pos_y} and ${origin_pos_z}""") + public String teleport = "**${player:name_unformatted}** entered ${world:name}. :cyclone:"; + + /** A player joined the game. */ + @Comment("A player joined the game") + public String join = "**${player:name_unformatted}** joined!"; + + /** A player left the game. */ + @Comment("A player left the game") + public String leave = "**${player:name_unformatted}** left!"; /** The server began to start. */ @Comment("The server began to start") @@ -170,41 +212,50 @@ public static class DiscordSchema /** * The server started and is accepting connections. * - *

Usages: {@code ${uptime}}. + *

    + *
  • {@code ${uptime [format]}} — the time taken for the server to start
  • + *
*/ @Comment(""" The server started and is accepting connections - Usages: ${uptime}""") - public String started = "Server started (took ${uptime:s.SSS}s) :ok_hand:"; + Usages: ${uptime [format]}""") + public String started = "Server started (took ${uptime s.SSS}s) :ok_hand:"; /** * The server began to stop. * - *

Usages: {@code ${uptime}}. + *

    + *
  • {@code ${uptime [format]}} — the total time for which the server has been online for
  • + *
*/ @Comment(""" The server began to stop - Usages: ${uptime}""") + Usages: ${uptime [format]}""") public String stopping = "Server is stopping... :raised_hand:"; /** * The server stopped and is offline. * - *

Usages: {@code ${uptime}}. + *

    + *
  • {@code ${uptime [format]}} — the total time for which the server has been online for
  • + *
*/ @Comment(""" The server stopped and is offline - Usages: ${uptime}""") + Usages: ${uptime [format]}""") public String stopped = "Server stopped! :no_entry:"; /** * The server stopped unexpectedly and is inaccessible. * - *

Usages: {@code ${reason}} and {@code ${uptime}}. + *

    + *
  • {@code ${reason}} — the reason for the server stopping, if crashed
  • + *
  • {@code ${uptime [format]}} — the total time for which the server has been online for
  • + *
*/ @Comment(""" The server stopped unexpectedly and is inaccessible - Usages: ${reason} and ${uptime}""") + Usages: ${reason} and ${uptime [format]}""") public String crashed = "Server crash detected! :warning:\n_${reason}_"; /** True if a crash report should be attached to any server crash messages. */ @@ -226,72 +277,121 @@ public static class MinecraftSchema /** * A user sent a message. * - *

Usages: {@code ${author}}, {@code ${tag}}, {@code ${username}}, {@code ${discriminator}}, - * {@code ${message}} and {@code ${raw}}. + *

    + *
  • {@code ${author}} — the author's nickname or username
  • + *
  • {@code ${tag}} — the author's tag (i.e. username#discriminator), e.g. Axieum#1001
  • + *
  • {@code ${username}} — the author's username, e.g. Axieum
  • + *
  • {@code ${discriminator}} — the author's username discriminator, e.g. 1001
  • + *
  • {@code ${message}} — the formatted message contents
  • + *
  • {@code ${raw}} — the raw message contents
  • + *
*/ @Comment(""" A user sent a message Usages: ${author}, ${tag}, ${username}, ${discriminator}, ${message} and ${raw}""") - public String chat = "[\"\",{\"text\":\"${author}\",\"color\":\"#00aaff\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"@${tag} \"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":[\"\",{\"text\":\"Sent from Discord\",\"italic\":true}]}},{\"text\":\" > \",\"color\":\"dark_gray\"},{\"text\":\"${message}\"}]"; + public String chat = "Sent from Discord'>${author} > ${message}"; /** * A user sent a message in reply to another. * - *

Usages: {@code ${author}}, {@code ${tag}}, {@code ${username}}, {@code ${discriminator}}, - * {@code ${message}}, {@code ${reply_author}}, {@code ${reply_tag}}, {@code ${reply_username}}, - * {@code ${reply_discriminator}} and {@code ${reply_message}}. + *

    + *
  • {@code ${author}} — the author's nickname or username
  • + *
  • {@code ${tag}} — the author's tag (i.e. username#discriminator), e.g. Axieum#1001
  • + *
  • {@code ${username}} — the author's username, e.g. Axieum
  • + *
  • {@code ${discriminator}} — the author's username discriminator, e.g. 1001
  • + *
  • {@code ${message}} — the formatted message contents
  • + *
  • {@code ${raw}} — the raw message contents
  • + *
  • {@code ${reply_author}} — the replied message author's nickname or username
  • + *
  • {@code ${reply_tag}} — the replied message author's tag (i.e. username#discriminator), e.g. Axieum#1001
  • + *
  • {@code ${reply_username}} — the replied message author's username, e.g. Axieum
  • + *
  • {@code ${reply_discriminator}} — the replied message author's username discriminator, e.g. 1001
  • + *
  • {@code ${reply_message}} — the replied message formatted message contents
  • + *
  • {@code ${reply_raw}} — the replied message raw message contents
  • + *
*/ @Comment(""" A user sent a message in reply to another - Usages: ${author}, ${tag}, ${username}, ${discriminator}, ${message}, ${reply_author}, ${reply_tag}, ${reply_username}, ${reply_discriminator} and ${reply_message}""") - public String reply = "[\"\",{\"text\":\"${author}\",\"color\":\"#00aaff\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"@${tag} \"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":[{\"text\":\"Sent from Discord\",\"italic\":true}]}},\" \",{\"text\":\"(in reply to ${reply_author})\",\"color\":\"#99dcff\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"@${reply_tag} \"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":[\"${reply_message:%.50s}...\"]}},{\"text\":\" > \",\"color\":\"dark_gray\"},\"${message}\"]"; + Usages: ${author}, ${tag}, ${username}, ${discriminator}, ${message}, ${raw}, ${reply_author}, ${reply_tag}, ${reply_username}, ${reply_discriminator}, ${reply_message} and ${reply_raw}""") + public String reply = "Sent from Discord'>${author} ${reply_author} > ${message}"; /** * A user edited their recently sent message. * - *

Usages: {@code ${author}}, {@code ${tag}}, {@code ${username}}, {@code ${discriminator}}, - * {@code ${diff}}, {@code ${message}}, {@code ${raw}}, {@code ${original}} and {@code ${original_raw}}. + *

    + *
  • {@code ${author}} — the author's nickname or username
  • + *
  • {@code ${tag}} — the author's tag (i.e. username#discriminator), e.g. Axieum#1001
  • + *
  • {@code ${username}} — the author's username, e.g. Axieum
  • + *
  • {@code ${discriminator}} — the author's username discriminator, e.g. 1001
  • + *
  • {@code ${diff}} — the difference between the original and new message
  • + *
  • {@code ${message}} — the new formatted message contents
  • + *
  • {@code ${raw}} — the new raw message contents
  • + *
  • {@code ${original}} — the old formatted message contents
  • + *
  • {@code ${original_raw}} — the old raw message contents
  • + *
*/ @Comment(""" A user edited their recently sent message Usages: ${author}, ${tag}, ${username}, ${discriminator}, ${diff}, ${message}, ${raw}, ${original} and ${original_raw}""") - public String edit = "[\"\",{\"text\":\"${author}\",\"color\":\"#00aaff\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"@${tag} \"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":[\"\",{\"text\":\"Sent from Discord\",\"italic\":true}]}},{\"text\":\" > \",\"color\":\"dark_gray\"},{\"text\":\"${diff}\"}]"; + public String edit = "Sent from Discord'>${author} > ${diff}"; /** * A user reacted to a recent message. * - *

Usages: {@code ${issuer}}, {@code ${issuer_tag}}, {@code ${issuer_username}}, - * {@code ${issuer_discriminator}}, {@code ${author}}, {@code ${author_tag}}, {@code ${author_username}}, - * {@code ${author_discriminator}} and {@code ${emote}}. + *

    + *
  • {@code ${issuer}} — the issuer's nickname or username
  • + *
  • {@code ${issuer_tag}} — the issuer's tag (i.e. username#discriminator), e.g. Axieum#1001
  • + *
  • {@code ${issuer_username}} — the issuer's username, e.g. Axieum
  • + *
  • {@code ${issuer_discriminator}} — the issuer's username discriminator, e.g. 1001
  • + *
  • {@code ${author}} — the author's nickname or username
  • + *
  • {@code ${author_tag}} — the author's tag (i.e. username#discriminator), e.g. Axieum#1001
  • + *
  • {@code ${author_username}} — the author's username, e.g. Axieum
  • + *
  • {@code ${author_discriminator}} — the author's username discriminator, e.g. 1001
  • + *
  • {@code ${emote}} — the emote used to react
  • + *
*/ @Comment(""" A user reacted to a recent message Usages: ${issuer}, ${issuer_tag}, ${issuer_username}, ${issuer_discriminator}, ${author}, ${author_tag}, ${author_username}, ${author_discriminator} and ${emote}""") - public String react = "[\"\",{\"text\":\"${issuer}\",\"color\":\"#00aaff\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"@${issuer_tag} \"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":[\"\",{\"text\":\"Sent from Discord\",\"italic\":true}]}},{\"text\":\" reacted with \"},{\"text\":\"${emote}\",\"color\":\"green\"},{\"text\": \" to \"},{\"text\":\"${author}\",\"color\":\"#00aaff\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"@${author_tag} \"}},{\"text\":\"'s message\"}]"; + public String react = "Sent from Discord'>${issuer} reacted with ${emote} to ${author}'s message"; /** * A user removed their reaction from a recent message. * - *

Usages: {@code ${issuer}}, {@code ${issuer_tag}}, {@code ${issuer_username}}, - * {@code ${issuer_discriminator}}, {@code ${author}}, {@code ${author_tag}}, - * {@code ${author_username}}, {@code ${author_discriminator}} and {@code ${emote}}. + *

    + *
  • {@code ${issuer}} — the issuer's nickname or username
  • + *
  • {@code ${issuer_tag}} — the issuer's tag (i.e. username#discriminator), e.g. Axieum#1001
  • + *
  • {@code ${issuer_username}} — the issuer's username, e.g. Axieum
  • + *
  • {@code ${issuer_discriminator}} — the issuer's username discriminator, e.g. 1001
  • + *
  • {@code ${author}} — the author's nickname or username
  • + *
  • {@code ${author_tag}} — the author's tag (i.e. username#discriminator), e.g. Axieum#1001
  • + *
  • {@code ${author_username}} — the author's username, e.g. Axieum
  • + *
  • {@code ${author_discriminator}} — the author's username discriminator, e.g. 1001
  • + *
  • {@code ${emote}} — the emote used to react
  • + *
*/ @Comment(""" A user removed their reaction from a recent message Usages: ${issuer}, ${issuer_tag}, ${issuer_username}, ${issuer_discriminator}, ${author}, ${author_tag}, ${author_username}, ${author_discriminator} and ${emote}""") - public String unreact = "[\"\",{\"text\":\"${issuer}\",\"color\":\"#00aaff\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"@${issuer_tag} \"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":[\"\",{\"text\":\"Sent from Discord\",\"italic\":true}]}},{\"text\":\" removed their reaction of \"},{\"text\":\"${emote}\",\"color\":\"red\"},{\"text\": \" from \"},{\"text\":\"${author}\",\"color\":\"#00aaff\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"@${author_tag} \"}},{\"text\":\"'s message\"}]"; + public String unreact = "Sent from Discord'>${issuer} removed their reaction of ${emote} from ${author}'s message"; /** * A user sent a message that contained attachments. * - *

Usages: {@code ${author}}, {@code ${tag}}, {@code ${username}}, - * {@code ${discriminator}}, {@code ${url}}, {@code ${name}}, {@code ${ext}} and - * {@code ${size}}. + *

    + *
  • {@code ${author}} — the author's nickname or username
  • + *
  • {@code ${tag}} — the author's tag (i.e. username#discriminator), e.g. Axieum#1001
  • + *
  • {@code ${username}} — the author's username, e.g. Axieum
  • + *
  • {@code ${discriminator}} — the author's username discriminator, e.g. 1001
  • + *
  • {@code ${url}} — the link to the file to download
  • + *
  • {@code ${name}} — the file name that was uploaded
  • + *
  • {@code ${ext}} — the file extension/type
  • + *
  • {@code ${size}} — the file size for humans
  • + *
*/ @Comment(""" A user sent a message that contained attachments Usages: ${author}, ${tag}, ${username}, ${discriminator}, ${url}, ${name}, ${ext} and ${size}""") - public String attachment = "[\"\",{\"text\":\"${author}\",\"color\":\"#00aaff\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"@${tag} \"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":[\"\",{\"text\":\"Sent from Discord\",\"italic\":true}]}},{\"text\":\" > \",\"color\":\"dark_gray\"},{\"text\":\"${name}\",\"color\":\"blue\",\"underlined\":true,\"clickEvent\":{\"action\":\"open_url\",\"value\":\"${url}\"},\"hoverEvent\":{\"action\":\"show_text\",\"contents\":{\"text\":\"${ext} (${size})\"}}}]"; + public String attachment = "Sent from Discord'>${author} > PlaceholderResult.value(e.getMessage()) + "reason", string(e.getMessage()) ) ) ).build() diff --git a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/CustomCommand.java b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/CustomCommand.java index 79fe37e..6c43d37 100644 --- a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/CustomCommand.java +++ b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/CustomCommand.java @@ -2,6 +2,7 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Objects; @@ -11,7 +12,6 @@ import eu.pb4.placeholders.api.PlaceholderContext; import eu.pb4.placeholders.api.PlaceholderHandler; -import eu.pb4.placeholders.api.PlaceholderResult; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.exceptions.ParsingException; @@ -42,6 +42,7 @@ import me.axieum.mcmod.minecord.api.cmds.event.MinecordCommandEvents; import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.impl.cmds.config.CommandConfig; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; import static me.axieum.mcmod.minecord.impl.cmds.MinecordCommandsImpl.LOGGER; import static me.axieum.mcmod.minecord.impl.cmds.MinecordCommandsImpl.getConfig; @@ -160,7 +161,12 @@ public void execute(@NotNull SlashCommandInteractionEvent event, @Nullable Minec if (output.prevMessage == null) { if (error == null) { output.thumbnailUrl = null; - source.sendFeedback(Text.literal(getConfig().messages.feedback), false); + source.sendFeedback( + PlaceholdersExt.parseText( + getConfig().messages.feedback, PlaceholderContext.of(source), Collections.emptyMap() + ), + false + ); } else { source.sendError(Text.literal(error.getMessage())); } @@ -187,9 +193,7 @@ private static String prepareCommand( // Prepare new command placeholders final @Nullable PlaceholderContext ctx = server != null ? PlaceholderContext.of(server) : null; final HashMap placeholders = new HashMap<>(options.size()); - options.forEach(option -> - placeholders.put(option.getName(), (c, a) -> PlaceholderResult.value(option.getAsString())) - ); + options.forEach(option -> placeholders.put(option.getName(), string(option.getAsString()))); // Parse the placeholders in the given command String result = PlaceholdersExt.parseString(command, ctx, placeholders).trim(); diff --git a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/UptimeCommand.java b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/UptimeCommand.java index 24b6248..1c1fc96 100644 --- a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/UptimeCommand.java +++ b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/UptimeCommand.java @@ -19,6 +19,7 @@ import me.axieum.mcmod.minecord.api.cmds.event.MinecordCommandEvents; import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.impl.cmds.config.CommandConfig; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.duration; import static me.axieum.mcmod.minecord.impl.cmds.MinecordCommandsImpl.getConfig; /** @@ -29,7 +30,7 @@ public class UptimeCommand extends MinecordCommand /** Reusable placeholders for all uptime commands. */ public static final Map PLACEHOLDERS = Map.of( // The total process uptime (to the nearest minute) - "uptime", PlaceholdersExt.duration(() -> + "uptime", duration(() -> Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime()).truncatedTo(ChronoUnit.MINUTES) ) ); diff --git a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/config/CommandConfig.java b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/config/CommandConfig.java index 123dd3d..7b169ad 100644 --- a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/config/CommandConfig.java +++ b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/config/CommandConfig.java @@ -42,7 +42,9 @@ public static class MessagesSchema /** * The error message used when any command unexpectedly fails. * - *

Usages: ${reason}. + *

    + *
  • {@code ${reason}} — the reason for the command failing
  • + *
*/ @Comment(""" The error message used when any command unexpectedly fails @@ -52,7 +54,10 @@ public static class MessagesSchema /** * The error message used when a user must wait before executing a command. * - *

Usages: ${cooldown} and ${remaining}. + *

    + *
  • {@code ${cooldown}} — the total cooldown before the command can be used again
  • + *
  • {@code ${remaining}} — the remaining time before the command can be used again
  • + *
*/ @Comment(""" The error message used when a user must wait before executing a command @@ -95,11 +100,13 @@ public UptimeCommandSchema() /** * A message template that is formatted and sent for the server's uptime. * - *

Usages: ${uptime}. + *

    + *
  • {@code ${uptime [format]}} — the total process uptime (to the nearest minute)
  • + *
*/ @Comment(""" A message template that is formatted and sent for the server's uptime - Usages: ${uptime}""") + Usages: ${uptime [format]}""") public String message = "The server has been online for ${uptime} :hourglass_flowing_sand:"; } @@ -144,7 +151,9 @@ public CustomCommandSchema() /** * A Minecraft command to execute. * - *

Usages: {@code ${name}} for "name" option value. + *

    + *
  • {@code ${name}} — for "name" option value
  • + *
*/ @Comment(""" A Minecraft command to execute @@ -186,12 +195,7 @@ public abstract static class BaseCommandSchema @Comment("The number of seconds a user must wait before using the command again") public int cooldown = 0; - /** - * To whom the cooldown applies. - * - *

Allowed values: {@code USER}, {@code CHANNEL}, {@code USER_CHANNEL}, {@code GUILD}, - * {@code USER_GUILD}, {@code SHARD}, {@code USER_SHARD} and {@code GLOBAL}. - */ + /** To whom the cooldown applies. */ @Comment(""" To whom the cooldown applies Allowed values: USER, CHANNEL, USER_CHANNEL, GUILD, USER_GUILD, SHARD, USER_SHARD and GLOBAL""") @@ -202,13 +206,7 @@ public abstract static class BaseCommandSchema */ public static class OptionSchema { - /** - * The type of option. - * - *

Allowed values: {@code ATTACHMENT}, {@code BOOLEAN}, {@code CHANNEL}, - * {@code INTEGER}, {@code MENTIONABLE}, {@code NUMBER}, {@code ROLE}, - * {@code STRING} and {@code USER}. - */ + /** The type of option. */ @Comment(""" The type of option Allowed values: ATTACHMENT, BOOLEAN, CHANNEL, INTEGER, MENTIONABLE, NUMBER, ROLE, STRING and USER""") diff --git a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/PresenceUpdateTask.java b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/PresenceUpdateTask.java index 589ef6f..b8ee6bc 100644 --- a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/PresenceUpdateTask.java +++ b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/PresenceUpdateTask.java @@ -21,6 +21,7 @@ import me.axieum.mcmod.minecord.api.presence.category.PresenceCategory; import me.axieum.mcmod.minecord.api.presence.category.PresenceSupplier; import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.duration; import static me.axieum.mcmod.minecord.impl.presence.MinecordPresenceImpl.LOGGER; /** @@ -58,7 +59,7 @@ public void run() final Optional ctx = Minecord.getInstance().getMinecraft().map(PlaceholderContext::of); final Map placeholders = Map.of( // The total process uptime (to the nearest minute) - "uptime", PlaceholdersExt.duration(() -> + "uptime", duration(() -> Duration.ofMillis(ManagementFactory.getRuntimeMXBean().getUptime()).truncatedTo(ChronoUnit.MINUTES) ) ); diff --git a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/config/PresenceConfig.java b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/config/PresenceConfig.java index 27c54d4..c99088e 100644 --- a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/config/PresenceConfig.java +++ b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/config/PresenceConfig.java @@ -150,13 +150,14 @@ public ActivitySchema(ActivityType type, String name, @Nullable String url) /** * The name of the activity. * - *

Usages: {@code ${version}}, {@code ${ip}}, {@code ${port}}, {@code ${motd}}, - * {@code ${difficulty}}, {@code ${max_players}}, {@code ${player_count}} and {@code ${uptime}}. + *

    + *
  • {@code ${uptime [format]}} — the total process uptime (to the nearest minute)
  • + *
*/ @SuppressWarnings("checkstyle:linelength") @Comment(""" The name of the activity - Usages: ${version}, ${ip}, ${port}, ${motd}, ${difficulty}, ${max_players}, ${player_count} and ${uptime}""") + Usages: ${uptime [format]}""") public String name = "Minecraft"; /** If defined, provides a link to the activity, e.g. Twitch stream. */ @@ -217,13 +218,13 @@ public PresenceConfig() // Playing Minecraft 1.17 new CategorySchema.PresenceSchema(false, OnlineStatus.ONLINE, new CategorySchema.PresenceSchema.ActivitySchema( - ActivityType.PLAYING, "Minecraft ${version}", null + ActivityType.PLAYING, "Minecraft ${server:version}", null ) ), // Watching 2 player(s) new CategorySchema.PresenceSchema(false, OnlineStatus.ONLINE, new CategorySchema.PresenceSchema.ActivitySchema( - ActivityType.WATCHING, "${player_count} player(s)", null + ActivityType.WATCHING, "${server:online} player(s)", null ) ), // Playing for 3 hours 24 minutes 10 seconds @@ -231,12 +232,6 @@ public PresenceConfig() new CategorySchema.PresenceSchema.ActivitySchema( ActivityType.PLAYING, "for ${uptime}", null ) - ), - // Playing on hard mode - new CategorySchema.PresenceSchema(false, OnlineStatus.ONLINE, - new CategorySchema.PresenceSchema.ActivitySchema( - ActivityType.PLAYING, "on ${difficulty} mode", null - ) ) )); From 11144a3674d83947afac49be6d12db2a73c90df9 Mon Sep 17 00:00:00 2001 From: Jonathan Hiles Date: Sun, 4 Jun 2023 17:16:59 +1000 Subject: [PATCH 5/7] perf: pre-parse placeholder templates --- .../minecord/api/util/PlaceholdersExt.java | 9 +- .../mcmod/minecord/impl/MinecordImpl.java | 5 + .../minecord/impl/config/I18nConfig.java | 12 +- .../placeholder/MinecordPlaceholders.java | 35 +++++ .../discord/MessageReactionListener.java | 4 +- .../discord/MessageReceivedListener.java | 6 +- .../discord/MessageUpdateListener.java | 2 +- .../minecraft/EntityDeathCallback.java | 4 +- .../minecraft/PlayerAdvancementCallback.java | 6 +- .../minecraft/PlayerChangeWorldCallback.java | 2 +- .../minecraft/PlayerConnectionCallback.java | 4 +- .../minecraft/PlayerDeathCallback.java | 10 +- .../minecraft/ServerLifecycleCallback.java | 10 +- .../minecraft/ServerMessageCallback.java | 12 +- .../minecord/impl/chat/config/ChatConfig.java | 134 ++++++++++++++++-- .../cmds/callback/DiscordCommandListener.java | 6 +- .../cmds/command/discord/CustomCommand.java | 12 +- .../cmds/command/discord/UptimeCommand.java | 2 +- .../impl/cmds/config/CommandConfig.java | 37 +++++ .../presence/category/PresenceSupplier.java | 8 +- .../impl/presence/PresenceUpdateTask.java | 2 +- .../impl/presence/config/PresenceConfig.java | 23 +-- 22 files changed, 277 insertions(+), 68 deletions(-) create mode 100644 minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/placeholder/MinecordPlaceholders.java diff --git a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java index 1317596..f25e64a 100644 --- a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java +++ b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java @@ -99,6 +99,13 @@ public static Text parseText( return parseText(parseNode(text), context, placeholderGetter); } + public static Text parseText( + @NotNull TextNode node, @Nullable PlaceholderContext context, @NotNull Map placeholders + ) + { + return parseText(node, context, placeholders::get); + } + public static Text parseText( @NotNull TextNode node, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter ) @@ -161,7 +168,7 @@ public static String parseString( @NotNull Map placeholders ) { - return parseText(node, context, placeholders::get).getString(); + return parseString(node, context, placeholders::get); } public static String parseString( diff --git a/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/MinecordImpl.java b/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/MinecordImpl.java index c70acd0..0430986 100644 --- a/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/MinecordImpl.java +++ b/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/MinecordImpl.java @@ -28,6 +28,7 @@ import me.axieum.mcmod.minecord.impl.callback.DiscordLifecycleListener; import me.axieum.mcmod.minecord.impl.callback.ServerLifecycleCallback; import me.axieum.mcmod.minecord.impl.config.MinecordConfig; +import me.axieum.mcmod.minecord.impl.placeholder.MinecordPlaceholders; /** * Minecord (API) implementation. @@ -51,6 +52,10 @@ public void onPreLaunch() { LOGGER.info("Minecord is getting ready..."); + // Register global placeholders + MinecordPlaceholders.register(); + + // Login to Discord try { // Prepare the JDA client final JDABuilder builder = JDABuilder.createDefault(getConfig().bot.token) diff --git a/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/config/I18nConfig.java b/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/config/I18nConfig.java index 8b792fe..cc8c625 100644 --- a/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/config/I18nConfig.java +++ b/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/config/I18nConfig.java @@ -21,11 +21,15 @@ public class I18nConfig implements ConfigData @RequiresRestart public String lang = Language.DEFAULT_LANGUAGE; + /** The name of the Minecraft server in system messages. */ + @Comment("The name of the Minecraft server in system messages") + public String serverName = "Server"; + /** A mapping of Minecraft dimension IDs to their respective names. */ @Comment("A mapping of Minecraft dimension IDs to their respective names") - public Map worlds = new HashMap<>(Map.ofEntries( - Map.entry("minecraft:overworld", "Overworld"), - Map.entry("minecraft:the_nether", "Nether"), - Map.entry("minecraft:the_end", "The End") + public Map worlds = new HashMap<>(Map.of( + "minecraft:overworld", "Overworld", + "minecraft:the_nether", "The Nether", + "minecraft:the_end", "The End" )); } diff --git a/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/placeholder/MinecordPlaceholders.java b/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/placeholder/MinecordPlaceholders.java new file mode 100644 index 0000000..c8d546d --- /dev/null +++ b/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/placeholder/MinecordPlaceholders.java @@ -0,0 +1,35 @@ +package me.axieum.mcmod.minecord.impl.placeholder; + +import eu.pb4.placeholders.api.PlaceholderResult; +import eu.pb4.placeholders.api.Placeholders; + +import net.minecraft.util.Identifier; + +import me.axieum.mcmod.minecord.api.util.StringUtils; + +import static me.axieum.mcmod.minecord.impl.MinecordImpl.getConfig; + +/** + * {@link eu.pb4.placeholders.api.Placeholders Placeholder API} global placeholders for Minecord API. + */ +public final class MinecordPlaceholders +{ + /** + * Registers Minecord API global placeholders with {@link eu.pb4.placeholders.api.Placeholders Placeholder API}. + */ + public static void register() + { + // minecord:player (or server) + Placeholders.register(new Identifier("minecord", "player"), (ctx, arg) -> + PlaceholderResult.value( + ctx.player() != null + ? ctx.player().getDisplayName().getString() + : getConfig().i18n.serverName + ) + ); + // minecord:world + Placeholders.register(new Identifier("minecord", "world"), (ctx, arg) -> + PlaceholderResult.value(ctx.world() != null ? StringUtils.getWorldName(ctx.world()) : "∞") + ); + } +} diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java index b7a43b0..3a4e949 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java @@ -73,7 +73,7 @@ public void onGenericMessageReaction(GenericMessageReactionEvent event) // A user reacted to a recent message if (isAdded) { MinecraftDispatcher.dispatch( - entry -> PlaceholdersExt.parseText(entry.minecraft.react, ctx, placeholders), + entry -> PlaceholdersExt.parseText(entry.minecraft.reactNode, ctx, placeholders), entry -> entry.minecraft.react != null && entry.id == channelId ); LOGGER.info(PlaceholdersExt.parseString( @@ -83,7 +83,7 @@ public void onGenericMessageReaction(GenericMessageReactionEvent event) // A user removed their reaction from a recent message } else { MinecraftDispatcher.dispatch( - entry -> PlaceholdersExt.parseText(entry.minecraft.unreact, ctx, placeholders), + entry -> PlaceholdersExt.parseText(entry.minecraft.unreactNode, ctx, placeholders), entry -> entry.minecraft.unreact != null && entry.id == channelId ); LOGGER.info(PlaceholdersExt.parseString( diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java index 4a10ad3..225a194 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java @@ -100,7 +100,7 @@ public void onText(MessageReceivedEvent event) // The message is a standalone message if (replyMessage == null) { MinecraftDispatcher.dispatch( - entry -> PlaceholdersExt.parseText(entry.minecraft.chat, ctx, placeholders), + entry -> PlaceholdersExt.parseText(entry.minecraft.chatNode, ctx, placeholders), entry -> entry.minecraft.chat != null && entry.id == channelId ); LOGGER.info(PlaceholdersExt.parseString("@${tag} > ${raw}", ctx, placeholders)); @@ -108,7 +108,7 @@ public void onText(MessageReceivedEvent event) // The message is in reply to another } else { MinecraftDispatcher.dispatch( - entry -> PlaceholdersExt.parseText(entry.minecraft.reply, ctx, placeholders), + entry -> PlaceholdersExt.parseText(entry.minecraft.replyNode, ctx, placeholders), entry -> entry.minecraft.reply != null && entry.id == channelId ); LOGGER.info(PlaceholdersExt.parseString("@${tag} (in reply to @${reply_tag}) > ${raw}", ctx, placeholders)); @@ -156,7 +156,7 @@ public void onAttachment(MessageReceivedEvent event, Message.Attachment attachme */ MinecraftDispatcher.dispatch( - entry -> PlaceholdersExt.parseText(entry.minecraft.attachment, ctx, placeholders), + entry -> PlaceholdersExt.parseText(entry.minecraft.attachmentNode, ctx, placeholders), entry -> entry.minecraft.attachment != null && entry.id == channelId ); LOGGER.info(PlaceholdersExt.parseString("@${tag} attached ${name} (${size})", ctx, placeholders)); diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java index 28a1bf1..9836b0b 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java @@ -90,7 +90,7 @@ public void onMessageUpdate(MessageUpdateEvent event) */ MinecraftDispatcher.dispatch( - entry -> PlaceholdersExt.parseText(entry.minecraft.edit, ctx, placeholders), + entry -> PlaceholdersExt.parseText(entry.minecraft.editNode, ctx, placeholders), entry -> entry.minecraft.edit != null && entry.id == channelId ); LOGGER.info(PlaceholdersExt.parseString("@${tag} > ${raw}", ctx, placeholders)); diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java index 2506628..a2052f7 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java @@ -40,8 +40,6 @@ public void onEntityDeath(LivingEntity entity, DamageSource source) "name", string(entityName), // The reason for the entity's death "cause", string(source.getDeathMessage(entity).getString().replaceFirst(entityName, "").trim()), - // The name of the world the entity died in - "world", string(StringUtils.getWorldName(entity.world)), // The X coordinate of where the entity died "pos_x", string(String.valueOf((int) entity.prevX)), // The Y coordinate of where the entity died @@ -56,7 +54,7 @@ public void onEntityDeath(LivingEntity entity, DamageSource source) DiscordDispatcher.embed( (embed, entry) -> embed.setColor(Color.RED).setDescription( - PlaceholdersExt.parseString(entry.discord.grief, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.griefNode, ctx, placeholders) ), entry -> entry.discord.grief != null && entry.hasWorld(entity.world) ); diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerAdvancementCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerAdvancementCallback.java index 25f83c2..2cff45d 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerAdvancementCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerAdvancementCallback.java @@ -48,7 +48,7 @@ public void onGrantCriterion(ServerPlayerEntity player, Advancement advancement, // A player reached an advancement goal case GOAL -> DiscordDispatcher.embed( (embed, entry) -> embed.setDescription( - PlaceholdersExt.parseString(entry.discord.advancementGoal, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.advancementGoalNode, ctx, placeholders) ), entry -> entry.discord.advancementGoal != null && entry.hasWorld(player.world) ); @@ -56,7 +56,7 @@ public void onGrantCriterion(ServerPlayerEntity player, Advancement advancement, // A player completed an advancement challenge case CHALLENGE -> DiscordDispatcher.embed( (embed, entry) -> embed.setDescription( - PlaceholdersExt.parseString(entry.discord.advancementChallenge, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.advancementChallengeNode, ctx, placeholders) ), entry -> entry.discord.advancementChallenge != null && entry.hasWorld(player.world) ); @@ -64,7 +64,7 @@ public void onGrantCriterion(ServerPlayerEntity player, Advancement advancement, // A player unlocked an advancement task default -> DiscordDispatcher.embed( (embed, entry) -> embed.setDescription( - PlaceholdersExt.parseString(entry.discord.advancementTask, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.advancementTaskNode, ctx, placeholders) ), entry -> entry.discord.advancementTask != null && entry.hasWorld(player.world) ); diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java index efcb9dc..f0000e2 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java @@ -55,7 +55,7 @@ public void afterChangeWorld(ServerPlayerEntity player, ServerWorld origin, Serv DiscordDispatcher.embedWithAvatar( (embed, entry) -> embed.setDescription( - PlaceholdersExt.parseString(entry.discord.teleport, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.teleportNode, ctx, placeholders) ), entry -> entry.discord.teleport != null && entry.hasWorld(dest), player.getUuidAsString() diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerConnectionCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerConnectionCallback.java index 5ea053e..c5b7a3e 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerConnectionCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerConnectionCallback.java @@ -43,7 +43,7 @@ public void onPlayReady(ServerPlayNetworkHandler handler, PacketSender sender, M DiscordDispatcher.embedWithAvatar( (embed, entry) -> embed.setDescription( - PlaceholdersExt.parseString(entry.discord.join, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.joinNode, ctx, placeholders) ), entry -> entry.discord.join != null && entry.hasWorld(player.world), player.getUuidAsString() @@ -70,7 +70,7 @@ public void onPlayDisconnect(ServerPlayNetworkHandler handler, MinecraftServer s DiscordDispatcher.embedWithAvatar( (embed, entry) -> embed.setDescription( - PlaceholdersExt.parseString(entry.discord.leave, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.leaveNode, ctx, placeholders) ), entry -> entry.discord.leave != null && entry.hasWorld(player.world), player.getUuidAsString() diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java index d46651d..808010b 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java @@ -1,5 +1,6 @@ package me.axieum.mcmod.minecord.impl.chat.callback.minecraft; +import java.awt.Color; import java.time.Duration; import java.util.Map; @@ -14,6 +15,7 @@ import me.axieum.mcmod.minecord.api.Minecord; import me.axieum.mcmod.minecord.api.chat.event.minecraft.EntityDeathEvents; import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; +import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.duration; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; @@ -44,7 +46,9 @@ public void onPlayerDeath(ServerPlayerEntity player, DamageSource source) // The player's total score before they died "score", string(String.valueOf(player.getScore())), // The player's number of experience levels before they died - "exp", string(String.valueOf(player.experienceLevel)) + "exp", string(String.valueOf(player.experienceLevel)), + // The name of the world the player died in + "world", string(StringUtils.getWorldName(player.world)) ); /* @@ -52,8 +56,8 @@ public void onPlayerDeath(ServerPlayerEntity player, DamageSource source) */ DiscordDispatcher.embedWithAvatar( - (embed, entry) -> embed.setDescription( - PlaceholdersExt.parseString(entry.discord.death, ctx, placeholders) + (embed, entry) -> embed.setColor(Color.RED).setDescription( + PlaceholdersExt.parseString(entry.discord.deathNode, ctx, placeholders) ), entry -> entry.discord.death != null && entry.hasWorld(player.world), player.getUuidAsString() diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerLifecycleCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerLifecycleCallback.java index 36598fb..8dd577e 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerLifecycleCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerLifecycleCallback.java @@ -50,7 +50,7 @@ public void onServerStarting(MinecraftServer server) DiscordDispatcher.embed( (embed, entry) -> embed.setDescription( - PlaceholdersExt.parseString(entry.discord.starting, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.startingNode, ctx, placeholders) ), entry -> entry.discord.starting != null ); @@ -77,7 +77,7 @@ public void onServerStarted(MinecraftServer server) DiscordDispatcher.embed( (embed, entry) -> embed.setColor(Color.GREEN).setDescription( - PlaceholdersExt.parseString(entry.discord.started, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.startedNode, ctx, placeholders) ), entry -> entry.discord.started != null ); @@ -104,7 +104,7 @@ public void onServerStopping(MinecraftServer server) DiscordDispatcher.embed( (embed, entry) -> embed.setDescription( - PlaceholdersExt.parseString(entry.discord.stopping, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.stoppingNode, ctx, placeholders) ), entry -> entry.discord.stopping != null ); @@ -135,7 +135,7 @@ public void onServerShutdown(MinecraftServer server, @Nullable CrashReport crash if (crashReport == null) { DiscordDispatcher.embed( (embed, entry) -> embed.setColor(Color.RED).setDescription( - PlaceholdersExt.parseString(entry.discord.stopped, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.stoppedNode, ctx, placeholders) ), entry -> entry.discord.stopped != null ); @@ -148,7 +148,7 @@ public void onServerShutdown(MinecraftServer server, @Nullable CrashReport crash // Dispatch the message DiscordDispatcher.embed( (embed, entry) -> embed.setColor(Color.ORANGE).setDescription( - PlaceholdersExt.parseString(entry.discord.crashed, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.crashedNode, ctx, placeholders) ), (action, entry) -> { // Conditionally attach the crash report if required diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java index 820586e..9d739b6 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java @@ -49,7 +49,7 @@ public void onChatMessage( */ DiscordDispatcher.dispatch( - (embed, entry) -> embed.setContent(PlaceholdersExt.parseString(entry.discord.chat, ctx, placeholders)), + (embed, entry) -> embed.setContent(PlaceholdersExt.parseString(entry.discord.chatNode, ctx, placeholders)), entry -> entry.discord.chat != null && entry.hasWorld(player.world) ); }); @@ -99,7 +99,7 @@ public void onEmoteCommandMessage(SignedMessage message, ServerCommandSource sou */ DiscordDispatcher.dispatch( - (embed, entry) -> embed.setContent(PlaceholdersExt.parseString(entry.discord.emote, ctx, placeholders)), + (embed, entry) -> embed.setContent(PlaceholdersExt.parseString(entry.discord.emoteNode, ctx, placeholders)), entry -> entry.discord.emote != null && (player == null || entry.hasWorld(source.getWorld())) ); }); @@ -123,7 +123,7 @@ public void onSayCommandMessage(SignedMessage message, ServerCommandSource sourc * Prepare the message placeholders. */ - final @Nullable PlaceholderContext ctx = player != null ? PlaceholderContext.of(player) : null; + final PlaceholderContext ctx = PlaceholderContext.of(source); final Map placeholders = new HashMap<>(Map.of( // The formatted message contents "message", string(StringUtils.minecraftToDiscord(message.getContent().getString())) @@ -134,7 +134,9 @@ public void onSayCommandMessage(SignedMessage message, ServerCommandSource sourc */ DiscordDispatcher.dispatch( - (embed, entry) -> embed.setContent(PlaceholdersExt.parseString(entry.discord.say, ctx, placeholders)), + (embed, entry) -> embed.setContent( + PlaceholdersExt.parseString(entry.discord.sayNode, ctx, placeholders) + ), entry -> entry.discord.say != null && (player == null || entry.hasWorld(source.getWorld())) ); }); @@ -160,7 +162,7 @@ public void onTellRawCommandMessage(Text message, ServerCommandSource source) DiscordDispatcher.dispatch( (embed, entry) -> embed.setContent( - PlaceholdersExt.parseString(entry.discord.tellraw, ctx, placeholders) + PlaceholdersExt.parseString(entry.discord.tellrawNode, ctx, placeholders) ), entry -> entry.discord.tellraw != null ); diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/config/ChatConfig.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/config/ChatConfig.java index 33364f9..dbb6a7d 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/config/ChatConfig.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/config/ChatConfig.java @@ -2,6 +2,7 @@ import java.util.Arrays; +import eu.pb4.placeholders.api.node.TextNode; import me.shedaniel.autoconfig.AutoConfig; import me.shedaniel.autoconfig.ConfigData; import me.shedaniel.autoconfig.ConfigHolder; @@ -15,6 +16,8 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.parseNode; + /** * Minecord Chat configuration schema. */ @@ -63,7 +66,10 @@ public static class DiscordSchema @Comment(""" A player sent an in-game chat message Usages: ${message}""") - public String chat = "`${world:name}` **${player:name_unformatted}** > ${message}"; + public String chat = "`${minecord:world}` **${minecord:player}** > ${message}"; + + /** Pre-parsed 'chat' text node. */ + public transient TextNode chatNode; /** * A player sent an in-game message via the {@code /me} command. @@ -78,7 +84,10 @@ public static class DiscordSchema A player sent an in-game message via the '/me' command Note: there is no player or world if sent from a command block or console! Usages: ${action}""") - public String emote = "`${world:name}` **${player:name_unformatted}** _${action}_"; + public String emote = "`${minecord:world}` **${minecord:player}** _${action}_"; + + /** Pre-parsed 'emote' text node. */ + public transient TextNode emoteNode; /** * An admin broadcast an in-game message via the {@code /say} command. @@ -86,14 +95,18 @@ public static class DiscordSchema *

Note: there is no player or world if sent from a command block or console! * *

    + *
  • {@code ${player}} — the name of the player or server
  • *
  • {@code ${message}} — the formatted message contents
  • *
*/ @Comment(""" An admin broadcast an in-game message via the '/say' command Note: there is no player or world if sent from a command block or console! - Usages: ${message}""") - public String say = "**[${player:name_unformatted}]** ${message}"; + Usages: ${player} and ${message}""") + public String say = "**[${minecord:player}]** ${message}"; + + /** Pre-parsed 'say' text node. */ + public transient TextNode sayNode; /** * An admin broadcast an in-game message to all players via the {@code /tellraw @a} command. @@ -107,6 +120,9 @@ public static class DiscordSchema Usages: ${message}""") public String tellraw = "${message}"; + /** Pre-parsed 'tellraw' text node. */ + public transient TextNode tellrawNode; + /** * A player had died. * @@ -120,24 +136,29 @@ public static class DiscordSchema @Comment(""" A player had died Usages: ${cause}, ${exp}, ${lifespan [format]} and ${score}""") - public String death = "**${player:name_unformatted}** ${cause}! :skull:\n_${world:name} at ${player:pos_x}, ${player:pos_y}, ${player:pos_z}_"; + public String death = "**${minecord:player}** ${cause}! :skull:\n_${minecord:world} at ${player:pos_x 0}, ${player:pos_y 0}, ${player:pos_z 0}_"; + + /** Pre-parsed 'death' text node. */ + public transient TextNode deathNode; /** * A named animal/monster (with name tag) had died. * *
    - *
  • {@code ${cause}} — the reason for the entity's death
  • *
  • {@code ${name}} — the entity's display name
  • + *
  • {@code ${cause}} — the reason for the entity's death
  • *
  • {@code ${pos_x}} — the X coordinate of where the entity died
  • *
  • {@code ${pos_y}} — the Y coordinate of where the entity died
  • *
  • {@code ${pos_z}} — the Z coordinate of where the entity died
  • - *
  • {@code ${world}} — the name of the world the entity died in
  • *
*/ @Comment(""" A named animal/monster (with name tag) had died - Usages: ${cause}, ${name}, ${pos_x}, ${pos_y}, ${pos_z} and ${world}""") - public String grief = "**${name}** ${cause}! :coffin:\n_${world} at ${pos_x}, ${pos_y}, ${pos_z}_"; + Usages: ${name}, ${cause}, ${pos_x}, ${pos_y}, ${pos_z} and ${world}""") + public String grief = "**${name}** ${cause}! :coffin:\n_${minecord:world} at ${pos_x}, ${pos_y}, ${pos_z}_"; + + /** Pre-parsed 'grief' text node. */ + public transient TextNode griefNode; /** * A player unlocked an advancement task. @@ -150,7 +171,10 @@ public static class DiscordSchema @Comment(""" A player unlocked an advancement task Usages: ${title} and ${description}""") - public String advancementTask = "**${player:name_unformatted}** has made the advancement **${title}**! :clap:\n_${description}_"; + public String advancementTask = "**${minecord:player}** has made the advancement **${title}**! :clap:\n_${description}_"; + + /** Pre-parsed 'advancementTask' text node. */ + public transient TextNode advancementTaskNode; /** * A player reached an advancement goal. @@ -163,7 +187,10 @@ public static class DiscordSchema @Comment(""" A player reached an advancement goal Usages: ${title} and ${description}""") - public String advancementGoal = "**${player:name_unformatted}** has reached the goal **${title}**! :clap:\n_${description}_"; + public String advancementGoal = "**${minecord:player}** has reached the goal **${title}**! :clap:\n_${description}_"; + + /** Pre-parsed 'advancementGoal' text node. */ + public transient TextNode advancementGoalNode; /** * A player completed an advancement challenge. @@ -176,7 +203,10 @@ public static class DiscordSchema @Comment(""" A player completed an advancement challenge Usages: ${title} and ${description}""") - public String advancementChallenge = "**${player:name_unformatted}** has completed the challenge **${title}**! :trophy:\n_${description}_"; + public String advancementChallenge = "**${minecord:player}** has completed the challenge **${title}**! :trophy:\n_${description}_"; + + /** Pre-parsed 'advancementChallenge' text node. */ + public transient TextNode advancementChallengeNode; /** * A player teleported to another dimension. @@ -195,20 +225,32 @@ public static class DiscordSchema @Comment(""" A player teleported to another dimension Usages: ${world}, ${pos_x}, ${pos_y}, ${pos_z}, ${origin}, ${origin_pos_x}, ${origin_pos_y} and ${origin_pos_z}""") - public String teleport = "**${player:name_unformatted}** entered ${world:name}. :cyclone:"; + public String teleport = "**${minecord:player}** entered ${world}. :cyclone:"; + + /** Pre-parsed 'teleport' text node. */ + public transient TextNode teleportNode; /** A player joined the game. */ @Comment("A player joined the game") - public String join = "**${player:name_unformatted}** joined!"; + public String join = "**${minecord:player}** joined!"; + + /** Pre-parsed 'join' text node. */ + public transient TextNode joinNode; /** A player left the game. */ @Comment("A player left the game") - public String leave = "**${player:name_unformatted}** left!"; + public String leave = "**${minecord:player}** left!"; + + /** Pre-parsed 'leave' text node. */ + public transient TextNode leaveNode; /** The server began to start. */ @Comment("The server began to start") public String starting = "Server is starting... :fingers_crossed:"; + /** Pre-parsed 'starting' text node. */ + public transient TextNode startingNode; + /** * The server started and is accepting connections. * @@ -221,6 +263,9 @@ public static class DiscordSchema Usages: ${uptime [format]}""") public String started = "Server started (took ${uptime s.SSS}s) :ok_hand:"; + /** Pre-parsed 'started' text node. */ + public transient TextNode startedNode; + /** * The server began to stop. * @@ -233,6 +278,9 @@ public static class DiscordSchema Usages: ${uptime [format]}""") public String stopping = "Server is stopping... :raised_hand:"; + /** Pre-parsed 'stopping' text node. */ + public transient TextNode stoppingNode; + /** * The server stopped and is offline. * @@ -245,6 +293,9 @@ public static class DiscordSchema Usages: ${uptime [format]}""") public String stopped = "Server stopped! :no_entry:"; + /** Pre-parsed 'stopped' text node. */ + public transient TextNode stoppedNode; + /** * The server stopped unexpectedly and is inaccessible. * @@ -258,6 +309,9 @@ public static class DiscordSchema Usages: ${reason} and ${uptime [format]}""") public String crashed = "Server crash detected! :warning:\n_${reason}_"; + /** Pre-parsed 'crashed' text node. */ + public transient TextNode crashedNode; + /** True if a crash report should be attached to any server crash messages. */ @Comment("True if a crash report should be attached to any server crash messages") public boolean uploadCrashReport = false; @@ -291,6 +345,9 @@ public static class MinecraftSchema Usages: ${author}, ${tag}, ${username}, ${discriminator}, ${message} and ${raw}""") public String chat = "Sent from Discord'>${author} > ${message}"; + /** Pre-parsed 'chat' text node. */ + public transient TextNode chatNode; + /** * A user sent a message in reply to another. * @@ -314,6 +371,9 @@ public static class MinecraftSchema Usages: ${author}, ${tag}, ${username}, ${discriminator}, ${message}, ${raw}, ${reply_author}, ${reply_tag}, ${reply_username}, ${reply_discriminator}, ${reply_message} and ${reply_raw}""") public String reply = "Sent from Discord'>${author} ${reply_author} > ${message}"; + /** Pre-parsed 'crashed' text node. */ + public transient TextNode replyNode; + /** * A user edited their recently sent message. * @@ -334,6 +394,9 @@ public static class MinecraftSchema Usages: ${author}, ${tag}, ${username}, ${discriminator}, ${diff}, ${message}, ${raw}, ${original} and ${original_raw}""") public String edit = "Sent from Discord'>${author} > ${diff}"; + /** Pre-parsed 'edit' text node. */ + public transient TextNode editNode; + /** * A user reacted to a recent message. * @@ -354,6 +417,9 @@ public static class MinecraftSchema Usages: ${issuer}, ${issuer_tag}, ${issuer_username}, ${issuer_discriminator}, ${author}, ${author_tag}, ${author_username}, ${author_discriminator} and ${emote}""") public String react = "Sent from Discord'>${issuer} reacted with ${emote} to ${author}'s message"; + /** Pre-parsed 'react' text node. */ + public transient TextNode reactNode; + /** * A user removed their reaction from a recent message. * @@ -374,6 +440,9 @@ public static class MinecraftSchema Usages: ${issuer}, ${issuer_tag}, ${issuer_username}, ${issuer_discriminator}, ${author}, ${author_tag}, ${author_username}, ${author_discriminator} and ${emote}""") public String unreact = "Sent from Discord'>${issuer} removed their reaction of ${emote} from ${author}'s message"; + /** Pre-parsed 'unreact' text node. */ + public transient TextNode unreactNode; + /** * A user sent a message that contained attachments. * @@ -392,6 +461,9 @@ public static class MinecraftSchema A user sent a message that contained attachments Usages: ${author}, ${tag}, ${username}, ${discriminator}, ${url}, ${name}, ${ext} and ${size}""") public String attachment = "Sent from Discord'>${author} > entry.id == identifier); } + @Override + public void validatePostLoad() + { + Arrays.stream(entries).forEach(entry -> { + // Parse Discord event templates + entry.discord.chatNode = parseNode(entry.discord.chat); + entry.discord.emoteNode = parseNode(entry.discord.emote); + entry.discord.sayNode = parseNode(entry.discord.say); + entry.discord.tellrawNode = parseNode(entry.discord.tellraw); + entry.discord.deathNode = parseNode(entry.discord.death); + entry.discord.griefNode = parseNode(entry.discord.grief); + entry.discord.advancementTaskNode = parseNode(entry.discord.advancementTask); + entry.discord.advancementGoalNode = parseNode(entry.discord.advancementGoal); + entry.discord.advancementChallengeNode = parseNode(entry.discord.advancementChallenge); + entry.discord.teleportNode = parseNode(entry.discord.teleport); + entry.discord.joinNode = parseNode(entry.discord.join); + entry.discord.leaveNode = parseNode(entry.discord.leave); + entry.discord.startingNode = parseNode(entry.discord.starting); + entry.discord.startedNode = parseNode(entry.discord.started); + entry.discord.stoppingNode = parseNode(entry.discord.stopping); + entry.discord.stoppedNode = parseNode(entry.discord.stopped); + entry.discord.crashedNode = parseNode(entry.discord.crashed); + // Parse Minecraft event templates + entry.minecraft.chatNode = parseNode(entry.minecraft.chat); + entry.minecraft.replyNode = parseNode(entry.minecraft.reply); + entry.minecraft.editNode = parseNode(entry.minecraft.edit); + entry.minecraft.reactNode = parseNode(entry.minecraft.react); + entry.minecraft.unreactNode = parseNode(entry.minecraft.unreact); + entry.minecraft.attachmentNode = parseNode(entry.minecraft.attachment); + }); + } + /** * Registers and prepares a new configuration instance. * diff --git a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/callback/DiscordCommandListener.java b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/callback/DiscordCommandListener.java index 054d1c3..fb54fb9 100644 --- a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/callback/DiscordCommandListener.java +++ b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/callback/DiscordCommandListener.java @@ -62,7 +62,7 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) LOGGER.warn("@{} used '{}' but the server is not yet ready!", username, raw); event.getHook().sendMessageEmbeds( new EmbedBuilder().setColor(0xff8800).setDescription( - PlaceholdersExt.parseString(getConfig().messages.unavailable, pCtx, Collections.emptyMap()) + PlaceholdersExt.parseString(getConfig().messages.unavailableNode, pCtx, Collections.emptyMap()) ).build() ).queue(); return; @@ -77,7 +77,7 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) event.getHook().setEphemeral(true).sendMessageEmbeds( new EmbedBuilder().setColor(0xff8800).setDescription( PlaceholdersExt.parseString( - getConfig().messages.cooldown, pCtx, Map.of( + getConfig().messages.cooldownNode, pCtx, Map.of( // The total cooldown before the command can be used again "cooldown", duration(Duration.ofSeconds(command.getCooldown())), // The remaining time before the command can be used again @@ -110,7 +110,7 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) event.getHook().sendMessageEmbeds( new EmbedBuilder().setColor(0xff0000).setDescription( PlaceholdersExt.parseString( - getConfig().messages.failed, pCtx, Map.of( + getConfig().messages.failedNode, pCtx, Map.of( // The reason for the command failing "reason", string(e.getMessage()) ) diff --git a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/CustomCommand.java b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/CustomCommand.java index 6c43d37..faa5169 100644 --- a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/CustomCommand.java +++ b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/CustomCommand.java @@ -12,6 +12,8 @@ import eu.pb4.placeholders.api.PlaceholderContext; import eu.pb4.placeholders.api.PlaceholderHandler; +import eu.pb4.placeholders.api.node.EmptyNode; +import eu.pb4.placeholders.api.node.TextNode; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.exceptions.ParsingException; @@ -90,7 +92,7 @@ public void execute(@NotNull SlashCommandInteractionEvent event, @Nullable Minec // Prepare the Minecraft command final String origCommand; try { - origCommand = prepareCommand(config.command, event.getOptions(), server); + origCommand = prepareCommand(config.commandNode, event.getOptions(), server); } catch (ParsingException | IllegalArgumentException e) { throw new Exception("Unable to prepare Minecraft command!", e); } @@ -163,7 +165,7 @@ public void execute(@NotNull SlashCommandInteractionEvent event, @Nullable Minec output.thumbnailUrl = null; source.sendFeedback( PlaceholdersExt.parseText( - getConfig().messages.feedback, PlaceholderContext.of(source), Collections.emptyMap() + getConfig().messages.feedbackNode, PlaceholderContext.of(source), Collections.emptyMap() ), false ); @@ -176,19 +178,19 @@ public void execute(@NotNull SlashCommandInteractionEvent event, @Nullable Minec /** * Substitutes a Minecraft command template with options from a Discord command. * - * @param command Minecraft command template using {n} for the nth argument, and {} for all + * @param command Minecraft command template using {@code ${}} for the 'name' argument * @param options Discord command options to substitute into the template * @param server optional Minecraft server for placeholder context * @return a prepared Minecraft command * @throws ParsingException if an invalid command option type is encountered */ private static String prepareCommand( - @NotNull String command, + @NotNull TextNode command, List options, @Nullable MinecraftServer server ) throws ParsingException { - if (command.length() == 0) return command; + if (command == EmptyNode.INSTANCE) return ""; // Prepare new command placeholders final @Nullable PlaceholderContext ctx = server != null ? PlaceholderContext.of(server) : null; diff --git a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/UptimeCommand.java b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/UptimeCommand.java index 1c1fc96..2551ce0 100644 --- a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/UptimeCommand.java +++ b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/command/discord/UptimeCommand.java @@ -60,7 +60,7 @@ public void execute(@NotNull SlashCommandInteractionEvent event, @Nullable Minec // Prepare an embed to be sent to the user EmbedBuilder embed = new EmbedBuilder().setDescription( - PlaceholdersExt.parseString(getConfig().builtin.uptime.message, ctx, PLACEHOLDERS) + PlaceholdersExt.parseString(getConfig().builtin.uptime.messageNode, ctx, PLACEHOLDERS) ); // Fire an event to allow the embed to be mutated diff --git a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/config/CommandConfig.java b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/config/CommandConfig.java index 7b169ad..03c28d7 100644 --- a/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/config/CommandConfig.java +++ b/minecord-cmds/src/main/java/me/axieum/mcmod/minecord/impl/cmds/config/CommandConfig.java @@ -1,5 +1,8 @@ package me.axieum.mcmod.minecord.impl.cmds.config; +import java.util.Arrays; + +import eu.pb4.placeholders.api.node.TextNode; import me.shedaniel.autoconfig.AutoConfig; import me.shedaniel.autoconfig.ConfigData; import me.shedaniel.autoconfig.ConfigHolder; @@ -19,6 +22,8 @@ import me.axieum.mcmod.minecord.api.cmds.command.CooldownScope; import me.axieum.mcmod.minecord.impl.cmds.MinecordCommandsImpl; +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.parseNode; + /** * Minecord Commands configuration schema. */ @@ -39,6 +44,9 @@ public static class MessagesSchema @Comment("The error message used when the Minecraft server is unavailable") public String unavailable = "The server is not yet ready - please wait :warning:"; + /** Pre-parsed 'unavailable' text node. */ + public transient TextNode unavailableNode; + /** * The error message used when any command unexpectedly fails. * @@ -51,6 +59,9 @@ public static class MessagesSchema Usages: ${reason}""") public String failed = "**Oh no** - something went wrong! :warning:\n_${reason}_"; + /** Pre-parsed 'failed' text node. */ + public transient TextNode failedNode; + /** * The error message used when a user must wait before executing a command. * @@ -64,10 +75,16 @@ public static class MessagesSchema Usages: ${cooldown} and ${remaining}""") public String cooldown = "Please wait another ${remaining} before doing that! :alarm_clock:"; + /** Pre-parsed 'cooldown' text node. */ + public transient TextNode cooldownNode; + /** The default message used when a command does not provide any feedback of its own, e.g. {@code /say} */ @Comment(""" The default message used when a command does not provide any feedback of its own, e.g. '/say'""") public String feedback = "Consider it done! :thumbsup:"; + + /** Pre-parsed 'feedback' text node. */ + public transient TextNode feedbackNode; } /** Built-in Discord commands. */ @@ -108,6 +125,9 @@ public UptimeCommandSchema() A message template that is formatted and sent for the server's uptime Usages: ${uptime [format]}""") public String message = "The server has been online for ${uptime} :hourglass_flowing_sand:"; + + /** Pre-parsed 'message' text node. */ + public transient TextNode messageNode; } /** Built-in TPS command. */ @@ -160,6 +180,9 @@ public CustomCommandSchema() Usages: ${} for "" option value""") public String command = "/whitelist ${args:-}"; + /** Pre-parsed 'command' text node. */ + public transient TextNode commandNode; + /** A list of command options. */ @Category("Options") @Comment("A list of command options") @@ -270,6 +293,20 @@ public OptionData getOptionData() throws IllegalArgumentException } } + @Override + public void validatePostLoad() + { + // Parse message templates + messages.unavailableNode = parseNode(messages.unavailable); + messages.failedNode = parseNode(messages.failed); + messages.cooldownNode = parseNode(messages.cooldown); + messages.feedbackNode = parseNode(messages.feedback); + builtin.uptime.messageNode = parseNode(builtin.uptime.message); + + // Parse command templates + Arrays.stream(custom).forEach(cmd -> cmd.commandNode = parseNode(cmd.command)); + } + /** * Registers and prepares a new configuration instance. * diff --git a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/api/presence/category/PresenceSupplier.java b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/api/presence/category/PresenceSupplier.java index cf8c8ad..a6f3b65 100644 --- a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/api/presence/category/PresenceSupplier.java +++ b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/api/presence/category/PresenceSupplier.java @@ -1,11 +1,15 @@ package me.axieum.mcmod.minecord.api.presence.category; +import java.util.Collections; import java.util.Optional; import java.util.function.Function; +import eu.pb4.placeholders.api.node.TextNode; import net.dv8tion.jda.api.OnlineStatus; import net.dv8tion.jda.api.entities.Activity; +import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; + /** * A Minecord presence supplier. */ @@ -38,7 +42,7 @@ default Optional getStatus() */ default Optional getActivity() { - return getActivity(Function.identity()); + return getActivity(nameNode -> PlaceholdersExt.parseString(nameNode, null, Collections.emptyMap())); } /** @@ -47,7 +51,7 @@ default Optional getActivity() * @param nameMutator mutator for the activity name * @return game activity, or empty if not changing */ - default Optional getActivity(Function nameMutator) + default Optional getActivity(Function nameMutator) { return Optional.empty(); } diff --git a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/PresenceUpdateTask.java b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/PresenceUpdateTask.java index b8ee6bc..66afe2c 100644 --- a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/PresenceUpdateTask.java +++ b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/PresenceUpdateTask.java @@ -74,7 +74,7 @@ public void run() final Boolean idle = supplier.isIdle().orElse(current.isIdle()); final @Nullable OnlineStatus status = supplier.getStatus().orElse(current.getStatus()); final @Nullable Activity activity = supplier.getActivity( - name -> PlaceholdersExt.parseString(name, ctx.orElse(null), placeholders) + nameNode -> PlaceholdersExt.parseString(nameNode, ctx.orElse(null), placeholders) ).orElse(currentActivity); /* diff --git a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/config/PresenceConfig.java b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/config/PresenceConfig.java index c99088e..22b07cf 100644 --- a/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/config/PresenceConfig.java +++ b/minecord-presence/src/main/java/me/axieum/mcmod/minecord/impl/presence/config/PresenceConfig.java @@ -1,10 +1,12 @@ package me.axieum.mcmod.minecord.impl.presence.config; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.Function; +import eu.pb4.placeholders.api.node.TextNode; import me.shedaniel.autoconfig.AutoConfig; import me.shedaniel.autoconfig.ConfigData; import me.shedaniel.autoconfig.ConfigHolder; @@ -24,6 +26,8 @@ import me.axieum.mcmod.minecord.api.presence.category.PresenceSupplier; import me.axieum.mcmod.minecord.impl.presence.MinecordPresenceImpl; + +import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.parseNode; import static me.axieum.mcmod.minecord.impl.presence.MinecordPresenceImpl.LOGGER; /** @@ -100,12 +104,7 @@ public PresenceSchema( @Comment("If defined, overrides whether the bot is idling") public @Nullable Boolean idle = null; - /** - * If defined, overrides the online status. - * - *

Allowed values: {@code null}, {@code ONLINE}, {@code IDLE}, - * {@code DO_NOT_DISTURB}, {@code INVISIBLE} and {@code OFFLINE}. - */ + /** If defined, overrides the online status. */ @Comment(""" If defined, overrides the online status Allowed values: null, ONLINE, IDLE, DO_NOT_DISTURB, INVISIBLE and OFFLINE""") @@ -160,6 +159,9 @@ public ActivitySchema(ActivityType type, String name, @Nullable String url) Usages: ${uptime [format]}""") public String name = "Minecraft"; + /** Pre-parsed 'name' text node. */ + public transient TextNode nameNode; + /** If defined, provides a link to the activity, e.g. Twitch stream. */ @Comment("If defined, provides a link to the activity, e.g. Twitch stream") public @Nullable String url = null; @@ -187,10 +189,10 @@ public Optional isIdle() } @Override - public Optional getActivity(Function nameMutator) + public Optional getActivity(Function nameMutator) { return Optional.ofNullable(activity).map(activity -> - Activity.of(activity.type, nameMutator.apply(activity.name), activity.url) + Activity.of(activity.type, nameMutator.apply(activity.nameNode), activity.url) ); } }; @@ -268,6 +270,11 @@ public void validatePostLoad() throws ValidationException ); category.interval = 15; } + + // Parse presence templates + Arrays.stream(category.presences) + .filter(presence -> presence.activity != null) + .forEach(presence -> presence.activity.nameNode = parseNode(presence.activity.name)); } } From 7c9ffe308b2b35ca93fc56aba20f76f9882f8484 Mon Sep 17 00:00:00 2001 From: Jonathan Hiles Date: Sun, 4 Jun 2023 19:36:16 +1000 Subject: [PATCH 6/7] fix: incorrect placeholder values --- .../minecord/api/util/PlaceholdersExt.java | 4 ++- .../mcmod/minecord/api/util/StringUtils.java | 24 +++++++++++++++++- .../placeholder/MinecordPlaceholders.java | 3 ++- .../minecord/api/util/StringUtilsTests.java | 25 +++++++++++++++++++ .../discord/MessageReactionListener.java | 3 ++- .../discord/MessageReceivedListener.java | 11 +++++--- .../discord/MessageUpdateListener.java | 9 ++++--- .../minecraft/EntityDeathCallback.java | 1 - .../minecraft/PlayerChangeWorldCallback.java | 10 +++++--- .../minecraft/PlayerDeathCallback.java | 4 +-- .../minecraft/ServerMessageCallback.java | 8 ++++-- .../minecord/impl/chat/config/ChatConfig.java | 9 +++---- .../mixin/chat/LivingEntityAccessor.java | 22 ++++++++++++++++ .../main/resources/minecord-chat.mixins.json | 1 + .../cmds/callback/DiscordCommandListener.java | 4 ++- .../impl/cmds/config/CommandConfig.java | 1 - .../impl/presence/config/PresenceConfig.java | 1 - 17 files changed, 111 insertions(+), 29 deletions(-) create mode 100644 minecord-chat/src/main/java/me/axieum/mcmod/minecord/mixin/chat/LivingEntityAccessor.java diff --git a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java index f25e64a..3279059 100644 --- a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java +++ b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java @@ -100,7 +100,9 @@ public static Text parseText( } public static Text parseText( - @NotNull TextNode node, @Nullable PlaceholderContext context, @NotNull Map placeholders + @NotNull TextNode node, + @Nullable PlaceholderContext context, + @NotNull Map placeholders ) { return parseText(node, context, placeholders::get); diff --git a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/StringUtils.java b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/StringUtils.java index ed8f167..d9aa088 100644 --- a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/StringUtils.java +++ b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/StringUtils.java @@ -8,6 +8,7 @@ import java.util.regex.MatchResult; import java.util.regex.Pattern; +import com.vdurmont.emoji.EmojiParser; import net.dv8tion.jda.api.entities.IMentionable; import net.minecraft.util.Formatting; @@ -25,7 +26,7 @@ public final class StringUtils /** Mapping of Minecraft world identifiers to their human-readable names. */ public static final HashMap WORLD_NAMES = new HashMap<>(3); /** String templates for translating between Minecraft and Discord formatted strings. */ - public static StringTemplate minecraftDiscordST; + public static StringTemplate discordMinecraftST, minecraftDiscordST; private StringUtils() {} @@ -46,6 +47,14 @@ public static String bytesToHuman(long bytes) return String.format("%.1f %cB", bytes / 1000.0, ci.current()); } + static { + discordMinecraftST = new StringTemplate() + // Translate emojis from unicode + .transform(EmojiParser::parseToAliases) + // Strip any leftover formatting + .transform(Formatting::strip); + } + static { final Pattern breaks = Pattern.compile("(?s)\\n+"); final Pattern bold = Pattern.compile("(?<=§l)(.+?)(?=\\s?§r|$)"); @@ -100,6 +109,19 @@ public static String bytesToHuman(long bytes) .transform(Formatting::strip); } + /** + * Translates a Discord flavoured markdown string to a + * Minecraft-formatted string. + * + * @param contents Discord flavoured markdown string + * @return Minecraft-formatted string + */ + public static String discordToMinecraft(final String contents) + { + // Apply the appropriate string template against the given contents and return + return discordMinecraftST.format(contents); + } + /** * Translates a Minecraft-formatted string to Discord flavoured markdown. * diff --git a/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/placeholder/MinecordPlaceholders.java b/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/placeholder/MinecordPlaceholders.java index c8d546d..ffadd42 100644 --- a/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/placeholder/MinecordPlaceholders.java +++ b/minecord-api/src/main/java/me/axieum/mcmod/minecord/impl/placeholder/MinecordPlaceholders.java @@ -6,7 +6,6 @@ import net.minecraft.util.Identifier; import me.axieum.mcmod.minecord.api.util.StringUtils; - import static me.axieum.mcmod.minecord.impl.MinecordImpl.getConfig; /** @@ -14,6 +13,8 @@ */ public final class MinecordPlaceholders { + private MinecordPlaceholders() {} + /** * Registers Minecord API global placeholders with {@link eu.pb4.placeholders.api.Placeholders Placeholder API}. */ diff --git a/minecord-api/src/test/java/me/axieum/mcmod/minecord/api/util/StringUtilsTests.java b/minecord-api/src/test/java/me/axieum/mcmod/minecord/api/util/StringUtilsTests.java index 2b4a4b4..ee566d7 100644 --- a/minecord-api/src/test/java/me/axieum/mcmod/minecord/api/util/StringUtilsTests.java +++ b/minecord-api/src/test/java/me/axieum/mcmod/minecord/api/util/StringUtilsTests.java @@ -33,6 +33,31 @@ public void bytesToHuman() assertEquals("1.4 EB", StringUtils.bytesToHuman(1427965224842685628L)); } + @Nested + @DisplayName("Translate Discord flavoured markdown to Minecraft-formatted text") + public class DiscordToMinecraft + { + @Test + @DisplayName("Translate emojis from unicode formatted text") + public void emojis() + { + assertEquals( + "This is a smiley :slightly_smiling: face!", + StringUtils.discordToMinecraft("This is a smiley \uD83D\uDE42 face!") + ); + } + + @Test + @DisplayName("Strip any left over formatting") + public void stripFormatting() + { + assertEquals( + "This is green text!", + StringUtils.discordToMinecraft("This is §agreen§r text!") + ); + } + } + @Nested @DisplayName("Translate Minecraft-formatted text to Discord flavoured markdown") public class MinecraftToDiscord diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java index 3a4e949..ec349d0 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReactionListener.java @@ -2,6 +2,7 @@ import java.util.Map; +import com.vdurmont.emoji.EmojiParser; import eu.pb4.placeholders.api.PlaceholderContext; import eu.pb4.placeholders.api.PlaceholderHandler; import net.dv8tion.jda.api.events.message.react.GenericMessageReactionEvent; @@ -34,7 +35,7 @@ public void onGenericMessageReaction(GenericMessageReactionEvent event) event.retrieveMessage().queue(context -> { // Compute some useful properties of the event final boolean isAdded = event instanceof MessageReactionAddEvent; - final String emote = ":" + event.getEmoji().getName() + ":"; + final String emote = EmojiParser.parseToAliases(event.getEmoji().getName()); /* * Prepare the message placeholders. diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java index 225a194..19155f8 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageReceivedListener.java @@ -15,6 +15,7 @@ import me.axieum.mcmod.minecord.impl.chat.util.MinecraftDispatcher; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.markdown; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; +import static me.axieum.mcmod.minecord.api.util.StringUtils.discordToMinecraft; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.LOGGER; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.getConfig; @@ -66,7 +67,7 @@ public void onText(MessageReceivedEvent event) event.getMember() != null ? event.getMember().getEffectiveName() : event.getAuthor().getName() ), // The formatted message contents - "message", markdown(event.getMessage().getContentDisplay()), + "message", markdown(discordToMinecraft(event.getMessage().getContentDisplay())), // The raw message contents "raw", string(event.getMessage().getContentRaw()) )); @@ -87,7 +88,7 @@ public void onText(MessageReceivedEvent event) : replyMessage.getAuthor().getName() ), // The replied message formatted message contents - "reply_message", markdown(replyMessage.getContentDisplay()), + "reply_message", markdown(discordToMinecraft(replyMessage.getContentDisplay())), // The replied message raw message contents "reply_raw", string(replyMessage.getContentRaw()) )); @@ -103,7 +104,7 @@ public void onText(MessageReceivedEvent event) entry -> PlaceholdersExt.parseText(entry.minecraft.chatNode, ctx, placeholders), entry -> entry.minecraft.chat != null && entry.id == channelId ); - LOGGER.info(PlaceholdersExt.parseString("@${tag} > ${raw}", ctx, placeholders)); + LOGGER.info(PlaceholdersExt.parseString("@${tag} > ${message}", ctx, placeholders)); // The message is in reply to another } else { @@ -111,7 +112,9 @@ public void onText(MessageReceivedEvent event) entry -> PlaceholdersExt.parseText(entry.minecraft.replyNode, ctx, placeholders), entry -> entry.minecraft.reply != null && entry.id == channelId ); - LOGGER.info(PlaceholdersExt.parseString("@${tag} (in reply to @${reply_tag}) > ${raw}", ctx, placeholders)); + LOGGER.info(PlaceholdersExt.parseString( + "@${tag} (in reply to @${reply_tag}) > ${message}", ctx, placeholders + )); } } diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java index 9836b0b..097da92 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/discord/MessageUpdateListener.java @@ -22,6 +22,7 @@ import me.axieum.mcmod.minecord.impl.chat.util.MinecraftDispatcher; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.markdown; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; +import static me.axieum.mcmod.minecord.api.util.StringUtils.discordToMinecraft; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.LOGGER; import static me.axieum.mcmod.minecord.impl.chat.MinecordChat.getConfig; @@ -47,8 +48,8 @@ public void onMessageUpdate(MessageUpdateEvent event) // Only listen to message updates if we have the original cached MESSAGE_CACHE.computeIfPresent(event.getMessageId(), (id, context) -> { // Compute the textual difference - final String original = context.getContentDisplay(), - message = event.getMessage().getContentDisplay(); + final String original = discordToMinecraft(context.getContentDisplay()), + message = discordToMinecraft(event.getMessage().getContentDisplay()); final List diffs = DIFF_GENERATOR.generateDiffRows( Collections.singletonList(original), Collections.singletonList(message) ); @@ -78,7 +79,7 @@ public void onMessageUpdate(MessageUpdateEvent event) // The old raw message contents "original_raw", string(context.getContentRaw()), // The new formatted message contents - "message", markdown(event.getMessage().getContentDisplay()), + "message", markdown(message), // The new raw message contents "raw", string(event.getMessage().getContentRaw()), // The difference between the original and new message @@ -93,7 +94,7 @@ public void onMessageUpdate(MessageUpdateEvent event) entry -> PlaceholdersExt.parseText(entry.minecraft.editNode, ctx, placeholders), entry -> entry.minecraft.edit != null && entry.id == channelId ); - LOGGER.info(PlaceholdersExt.parseString("@${tag} > ${raw}", ctx, placeholders)); + LOGGER.info(PlaceholdersExt.parseString("@${tag} > ${message}", ctx, placeholders)); } // Update the message cache diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java index a2052f7..ece8c0a 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/EntityDeathCallback.java @@ -12,7 +12,6 @@ import me.axieum.mcmod.minecord.api.Minecord; import me.axieum.mcmod.minecord.api.chat.event.minecraft.EntityDeathEvents; import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; -import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java index f0000e2..3a59882 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerChangeWorldCallback.java @@ -7,6 +7,7 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents; @@ -14,6 +15,7 @@ import me.axieum.mcmod.minecord.api.util.PlaceholdersExt; import me.axieum.mcmod.minecord.api.util.StringUtils; import me.axieum.mcmod.minecord.impl.chat.util.DiscordDispatcher; +import me.axieum.mcmod.minecord.mixin.chat.LivingEntityAccessor; import static me.axieum.mcmod.minecord.api.util.PlaceholdersExt.string; /** @@ -25,6 +27,8 @@ public class PlayerChangeWorldCallback implements ServerEntityWorldChangeEvents. public void afterChangeWorld(ServerPlayerEntity player, ServerWorld origin, ServerWorld dest) { Minecord.getInstance().getJDA().ifPresent(jda -> { + final BlockPos lastBlockPos = ((LivingEntityAccessor) player).getLastBlockPos(); + /* * Prepare the message placeholders. */ @@ -42,11 +46,11 @@ public void afterChangeWorld(ServerPlayerEntity player, ServerWorld origin, Serv // The name of the world the player left "origin", string(StringUtils.getWorldName(origin)), // The X coordinate of where the player left - "origin_pos_x", string(String.valueOf((int) player.prevX)), + "origin_pos_x", string(String.valueOf(lastBlockPos.getX())), // The Y coordinate of where the player left - "origin_pos_y", string(String.valueOf((int) player.prevY)), + "origin_pos_y", string(String.valueOf(lastBlockPos.getY())), // The Z coordinate of where the player left - "origin_pos_z", string(String.valueOf((int) player.prevZ)) + "origin_pos_z", string(String.valueOf(lastBlockPos.getZ())) ); /* diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java index 808010b..9cfa133 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/PlayerDeathCallback.java @@ -40,8 +40,8 @@ public void onPlayerDeath(ServerPlayerEntity player, DamageSource source) // The reason for the player's death "cause", string(source.getDeathMessage(player).getString().replaceFirst(playerName, "").trim()), // The total time for which the player was alive for - "lifespan", duration(Duration.ofMinutes( - player.getStatHandler().getStat(Stats.CUSTOM.getOrCreateStat(Stats.TIME_SINCE_DEATH)) + "lifespan", duration(Duration.ofSeconds( + player.getStatHandler().getStat(Stats.CUSTOM.getOrCreateStat(Stats.TIME_SINCE_DEATH)) / 20 )), // The player's total score before they died "score", string(String.valueOf(player.getScore())), diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java index 9d739b6..aa71d95 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/callback/minecraft/ServerMessageCallback.java @@ -49,7 +49,9 @@ public void onChatMessage( */ DiscordDispatcher.dispatch( - (embed, entry) -> embed.setContent(PlaceholdersExt.parseString(entry.discord.chatNode, ctx, placeholders)), + (embed, entry) -> embed.setContent( + PlaceholdersExt.parseString(entry.discord.chatNode, ctx, placeholders) + ), entry -> entry.discord.chat != null && entry.hasWorld(player.world) ); }); @@ -99,7 +101,9 @@ public void onEmoteCommandMessage(SignedMessage message, ServerCommandSource sou */ DiscordDispatcher.dispatch( - (embed, entry) -> embed.setContent(PlaceholdersExt.parseString(entry.discord.emoteNode, ctx, placeholders)), + (embed, entry) -> embed.setContent( + PlaceholdersExt.parseString(entry.discord.emoteNode, ctx, placeholders) + ), entry -> entry.discord.emote != null && (player == null || entry.hasWorld(source.getWorld())) ); }); diff --git a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/config/ChatConfig.java b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/config/ChatConfig.java index dbb6a7d..4abdc45 100644 --- a/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/config/ChatConfig.java +++ b/minecord-chat/src/main/java/me/axieum/mcmod/minecord/impl/chat/config/ChatConfig.java @@ -82,7 +82,6 @@ public static class DiscordSchema */ @Comment(""" A player sent an in-game message via the '/me' command - Note: there is no player or world if sent from a command block or console! Usages: ${action}""") public String emote = "`${minecord:world}` **${minecord:player}** _${action}_"; @@ -95,14 +94,12 @@ public static class DiscordSchema *

Note: there is no player or world if sent from a command block or console! * *

    - *
  • {@code ${player}} — the name of the player or server
  • *
  • {@code ${message}} — the formatted message contents
  • *
*/ @Comment(""" An admin broadcast an in-game message via the '/say' command - Note: there is no player or world if sent from a command block or console! - Usages: ${player} and ${message}""") + Usages: ${message}""") public String say = "**[${minecord:player}]** ${message}"; /** Pre-parsed 'say' text node. */ @@ -154,7 +151,7 @@ public static class DiscordSchema */ @Comment(""" A named animal/monster (with name tag) had died - Usages: ${name}, ${cause}, ${pos_x}, ${pos_y}, ${pos_z} and ${world}""") + Usages: ${name}, ${cause}, ${pos_x}, ${pos_y} and ${pos_z}""") public String grief = "**${name}** ${cause}! :coffin:\n_${minecord:world} at ${pos_x}, ${pos_y}, ${pos_z}_"; /** Pre-parsed 'grief' text node. */ @@ -460,7 +457,7 @@ public static class MinecraftSchema @Comment(""" A user sent a message that contained attachments Usages: ${author}, ${tag}, ${username}, ${discriminator}, ${url}, ${name}, ${ext} and ${size}""") - public String attachment = "Sent from Discord'>${author} > Date: Sun, 4 Jun 2023 20:07:22 +1000 Subject: [PATCH 7/7] fix: add missing javadoc --- .../minecord/api/util/PlaceholdersExt.java | 142 +++++++++--------- 1 file changed, 74 insertions(+), 68 deletions(-) diff --git a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java index 3279059..2e6f525 100644 --- a/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java +++ b/minecord-api/src/main/java/me/axieum/mcmod/minecord/api/util/PlaceholdersExt.java @@ -61,44 +61,14 @@ private PlaceholdersExt() {} * Text-output placeholder parsers. */ - public static Text parseText(@NotNull String str, @NotNull Map placeholders) - { - return parseText(str, null, placeholders); - } - - public static Text parseText( - @NotNull String str, @Nullable PlaceholderContext context, @NotNull Map placeholders - ) - { - return parseText(str, context, placeholders::get); - } - - public static Text parseText( - @NotNull String str, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter - ) - { - return parseText(parseNode(str), context, placeholderGetter); - } - - public static Text parseText(@NotNull Text text, @NotNull Map placeholders) - { - return parseText(text, null, placeholders); - } - - public static Text parseText( - @NotNull Text text, @Nullable PlaceholderContext context, @NotNull Map placeholders - ) - { - return parseText(text, context, placeholders::get); - } - - public static Text parseText( - @NotNull Text text, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter - ) - { - return parseText(parseNode(text), context, placeholderGetter); - } - + /** + * Formats a placeholder template into a Minecraft text component. + * + * @param node pre-parsed placeholder text node + * @param context placeholder context + * @param placeholders mapping of placeholder key-value pairs + * @return formatted Minecraft text + */ public static Text parseText( @NotNull TextNode node, @Nullable PlaceholderContext context, @@ -108,8 +78,18 @@ public static Text parseText( return parseText(node, context, placeholders::get); } + /** + * Formats a placeholder template into a Minecraft text component. + * + * @param node pre-parsed placeholder text node + * @param context placeholder context + * @param placeholderGetter function that takes placeholder key and returns value + * @return formatted Minecraft text + */ public static Text parseText( - @NotNull TextNode node, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter + @NotNull TextNode node, + @Nullable PlaceholderContext context, + @NotNull PlaceholderGetter placeholderGetter ) { return context != null @@ -117,6 +97,12 @@ public static Text parseText( : Placeholders.parseNodes(node, PLACEHOLDER_PATTERN, placeholderGetter).toText(ParserContext.of(), true); } + /** + * Parses text into a placeholder text node. + * + * @param text placeholder template text + * @return parsed placeholder text node + */ public static @NotNull TextNode parseNode(@Nullable Text text) { return text != null ? new ParentNode(NODE_PARSER.parseNodes(TextNode.convert(text))) : EmptyNode.INSTANCE; @@ -126,44 +112,48 @@ public static Text parseText( * String-output placeholder parsers. */ - public static String parseString(@NotNull Text text, @NotNull Map placeholders) - { - return parseString(text, null, placeholders); - } - - public static String parseString( - @NotNull Text text, @Nullable PlaceholderContext context, @NotNull Map placeholders - ) - { - return parseString(text, context, placeholders::get); - } - - public static String parseString( - @NotNull Text text, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter - ) - { - return parseString(parseNode(text), context, placeholderGetter); - } - - public static String parseString(@NotNull String str, @NotNull Map placeholders) - { - return parseString(str, null, placeholders); - } - + /** + * Formats a placeholder template into a string. + * + * @param string placeholder template string + * @param context placeholder context + * @param placeholders mapping of placeholder key-value pairs + * @return formatted Minecraft text + */ public static String parseString( - @NotNull String str, @Nullable PlaceholderContext context, @NotNull Map placeholders + @NotNull String string, + @Nullable PlaceholderContext context, + @NotNull Map placeholders ) { - return parseString(str, context, placeholders::get); + return parseString(string, context, placeholders::get); } + /** + * Formats a placeholder template into a string. + * + * @param string placeholder template string + * @param context placeholder context + * @param placeholderGetter function that takes placeholder key and returns value + * @return formatted Minecraft text + */ public static String parseString( - @NotNull String str, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter + @NotNull String string, + @Nullable PlaceholderContext context, + @NotNull PlaceholderGetter placeholderGetter ) { - return parseString(parseNode(str), context, placeholderGetter); + return parseString(parseNode(string), context, placeholderGetter); } + /** + * Formats a placeholder template into a string. + * + * @param node pre-parsed placeholder text node + * @param context placeholder context + * @param placeholders mapping of placeholder key-value pairs + * @return formatted Minecraft text + */ public static String parseString( @NotNull TextNode node, @Nullable PlaceholderContext context, @@ -173,13 +163,29 @@ public static String parseString( return parseString(node, context, placeholders::get); } + /** + * Formats a placeholder template into a string. + * + * @param node pre-parsed placeholder text node + * @param context placeholder context + * @param placeholderGetter function that takes placeholder key and returns value + * @return formatted Minecraft text + */ public static String parseString( - @NotNull TextNode node, @Nullable PlaceholderContext context, @NotNull PlaceholderGetter placeholderGetter + @NotNull TextNode node, + @Nullable PlaceholderContext context, + @NotNull PlaceholderGetter placeholderGetter ) { return parseText(node, context, placeholderGetter).getString(); } + /** + * Parses a string into a placeholder text node. + * + * @param string placeholder template string + * @return parsed placeholder text node + */ public static @NotNull TextNode parseNode(@Nullable String string) { return string != null && !string.isEmpty() ? NODE_PARSER.parseNode(string) : EmptyNode.INSTANCE;