Add PlaceholderPrefix

This commit is contained in:
Vankka 2023-12-31 16:51:50 +02:00
parent f0c5c7f1af
commit 5652a7d544
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
24 changed files with 217 additions and 95 deletions

View File

@ -23,11 +23,15 @@
package com.discordsrv.api.color; package com.discordsrv.api.color;
import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import java.util.Objects; import java.util.Objects;
/** /**
* Simple helper class for handling rgb colors, that is compatible with headless java installations. * Simple helper class for handling rgb colors, that is compatible with headless java installations.
*/ */
@PlaceholderPrefix("color_")
public class Color { public class Color {
/** /**
@ -69,22 +73,27 @@ public class Color {
this.rgb = Integer.parseInt(hex, 16); this.rgb = Integer.parseInt(hex, 16);
} }
@Placeholder("rgb")
public int rgb() { public int rgb() {
return rgb; return rgb;
} }
@Placeholder("hex")
public String hex() { public String hex() {
return Integer.toHexString(0xF000000 | rgb).substring(1); return Integer.toHexString(0xF000000 | rgb).substring(1);
} }
@Placeholder("red")
public int red() { public int red() {
return (rgb & 0xFF0000) >> 16; return (rgb & 0xFF0000) >> 16;
} }
@Placeholder("green")
public int green() { public int green() {
return (rgb & 0x00FF00) >> 8; return (rgb & 0x00FF00) >> 8;
} }
@Placeholder("blue")
public int blue() { public int blue() {
return rgb & 0x0000FF; return rgb & 0x0000FF;
} }

View File

@ -25,6 +25,7 @@ package com.discordsrv.api.discord.entity;
import com.discordsrv.api.discord.entity.channel.DiscordDMChannel; import com.discordsrv.api.discord.entity.channel.DiscordDMChannel;
import com.discordsrv.api.placeholder.annotation.Placeholder; import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -34,6 +35,7 @@ import java.util.concurrent.CompletableFuture;
/** /**
* A Discord user. * A Discord user.
*/ */
@PlaceholderPrefix("user_")
public interface DiscordUser extends JDAEntity<User>, Snowflake, Mentionable { public interface DiscordUser extends JDAEntity<User>, Snowflake, Mentionable {
/** /**
@ -52,7 +54,7 @@ public interface DiscordUser extends JDAEntity<User>, Snowflake, Mentionable {
* Gets the username of the Discord user. * Gets the username of the Discord user.
* @return the user's username * @return the user's username
*/ */
@Placeholder("user_name") @Placeholder("name")
@NotNull @NotNull
String getUsername(); String getUsername();
@ -60,7 +62,7 @@ public interface DiscordUser extends JDAEntity<User>, Snowflake, Mentionable {
* Gets the effective display name of the Discord user. * Gets the effective display name of the Discord user.
* @return the user's effective display name * @return the user's effective display name
*/ */
@Placeholder("user_effective_name") @Placeholder("effective_name")
@NotNull @NotNull
String getEffectiveName(); String getEffectiveName();
@ -68,7 +70,7 @@ public interface DiscordUser extends JDAEntity<User>, Snowflake, Mentionable {
* Gets the Discord user's discriminator. * Gets the Discord user's discriminator.
* @return the user's discriminator * @return the user's discriminator
*/ */
@Placeholder("user_discriminator") @Placeholder("discriminator")
@NotNull @NotNull
String getDiscriminator(); String getDiscriminator();
@ -76,7 +78,7 @@ public interface DiscordUser extends JDAEntity<User>, Snowflake, Mentionable {
* Gets the Discord user's avatar url, if an avatar is set. * Gets the Discord user's avatar url, if an avatar is set.
* @return the user's avatar url or {@code null} * @return the user's avatar url or {@code null}
*/ */
@Placeholder("user_avatar_url") @Placeholder("avatar_url")
@Nullable @Nullable
String getAvatarUrl(); String getAvatarUrl();
@ -85,7 +87,7 @@ public interface DiscordUser extends JDAEntity<User>, Snowflake, Mentionable {
* if an avatar isn't set it'll be the url to the default avatar provided by Discord. * if an avatar isn't set it'll be the url to the default avatar provided by Discord.
* @return the user's avatar url * @return the user's avatar url
*/ */
@Placeholder("user_effective_avatar_url") @Placeholder("effective_avatar_url")
@NotNull @NotNull
String getEffectiveAvatarUrl(); String getEffectiveAvatarUrl();
@ -93,7 +95,7 @@ public interface DiscordUser extends JDAEntity<User>, Snowflake, Mentionable {
* Gets the Discord user's username, including discriminator if any. * Gets the Discord user's username, including discriminator if any.
* @return the Discord user's username * @return the Discord user's username
*/ */
@Placeholder("user_tag") @Placeholder("tag")
default String getAsTag() { default String getAsTag() {
String username = getUsername(); String username = getUsername();
String discriminator = getDiscriminator(); String discriminator = getDiscriminator();

View File

@ -23,6 +23,8 @@
package com.discordsrv.api.discord.entity; package com.discordsrv.api.discord.entity;
import com.discordsrv.api.placeholder.annotation.Placeholder;
/** /**
* A snowflake identifier. * A snowflake identifier.
*/ */
@ -32,5 +34,6 @@ public interface Snowflake {
* Gets the id of this entity. * Gets the id of this entity.
* @return the id of this entity * @return the id of this entity
*/ */
@Placeholder("id")
long getId(); long getId();
} }

View File

@ -1,7 +1,9 @@
package com.discordsrv.api.discord.entity.channel; package com.discordsrv.api.discord.entity.channel;
import com.discordsrv.api.discord.entity.Snowflake; import com.discordsrv.api.discord.entity.Snowflake;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
@PlaceholderPrefix("channel_")
public interface DiscordChannel extends Snowflake { public interface DiscordChannel extends Snowflake {
/** /**

View File

@ -26,8 +26,10 @@ package com.discordsrv.api.discord.entity.channel;
import com.discordsrv.api.discord.entity.Snowflake; import com.discordsrv.api.discord.entity.Snowflake;
import com.discordsrv.api.discord.entity.guild.DiscordGuild; import com.discordsrv.api.discord.entity.guild.DiscordGuild;
import com.discordsrv.api.placeholder.annotation.Placeholder; import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@PlaceholderPrefix("channel_")
public interface DiscordGuildChannel extends Snowflake { public interface DiscordGuildChannel extends Snowflake {
/** /**
@ -35,13 +37,14 @@ public interface DiscordGuildChannel extends Snowflake {
* @return the name of the channel * @return the name of the channel
*/ */
@NotNull @NotNull
@Placeholder("channel_name") @Placeholder("name")
String getName(); String getName();
/** /**
* Gets the Discord server that this channel is in. * Gets the Discord server that this channel is in.
* @return the Discord server that contains this channel * @return the Discord server that contains this channel
*/ */
@Placeholder(value = "server", relookup = "server")
@NotNull @NotNull
DiscordGuild getGuild(); DiscordGuild getGuild();
@ -50,6 +53,6 @@ public interface DiscordGuildChannel extends Snowflake {
* @return the https url to go to this channel * @return the https url to go to this channel
*/ */
@NotNull @NotNull
@Placeholder("channel_jump_url") @Placeholder("jump_url")
String getJumpUrl(); String getJumpUrl();
} }

View File

@ -25,8 +25,10 @@ package com.discordsrv.api.discord.entity.guild;
import com.discordsrv.api.discord.entity.JDAEntity; import com.discordsrv.api.discord.entity.JDAEntity;
import com.discordsrv.api.discord.entity.Snowflake; import com.discordsrv.api.discord.entity.Snowflake;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import net.dv8tion.jda.api.entities.emoji.CustomEmoji; import net.dv8tion.jda.api.entities.emoji.CustomEmoji;
@PlaceholderPrefix("emoji_")
public interface DiscordCustomEmoji extends JDAEntity<CustomEmoji>, Snowflake { public interface DiscordCustomEmoji extends JDAEntity<CustomEmoji>, Snowflake {
String getName(); String getName();

View File

@ -26,6 +26,7 @@ package com.discordsrv.api.discord.entity.guild;
import com.discordsrv.api.discord.entity.JDAEntity; import com.discordsrv.api.discord.entity.JDAEntity;
import com.discordsrv.api.discord.entity.Snowflake; import com.discordsrv.api.discord.entity.Snowflake;
import com.discordsrv.api.placeholder.annotation.Placeholder; import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Guild;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -37,13 +38,14 @@ import java.util.concurrent.CompletableFuture;
/** /**
* A Discord server. * A Discord server.
*/ */
@PlaceholderPrefix("server_")
public interface DiscordGuild extends JDAEntity<Guild>, Snowflake { public interface DiscordGuild extends JDAEntity<Guild>, Snowflake {
/** /**
* Gets the name of this Discord guild. * Gets the name of this Discord guild.
* @return the guild's name * @return the guild's name
*/ */
@Placeholder("server_name") @Placeholder("name")
@NotNull @NotNull
String getName(); String getName();
@ -51,7 +53,7 @@ public interface DiscordGuild extends JDAEntity<Guild>, Snowflake {
* Gets the member count of the guild. * Gets the member count of the guild.
* @return the guild's member count * @return the guild's member count
*/ */
@Placeholder("server_member_count") @Placeholder("member_count")
int getMemberCount(); int getMemberCount();
/** /**

View File

@ -28,6 +28,7 @@ import com.discordsrv.api.discord.entity.DiscordUser;
import com.discordsrv.api.discord.entity.JDAEntity; import com.discordsrv.api.discord.entity.JDAEntity;
import com.discordsrv.api.discord.entity.Mentionable; import com.discordsrv.api.discord.entity.Mentionable;
import com.discordsrv.api.placeholder.annotation.Placeholder; import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Member;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -39,6 +40,7 @@ import java.util.concurrent.CompletableFuture;
/** /**
* A Discord server member. * A Discord server member.
*/ */
@PlaceholderPrefix("user_")
public interface DiscordGuildMember extends JDAEntity<Member>, Mentionable { public interface DiscordGuildMember extends JDAEntity<Member>, Mentionable {
/** /**
@ -52,6 +54,7 @@ public interface DiscordGuildMember extends JDAEntity<Member>, Mentionable {
* Gets the Discord server this member is from. * Gets the Discord server this member is from.
* @return the Discord server this member is from. * @return the Discord server this member is from.
*/ */
@Placeholder(value = "server", relookup = "server")
@NotNull @NotNull
DiscordGuild getGuild(); DiscordGuild getGuild();
@ -101,7 +104,7 @@ public interface DiscordGuildMember extends JDAEntity<Member>, Mentionable {
* Gets the effective name of this Discord server member. * Gets the effective name of this Discord server member.
* @return the Discord server member's effective name * @return the Discord server member's effective name
*/ */
@Placeholder("user_effective_server_name") @Placeholder("effective_server_name")
@NotNull @NotNull
default String getEffectiveServerName() { default String getEffectiveServerName() {
String nickname = getNickname(); String nickname = getNickname();
@ -112,7 +115,7 @@ public interface DiscordGuildMember extends JDAEntity<Member>, Mentionable {
* Gets the avatar url that is active for this user in this server. * Gets the avatar url that is active for this user in this server.
* @return the user's avatar url in this server * @return the user's avatar url in this server
*/ */
@Placeholder("user_effective_server_avatar_url") @Placeholder("effective_server_avatar_url")
@NotNull @NotNull
String getEffectiveServerAvatarUrl(); String getEffectiveServerAvatarUrl();
@ -120,13 +123,14 @@ public interface DiscordGuildMember extends JDAEntity<Member>, Mentionable {
* Gets the color of this user's highest role that has a color. * Gets the color of this user's highest role that has a color.
* @return the color that will be used for this user * @return the color that will be used for this user
*/ */
@Placeholder("user_color") @Placeholder("color")
Color getColor(); Color getColor();
/** /**
* Gets the time the member joined the server. * Gets the time the member joined the server.
* @return the time the member joined the server * @return the time the member joined the server
*/ */
@Placeholder(value = "time_joined", relookup = "date")
@NotNull @NotNull
OffsetDateTime getTimeJoined(); OffsetDateTime getTimeJoined();
@ -134,6 +138,7 @@ public interface DiscordGuildMember extends JDAEntity<Member>, Mentionable {
* Time the member started boosting. * Time the member started boosting.
* @return the time the member started boosting or {@code null} * @return the time the member started boosting or {@code null}
*/ */
@Placeholder(value = "time_boosted", relookup = "date")
@Nullable @Nullable
OffsetDateTime getTimeBoosted(); OffsetDateTime getTimeBoosted();
@ -141,7 +146,7 @@ public interface DiscordGuildMember extends JDAEntity<Member>, Mentionable {
* If the Discord server member is boosted. * If the Discord server member is boosted.
* @return {@code true} if this Discord server member is boosting * @return {@code true} if this Discord server member is boosting
*/ */
@Placeholder("user_isboosting") @Placeholder("isboosting")
default boolean isBoosting() { default boolean isBoosting() {
return getTimeBoosted() != null; return getTimeBoosted() != null;
} }

View File

@ -28,12 +28,14 @@ import com.discordsrv.api.discord.entity.JDAEntity;
import com.discordsrv.api.discord.entity.Mentionable; import com.discordsrv.api.discord.entity.Mentionable;
import com.discordsrv.api.discord.entity.Snowflake; import com.discordsrv.api.discord.entity.Snowflake;
import com.discordsrv.api.placeholder.annotation.Placeholder; import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Role;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
/** /**
* A Discord server role. * A Discord server role.
*/ */
@PlaceholderPrefix("role_")
public interface DiscordRole extends JDAEntity<Role>, Snowflake, Mentionable { public interface DiscordRole extends JDAEntity<Role>, Snowflake, Mentionable {
/** /**
@ -45,6 +47,7 @@ public interface DiscordRole extends JDAEntity<Role>, Snowflake, Mentionable {
* The Discord server this role is from. * The Discord server this role is from.
* @return the Discord server * @return the Discord server
*/ */
@Placeholder(value = "server", relookup = "server")
@NotNull @NotNull
DiscordGuild getGuild(); DiscordGuild getGuild();
@ -52,7 +55,7 @@ public interface DiscordRole extends JDAEntity<Role>, Snowflake, Mentionable {
* Gets the name of the Discord role. * Gets the name of the Discord role.
* @return the role name * @return the role name
*/ */
@Placeholder("role_name") @Placeholder("name")
@NotNull @NotNull
String getName(); String getName();
@ -69,7 +72,7 @@ public interface DiscordRole extends JDAEntity<Role>, Snowflake, Mentionable {
* @return the color of this role, or {@link #DEFAULT_COLOR} if there is no color set * @return the color of this role, or {@link #DEFAULT_COLOR} if there is no color set
* @see #hasColor() * @see #hasColor()
*/ */
@Placeholder("role_color") @Placeholder(value = "color", relookup = "color")
@NotNull @NotNull
Color getColor(); Color getColor();

View File

@ -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.DiscordGuild;
import com.discordsrv.api.discord.entity.guild.DiscordGuildMember; import com.discordsrv.api.discord.entity.guild.DiscordGuildMember;
import com.discordsrv.api.placeholder.annotation.Placeholder; import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable; import org.jetbrains.annotations.Unmodifiable;
@ -41,6 +42,7 @@ import java.util.concurrent.CompletableFuture;
/** /**
* A message received from Discord. * A message received from Discord.
*/ */
@PlaceholderPrefix("message_")
public interface ReceivedDiscordMessage extends Snowflake { public interface ReceivedDiscordMessage extends Snowflake {
/** /**
@ -49,6 +51,7 @@ public interface ReceivedDiscordMessage extends Snowflake {
* @return the message content or {@code null} * @return the message content or {@code null}
*/ */
@Nullable @Nullable
@Placeholder("content")
String getContent(); String getContent();
/** /**
@ -70,7 +73,7 @@ public interface ReceivedDiscordMessage extends Snowflake {
* @return the jump url * @return the jump url
*/ */
@NotNull @NotNull
@Placeholder("message_jump_url") @Placeholder("jump_url")
String getJumpUrl(); String getJumpUrl();
/** /**
@ -92,6 +95,7 @@ public interface ReceivedDiscordMessage extends Snowflake {
* @return the user that sent the message * @return the user that sent the message
*/ */
@NotNull @NotNull
@Placeholder(value = "user", relookup = "user")
DiscordUser getAuthor(); DiscordUser getAuthor();
/** /**
@ -99,6 +103,7 @@ public interface ReceivedDiscordMessage extends Snowflake {
* @return the channel the message was sent in * @return the channel the message was sent in
*/ */
@NotNull @NotNull
@Placeholder(value = "channel", relookup = "channel")
DiscordMessageChannel getChannel(); DiscordMessageChannel getChannel();
/** /**
@ -135,6 +140,7 @@ public interface ReceivedDiscordMessage extends Snowflake {
* @return an optional potentially containing the Discord server the message was posted in * @return an optional potentially containing the Discord server the message was posted in
*/ */
@Nullable @Nullable
@Placeholder(value = "server", relookup = "server")
default DiscordGuild getGuild() { default DiscordGuild getGuild() {
DiscordTextChannel textChannel = getTextChannel(); DiscordTextChannel textChannel = getTextChannel();

View File

@ -44,7 +44,7 @@ public @interface Placeholder {
String value(); 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. * The object returned by the {@link Placeholder} method/field will be added as context.
* @return the prefix used for the next lookup * @return the prefix used for the next lookup
*/ */

View File

@ -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;
}

View File

@ -81,6 +81,7 @@ import com.discordsrv.common.storage.Storage;
import com.discordsrv.common.storage.StorageType; import com.discordsrv.common.storage.StorageType;
import com.discordsrv.common.storage.impl.MemoryStorage; import com.discordsrv.common.storage.impl.MemoryStorage;
import com.discordsrv.common.update.UpdateChecker; import com.discordsrv.common.update.UpdateChecker;
import com.discordsrv.common.uuid.util.UUIDUtil;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
@ -561,6 +562,7 @@ public abstract class AbstractDiscordSRV<
placeholderService().addResultMapper(new ComponentResultStringifier(this)); placeholderService().addResultMapper(new ComponentResultStringifier(this));
placeholderService().addGlobalContext(new GlobalTextHandlingContext(this)); placeholderService().addGlobalContext(new GlobalTextHandlingContext(this));
placeholderService().addGlobalContext(new GlobalDateFormattingContext(this)); placeholderService().addGlobalContext(new GlobalDateFormattingContext(this));
placeholderService().addGlobalContext(UUIDUtil.class);
// Modules // Modules
registerModule(ConsoleModule::new); registerModule(ConsoleModule::new);

View File

@ -15,5 +15,5 @@ public class AvatarProviderConfig {
@Comment("The template for URLs of player avatars\n" + @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 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") "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%";
} }

View File

@ -46,15 +46,9 @@ public class LogEntry {
return throwable; return throwable;
} }
@Placeholder(value = "log_time", relookup = "date")
public ZonedDateTime logTime() { public ZonedDateTime logTime() {
return logTime; return logTime;
} }
@Placeholder("log_time")
public PlaceholderLookupResult _logTimePlaceholder(@PlaceholderRemainder String format) {
Set<Object> extras = new LinkedHashSet<>();
extras.add(logTime());
return PlaceholderLookupResult.newLookup("date:'" + format + "'", extras);
}
} }

View File

@ -66,6 +66,7 @@ public class DiscordUserImpl implements DiscordUser {
return user.getEffectiveName(); return user.getEffectiveName();
} }
@SuppressWarnings("deprecation")
@Override @Override
public @NotNull String getDiscriminator() { public @NotNull String getDiscriminator() {
return user.getDiscriminator(); return user.getDiscriminator();

View File

@ -130,12 +130,12 @@ public class DiscordGuildMemberImpl implements DiscordGuildMember {
// Placeholders // Placeholders
// //
@Placeholder(value = "user_highest_role", relookup = "role") @Placeholder(value = "highest_role", relookup = "role")
public DiscordRole _highestRole() { public DiscordRole _highestRole() {
return !roles.isEmpty() ? roles.get(0) : null; return !roles.isEmpty() ? roles.get(0) : null;
} }
@Placeholder(value = "user_hoisted_role", relookup = "role") @Placeholder(value = "hoisted_role", relookup = "role")
public DiscordRole _hoistedRole() { public DiscordRole _hoistedRole() {
for (DiscordRole role : roles) { for (DiscordRole role : roles) {
if (role.isHoisted()) { if (role.isHoisted()) {
@ -145,7 +145,7 @@ public class DiscordGuildMemberImpl implements DiscordGuildMember {
return null; return null;
} }
@Placeholder("user_roles") @Placeholder("roles")
public Component _allRoles(@PlaceholderRemainder String suffix) { public Component _allRoles(@PlaceholderRemainder String suffix) {
List<Component> components = new ArrayList<>(); List<Component> components = new ArrayList<>();
for (DiscordRole role : getRoles()) { for (DiscordRole role : getRoles()) {

View File

@ -268,7 +268,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
// Placeholders // Placeholders
// //
@Placeholder("message_reply") @Placeholder("reply")
public Component _reply(BaseChannelConfig config) { public Component _reply(BaseChannelConfig config) {
if (replyingTo == null) { if (replyingTo == null) {
return 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) { public Component _attachments(BaseChannelConfig config, @PlaceholderRemainder String suffix) {
String attachmentFormat = config.discordToMinecraft.attachmentFormat; String attachmentFormat = config.discordToMinecraft.attachmentFormat;
List<Component> components = new ArrayList<>(); List<Component> components = new ArrayList<>();

View File

@ -22,13 +22,14 @@ import com.discordsrv.api.event.events.placeholder.PlaceholderLookupEvent;
import com.discordsrv.api.placeholder.PlaceholderLookupResult; import com.discordsrv.api.placeholder.PlaceholderLookupResult;
import com.discordsrv.api.placeholder.PlaceholderService; import com.discordsrv.api.placeholder.PlaceholderService;
import com.discordsrv.api.placeholder.annotation.Placeholder; 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.annotation.PlaceholderRemainder;
import com.discordsrv.api.placeholder.mapper.PlaceholderResultMapper; import com.discordsrv.api.placeholder.mapper.PlaceholderResultMapper;
import com.discordsrv.api.placeholder.provider.PlaceholderProvider;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.logging.Logger; import com.discordsrv.common.logging.Logger;
import com.discordsrv.common.logging.NamedLogger; import com.discordsrv.common.logging.NamedLogger;
import com.discordsrv.common.placeholder.provider.AnnotationPlaceholderProvider; 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.CacheLoader;
import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.LoadingCache;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -43,6 +44,7 @@ import java.lang.reflect.Parameter;
import java.util.*; import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -225,6 +227,8 @@ public class PlaceholderServiceImpl implements PlaceholderService {
} }
private Object getResultRepresentation(List<PlaceholderLookupResult> results, String placeholder, Matcher matcher) { private Object getResultRepresentation(List<PlaceholderLookupResult> results, String placeholder, Matcher matcher) {
Map<String, AtomicInteger> preventInfiniteLoop = new HashMap<>();
Object best = null; Object best = null;
for (PlaceholderLookupResult result : results) { for (PlaceholderLookupResult result : results) {
while (result != null) { while (result != null) {
@ -253,7 +257,15 @@ public class PlaceholderServiceImpl implements PlaceholderService {
replacement = "Error"; replacement = "Error";
break; break;
case NEW_LOOKUP: 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; newLookup = true;
break; break;
} }
@ -273,59 +285,65 @@ public class PlaceholderServiceImpl implements PlaceholderService {
private static class ClassProviderLoader implements CacheLoader<Class<?>, Set<PlaceholderProvider>> { private static class ClassProviderLoader implements CacheLoader<Class<?>, Set<PlaceholderProvider>> {
private Set<Class<?>> getAll(Class<?> clazz) { private Set<PlaceholderProvider> loadProviders(Class<?> clazz, PlaceholderPrefix prefix) {
Set<Class<?>> classes = new LinkedHashSet<>(); Set<PlaceholderProvider> providers = new LinkedHashSet<>();
classes.add(clazz);
for (Class<?> anInterface : clazz.getInterfaces()) { Class<?> currentClass = clazz;
classes.addAll(getAll(anInterface)); 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<PlaceholderProvider> load(@NonNull Class<?> key) {
Set<PlaceholderProvider> providers = new HashSet<>();
Set<Class<?>> classes = getAll(key);
for (Class<?> clazz : classes) {
for (Method method : clazz.getMethods()) { for (Method method : clazz.getMethods()) {
if (!method.getDeclaringClass().equals(currentClass)) {
continue;
}
Placeholder annotation = method.getAnnotation(Placeholder.class); Placeholder annotation = method.getAnnotation(Placeholder.class);
if (annotation == null) { if (annotation == null) {
continue; continue;
} }
boolean startsWith = !annotation.relookup().isEmpty(); PlaceholderRemainder remainder = null;
if (!startsWith) {
for (Parameter parameter : method.getParameters()) { for (Parameter parameter : method.getParameters()) {
if (parameter.getAnnotation(PlaceholderRemainder.class) != null) { remainder = parameter.getAnnotation(PlaceholderRemainder.class);
startsWith = true; if (remainder != null) {
break; break;
} }
} }
}
boolean isStatic = Modifier.isStatic(method.getModifiers()); 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()) { for (Field field : clazz.getFields()) {
if (!field.getDeclaringClass().equals(currentClass)) {
continue;
}
Placeholder annotation = field.getAnnotation(Placeholder.class); Placeholder annotation = field.getAnnotation(Placeholder.class);
if (annotation == null) { if (annotation == null) {
continue; continue;
} }
boolean isStatic = Modifier.isStatic(field.getModifiers()); 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; return providers;
} }
@Override
public @Nullable Set<PlaceholderProvider> load(@NonNull Class<?> key) {
return loadProviders(key, null);
}
} }
} }

View File

@ -20,6 +20,8 @@ package com.discordsrv.common.placeholder.provider;
import com.discordsrv.api.placeholder.PlaceholderLookupResult; import com.discordsrv.api.placeholder.PlaceholderLookupResult;
import com.discordsrv.api.placeholder.annotation.Placeholder; 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.api.placeholder.provider.PlaceholderProvider;
import com.discordsrv.common.placeholder.provider.util.PlaceholderMethodUtil; import com.discordsrv.common.placeholder.provider.util.PlaceholderMethodUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -32,31 +34,35 @@ import java.util.Set;
public class AnnotationPlaceholderProvider implements PlaceholderProvider { public class AnnotationPlaceholderProvider implements PlaceholderProvider {
private final Placeholder annotation; private final Placeholder annotation;
private final PlaceholderPrefix prefixAnnotation;
private final PlaceholderRemainder remainderAnnotation;
private final Class<?> type; private final Class<?> type;
private final Method method; private final Method method;
private final boolean startsWith;
private final Field field; private final Field field;
public AnnotationPlaceholderProvider(Placeholder annotation, Class<?> type, boolean startsWith, Method method) { public AnnotationPlaceholderProvider(Placeholder annotation, PlaceholderPrefix prefixAnnotation, PlaceholderRemainder remainderAnnotation, Class<?> type, Method method) {
this.annotation = annotation; this(annotation, prefixAnnotation, remainderAnnotation, type, method, null);
this.type = type;
this.startsWith = startsWith;
this.method = method;
this.field = 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.annotation = annotation;
this.prefixAnnotation = prefixAnnotation;
this.remainderAnnotation = remainderAnnotation;
this.type = type; this.type = type;
this.startsWith = startsWith; this.method = method;
this.method = null;
this.field = field; this.field = field;
} }
@Override @Override
public @NotNull PlaceholderLookupResult lookup(@NotNull String placeholder, @NotNull Set<Object> context) { public @NotNull PlaceholderLookupResult lookup(@NotNull String placeholder, @NotNull Set<Object> 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() if (annotationPlaceholder.isEmpty()
|| !(startsWith ? placeholder.startsWith(annotationPlaceholder) : placeholder.equals(annotationPlaceholder)) || !(startsWith ? placeholder.startsWith(annotationPlaceholder) : placeholder.equals(annotationPlaceholder))
|| (type != null && context.isEmpty())) { || (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; Object result;
try { try {
@ -89,8 +95,10 @@ public class AnnotationPlaceholderProvider implements PlaceholderProvider {
return PlaceholderLookupResult.lookupFailed(t); return PlaceholderLookupResult.lookupFailed(t);
} }
String reLookup = annotation.relookup(); if (reLookup.isEmpty() && remainderAnnotation == null) {
if (!reLookup.isEmpty()) { reLookup = annotation.value();
}
if (!reLookup.isEmpty() && !remainder.isEmpty()) {
if (result == null) { if (result == null) {
return PlaceholderLookupResult.success(null); return PlaceholderLookupResult.success(null);
} }

View File

@ -19,6 +19,7 @@
package com.discordsrv.common.player; package com.discordsrv.common.player;
import com.discordsrv.api.placeholder.annotation.Placeholder; import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.player.provider.model.SkinInfo; import com.discordsrv.common.player.provider.model.SkinInfo;
import com.discordsrv.common.profile.Profile; import com.discordsrv.common.profile.Profile;
@ -30,6 +31,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@PlaceholderPrefix("player_")
public interface IOfflinePlayer extends Identified { public interface IOfflinePlayer extends Identified {
DiscordSRV discordSRV(); DiscordSRV discordSRV();
@ -39,12 +41,12 @@ public interface IOfflinePlayer extends Identified {
return discordSRV().profileManager().lookupProfile(uniqueId()); return discordSRV().profileManager().lookupProfile(uniqueId());
} }
@Placeholder("player_name") @Placeholder("name")
@Nullable @Nullable
String username(); String username();
@ApiStatus.NonExtendable @ApiStatus.NonExtendable
@Placeholder("player_uuid") @Placeholder(value = "uuid", relookup = "uuid")
@NotNull @NotNull
default UUID uniqueId() { default UUID uniqueId() {
return identity().uuid(); return identity().uuid();
@ -53,14 +55,14 @@ public interface IOfflinePlayer extends Identified {
@Nullable @Nullable
SkinInfo skinInfo(); SkinInfo skinInfo();
@Placeholder("player_skin_texture_id") @Placeholder("skin_texture_id")
@Nullable @Nullable
default String skinTextureId() { default String skinTextureId() {
SkinInfo info = skinInfo(); SkinInfo info = skinInfo();
return info != null ? info.textureId() : null; return info != null ? info.textureId() : null;
} }
@Placeholder("player_skin_model") @Placeholder("skin_model")
@Nullable @Nullable
default String skinModel() { default String skinModel() {
SkinInfo info = skinInfo(); SkinInfo info = skinInfo();

View File

@ -19,6 +19,7 @@
package com.discordsrv.common.player; package com.discordsrv.common.player;
import com.discordsrv.api.placeholder.annotation.Placeholder; import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import com.discordsrv.api.player.DiscordSRVPlayer; import com.discordsrv.api.player.DiscordSRVPlayer;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.sender.ICommandSender; import com.discordsrv.common.command.game.sender.ICommandSender;
@ -32,6 +33,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.UUID; import java.util.UUID;
@PlaceholderPrefix("player_")
public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSender { public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSender {
@Override @Override
@ -47,29 +49,23 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende
} }
@NotNull @NotNull
@Placeholder("player_name") @Placeholder("name")
String username(); String username();
@Override @Override
@ApiStatus.NonExtendable @ApiStatus.NonExtendable
@Placeholder("player_uuid") @Placeholder(value = "uuid", relookup = "uuid")
default @NotNull UUID uniqueId() { default @NotNull UUID uniqueId() {
return identity().uuid(); return identity().uuid();
} }
@ApiStatus.NonExtendable
@Placeholder("player_uuid_nodashes")
default @NotNull String uniqueIdNoDashes() {
return uniqueId().toString().replace("-", "");
}
@NotNull @NotNull
@Placeholder("player_display_name") @Placeholder("display_name")
Component displayName(); Component displayName();
@Nullable @Nullable
@ApiStatus.NonExtendable @ApiStatus.NonExtendable
@Placeholder("player_avatar_url") @Placeholder("avatar_url")
default String getAvatarUrl() { default String getAvatarUrl() {
AvatarProviderConfig avatarConfig = discordSRV().config().avatarProvider; AvatarProviderConfig avatarConfig = discordSRV().config().avatarProvider;
String avatarUrlTemplate = avatarConfig.avatarUrlTemplate; String avatarUrlTemplate = avatarConfig.avatarUrlTemplate;
@ -78,7 +74,7 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende
// Offline mode // Offline mode
if (uniqueId().version() == 3) avatarUrlTemplate = "https://cravatar.eu/helmavatar/%player_name%/128.png#%player_skin_texture_id%"; if (uniqueId().version() == 3) avatarUrlTemplate = "https://cravatar.eu/helmavatar/%player_name%/128.png#%player_skin_texture_id%";
// Bedrock // 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) { if (avatarUrlTemplate == null) {
@ -90,28 +86,28 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende
@Nullable @Nullable
@ApiStatus.NonExtendable @ApiStatus.NonExtendable
@Placeholder("player_meta_prefix") @Placeholder("meta_prefix")
default Component getMetaPrefix() { default Component getMetaPrefix() {
return PermissionUtil.getMetaPrefix(discordSRV(), uniqueId()); return PermissionUtil.getMetaPrefix(discordSRV(), uniqueId());
} }
@Nullable @Nullable
@ApiStatus.NonExtendable @ApiStatus.NonExtendable
@Placeholder("player_meta_suffix") @Placeholder("meta_suffix")
default Component getMetaSuffix() { default Component getMetaSuffix() {
return PermissionUtil.getMetaSuffix(discordSRV(), uniqueId()); return PermissionUtil.getMetaSuffix(discordSRV(), uniqueId());
} }
@Nullable @Nullable
@ApiStatus.NonExtendable @ApiStatus.NonExtendable
@Placeholder("player_prefix") @Placeholder("prefix")
default Component getPrefix() { default Component getPrefix() {
return PermissionUtil.getPrefix(discordSRV(), uniqueId()); return PermissionUtil.getPrefix(discordSRV(), uniqueId());
} }
@Nullable @Nullable
@ApiStatus.NonExtendable @ApiStatus.NonExtendable
@Placeholder("player_suffix") @Placeholder("suffix")
default Component getSuffix() { default Component getSuffix() {
return PermissionUtil.getSuffix(discordSRV(), uniqueId()); return PermissionUtil.getSuffix(discordSRV(), uniqueId());
} }

View File

@ -1,9 +1,12 @@
package com.discordsrv.common.uuid.util; 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 org.jetbrains.annotations.NotNull;
import java.util.UUID; import java.util.UUID;
@PlaceholderPrefix("uuid_")
public final class UUIDUtil { public final class UUIDUtil {
private UUIDUtil() {} private UUIDUtil() {}
@ -31,6 +34,7 @@ public final class UUIDUtil {
return UUID.fromString(fullLengthUUID); return UUID.fromString(fullLengthUUID);
} }
@Placeholder("short")
public static String toShort(@NotNull UUID uuid) { public static String toShort(@NotNull UUID uuid) {
return uuid.toString().replace("-", ""); return uuid.toString().replace("-", "");
} }

View File

@ -20,6 +20,7 @@ package com.discordsrv.common.placeholder;
import com.discordsrv.api.placeholder.PlaceholderService; import com.discordsrv.api.placeholder.PlaceholderService;
import com.discordsrv.api.placeholder.annotation.Placeholder; import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import com.discordsrv.common.MockDiscordSRV; import com.discordsrv.common.MockDiscordSRV;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -69,6 +70,26 @@ public class PlaceholderServiceTest {
assertEquals("b", service.replacePlaceholders("%empty|static_method%", PlaceholderContext.class)); 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 { public static class PlaceholderContext {
@Placeholder("static_field") @Placeholder("static_field")
@ -95,4 +116,16 @@ public class PlaceholderServiceTest {
return output; 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";
}
} }