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