diff --git a/common/src/main/java/com/discordsrv/common/module/type/PermissionDataProvider.java b/api/src/main/java/com/discordsrv/api/module/type/PermissionDataProvider.java
similarity index 68%
rename from common/src/main/java/com/discordsrv/common/module/type/PermissionDataProvider.java
rename to api/src/main/java/com/discordsrv/api/module/type/PermissionDataProvider.java
index 67f09762..41a12bd7 100644
--- a/common/src/main/java/com/discordsrv/common/module/type/PermissionDataProvider.java
+++ b/api/src/main/java/com/discordsrv/api/module/type/PermissionDataProvider.java
@@ -1,24 +1,28 @@
/*
- * This file is part of DiscordSRV, licensed under the GPLv3 License
+ * This file is part of the DiscordSRV API, licensed under the MIT License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
*/
-package com.discordsrv.common.module.type;
+package com.discordsrv.api.module.type;
-import com.discordsrv.api.module.type.Module;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
diff --git a/api/src/main/java/com/discordsrv/api/placeholder/mapper/PlaceholderResultMapper.java b/api/src/main/java/com/discordsrv/api/placeholder/mapper/PlaceholderResultMapper.java
index b6caf9d7..8a595bc7 100644
--- a/api/src/main/java/com/discordsrv/api/placeholder/mapper/PlaceholderResultMapper.java
+++ b/api/src/main/java/com/discordsrv/api/placeholder/mapper/PlaceholderResultMapper.java
@@ -29,9 +29,10 @@ import org.jetbrains.annotations.NotNull;
public interface PlaceholderResultMapper {
/**
- * Converts a successful placeholder lookup result into a {@link String}.
+ * Converts a successful placeholder lookup result into an object that is better suited to be used in place of the result.
* @param result the result
- * @return the result in {@link String} form or {@code null} if this stringifier doesn't know what to do with this result
+ * @return the result in the form that should be used for converting to a {@link String} or used directly by placeholder service users
+ * or {@code null} if this mapper doesn't know what to do with this result
*/
Object convertResult(@NotNull Object result);
diff --git a/api/src/main/java/com/discordsrv/api/placeholder/util/Placeholders.java b/api/src/main/java/com/discordsrv/api/placeholder/util/Placeholders.java
index dae9ebf0..ee183390 100644
--- a/api/src/main/java/com/discordsrv/api/placeholder/util/Placeholders.java
+++ b/api/src/main/java/com/discordsrv/api/placeholder/util/Placeholders.java
@@ -85,16 +85,20 @@ public class Placeholders {
for (Map.Entry> entry : replacements.entrySet()) {
Pattern pattern = entry.getKey();
Matcher matcher = pattern.matcher(input);
- if (!matcher.find()) {
+ StringBuffer buffer = new StringBuffer();
+ int lastEnd = -1;
+ while (matcher.find()) {
+ lastEnd = matcher.end();
+ Function replacement = entry.getValue();
+ Object value = replacement.apply(matcher);
+
+ matcher.appendReplacement(buffer, Matcher.quoteReplacement(String.valueOf(value)));
+ }
+ if (lastEnd == -1) {
continue;
}
-
- Function replacement = entry.getValue();
- Object value = replacement.apply(matcher);
-
- input = matcher.replaceAll(
- Matcher.quoteReplacement(
- String.valueOf(value)));
+ buffer.append(input.substring(lastEnd));
+ input = buffer.toString();
}
return input;
}
diff --git a/api/src/test/java/com/discordsrv/api/placeholder/util/PlaceholdersTest.java b/api/src/test/java/com/discordsrv/api/placeholder/util/PlaceholdersTest.java
index 0554dc46..67a23bc7 100644
--- a/api/src/test/java/com/discordsrv/api/placeholder/util/PlaceholdersTest.java
+++ b/api/src/test/java/com/discordsrv/api/placeholder/util/PlaceholdersTest.java
@@ -25,6 +25,8 @@ package com.discordsrv.api.placeholder.util;
import org.junit.jupiter.api.Test;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
public class PlaceholdersTest {
@@ -38,4 +40,40 @@ public class PlaceholdersTest {
assertEquals("b", placeholders.toString());
}
+
+ @Test
+ public void uselessContentTest() {
+ Placeholders placeholders = new Placeholders("stuff a stuff");
+
+ placeholders.replace("a", "b");
+
+ assertEquals("stuff b stuff", placeholders.toString());
+ }
+
+ @Test
+ public void multipleTest() {
+ Placeholders placeholders = new Placeholders("a b");
+
+ placeholders.replace("a", "c");
+ placeholders.replace("b", "d");
+
+ assertEquals("c d", placeholders.toString());
+ }
+
+ @Test
+ public void multipleSamePatternTest() {
+ Placeholders placeholders = new Placeholders("a a");
+
+ AtomicBoolean used = new AtomicBoolean(false);
+ placeholders.replace("a", matcher -> {
+ if (used.get()) {
+ return "c";
+ } else {
+ used.set(true);
+ return "b";
+ }
+ });
+
+ assertEquals("b c", placeholders.toString());
+ }
}
diff --git a/bukkit/src/main/java/com/discordsrv/bukkit/integration/VaultIntegration.java b/bukkit/src/main/java/com/discordsrv/bukkit/integration/VaultIntegration.java
index 58228266..6517b1b6 100644
--- a/bukkit/src/main/java/com/discordsrv/bukkit/integration/VaultIntegration.java
+++ b/bukkit/src/main/java/com/discordsrv/bukkit/integration/VaultIntegration.java
@@ -22,7 +22,7 @@ import com.discordsrv.bukkit.BukkitDiscordSRV;
import com.discordsrv.common.exception.MessageException;
import com.discordsrv.common.function.CheckedSupplier;
import com.discordsrv.common.future.util.CompletableFutureUtil;
-import com.discordsrv.common.module.type.PermissionDataProvider;
+import com.discordsrv.api.module.type.PermissionDataProvider;
import com.discordsrv.common.module.type.PluginIntegration;
import net.milkbowl.vault.chat.Chat;
import net.milkbowl.vault.permission.Permission;
diff --git a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java
index 9b55eb36..8bd9ea59 100644
--- a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java
+++ b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java
@@ -62,7 +62,7 @@ import com.discordsrv.common.messageforwarding.game.StartMessageModule;
import com.discordsrv.common.messageforwarding.game.StopMessageModule;
import com.discordsrv.common.module.ModuleManager;
import com.discordsrv.common.module.type.AbstractModule;
-import com.discordsrv.common.placeholder.ComponentResultStringifier;
+import com.discordsrv.common.placeholder.result.ComponentResultStringifier;
import com.discordsrv.common.placeholder.PlaceholderServiceImpl;
import com.discordsrv.common.placeholder.context.GlobalTextHandlingContext;
import com.discordsrv.common.profile.ProfileManager;
diff --git a/common/src/main/java/com/discordsrv/common/config/main/channels/MinecraftToDiscordChatConfig.java b/common/src/main/java/com/discordsrv/common/config/main/channels/MinecraftToDiscordChatConfig.java
index 36eeed90..2dc51089 100644
--- a/common/src/main/java/com/discordsrv/common/config/main/channels/MinecraftToDiscordChatConfig.java
+++ b/common/src/main/java/com/discordsrv/common/config/main/channels/MinecraftToDiscordChatConfig.java
@@ -35,7 +35,7 @@ public class MinecraftToDiscordChatConfig implements IMessageConfig {
@Untranslated(Untranslated.Type.VALUE)
public SendableDiscordMessage.Builder format = SendableDiscordMessage.builder()
- .setWebhookUsername("%player_display_name%")
+ .setWebhookUsername("%player_meta_prefix|player_prefix%%player_display_name|player_name%%player_meta_suffix|player_suffix%")
.setWebhookAvatarUrl("%player_avatar_url%")
.setContent("%message%");
diff --git a/common/src/main/java/com/discordsrv/common/groupsync/GroupSyncModule.java b/common/src/main/java/com/discordsrv/common/groupsync/GroupSyncModule.java
index 0ccf3427..3f1e2c23 100644
--- a/common/src/main/java/com/discordsrv/common/groupsync/GroupSyncModule.java
+++ b/common/src/main/java/com/discordsrv/common/groupsync/GroupSyncModule.java
@@ -34,7 +34,7 @@ import com.discordsrv.common.groupsync.enums.GroupSyncResult;
import com.discordsrv.common.groupsync.enums.GroupSyncSide;
import com.discordsrv.common.logging.NamedLogger;
import com.discordsrv.common.module.type.AbstractModule;
-import com.discordsrv.common.module.type.PermissionDataProvider;
+import com.discordsrv.api.module.type.PermissionDataProvider;
import com.discordsrv.common.player.IPlayer;
import com.github.benmanes.caffeine.cache.Cache;
import org.apache.commons.lang3.StringUtils;
diff --git a/common/src/main/java/com/discordsrv/common/integration/LuckPermsIntegration.java b/common/src/main/java/com/discordsrv/common/integration/LuckPermsIntegration.java
index e41a9bdc..fee536c6 100644
--- a/common/src/main/java/com/discordsrv/common/integration/LuckPermsIntegration.java
+++ b/common/src/main/java/com/discordsrv/common/integration/LuckPermsIntegration.java
@@ -23,7 +23,7 @@ import com.discordsrv.common.exception.MessageException;
import com.discordsrv.common.future.util.CompletableFutureUtil;
import com.discordsrv.common.groupsync.GroupSyncModule;
import com.discordsrv.common.groupsync.enums.GroupSyncCause;
-import com.discordsrv.common.module.type.PermissionDataProvider;
+import com.discordsrv.api.module.type.PermissionDataProvider;
import com.discordsrv.common.module.type.PluginIntegration;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
diff --git a/common/src/main/java/com/discordsrv/common/messageforwarding/game/AbstractGameMessageModule.java b/common/src/main/java/com/discordsrv/common/messageforwarding/game/AbstractGameMessageModule.java
index 00c96f48..3f20b88a 100644
--- a/common/src/main/java/com/discordsrv/common/messageforwarding/game/AbstractGameMessageModule.java
+++ b/common/src/main/java/com/discordsrv/common/messageforwarding/game/AbstractGameMessageModule.java
@@ -121,7 +121,7 @@ public abstract class AbstractGameMessageModule extend
messageFutures = sendMessageToChannels(
moduleConfig, format, messageChannels, message,
// Context
- channelConfig, player
+ config, player
);
return CompletableFuture.allOf(messageFutures.keySet().toArray(new CompletableFuture[0]))
diff --git a/common/src/main/java/com/discordsrv/common/permission/util/PermissionUtil.java b/common/src/main/java/com/discordsrv/common/permission/util/PermissionUtil.java
new file mode 100644
index 00000000..23d3e1ff
--- /dev/null
+++ b/common/src/main/java/com/discordsrv/common/permission/util/PermissionUtil.java
@@ -0,0 +1,78 @@
+/*
+ * This file is part of DiscordSRV, licensed under the GPLv3 License
+ * Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.discordsrv.common.permission.util;
+
+import com.discordsrv.api.module.type.PermissionDataProvider;
+import com.discordsrv.common.DiscordSRV;
+import com.discordsrv.common.component.util.ComponentUtil;
+import net.kyori.adventure.text.Component;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+public final class PermissionUtil {
+
+ public static final String PREFIX_META_KEY = "discordsrv_prefix";
+ public static final String SUFFIX_META_KEY = "discordsrv_suffix";
+
+ private PermissionUtil() {}
+
+ public static Component getMetaPrefix(DiscordSRV discordSRV, UUID uuid) {
+ return getMeta(discordSRV, uuid, PREFIX_META_KEY);
+ }
+ public static Component getMetaSuffix(DiscordSRV discordSRV, UUID uuid) {
+ return getMeta(discordSRV, uuid, SUFFIX_META_KEY);
+ }
+
+ public static Component getPrefix(DiscordSRV discordSRV, UUID uuid) {
+ return getLegacy(discordSRV, perm -> perm.getPrefix(uuid));
+ }
+
+ public static Component getSuffix(DiscordSRV discordSRV, UUID uuid) {
+ return getLegacy(discordSRV, perm -> perm.getSuffix(uuid));
+ }
+
+ private static Component getMeta(DiscordSRV discordSRV, UUID uuid, String metaKey) {
+ PermissionDataProvider.Meta meta = discordSRV.getModule(PermissionDataProvider.Meta.class);
+ if (meta == null) {
+ return null;
+ }
+
+ String data = meta.getMeta(uuid, metaKey).join();
+ return translate(discordSRV, data);
+ }
+
+ private static Component getLegacy(
+ DiscordSRV discordSRV,
+ Function> legacy
+ ) {
+ PermissionDataProvider.PrefixAndSuffix permission = discordSRV.getModule(PermissionDataProvider.PrefixAndSuffix.class);
+ if (permission == null) {
+ return null;
+ }
+
+ String data = legacy.apply(permission).join();
+ return translate(discordSRV, data);
+ }
+
+ private static Component translate(DiscordSRV discordSRV, String data) {
+ return data != null ? ComponentUtil.fromAPI(discordSRV.componentFactory().enhancedBuilder(data).build()) : null;
+ }
+}
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 daeecd71..3568e984 100644
--- a/common/src/main/java/com/discordsrv/common/placeholder/PlaceholderServiceImpl.java
+++ b/common/src/main/java/com/discordsrv/common/placeholder/PlaceholderServiceImpl.java
@@ -19,11 +19,11 @@
package com.discordsrv.common.placeholder;
import com.discordsrv.api.event.events.placeholder.PlaceholderLookupEvent;
-import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.PlaceholderLookupResult;
-import com.discordsrv.api.placeholder.mapper.PlaceholderResultMapper;
import com.discordsrv.api.placeholder.PlaceholderService;
+import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderRemainder;
+import com.discordsrv.api.placeholder.mapper.PlaceholderResultMapper;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.placeholder.provider.AnnotationPlaceholderProvider;
import com.discordsrv.common.placeholder.provider.PlaceholderProvider;
@@ -182,14 +182,14 @@ public class PlaceholderServiceImpl implements PlaceholderService {
private String getResultAsString(Object result) {
if (result == null) {
- return "null";
+ return "";
} else if (result instanceof CharSequence) {
return result.toString();
}
Object output = null;
- for (PlaceholderResultMapper stringifier : mappers) {
- output = stringifier.convertResult(result);
+ for (PlaceholderResultMapper mapper : mappers) {
+ output = mapper.convertResult(result);
if (output != null) {
break;
}
@@ -240,7 +240,10 @@ public class PlaceholderServiceImpl implements PlaceholderService {
switch (type) {
case SUCCESS:
replacement = result.getValue();
- if (replacement != null && StringUtils.isNotBlank(getResultAsString(replacement))) {
+ if (replacement == null) {
+ replacement = getResultAsString(null);
+ }
+ if (StringUtils.isNotBlank(getResultAsString(replacement))) {
return replacement;
}
break;
diff --git a/common/src/main/java/com/discordsrv/common/placeholder/provider/util/PlaceholderMethodUtil.java b/common/src/main/java/com/discordsrv/common/placeholder/provider/util/PlaceholderMethodUtil.java
index 564d36a1..f1037e0c 100644
--- a/common/src/main/java/com/discordsrv/common/placeholder/provider/util/PlaceholderMethodUtil.java
+++ b/common/src/main/java/com/discordsrv/common/placeholder/provider/util/PlaceholderMethodUtil.java
@@ -49,7 +49,6 @@ public final class PlaceholderMethodUtil {
for (Object o : context) {
Class> objectType = o.getClass();
apply(parameters, (parameter, i) -> {
- Class> type = parameter.getType();
if (parameter.getType().isAssignableFrom(objectType)) {
parameters[i] = null;
parameterValues[i] = o;
diff --git a/common/src/main/java/com/discordsrv/common/placeholder/ComponentResultStringifier.java b/common/src/main/java/com/discordsrv/common/placeholder/result/ComponentResultStringifier.java
similarity index 97%
rename from common/src/main/java/com/discordsrv/common/placeholder/ComponentResultStringifier.java
rename to common/src/main/java/com/discordsrv/common/placeholder/result/ComponentResultStringifier.java
index 8c9a8e46..41360001 100644
--- a/common/src/main/java/com/discordsrv/common/placeholder/ComponentResultStringifier.java
+++ b/common/src/main/java/com/discordsrv/common/placeholder/result/ComponentResultStringifier.java
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package com.discordsrv.common.placeholder;
+package com.discordsrv.common.placeholder.result;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.placeholder.mapper.PlaceholderResultMapper;
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 83d246f0..813e4bce 100644
--- a/common/src/main/java/com/discordsrv/common/player/IPlayer.java
+++ b/common/src/main/java/com/discordsrv/common/player/IPlayer.java
@@ -25,6 +25,7 @@ import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.command.game.sender.ICommandSender;
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
import com.discordsrv.common.function.OrDefault;
+import com.discordsrv.common.permission.util.PermissionUtil;
import com.discordsrv.common.profile.Profile;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.ApiStatus;
@@ -35,6 +36,7 @@ import java.util.UUID;
public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSender {
+ @Override
DiscordSRV discordSRV();
@ApiStatus.NonExtendable
@@ -47,6 +49,7 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende
@Override
@ApiStatus.NonExtendable
+ @Placeholder("player_uuid")
default @NotNull UUID uniqueId() {
return identity().uuid();
}
@@ -56,6 +59,7 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende
Component displayName();
@Nullable
+ @ApiStatus.NonExtendable
@Placeholder("player_avatar_url")
default String getAvatarUrl(OrDefault config) {
String avatarUrlProvider = config.get(cfg -> cfg.avatarUrlProvider);
@@ -70,4 +74,32 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende
.toString();
}
+ @Nullable
+ @ApiStatus.NonExtendable
+ @Placeholder("player_meta_prefix")
+ default Component getMetaPrefix() {
+ return PermissionUtil.getMetaPrefix(discordSRV(), uniqueId());
+ }
+
+ @Nullable
+ @ApiStatus.NonExtendable
+ @Placeholder("player_meta_suffix")
+ default Component getMetaSuffix() {
+ return PermissionUtil.getMetaSuffix(discordSRV(), uniqueId());
+ }
+
+ @Nullable
+ @ApiStatus.NonExtendable
+ @Placeholder("player_prefix")
+ default Component getPrefix() {
+ return PermissionUtil.getPrefix(discordSRV(), uniqueId());
+ }
+
+ @Nullable
+ @ApiStatus.NonExtendable
+ @Placeholder("player_suffix")
+ default Component getSuffix() {
+ return PermissionUtil.getSuffix(discordSRV(), uniqueId());
+ }
+
}