From 5652a7d544301b9a2a0bd843f66cd5e1c3e363c4 Mon Sep 17 00:00:00 2001 From: Vankka Date: Sun, 31 Dec 2023 16:51:50 +0200 Subject: [PATCH] Add PlaceholderPrefix --- .../java/com/discordsrv/api/color/Color.java | 9 +++ .../api/discord/entity/DiscordUser.java | 14 ++-- .../api/discord/entity/Snowflake.java | 3 + .../entity/channel/DiscordChannel.java | 2 + .../entity/channel/DiscordGuildChannel.java | 7 +- .../entity/guild/DiscordCustomEmoji.java | 2 + .../discord/entity/guild/DiscordGuild.java | 6 +- .../entity/guild/DiscordGuildMember.java | 13 ++- .../api/discord/entity/guild/DiscordRole.java | 7 +- .../message/ReceivedDiscordMessage.java | 8 +- .../placeholder/annotation/Placeholder.java | 2 +- .../annotation/PlaceholderPrefix.java | 27 +++++++ .../discordsrv/common/AbstractDiscordSRV.java | 2 + .../config/main/AvatarProviderConfig.java | 2 +- .../common/console/entry/LogEntry.java | 8 +- .../discord/api/entity/DiscordUserImpl.java | 1 + .../entity/guild/DiscordGuildMemberImpl.java | 6 +- .../message/ReceivedDiscordMessageImpl.java | 4 +- .../placeholder/PlaceholderServiceImpl.java | 80 ++++++++++++------- .../AnnotationPlaceholderProvider.java | 36 +++++---- .../common/player/IOfflinePlayer.java | 10 ++- .../com/discordsrv/common/player/IPlayer.java | 26 +++--- .../discordsrv/common/uuid/util/UUIDUtil.java | 4 + .../placeholder/PlaceholderServiceTest.java | 33 ++++++++ 24 files changed, 217 insertions(+), 95 deletions(-) create mode 100644 api/src/main/java/com/discordsrv/api/placeholder/annotation/PlaceholderPrefix.java diff --git a/api/src/main/java/com/discordsrv/api/color/Color.java b/api/src/main/java/com/discordsrv/api/color/Color.java index f85a5ad3..0d8f687e 100644 --- a/api/src/main/java/com/discordsrv/api/color/Color.java +++ b/api/src/main/java/com/discordsrv/api/color/Color.java @@ -23,11 +23,15 @@ package com.discordsrv.api.color; +import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; + import java.util.Objects; /** * Simple helper class for handling rgb colors, that is compatible with headless java installations. */ +@PlaceholderPrefix("color_") public class Color { /** @@ -69,22 +73,27 @@ public class Color { this.rgb = Integer.parseInt(hex, 16); } + @Placeholder("rgb") public int rgb() { return rgb; } + @Placeholder("hex") public String hex() { return Integer.toHexString(0xF000000 | rgb).substring(1); } + @Placeholder("red") public int red() { return (rgb & 0xFF0000) >> 16; } + @Placeholder("green") public int green() { return (rgb & 0x00FF00) >> 8; } + @Placeholder("blue") public int blue() { return rgb & 0x0000FF; } diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/DiscordUser.java b/api/src/main/java/com/discordsrv/api/discord/entity/DiscordUser.java index cc804533..5e96f08e 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/DiscordUser.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/DiscordUser.java @@ -25,6 +25,7 @@ package com.discordsrv.api.discord.entity; import com.discordsrv.api.discord.entity.channel.DiscordDMChannel; import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; import net.dv8tion.jda.api.entities.User; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -34,6 +35,7 @@ import java.util.concurrent.CompletableFuture; /** * A Discord user. */ +@PlaceholderPrefix("user_") public interface DiscordUser extends JDAEntity, Snowflake, Mentionable { /** @@ -52,7 +54,7 @@ public interface DiscordUser extends JDAEntity, Snowflake, Mentionable { * Gets the username of the Discord user. * @return the user's username */ - @Placeholder("user_name") + @Placeholder("name") @NotNull String getUsername(); @@ -60,7 +62,7 @@ public interface DiscordUser extends JDAEntity, Snowflake, Mentionable { * Gets the effective display name of the Discord user. * @return the user's effective display name */ - @Placeholder("user_effective_name") + @Placeholder("effective_name") @NotNull String getEffectiveName(); @@ -68,7 +70,7 @@ public interface DiscordUser extends JDAEntity, Snowflake, Mentionable { * Gets the Discord user's discriminator. * @return the user's discriminator */ - @Placeholder("user_discriminator") + @Placeholder("discriminator") @NotNull String getDiscriminator(); @@ -76,7 +78,7 @@ public interface DiscordUser extends JDAEntity, Snowflake, Mentionable { * Gets the Discord user's avatar url, if an avatar is set. * @return the user's avatar url or {@code null} */ - @Placeholder("user_avatar_url") + @Placeholder("avatar_url") @Nullable String getAvatarUrl(); @@ -85,7 +87,7 @@ public interface DiscordUser extends JDAEntity, Snowflake, Mentionable { * if an avatar isn't set it'll be the url to the default avatar provided by Discord. * @return the user's avatar url */ - @Placeholder("user_effective_avatar_url") + @Placeholder("effective_avatar_url") @NotNull String getEffectiveAvatarUrl(); @@ -93,7 +95,7 @@ public interface DiscordUser extends JDAEntity, Snowflake, Mentionable { * Gets the Discord user's username, including discriminator if any. * @return the Discord user's username */ - @Placeholder("user_tag") + @Placeholder("tag") default String getAsTag() { String username = getUsername(); String discriminator = getDiscriminator(); diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/Snowflake.java b/api/src/main/java/com/discordsrv/api/discord/entity/Snowflake.java index 8e4c53a9..e71168e7 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/Snowflake.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/Snowflake.java @@ -23,6 +23,8 @@ package com.discordsrv.api.discord.entity; +import com.discordsrv.api.placeholder.annotation.Placeholder; + /** * A snowflake identifier. */ @@ -32,5 +34,6 @@ public interface Snowflake { * Gets the id of this entity. * @return the id of this entity */ + @Placeholder("id") long getId(); } diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordChannel.java b/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordChannel.java index 93d9b165..1804413c 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordChannel.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordChannel.java @@ -1,7 +1,9 @@ package com.discordsrv.api.discord.entity.channel; import com.discordsrv.api.discord.entity.Snowflake; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; +@PlaceholderPrefix("channel_") public interface DiscordChannel extends Snowflake { /** diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordGuildChannel.java b/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordGuildChannel.java index eb125f85..88178c10 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordGuildChannel.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/channel/DiscordGuildChannel.java @@ -26,8 +26,10 @@ package com.discordsrv.api.discord.entity.channel; import com.discordsrv.api.discord.entity.Snowflake; import com.discordsrv.api.discord.entity.guild.DiscordGuild; import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; import org.jetbrains.annotations.NotNull; +@PlaceholderPrefix("channel_") public interface DiscordGuildChannel extends Snowflake { /** @@ -35,13 +37,14 @@ public interface DiscordGuildChannel extends Snowflake { * @return the name of the channel */ @NotNull - @Placeholder("channel_name") + @Placeholder("name") String getName(); /** * Gets the Discord server that this channel is in. * @return the Discord server that contains this channel */ + @Placeholder(value = "server", relookup = "server") @NotNull DiscordGuild getGuild(); @@ -50,6 +53,6 @@ public interface DiscordGuildChannel extends Snowflake { * @return the https url to go to this channel */ @NotNull - @Placeholder("channel_jump_url") + @Placeholder("jump_url") String getJumpUrl(); } diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordCustomEmoji.java b/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordCustomEmoji.java index 82abcde6..b830c886 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordCustomEmoji.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordCustomEmoji.java @@ -25,8 +25,10 @@ package com.discordsrv.api.discord.entity.guild; import com.discordsrv.api.discord.entity.JDAEntity; import com.discordsrv.api.discord.entity.Snowflake; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; import net.dv8tion.jda.api.entities.emoji.CustomEmoji; +@PlaceholderPrefix("emoji_") public interface DiscordCustomEmoji extends JDAEntity, Snowflake { String getName(); diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordGuild.java b/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordGuild.java index 5ba828f8..541e6433 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordGuild.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordGuild.java @@ -26,6 +26,7 @@ package com.discordsrv.api.discord.entity.guild; import com.discordsrv.api.discord.entity.JDAEntity; import com.discordsrv.api.discord.entity.Snowflake; import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; import net.dv8tion.jda.api.entities.Guild; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -37,13 +38,14 @@ import java.util.concurrent.CompletableFuture; /** * A Discord server. */ +@PlaceholderPrefix("server_") public interface DiscordGuild extends JDAEntity, Snowflake { /** * Gets the name of this Discord guild. * @return the guild's name */ - @Placeholder("server_name") + @Placeholder("name") @NotNull String getName(); @@ -51,7 +53,7 @@ public interface DiscordGuild extends JDAEntity, Snowflake { * Gets the member count of the guild. * @return the guild's member count */ - @Placeholder("server_member_count") + @Placeholder("member_count") int getMemberCount(); /** diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordGuildMember.java b/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordGuildMember.java index 10f04dab..bd951175 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordGuildMember.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordGuildMember.java @@ -28,6 +28,7 @@ import com.discordsrv.api.discord.entity.DiscordUser; import com.discordsrv.api.discord.entity.JDAEntity; import com.discordsrv.api.discord.entity.Mentionable; import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; import net.dv8tion.jda.api.entities.Member; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -39,6 +40,7 @@ import java.util.concurrent.CompletableFuture; /** * A Discord server member. */ +@PlaceholderPrefix("user_") public interface DiscordGuildMember extends JDAEntity, Mentionable { /** @@ -52,6 +54,7 @@ public interface DiscordGuildMember extends JDAEntity, Mentionable { * Gets the Discord server this member is from. * @return the Discord server this member is from. */ + @Placeholder(value = "server", relookup = "server") @NotNull DiscordGuild getGuild(); @@ -101,7 +104,7 @@ public interface DiscordGuildMember extends JDAEntity, Mentionable { * Gets the effective name of this Discord server member. * @return the Discord server member's effective name */ - @Placeholder("user_effective_server_name") + @Placeholder("effective_server_name") @NotNull default String getEffectiveServerName() { String nickname = getNickname(); @@ -112,7 +115,7 @@ public interface DiscordGuildMember extends JDAEntity, Mentionable { * Gets the avatar url that is active for this user in this server. * @return the user's avatar url in this server */ - @Placeholder("user_effective_server_avatar_url") + @Placeholder("effective_server_avatar_url") @NotNull String getEffectiveServerAvatarUrl(); @@ -120,13 +123,14 @@ public interface DiscordGuildMember extends JDAEntity, Mentionable { * Gets the color of this user's highest role that has a color. * @return the color that will be used for this user */ - @Placeholder("user_color") + @Placeholder("color") Color getColor(); /** * Gets the time the member joined the server. * @return the time the member joined the server */ + @Placeholder(value = "time_joined", relookup = "date") @NotNull OffsetDateTime getTimeJoined(); @@ -134,6 +138,7 @@ public interface DiscordGuildMember extends JDAEntity, Mentionable { * Time the member started boosting. * @return the time the member started boosting or {@code null} */ + @Placeholder(value = "time_boosted", relookup = "date") @Nullable OffsetDateTime getTimeBoosted(); @@ -141,7 +146,7 @@ public interface DiscordGuildMember extends JDAEntity, Mentionable { * If the Discord server member is boosted. * @return {@code true} if this Discord server member is boosting */ - @Placeholder("user_isboosting") + @Placeholder("isboosting") default boolean isBoosting() { return getTimeBoosted() != null; } diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordRole.java b/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordRole.java index ffb2e52b..83d874ea 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordRole.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/guild/DiscordRole.java @@ -28,12 +28,14 @@ import com.discordsrv.api.discord.entity.JDAEntity; import com.discordsrv.api.discord.entity.Mentionable; import com.discordsrv.api.discord.entity.Snowflake; import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; import net.dv8tion.jda.api.entities.Role; import org.jetbrains.annotations.NotNull; /** * A Discord server role. */ +@PlaceholderPrefix("role_") public interface DiscordRole extends JDAEntity, Snowflake, Mentionable { /** @@ -45,6 +47,7 @@ public interface DiscordRole extends JDAEntity, Snowflake, Mentionable { * The Discord server this role is from. * @return the Discord server */ + @Placeholder(value = "server", relookup = "server") @NotNull DiscordGuild getGuild(); @@ -52,7 +55,7 @@ public interface DiscordRole extends JDAEntity, Snowflake, Mentionable { * Gets the name of the Discord role. * @return the role name */ - @Placeholder("role_name") + @Placeholder("name") @NotNull String getName(); @@ -69,7 +72,7 @@ public interface DiscordRole extends JDAEntity, Snowflake, Mentionable { * @return the color of this role, or {@link #DEFAULT_COLOR} if there is no color set * @see #hasColor() */ - @Placeholder("role_color") + @Placeholder(value = "color", relookup = "color") @NotNull Color getColor(); diff --git a/api/src/main/java/com/discordsrv/api/discord/entity/message/ReceivedDiscordMessage.java b/api/src/main/java/com/discordsrv/api/discord/entity/message/ReceivedDiscordMessage.java index fb1740c4..d5149182 100644 --- a/api/src/main/java/com/discordsrv/api/discord/entity/message/ReceivedDiscordMessage.java +++ b/api/src/main/java/com/discordsrv/api/discord/entity/message/ReceivedDiscordMessage.java @@ -31,6 +31,7 @@ import com.discordsrv.api.discord.entity.channel.DiscordTextChannel; import com.discordsrv.api.discord.entity.guild.DiscordGuild; import com.discordsrv.api.discord.entity.guild.DiscordGuildMember; import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; @@ -41,6 +42,7 @@ import java.util.concurrent.CompletableFuture; /** * A message received from Discord. */ +@PlaceholderPrefix("message_") public interface ReceivedDiscordMessage extends Snowflake { /** @@ -49,6 +51,7 @@ public interface ReceivedDiscordMessage extends Snowflake { * @return the message content or {@code null} */ @Nullable + @Placeholder("content") String getContent(); /** @@ -70,7 +73,7 @@ public interface ReceivedDiscordMessage extends Snowflake { * @return the jump url */ @NotNull - @Placeholder("message_jump_url") + @Placeholder("jump_url") String getJumpUrl(); /** @@ -92,6 +95,7 @@ public interface ReceivedDiscordMessage extends Snowflake { * @return the user that sent the message */ @NotNull + @Placeholder(value = "user", relookup = "user") DiscordUser getAuthor(); /** @@ -99,6 +103,7 @@ public interface ReceivedDiscordMessage extends Snowflake { * @return the channel the message was sent in */ @NotNull + @Placeholder(value = "channel", relookup = "channel") DiscordMessageChannel getChannel(); /** @@ -135,6 +140,7 @@ public interface ReceivedDiscordMessage extends Snowflake { * @return an optional potentially containing the Discord server the message was posted in */ @Nullable + @Placeholder(value = "server", relookup = "server") default DiscordGuild getGuild() { DiscordTextChannel textChannel = getTextChannel(); diff --git a/api/src/main/java/com/discordsrv/api/placeholder/annotation/Placeholder.java b/api/src/main/java/com/discordsrv/api/placeholder/annotation/Placeholder.java index bede393c..f98711c0 100644 --- a/api/src/main/java/com/discordsrv/api/placeholder/annotation/Placeholder.java +++ b/api/src/main/java/com/discordsrv/api/placeholder/annotation/Placeholder.java @@ -44,7 +44,7 @@ public @interface Placeholder { String value(); /** - * Creates a new lookup with {@link #value()} replaced with this. + * Creates a new lookup with {@link #value()} replaced with this, if the placeholder is longer than the given {@link #value()}. * The object returned by the {@link Placeholder} method/field will be added as context. * @return the prefix used for the next lookup */ diff --git a/api/src/main/java/com/discordsrv/api/placeholder/annotation/PlaceholderPrefix.java b/api/src/main/java/com/discordsrv/api/placeholder/annotation/PlaceholderPrefix.java new file mode 100644 index 00000000..37d57f6c --- /dev/null +++ b/api/src/main/java/com/discordsrv/api/placeholder/annotation/PlaceholderPrefix.java @@ -0,0 +1,27 @@ +package com.discordsrv.api.placeholder.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies a prefix for all placeholders declared in this type. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface PlaceholderPrefix { + + /** + * The prefix for all placeholders in this type + * @return the prefix + */ + String value(); + + /** + * If this prefix should not follow {@link PlaceholderPrefix} of classes that are using this class/interface as a superclass. + * @return {@code true} to not allow overwriting this prefix + */ + boolean ignoreParents() default false; + +} diff --git a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java index 74e1014b..6cce710f 100644 --- a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java +++ b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java @@ -81,6 +81,7 @@ import com.discordsrv.common.storage.Storage; import com.discordsrv.common.storage.StorageType; import com.discordsrv.common.storage.impl.MemoryStorage; import com.discordsrv.common.update.UpdateChecker; +import com.discordsrv.common.uuid.util.UUIDUtil; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import net.dv8tion.jda.api.JDA; @@ -561,6 +562,7 @@ public abstract class AbstractDiscordSRV< placeholderService().addResultMapper(new ComponentResultStringifier(this)); placeholderService().addGlobalContext(new GlobalTextHandlingContext(this)); placeholderService().addGlobalContext(new GlobalDateFormattingContext(this)); + placeholderService().addGlobalContext(UUIDUtil.class); // Modules registerModule(ConsoleModule::new); diff --git a/common/src/main/java/com/discordsrv/common/config/main/AvatarProviderConfig.java b/common/src/main/java/com/discordsrv/common/config/main/AvatarProviderConfig.java index 55d14556..9cdd37f2 100644 --- a/common/src/main/java/com/discordsrv/common/config/main/AvatarProviderConfig.java +++ b/common/src/main/java/com/discordsrv/common/config/main/AvatarProviderConfig.java @@ -15,5 +15,5 @@ public class AvatarProviderConfig { @Comment("The template for URLs of player avatars\n" + "This will be used for official Java players only if auto-decide-avatar-url is set to true\n" + "This will be used ALWAYS if auto-decide-avatar-url is set to false") - public String avatarUrlTemplate = "https://crafatar.com/avatars/%player_uuid_nodashes%.png?size=128&overlay#%player_texture%"; + public String avatarUrlTemplate = "https://crafatar.com/avatars/%player_uuid_short%.png?size=128&overlay#%player_skin_texture_id%"; } diff --git a/common/src/main/java/com/discordsrv/common/console/entry/LogEntry.java b/common/src/main/java/com/discordsrv/common/console/entry/LogEntry.java index 88e1d032..63d96cae 100644 --- a/common/src/main/java/com/discordsrv/common/console/entry/LogEntry.java +++ b/common/src/main/java/com/discordsrv/common/console/entry/LogEntry.java @@ -46,15 +46,9 @@ public class LogEntry { return throwable; } + @Placeholder(value = "log_time", relookup = "date") public ZonedDateTime logTime() { return logTime; } - @Placeholder("log_time") - public PlaceholderLookupResult _logTimePlaceholder(@PlaceholderRemainder String format) { - Set extras = new LinkedHashSet<>(); - extras.add(logTime()); - - return PlaceholderLookupResult.newLookup("date:'" + format + "'", extras); - } } diff --git a/common/src/main/java/com/discordsrv/common/discord/api/entity/DiscordUserImpl.java b/common/src/main/java/com/discordsrv/common/discord/api/entity/DiscordUserImpl.java index edf0e1eb..3b82ac18 100644 --- a/common/src/main/java/com/discordsrv/common/discord/api/entity/DiscordUserImpl.java +++ b/common/src/main/java/com/discordsrv/common/discord/api/entity/DiscordUserImpl.java @@ -66,6 +66,7 @@ public class DiscordUserImpl implements DiscordUser { return user.getEffectiveName(); } + @SuppressWarnings("deprecation") @Override public @NotNull String getDiscriminator() { return user.getDiscriminator(); diff --git a/common/src/main/java/com/discordsrv/common/discord/api/entity/guild/DiscordGuildMemberImpl.java b/common/src/main/java/com/discordsrv/common/discord/api/entity/guild/DiscordGuildMemberImpl.java index 30fb9bc3..b3fbabb9 100644 --- a/common/src/main/java/com/discordsrv/common/discord/api/entity/guild/DiscordGuildMemberImpl.java +++ b/common/src/main/java/com/discordsrv/common/discord/api/entity/guild/DiscordGuildMemberImpl.java @@ -130,12 +130,12 @@ public class DiscordGuildMemberImpl implements DiscordGuildMember { // Placeholders // - @Placeholder(value = "user_highest_role", relookup = "role") + @Placeholder(value = "highest_role", relookup = "role") public DiscordRole _highestRole() { return !roles.isEmpty() ? roles.get(0) : null; } - @Placeholder(value = "user_hoisted_role", relookup = "role") + @Placeholder(value = "hoisted_role", relookup = "role") public DiscordRole _hoistedRole() { for (DiscordRole role : roles) { if (role.isHoisted()) { @@ -145,7 +145,7 @@ public class DiscordGuildMemberImpl implements DiscordGuildMember { return null; } - @Placeholder("user_roles") + @Placeholder("roles") public Component _allRoles(@PlaceholderRemainder String suffix) { List components = new ArrayList<>(); for (DiscordRole role : getRoles()) { diff --git a/common/src/main/java/com/discordsrv/common/discord/api/entity/message/ReceivedDiscordMessageImpl.java b/common/src/main/java/com/discordsrv/common/discord/api/entity/message/ReceivedDiscordMessageImpl.java index 3d2cf1e1..c9c88af4 100644 --- a/common/src/main/java/com/discordsrv/common/discord/api/entity/message/ReceivedDiscordMessageImpl.java +++ b/common/src/main/java/com/discordsrv/common/discord/api/entity/message/ReceivedDiscordMessageImpl.java @@ -268,7 +268,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage { // Placeholders // - @Placeholder("message_reply") + @Placeholder("reply") public Component _reply(BaseChannelConfig config) { if (replyingTo == null) { return null; @@ -291,7 +291,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage { ); } - @Placeholder("message_attachments") + @Placeholder("attachments") public Component _attachments(BaseChannelConfig config, @PlaceholderRemainder String suffix) { String attachmentFormat = config.discordToMinecraft.attachmentFormat; List components = new ArrayList<>(); diff --git a/common/src/main/java/com/discordsrv/common/placeholder/PlaceholderServiceImpl.java b/common/src/main/java/com/discordsrv/common/placeholder/PlaceholderServiceImpl.java index 87393e26..a7edd574 100644 --- a/common/src/main/java/com/discordsrv/common/placeholder/PlaceholderServiceImpl.java +++ b/common/src/main/java/com/discordsrv/common/placeholder/PlaceholderServiceImpl.java @@ -22,13 +22,14 @@ import com.discordsrv.api.event.events.placeholder.PlaceholderLookupEvent; import com.discordsrv.api.placeholder.PlaceholderLookupResult; import com.discordsrv.api.placeholder.PlaceholderService; import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; import com.discordsrv.api.placeholder.annotation.PlaceholderRemainder; import com.discordsrv.api.placeholder.mapper.PlaceholderResultMapper; +import com.discordsrv.api.placeholder.provider.PlaceholderProvider; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.logging.Logger; import com.discordsrv.common.logging.NamedLogger; import com.discordsrv.common.placeholder.provider.AnnotationPlaceholderProvider; -import com.discordsrv.api.placeholder.provider.PlaceholderProvider; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.LoadingCache; import org.apache.commons.lang3.StringUtils; @@ -43,6 +44,7 @@ import java.lang.reflect.Parameter; import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -225,6 +227,8 @@ public class PlaceholderServiceImpl implements PlaceholderService { } private Object getResultRepresentation(List results, String placeholder, Matcher matcher) { + Map preventInfiniteLoop = new HashMap<>(); + Object best = null; for (PlaceholderLookupResult result : results) { while (result != null) { @@ -253,7 +257,15 @@ public class PlaceholderServiceImpl implements PlaceholderService { replacement = "Error"; break; case NEW_LOOKUP: - result = lookupPlaceholder((String) result.getValue(), result.getExtras()); + String placeholderKey = (String) result.getValue(); + + AtomicInteger infiniteLoop = preventInfiniteLoop.computeIfAbsent(placeholderKey, key -> new AtomicInteger(0)); + if (infiniteLoop.incrementAndGet() > 10) { + replacement = "Infinite Loop"; + break; + } + + result = lookupPlaceholder(placeholderKey, result.getExtras()); newLookup = true; break; } @@ -273,59 +285,65 @@ public class PlaceholderServiceImpl implements PlaceholderService { private static class ClassProviderLoader implements CacheLoader, Set> { - private Set> getAll(Class clazz) { - Set> classes = new LinkedHashSet<>(); - classes.add(clazz); + private Set loadProviders(Class clazz, PlaceholderPrefix prefix) { + Set providers = new LinkedHashSet<>(); - for (Class anInterface : clazz.getInterfaces()) { - classes.addAll(getAll(anInterface)); - } + Class currentClass = clazz; + while (currentClass != null) { + PlaceholderPrefix currentPrefix = currentClass.getAnnotation(PlaceholderPrefix.class); + if (currentPrefix != null && prefix == null) { + prefix = currentPrefix; + } + PlaceholderPrefix usePrefix = (currentPrefix != null && currentPrefix.ignoreParents()) ? currentPrefix : prefix; - Class superClass = clazz.getSuperclass(); - if (superClass != null) { - classes.addAll(getAll(superClass)); - } - - return classes; - } - - @Override - public @Nullable Set load(@NonNull Class key) { - Set providers = new HashSet<>(); - - Set> classes = getAll(key); - for (Class clazz : classes) { for (Method method : clazz.getMethods()) { + if (!method.getDeclaringClass().equals(currentClass)) { + continue; + } + Placeholder annotation = method.getAnnotation(Placeholder.class); if (annotation == null) { continue; } - boolean startsWith = !annotation.relookup().isEmpty(); - if (!startsWith) { - for (Parameter parameter : method.getParameters()) { - if (parameter.getAnnotation(PlaceholderRemainder.class) != null) { - startsWith = true; - break; - } + PlaceholderRemainder remainder = null; + for (Parameter parameter : method.getParameters()) { + remainder = parameter.getAnnotation(PlaceholderRemainder.class); + if (remainder != null) { + break; } } boolean isStatic = Modifier.isStatic(method.getModifiers()); - providers.add(new AnnotationPlaceholderProvider(annotation, isStatic ? null : clazz, startsWith, method)); + providers.add(new AnnotationPlaceholderProvider(annotation, usePrefix, remainder, isStatic ? null : clazz, method)); } for (Field field : clazz.getFields()) { + if (!field.getDeclaringClass().equals(currentClass)) { + continue; + } + Placeholder annotation = field.getAnnotation(Placeholder.class); if (annotation == null) { continue; } boolean isStatic = Modifier.isStatic(field.getModifiers()); - providers.add(new AnnotationPlaceholderProvider(annotation, isStatic ? null : clazz, !annotation.relookup().isEmpty(), field)); + providers.add(new AnnotationPlaceholderProvider(annotation, usePrefix, isStatic ? null : clazz, field)); } + + for (Class anInterface : currentClass.getInterfaces()) { + providers.addAll(loadProviders(anInterface, prefix)); + } + + currentClass = currentClass.getSuperclass(); } return providers; } + + @Override + public @Nullable Set load(@NonNull Class key) { + return loadProviders(key, null); + } } } diff --git a/common/src/main/java/com/discordsrv/common/placeholder/provider/AnnotationPlaceholderProvider.java b/common/src/main/java/com/discordsrv/common/placeholder/provider/AnnotationPlaceholderProvider.java index 72ca465a..ee6ba9fe 100644 --- a/common/src/main/java/com/discordsrv/common/placeholder/provider/AnnotationPlaceholderProvider.java +++ b/common/src/main/java/com/discordsrv/common/placeholder/provider/AnnotationPlaceholderProvider.java @@ -20,6 +20,8 @@ package com.discordsrv.common.placeholder.provider; import com.discordsrv.api.placeholder.PlaceholderLookupResult; import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; +import com.discordsrv.api.placeholder.annotation.PlaceholderRemainder; import com.discordsrv.api.placeholder.provider.PlaceholderProvider; import com.discordsrv.common.placeholder.provider.util.PlaceholderMethodUtil; import org.jetbrains.annotations.NotNull; @@ -32,31 +34,35 @@ import java.util.Set; public class AnnotationPlaceholderProvider implements PlaceholderProvider { private final Placeholder annotation; + private final PlaceholderPrefix prefixAnnotation; + private final PlaceholderRemainder remainderAnnotation; private final Class type; private final Method method; - private final boolean startsWith; private final Field field; - public AnnotationPlaceholderProvider(Placeholder annotation, Class type, boolean startsWith, Method method) { - this.annotation = annotation; - this.type = type; - this.startsWith = startsWith; - this.method = method; - this.field = null; + public AnnotationPlaceholderProvider(Placeholder annotation, PlaceholderPrefix prefixAnnotation, PlaceholderRemainder remainderAnnotation, Class type, Method method) { + this(annotation, prefixAnnotation, remainderAnnotation, type, method, null); } - public AnnotationPlaceholderProvider(Placeholder annotation, Class type, boolean startsWith, Field field) { + public AnnotationPlaceholderProvider(Placeholder annotation, PlaceholderPrefix prefixAnnotation, Class type, Field field) { + this(annotation, prefixAnnotation, null, type, null, field); + } + + private AnnotationPlaceholderProvider(Placeholder annotation, PlaceholderPrefix prefixAnnotation, PlaceholderRemainder remainderAnnotation, Class type, Method method, Field field) { this.annotation = annotation; + this.prefixAnnotation = prefixAnnotation; + this.remainderAnnotation = remainderAnnotation; this.type = type; - this.startsWith = startsWith; - this.method = null; + this.method = method; this.field = field; } @Override public @NotNull PlaceholderLookupResult lookup(@NotNull String placeholder, @NotNull Set context) { - String annotationPlaceholder = annotation.value(); + String annotationPlaceholder = (prefixAnnotation != null ? prefixAnnotation.value() : "") + annotation.value(); + String reLookup = annotation.relookup(); + boolean startsWith = !reLookup.isEmpty() || remainderAnnotation != null; if (annotationPlaceholder.isEmpty() || !(startsWith ? placeholder.startsWith(annotationPlaceholder) : placeholder.equals(annotationPlaceholder)) || (type != null && context.isEmpty())) { @@ -75,7 +81,7 @@ public class AnnotationPlaceholderProvider implements PlaceholderProvider { } } - String remainder = placeholder.replace(annotationPlaceholder, ""); + String remainder = placeholder.substring(annotationPlaceholder.length()); Object result; try { @@ -89,8 +95,10 @@ public class AnnotationPlaceholderProvider implements PlaceholderProvider { return PlaceholderLookupResult.lookupFailed(t); } - String reLookup = annotation.relookup(); - if (!reLookup.isEmpty()) { + if (reLookup.isEmpty() && remainderAnnotation == null) { + reLookup = annotation.value(); + } + if (!reLookup.isEmpty() && !remainder.isEmpty()) { if (result == null) { return PlaceholderLookupResult.success(null); } diff --git a/common/src/main/java/com/discordsrv/common/player/IOfflinePlayer.java b/common/src/main/java/com/discordsrv/common/player/IOfflinePlayer.java index c04d4990..911a1013 100644 --- a/common/src/main/java/com/discordsrv/common/player/IOfflinePlayer.java +++ b/common/src/main/java/com/discordsrv/common/player/IOfflinePlayer.java @@ -19,6 +19,7 @@ package com.discordsrv.common.player; import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.player.provider.model.SkinInfo; import com.discordsrv.common.profile.Profile; @@ -30,6 +31,7 @@ import org.jetbrains.annotations.Nullable; import java.util.UUID; import java.util.concurrent.CompletableFuture; +@PlaceholderPrefix("player_") public interface IOfflinePlayer extends Identified { DiscordSRV discordSRV(); @@ -39,12 +41,12 @@ public interface IOfflinePlayer extends Identified { return discordSRV().profileManager().lookupProfile(uniqueId()); } - @Placeholder("player_name") + @Placeholder("name") @Nullable String username(); @ApiStatus.NonExtendable - @Placeholder("player_uuid") + @Placeholder(value = "uuid", relookup = "uuid") @NotNull default UUID uniqueId() { return identity().uuid(); @@ -53,14 +55,14 @@ public interface IOfflinePlayer extends Identified { @Nullable SkinInfo skinInfo(); - @Placeholder("player_skin_texture_id") + @Placeholder("skin_texture_id") @Nullable default String skinTextureId() { SkinInfo info = skinInfo(); return info != null ? info.textureId() : null; } - @Placeholder("player_skin_model") + @Placeholder("skin_model") @Nullable default String skinModel() { SkinInfo info = skinInfo(); diff --git a/common/src/main/java/com/discordsrv/common/player/IPlayer.java b/common/src/main/java/com/discordsrv/common/player/IPlayer.java index efc86b43..95bdb864 100644 --- a/common/src/main/java/com/discordsrv/common/player/IPlayer.java +++ b/common/src/main/java/com/discordsrv/common/player/IPlayer.java @@ -19,6 +19,7 @@ package com.discordsrv.common.player; import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; import com.discordsrv.api.player.DiscordSRVPlayer; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.command.game.sender.ICommandSender; @@ -32,6 +33,7 @@ import org.jetbrains.annotations.Nullable; import java.util.UUID; +@PlaceholderPrefix("player_") public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSender { @Override @@ -47,29 +49,23 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende } @NotNull - @Placeholder("player_name") + @Placeholder("name") String username(); @Override @ApiStatus.NonExtendable - @Placeholder("player_uuid") + @Placeholder(value = "uuid", relookup = "uuid") default @NotNull UUID uniqueId() { return identity().uuid(); } - @ApiStatus.NonExtendable - @Placeholder("player_uuid_nodashes") - default @NotNull String uniqueIdNoDashes() { - return uniqueId().toString().replace("-", ""); - } - @NotNull - @Placeholder("player_display_name") + @Placeholder("display_name") Component displayName(); @Nullable @ApiStatus.NonExtendable - @Placeholder("player_avatar_url") + @Placeholder("avatar_url") default String getAvatarUrl() { AvatarProviderConfig avatarConfig = discordSRV().config().avatarProvider; String avatarUrlTemplate = avatarConfig.avatarUrlTemplate; @@ -78,7 +74,7 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende // Offline mode if (uniqueId().version() == 3) avatarUrlTemplate = "https://cravatar.eu/helmavatar/%player_name%/128.png#%player_skin_texture_id%"; // Bedrock - else if (uniqueId().getLeastSignificantBits() == 0) avatarUrlTemplate = "https://api.tydiumcraft.net/skin?uuid=%player_uuid_nodashes%&type=avatar&size=128"; + else if (uniqueId().getLeastSignificantBits() == 0) avatarUrlTemplate = "https://api.tydiumcraft.net/skin?uuid=%player_uuid_short%&type=avatar&size=128"; } if (avatarUrlTemplate == null) { @@ -90,28 +86,28 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende @Nullable @ApiStatus.NonExtendable - @Placeholder("player_meta_prefix") + @Placeholder("meta_prefix") default Component getMetaPrefix() { return PermissionUtil.getMetaPrefix(discordSRV(), uniqueId()); } @Nullable @ApiStatus.NonExtendable - @Placeholder("player_meta_suffix") + @Placeholder("meta_suffix") default Component getMetaSuffix() { return PermissionUtil.getMetaSuffix(discordSRV(), uniqueId()); } @Nullable @ApiStatus.NonExtendable - @Placeholder("player_prefix") + @Placeholder("prefix") default Component getPrefix() { return PermissionUtil.getPrefix(discordSRV(), uniqueId()); } @Nullable @ApiStatus.NonExtendable - @Placeholder("player_suffix") + @Placeholder("suffix") default Component getSuffix() { return PermissionUtil.getSuffix(discordSRV(), uniqueId()); } diff --git a/common/src/main/java/com/discordsrv/common/uuid/util/UUIDUtil.java b/common/src/main/java/com/discordsrv/common/uuid/util/UUIDUtil.java index 234458c5..fb05fd9b 100644 --- a/common/src/main/java/com/discordsrv/common/uuid/util/UUIDUtil.java +++ b/common/src/main/java/com/discordsrv/common/uuid/util/UUIDUtil.java @@ -1,9 +1,12 @@ package com.discordsrv.common.uuid.util; +import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; import org.jetbrains.annotations.NotNull; import java.util.UUID; +@PlaceholderPrefix("uuid_") public final class UUIDUtil { private UUIDUtil() {} @@ -31,6 +34,7 @@ public final class UUIDUtil { return UUID.fromString(fullLengthUUID); } + @Placeholder("short") public static String toShort(@NotNull UUID uuid) { return uuid.toString().replace("-", ""); } diff --git a/common/src/test/java/com/discordsrv/common/placeholder/PlaceholderServiceTest.java b/common/src/test/java/com/discordsrv/common/placeholder/PlaceholderServiceTest.java index 97030a01..2e8924c9 100644 --- a/common/src/test/java/com/discordsrv/common/placeholder/PlaceholderServiceTest.java +++ b/common/src/test/java/com/discordsrv/common/placeholder/PlaceholderServiceTest.java @@ -20,6 +20,7 @@ package com.discordsrv.common.placeholder; import com.discordsrv.api.placeholder.PlaceholderService; import com.discordsrv.api.placeholder.annotation.Placeholder; +import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix; import com.discordsrv.common.MockDiscordSRV; import org.junit.jupiter.api.Test; @@ -69,6 +70,26 @@ public class PlaceholderServiceTest { assertEquals("b", service.replacePlaceholders("%empty|static_method%", PlaceholderContext.class)); } + @Test + public void prefixFailTest() { + assertEquals("%placeholder%", service.replacePlaceholders("%placeholder%", PrefixContext.class)); + } + + @Test + public void prefixTest() { + assertEquals("value", service.replacePlaceholders("%prefix_placeholder%", PrefixContext.class)); + } + + @Test + public void prefixInheritFailTest() { + assertEquals("%prefix_noprefix%", service.replacePlaceholders("%prefix_noprefix%", PrefixInheritanceContext.class)); + } + + @Test + public void prefixInheritTest() { + assertEquals("value", service.replacePlaceholders("%noprefix%", PrefixInheritanceContext.class)); + } + public static class PlaceholderContext { @Placeholder("static_field") @@ -95,4 +116,16 @@ public class PlaceholderServiceTest { return output; } } + + @PlaceholderPrefix("prefix_") + public static class PrefixContext { + + @Placeholder("placeholder") + public static String placeholder = "value"; + } + public static class PrefixInheritanceContext extends PrefixContext { + + @Placeholder("noprefix") + public static String noPrefix = "value"; + } }