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

View File

@ -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<User>, Snowflake, Mentionable {
/**
@ -52,7 +54,7 @@ public interface DiscordUser extends JDAEntity<User>, 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<User>, 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<User>, 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<User>, 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<User>, 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<User>, 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();

View File

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

View File

@ -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 {
/**

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.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();
}

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.Snowflake;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import net.dv8tion.jda.api.entities.emoji.CustomEmoji;
@PlaceholderPrefix("emoji_")
public interface DiscordCustomEmoji extends JDAEntity<CustomEmoji>, Snowflake {
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.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<Guild>, 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<Guild>, Snowflake {
* Gets the member count of the guild.
* @return the guild's member count
*/
@Placeholder("server_member_count")
@Placeholder("member_count")
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.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<Member>, Mentionable {
/**
@ -52,6 +54,7 @@ public interface DiscordGuildMember extends JDAEntity<Member>, 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<Member>, 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<Member>, 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<Member>, 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<Member>, 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<Member>, 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;
}

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.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<Role>, Snowflake, Mentionable {
/**
@ -45,6 +47,7 @@ public interface DiscordRole extends JDAEntity<Role>, 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<Role>, 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<Role>, 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();

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.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();

View File

@ -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
*/

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.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);

View File

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

View File

@ -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<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();
}
@SuppressWarnings("deprecation")
@Override
public @NotNull String getDiscriminator() {
return user.getDiscriminator();

View File

@ -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<Component> components = new ArrayList<>();
for (DiscordRole role : getRoles()) {

View File

@ -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<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.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<PlaceholderLookupResult> results, String placeholder, Matcher matcher) {
Map<String, AtomicInteger> 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<Class<?>, Set<PlaceholderProvider>> {
private Set<Class<?>> getAll(Class<?> clazz) {
Set<Class<?>> classes = new LinkedHashSet<>();
classes.add(clazz);
private Set<PlaceholderProvider> loadProviders(Class<?> clazz, PlaceholderPrefix prefix) {
Set<PlaceholderProvider> 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<PlaceholderProvider> load(@NonNull Class<?> key) {
Set<PlaceholderProvider> providers = new HashSet<>();
Set<Class<?>> 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<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.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<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()
|| !(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);
}

View File

@ -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();

View File

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

View File

@ -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("-", "");
}

View File

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