More progress on placeholders & Minecraft -> Discord chat

This commit is contained in:
Vankka 2021-08-14 21:16:32 +03:00
parent 3fb79cc50c
commit ff8385dfda
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
40 changed files with 670 additions and 298 deletions

View File

@ -0,0 +1,58 @@
/*
* This file is part of the DiscordSRV API, licensed under the MIT License
* Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.api.component;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public interface EnhancedTextBuilder {
EnhancedTextBuilder addContext(Object... context);
default EnhancedTextBuilder addReplacement(String target, Object replacement) {
return addReplacement(Pattern.compile(target, Pattern.LITERAL), replacement);
}
default EnhancedTextBuilder addReplacement(Pattern target, Object replacement) {
return addReplacement(target, matcher -> replacement);
}
default EnhancedTextBuilder addReplacement(String target, Supplier<Object> replacement) {
return addReplacement(Pattern.compile(target, Pattern.LITERAL), replacement);
}
default EnhancedTextBuilder addReplacement(Pattern target, Supplier<Object> replacement) {
return addReplacement(target, matcher -> replacement.get());
}
default EnhancedTextBuilder addReplacement(String target, Function<Matcher, Object> replacement) {
return addReplacement(Pattern.compile(target, Pattern.LITERAL), replacement);
}
EnhancedTextBuilder addReplacement(Pattern target, Function<Matcher, Object> replacement);
MinecraftComponent build();
}

View File

@ -66,7 +66,7 @@ public interface MinecraftComponent {
* *
* @param gsonSerializerClass the gson serializer class * @param gsonSerializerClass the gson serializer class
* @return a adapter that will convert to/from relocated or unrelocated adventure classes to/from json * @return a adapter that will convert to/from relocated or unrelocated adventure classes to/from json
* @throws IllegalArgumentException if the provided class is not a Adventure GsonComponentSerializer * @throws IllegalArgumentException if the provided class is not an Adventure GsonComponentSerializer
*/ */
@NotNull @NotNull
Adapter adventureAdapter(@NotNull Class<?> gsonSerializerClass); Adapter adventureAdapter(@NotNull Class<?> gsonSerializerClass);
@ -96,7 +96,7 @@ public interface MinecraftComponent {
} }
/** /**
* A Adventure adapter, converts from/to given adventure components from/to json. * An Adventure adapter, converts from/to given adventure components from/to json.
*/ */
interface Adapter { interface Adapter {

View File

@ -39,4 +39,6 @@ public interface MinecraftComponentFactory {
*/ */
@NotNull @NotNull
MinecraftComponent empty(); MinecraftComponent empty();
EnhancedTextBuilder enhancedBuilder(String content);
} }

View File

@ -27,6 +27,7 @@ import com.discordsrv.api.discord.api.entity.channel.DiscordDMChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel; import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel; import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel;
import com.discordsrv.api.discord.api.entity.guild.DiscordGuild; import com.discordsrv.api.discord.api.entity.guild.DiscordGuild;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.api.discord.api.entity.user.DiscordUser; import com.discordsrv.api.discord.api.entity.user.DiscordUser;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -37,6 +38,8 @@ import java.util.Optional;
*/ */
public interface DiscordAPI { public interface DiscordAPI {
SendableDiscordMessage.Formatter format(SendableDiscordMessage.Builder message);
/** /**
* Gets a Discord message channel by id, the provided entity can be cached and will not update if it changes on Discord. * Gets a Discord message channel by id, the provided entity can be cached and will not update if it changes on Discord.
* @param id the id for the message channel * @param id the id for the message channel

View File

@ -29,6 +29,10 @@ import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* A message that can be sent to Discord. * A message that can be sent to Discord.
@ -190,5 +194,33 @@ public interface SendableDiscordMessage {
SendableDiscordMessage build(); SendableDiscordMessage build();
} }
interface Formatter {
Formatter addContext(Object... context);
default Formatter addReplacement(String target, Object replacement) {
return addReplacement(Pattern.compile(target, Pattern.LITERAL), replacement);
}
default Formatter addReplacement(Pattern target, Object replacement) {
return addReplacement(target, matcher -> replacement);
}
default Formatter addReplacement(String target, Supplier<Object> replacement) {
return addReplacement(Pattern.compile(target, Pattern.LITERAL), replacement);
}
default Formatter addReplacement(Pattern target, Supplier<Object> replacement) {
return addReplacement(target, matcher -> replacement.get());
}
default Formatter addReplacement(String target, Function<Matcher, Object> replacement) {
return addReplacement(Pattern.compile(target, Pattern.LITERAL), replacement);
}
Formatter addReplacement(Pattern target, Function<Matcher, Object> replacement);
SendableDiscordMessage build();
}
} }

View File

@ -42,10 +42,10 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
private final String webhookAvatarUrl; private final String webhookAvatarUrl;
protected SendableDiscordMessageImpl(String content, protected SendableDiscordMessageImpl(String content,
List<DiscordMessageEmbed> embeds, List<DiscordMessageEmbed> embeds,
Set<AllowedMention> allowedMentions, Set<AllowedMention> allowedMentions,
String webhookUsername, String webhookUsername,
String webhookAvatarUrl) { String webhookAvatarUrl) {
this.content = content; this.content = content;
this.embeds = embeds; this.embeds = embeds;
this.allowedMentions = allowedMentions; this.allowedMentions = allowedMentions;
@ -78,7 +78,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
return webhookAvatarUrl; return webhookAvatarUrl;
} }
public static class BuilderImpl implements Builder { public static class BuilderImpl implements SendableDiscordMessage.Builder {
private String content; private String content;
private final List<DiscordMessageEmbed> embeds = new ArrayList<>(); private final List<DiscordMessageEmbed> embeds = new ArrayList<>();

View File

@ -24,43 +24,32 @@
package com.discordsrv.api.event.events.message.send.game; package com.discordsrv.api.event.events.message.send.game;
import com.discordsrv.api.channel.GameChannel; import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.api.event.events.Cancellable; import com.discordsrv.api.event.events.Cancellable;
import com.discordsrv.api.event.events.Processable; import com.discordsrv.api.event.events.Processable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class AbstractGameMessageSendEvent implements Cancellable, Processable { public abstract class AbstractGameMessageSendEvent implements Cancellable, Processable {
private String discordMessage; private SendableDiscordMessage discordMessage;
private String discordUsername;
private GameChannel targetChannel; private GameChannel targetChannel;
private boolean cancelled; private boolean cancelled;
private boolean processed; private boolean processed;
public AbstractGameMessageSendEvent(@NotNull String discordMessage, @Nullable String discordUsername, @NotNull GameChannel targetChannel) { public AbstractGameMessageSendEvent(@NotNull SendableDiscordMessage discordMessage, @NotNull GameChannel targetChannel) {
this.discordMessage = discordMessage; this.discordMessage = discordMessage;
this.discordUsername = discordUsername;
this.targetChannel = targetChannel; this.targetChannel = targetChannel;
} }
@NotNull @NotNull
public String getDiscordMessage() { public SendableDiscordMessage getDiscordMessage() {
return discordMessage; return discordMessage;
} }
public void setDiscordMessage(@NotNull String discordMessage) { public void setDiscordMessage(@NotNull SendableDiscordMessage discordMessage) {
this.discordMessage = discordMessage; this.discordMessage = discordMessage;
} }
@Nullable
public String getDiscordUsername() {
return discordUsername;
}
public void setDiscordUsername(@Nullable String discordUsername) {
this.discordUsername = discordUsername;
}
@NotNull @NotNull
public GameChannel getTargetChannel() { public GameChannel getTargetChannel() {
return targetChannel; return targetChannel;

View File

@ -24,12 +24,12 @@
package com.discordsrv.api.event.events.message.send.game; package com.discordsrv.api.event.events.message.send.game;
import com.discordsrv.api.channel.GameChannel; import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ChatMessageSendEvent extends AbstractGameMessageSendEvent { public class ChatMessageSendEvent extends AbstractGameMessageSendEvent {
public ChatMessageSendEvent(@NotNull String discordMessage, @Nullable String discordUsername, @NotNull GameChannel targetChannel) { public ChatMessageSendEvent(@NotNull SendableDiscordMessage discordMessage, @NotNull GameChannel targetChannel) {
super(discordMessage, discordUsername, targetChannel); super(discordMessage, targetChannel);
} }
} }

View File

@ -32,7 +32,7 @@ public class PlaceholderLookupResult {
public static final PlaceholderLookupResult UNKNOWN_PLACEHOLDER = new PlaceholderLookupResult(Type.UNKNOWN_PLACEHOLDER); public static final PlaceholderLookupResult UNKNOWN_PLACEHOLDER = new PlaceholderLookupResult(Type.UNKNOWN_PLACEHOLDER);
public static PlaceholderLookupResult success(Object result) { public static PlaceholderLookupResult success(Object result) {
return new PlaceholderLookupResult(String.valueOf(result)); return new PlaceholderLookupResult(result);
} }
public static PlaceholderLookupResult newLookup(String placeholder, Set<Object> extras) { public static PlaceholderLookupResult newLookup(String placeholder, Set<Object> extras) {
@ -40,7 +40,7 @@ public class PlaceholderLookupResult {
} }
private final Type type; private final Type type;
private final String value; private final Object value;
private final Set<Object> extras; private final Set<Object> extras;
protected PlaceholderLookupResult(Type type) { protected PlaceholderLookupResult(Type type) {
@ -49,7 +49,7 @@ public class PlaceholderLookupResult {
this.extras = null; this.extras = null;
} }
protected PlaceholderLookupResult(String value) { protected PlaceholderLookupResult(Object value) {
this.type = Type.SUCCESS; this.type = Type.SUCCESS;
this.value = value; this.value = value;
this.extras = null; this.extras = null;
@ -65,7 +65,7 @@ public class PlaceholderLookupResult {
return type; return type;
} }
public String getValue() { public Object getValue() {
return value; return value;
} }

View File

@ -0,0 +1,37 @@
/*
* This file is part of the DiscordSRV API, licensed under the MIT License
* Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.api.placeholder;
import org.jetbrains.annotations.NotNull;
@FunctionalInterface
public interface PlaceholderResultConverter {
/**
* Converts a successful placeholder lookup result into a {@link String}.
* @param result the result
* @return the result in {@link String} form
*/
String convertPlaceholderResult(@NotNull Object result);
}

View File

@ -23,7 +23,10 @@
package com.discordsrv.api.placeholder; package com.discordsrv.api.placeholder;
import org.jetbrains.annotations.NotNull;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public interface PlaceholderService { public interface PlaceholderService {
@ -38,9 +41,16 @@ public interface PlaceholderService {
*/ */
Pattern RECURSIVE_PATTERN = Pattern.compile("(\\{)(.+)(})"); Pattern RECURSIVE_PATTERN = Pattern.compile("(\\{)(.+)(})");
PlaceholderLookupResult lookupPlaceholder(String placeholder, Set<Object> context); void addResultConverter(@NotNull PlaceholderResultConverter resultConverter);
PlaceholderLookupResult lookupPlaceholder(String placeholder, Object... context); void removeResultConverter(@NotNull PlaceholderResultConverter resultConverter);
String replacePlaceholders(@NotNull String placeholder, @NotNull Set<Object> context);
String replacePlaceholders(@NotNull String placeholder, @NotNull Object... context);
PlaceholderLookupResult lookupPlaceholder(@NotNull String placeholder, @NotNull Set<Object> context);
PlaceholderLookupResult lookupPlaceholder(@NotNull String placeholder, @NotNull Object... context);
Object getResult(@NotNull Matcher matcher, @NotNull Set<Object> context);
String getResultAsString(@NotNull Matcher matcher, @NotNull Set<Object> context);
String replacePlaceholders(String placeholder, Set<Object> context);
String replacePlaceholders(String placeholder, Object... context);
} }

View File

@ -26,9 +26,11 @@ import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.craftbukkit.BukkitComponentSerializer; import net.kyori.adventure.text.serializer.craftbukkit.BukkitComponentSerializer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@SuppressWarnings("NullableProblems") // BukkitOfflinePlayer nullability
public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer { public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer {
private static final Method DISPLAY_NAME_METHOD; // Paper 1.16+ private static final Method DISPLAY_NAME_METHOD; // Paper 1.16+
@ -72,12 +74,13 @@ public class BukkitPlayer extends BukkitOfflinePlayer implements IPlayer {
@SuppressWarnings("deprecation") // Paper @SuppressWarnings("deprecation") // Paper
@Override @Override
public Component displayName() { public @NotNull Component displayName() {
if (DISPLAY_NAME_METHOD != null) { if (DISPLAY_NAME_METHOD != null) {
try { try {
return ComponentUtil.fromUnrelocated(DISPLAY_NAME_METHOD.invoke(player)); return ComponentUtil.fromUnrelocated(DISPLAY_NAME_METHOD.invoke(player));
} catch (Throwable ignored) {} } catch (Throwable ignored) {}
} }
// Use the legacy method // Use the legacy method
return BukkitComponentSerializer.legacy().deserialize(player.getDisplayName()); return BukkitComponentSerializer.legacy().deserialize(player.getDisplayName());
} }

View File

@ -67,7 +67,7 @@ public class BungeePlayer implements IPlayer {
} }
@Override @Override
public Component displayName() { public @NotNull Component displayName() {
return BungeeComponentUtil.fromLegacy(player.getDisplayName()); return BungeeComponentUtil.fromLegacy(player.getDisplayName());
} }
} }

View File

@ -41,6 +41,7 @@ import com.discordsrv.common.listener.DefaultChatListener;
import com.discordsrv.common.logging.DependencyLoggingFilter; import com.discordsrv.common.logging.DependencyLoggingFilter;
import com.discordsrv.common.logging.logger.backend.LoggingBackend; import com.discordsrv.common.logging.logger.backend.LoggingBackend;
import com.discordsrv.common.placeholder.PlaceholderServiceImpl; import com.discordsrv.common.placeholder.PlaceholderServiceImpl;
import com.discordsrv.common.placeholder.converter.ComponentResultConverter;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -84,7 +85,7 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
protected final void load() { protected final void load() {
this.eventBus = new EventBusImpl(this); this.eventBus = new EventBusImpl(this);
this.placeholderService = new PlaceholderServiceImpl(this); this.placeholderService = new PlaceholderServiceImpl(this);
this.componentFactory = new ComponentFactory(); this.componentFactory = new ComponentFactory(this);
this.discordAPI = new DiscordAPIImpl(this); this.discordAPI = new DiscordAPIImpl(this);
this.discordConnectionDetails = new DiscordConnectionDetailsImpl(this); this.discordConnectionDetails = new DiscordConnectionDetailsImpl(this);
} }
@ -239,6 +240,9 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
discordConnectionManager = new JDAConnectionManager(this); discordConnectionManager = new JDAConnectionManager(this);
discordConnectionManager.connect().join(); discordConnectionManager.connect().join();
// Placeholder result converters
placeholderService().addResultConverter(new ComponentResultConverter());
// Register PlayerProvider listeners // Register PlayerProvider listeners
playerProvider().subscribe(); playerProvider().subscribe();

View File

@ -22,7 +22,6 @@ import com.discordsrv.api.channel.GameChannel;
import com.discordsrv.api.event.events.channel.GameChannelLookupEvent; import com.discordsrv.api.event.events.channel.GameChannelLookupEvent;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.channels.BaseChannelConfig; import com.discordsrv.common.config.main.channels.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.ChannelConfigHolder;
import com.discordsrv.common.function.OrDefault; import com.discordsrv.common.function.OrDefault;
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;
@ -55,7 +54,7 @@ public class ChannelConfig {
}); });
} }
private Map<String, ChannelConfigHolder> channels() { private Map<String, BaseChannelConfig> channels() {
return discordSRV.config().channels; return discordSRV.config().channels;
} }
@ -64,12 +63,12 @@ public class ChannelConfig {
} }
public OrDefault<BaseChannelConfig> orDefault(String ownerName, String channelName) { public OrDefault<BaseChannelConfig> orDefault(String ownerName, String channelName) {
ChannelConfigHolder defaultConfig = channels().computeIfAbsent( BaseChannelConfig defaultConfig = channels().computeIfAbsent(
"default", key -> new ChannelConfigHolder(new BaseChannelConfig())); "default", key -> new BaseChannelConfig());
return new OrDefault<>( return new OrDefault<>(
get(ownerName, channelName), get(ownerName, channelName),
defaultConfig.get() defaultConfig
); );
} }
@ -79,15 +78,15 @@ public class ChannelConfig {
public BaseChannelConfig get(String ownerName, String channelName) { public BaseChannelConfig get(String ownerName, String channelName) {
if (ownerName != null) { if (ownerName != null) {
ChannelConfigHolder config = channels().get(ownerName + ":" + channelName); BaseChannelConfig config = channels().get(ownerName + ":" + channelName);
if (config != null) { if (config != null) {
return config.get(); return config;
} }
GameChannel gameChannel = channels.get(channelName); GameChannel gameChannel = channels.get(channelName);
if (gameChannel != null && gameChannel.getOwnerName().equals(ownerName)) { if (gameChannel != null && gameChannel.getOwnerName().equals(ownerName)) {
config = channels().get(channelName); config = channels().get(channelName);
return config != null ? config.get() : null; return config;
} }
return null; return null;
} }

View File

@ -18,14 +18,27 @@
package com.discordsrv.common.component; package com.discordsrv.common.component;
import com.discordsrv.api.component.EnhancedTextBuilder;
import com.discordsrv.api.component.MinecraftComponent; import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.component.MinecraftComponentFactory; import com.discordsrv.api.component.MinecraftComponentFactory;
import com.discordsrv.common.DiscordSRV;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public class ComponentFactory implements MinecraftComponentFactory { public class ComponentFactory implements MinecraftComponentFactory {
private final DiscordSRV discordSRV;
public ComponentFactory(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
}
@Override @Override
public @NotNull MinecraftComponent empty() { public @NotNull MinecraftComponent empty() {
return MinecraftComponentImpl.empty(); return MinecraftComponentImpl.empty();
} }
@Override
public EnhancedTextBuilder enhancedBuilder(String content) {
return new EnhancedTextBuilderImpl(discordSRV, content);
}
} }

View File

@ -0,0 +1,72 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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 <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.component;
import com.discordsrv.api.component.EnhancedTextBuilder;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.placeholder.PlaceholderService;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil;
import dev.vankka.enhancedlegacytext.EnhancedComponentBuilder;
import dev.vankka.enhancedlegacytext.EnhancedLegacyText;
import net.kyori.adventure.text.Component;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EnhancedTextBuilderImpl implements EnhancedTextBuilder {
private final Set<Object> context = new HashSet<>();
private final Map<Pattern, Function<Matcher, Object>> replacements = new HashMap<>();
private final DiscordSRV discordSRV;
private final String enhancedFormat;
public EnhancedTextBuilderImpl(DiscordSRV discordSRV, String enhancedFormat) {
this.discordSRV = discordSRV;
this.enhancedFormat = enhancedFormat;
}
@Override
public EnhancedTextBuilder addContext(Object... context) {
this.context.addAll(Arrays.asList(context));
return this;
}
@Override
public EnhancedTextBuilder addReplacement(Pattern target, Function<Matcher, Object> replacement) {
this.replacements.put(target, replacement);
return this;
}
@Override
public MinecraftComponent build() {
EnhancedComponentBuilder builder = EnhancedLegacyText.get()
.buildComponent(enhancedFormat);
replacements.forEach(builder::replaceAll);
builder.replaceAll(PlaceholderService.PATTERN,
matcher -> discordSRV.placeholderService().getResult(matcher, context));
Component component = builder.build();
return ComponentUtil.toAPI(component);
}
}

View File

@ -26,6 +26,8 @@ import org.spongepowered.configurate.objectmapping.meta.Comment;
public class ConnectionConfig implements Config { public class ConnectionConfig implements Config {
public static final String FILE_NAME = "connections.yaml"; public static final String FILE_NAME = "connections.yaml";
@Override
public final String getFileName() { public final String getFileName() {
return FILE_NAME; return FILE_NAME;
} }
@ -37,5 +39,6 @@ public class ConnectionConfig implements Config {
@Comment("Don't know what this is? Neither do I") @Comment("Don't know what this is? Neither do I")
public String token = "Token here"; public String token = "Token here";
} }
} }

View File

@ -22,7 +22,6 @@ import com.discordsrv.common.config.Config;
import com.discordsrv.common.config.annotation.DefaultOnly; import com.discordsrv.common.config.annotation.DefaultOnly;
import com.discordsrv.common.config.main.channels.BaseChannelConfig; import com.discordsrv.common.config.main.channels.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.ChannelConfig; import com.discordsrv.common.config.main.channels.ChannelConfig;
import com.discordsrv.common.config.main.channels.ChannelConfigHolder;
import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import java.util.HashMap; import java.util.HashMap;
@ -32,13 +31,15 @@ import java.util.Map;
public class MainConfig implements Config { public class MainConfig implements Config {
public static final String FILE_NAME = "config.yaml"; public static final String FILE_NAME = "config.yaml";
@Override
public final String getFileName() { public final String getFileName() {
return FILE_NAME; return FILE_NAME;
} }
@DefaultOnly("default") @DefaultOnly("default")
public Map<String, ChannelConfigHolder> channels = new HashMap<String, ChannelConfigHolder>() {{ public Map<String, BaseChannelConfig> channels = new HashMap<String, BaseChannelConfig>() {{
put("default", new ChannelConfigHolder(new BaseChannelConfig())); put("default", new BaseChannelConfig());
put("global", new ChannelConfigHolder(new ChannelConfig())); put("global", new ChannelConfig());
}}; }};
} }

View File

@ -19,10 +19,50 @@
package com.discordsrv.common.config.main.channels; package com.discordsrv.common.config.main.channels;
import com.discordsrv.common.config.main.channels.minecraftodiscord.MinecraftToDiscordChatConfig; import com.discordsrv.common.config.main.channels.minecraftodiscord.MinecraftToDiscordChatConfig;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
import java.lang.reflect.Type;
@ConfigSerializable @ConfigSerializable
public class BaseChannelConfig { public class BaseChannelConfig {
public MinecraftToDiscordChatConfig minecraftToDiscord = new MinecraftToDiscordChatConfig(); public MinecraftToDiscordChatConfig minecraftToDiscord = new MinecraftToDiscordChatConfig();
public static class Serializer implements TypeSerializer<BaseChannelConfig> {
private final ObjectMapper.Factory mapperFactory;
public Serializer(ObjectMapper.Factory mapperFactory) {
this.mapperFactory = mapperFactory;
}
@Override
public BaseChannelConfig deserialize(Type type, ConfigurationNode node) throws SerializationException {
return (BaseChannelConfig) mapperFactory.asTypeSerializer()
.deserialize(
"default".equals(node.key()) ? BaseChannelConfig.class : ChannelConfig.class,
node
);
}
@Override
public void serialize(Type type, @Nullable BaseChannelConfig obj, ConfigurationNode node) throws SerializationException {
if (obj == null) {
node.set(null);
return;
}
mapperFactory.asTypeSerializer().serialize(
"default".equals(node.key()) ? BaseChannelConfig.class : ChannelConfig.class,
obj,
node
);
}
}
} }

View File

@ -20,7 +20,6 @@ package com.discordsrv.common.config.main.channels;
import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment; import org.spongepowered.configurate.objectmapping.meta.Comment;
import org.spongepowered.configurate.objectmapping.meta.Setting;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
@ -31,8 +30,6 @@ import java.util.List;
@ConfigSerializable @ConfigSerializable
public class ChannelConfig extends BaseChannelConfig { public class ChannelConfig extends BaseChannelConfig {
protected static final String CHANNEL_IDS_OPTION_NAME = "ChannelIds";
public ChannelConfig() { public ChannelConfig() {
// Clear everything besides channelIds by default (these will be filled back in by Configurate if they are in the config itself) // Clear everything besides channelIds by default (these will be filled back in by Configurate if they are in the config itself)
for (Field field : getClass().getFields()) { for (Field field : getClass().getFields()) {
@ -50,7 +47,6 @@ public class ChannelConfig extends BaseChannelConfig {
} }
} }
@Setting(CHANNEL_IDS_OPTION_NAME)
@Comment("The channels this in-game channel will forward to in Discord") @Comment("The channels this in-game channel will forward to in Discord")
public List<String> channelIds = new ArrayList<>(Collections.singletonList("channel-id-here")); public List<String> channelIds = new ArrayList<>(Collections.singletonList("channel-id-here"));

View File

@ -1,74 +0,0 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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 <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.config.main.channels;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
import java.lang.reflect.Type;
/**
* A bit of a trick to have two different types in the same map with Configurate.
*/
public class ChannelConfigHolder {
private final BaseChannelConfig obj;
public ChannelConfigHolder(BaseChannelConfig obj) {
this.obj = obj;
}
public BaseChannelConfig get() {
return obj;
}
public static class Serializer implements TypeSerializer<ChannelConfigHolder> {
private final ObjectMapper.Factory mapperFactory;
public Serializer(ObjectMapper.Factory mapperFactory) {
this.mapperFactory = mapperFactory;
}
@Override
public ChannelConfigHolder deserialize(Type type, ConfigurationNode node) throws SerializationException {
boolean channelsNotPresent = node.node(ChannelConfig.CHANNEL_IDS_OPTION_NAME).empty();
BaseChannelConfig channelConfig = (BaseChannelConfig) mapperFactory
.get(channelsNotPresent ? BaseChannelConfig.class : ChannelConfig.class)
.load(node);
return new ChannelConfigHolder(channelConfig);
}
@Override
public void serialize(Type type, @Nullable ChannelConfigHolder obj, ConfigurationNode node) throws SerializationException {
map(obj != null ? obj.get() : null, node);
}
@SuppressWarnings("unchecked")
private <T extends BaseChannelConfig> void map(BaseChannelConfig obj, ConfigurationNode node) throws SerializationException {
mapperFactory
.get((Class<T>) (obj instanceof ChannelConfig ? ChannelConfig.class : BaseChannelConfig.class))
.save((T) obj, node);
}
}
}

View File

@ -28,8 +28,6 @@ public class MinecraftToDiscordChatConfig {
@Setting("Format") @Setting("Format")
public SendableDiscordMessage.Builder messageFormat = SendableDiscordMessage.builder() public SendableDiscordMessage.Builder messageFormat = SendableDiscordMessage.builder()
.setWebhookUsername("%player_display_name%") .setWebhookUsername("%player_display_name%")
.setContent("%player_message%");// TODO .setContent("%message%");// TODO
@Setting("UseWebhooks")
public boolean useWebhooks = false;
} }

View File

@ -24,7 +24,9 @@ import com.discordsrv.common.config.manager.loader.YamlConfigLoaderProvider;
import com.discordsrv.common.config.manager.manager.TranslatedConfigManager; import com.discordsrv.common.config.manager.manager.TranslatedConfigManager;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader; import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
public abstract class ConnectionConfigManager<C extends ConnectionConfig> extends TranslatedConfigManager<C, YamlConfigurationLoader> implements YamlConfigLoaderProvider { public abstract class ConnectionConfigManager<C extends ConnectionConfig>
extends TranslatedConfigManager<C, YamlConfigurationLoader>
implements YamlConfigLoaderProvider {
public ConnectionConfigManager(DiscordSRV discordSRV) { public ConnectionConfigManager(DiscordSRV discordSRV) {
super(discordSRV); super(discordSRV);

View File

@ -18,22 +18,12 @@
package com.discordsrv.common.config.manager; package com.discordsrv.common.config.manager;
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.MainConfig; import com.discordsrv.common.config.main.MainConfig;
import com.discordsrv.common.config.main.channels.ChannelConfigHolder;
import com.discordsrv.common.config.manager.loader.YamlConfigLoaderProvider; import com.discordsrv.common.config.manager.loader.YamlConfigLoaderProvider;
import com.discordsrv.common.config.manager.manager.TranslatedConfigManager; import com.discordsrv.common.config.manager.manager.TranslatedConfigManager;
import com.discordsrv.common.config.serializer.ColorSerializer;
import com.discordsrv.common.config.serializer.DiscordMessageEmbedSerializer;
import com.discordsrv.common.config.serializer.SendableDiscordMessageSerializer;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader; import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import java.awt.Color;
public abstract class MainConfigManager<C extends MainConfig> public abstract class MainConfigManager<C extends MainConfig>
extends TranslatedConfigManager<C, YamlConfigurationLoader> extends TranslatedConfigManager<C, YamlConfigurationLoader>
implements YamlConfigLoaderProvider { implements YamlConfigLoaderProvider {
@ -46,17 +36,4 @@ public abstract class MainConfigManager<C extends MainConfig>
protected String fileName() { protected String fileName() {
return MainConfig.FILE_NAME; return MainConfig.FILE_NAME;
} }
@Override
public ConfigurationOptions defaultOptions() {
return super.defaultOptions()
.serializers(builder -> {
ObjectMapper.Factory objectMapper = defaultObjectMapper();
builder.register(Color.class, new ColorSerializer());
builder.register(ChannelConfigHolder.class, new ChannelConfigHolder.Serializer(objectMapper));
builder.register(DiscordMessageEmbed.Builder.class, new DiscordMessageEmbedSerializer());
builder.register(DiscordMessageEmbed.Field.class, new DiscordMessageEmbedSerializer.FieldSerializer());
builder.register(SendableDiscordMessage.Builder.class, new SendableDiscordMessageSerializer());
});
}
} }

View File

@ -18,9 +18,15 @@
package com.discordsrv.common.config.manager.manager; package com.discordsrv.common.config.manager.manager;
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.annotation.DefaultOnly; import com.discordsrv.common.config.annotation.DefaultOnly;
import com.discordsrv.common.config.main.channels.BaseChannelConfig;
import com.discordsrv.common.config.manager.loader.ConfigLoaderProvider; import com.discordsrv.common.config.manager.loader.ConfigLoaderProvider;
import com.discordsrv.common.config.serializer.ColorSerializer;
import com.discordsrv.common.config.serializer.DiscordMessageEmbedSerializer;
import com.discordsrv.common.config.serializer.SendableDiscordMessageSerializer;
import com.discordsrv.common.exception.ConfigException; import com.discordsrv.common.exception.ConfigException;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.spongepowered.configurate.CommentedConfigurationNode; import org.spongepowered.configurate.CommentedConfigurationNode;
@ -31,7 +37,9 @@ import org.spongepowered.configurate.loader.AbstractConfigurationLoader;
import org.spongepowered.configurate.objectmapping.ConfigSerializable; import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.ObjectMapper; import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.util.NamingSchemes;
import java.awt.Color;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -73,31 +81,40 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
public ConfigurationOptions defaultOptions() { public ConfigurationOptions defaultOptions() {
return ConfigurationOptions.defaults() return ConfigurationOptions.defaults()
.shouldCopyDefaults(false); .shouldCopyDefaults(false)
} .implicitInitialization(false)
.serializers(builder -> {
protected ObjectMapper.Factory.Builder objectMapperBuilder() { ObjectMapper.Factory objectMapper = configObjectMapper();
return ObjectMapper.factoryBuilder(); builder.register(Color.class, new ColorSerializer());
builder.register(BaseChannelConfig.class, new BaseChannelConfig.Serializer(objectMapper));
builder.register(DiscordMessageEmbed.Builder.class, new DiscordMessageEmbedSerializer());
builder.register(DiscordMessageEmbed.Field.class, new DiscordMessageEmbedSerializer.FieldSerializer());
builder.register(SendableDiscordMessage.Builder.class, new SendableDiscordMessageSerializer());
});
} }
public ConfigurationOptions configNodeOptions() { public ConfigurationOptions configNodeOptions() {
return defaultOptions(); return defaultOptions();
} }
protected ObjectMapper.Factory.Builder configObjectMapperBuilder() {
return objectMapperBuilder();
}
public ObjectMapper.Factory configObjectMapper() {
return configObjectMapper;
}
public ConfigurationOptions defaultNodeOptions() { public ConfigurationOptions defaultNodeOptions() {
return defaultOptions(); return defaultOptions();
} }
protected ObjectMapper.Factory.Builder objectMapperBuilder() {
return ObjectMapper.factoryBuilder()
.defaultNamingScheme(input -> {
String camelCase = NamingSchemes.CAMEL_CASE.coerce(input); // Gets rid of underscores and dashes
return Character.toUpperCase(camelCase.charAt(0)) + camelCase.substring(1);
});
}
protected ObjectMapper.Factory.Builder configObjectMapperBuilder() {
return objectMapperBuilder();
}
protected ObjectMapper.Factory.Builder defaultObjectMapperBuilder() { protected ObjectMapper.Factory.Builder defaultObjectMapperBuilder() {
return configObjectMapperBuilder() return objectMapperBuilder()
.addProcessor(DefaultOnly.class, (data, value) -> (value1, destination) -> { .addProcessor(DefaultOnly.class, (data, value) -> (value1, destination) -> {
String[] children = data.value(); String[] children = data.value();
boolean whitelist = data.whitelist(); boolean whitelist = data.whitelist();
@ -131,6 +148,10 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
}); });
} }
public ObjectMapper.Factory configObjectMapper() {
return configObjectMapper;
}
public ObjectMapper.Factory defaultObjectMapper() { public ObjectMapper.Factory defaultObjectMapper() {
return defaultObjectMapper; return defaultObjectMapper;
} }
@ -190,14 +211,19 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
} }
} }
@SuppressWarnings("unchecked")
@Override @Override
public void save() throws ConfigException { public void save() throws ConfigException {
try { try {
CommentedConfigurationNode node = loader.createNode(); CommentedConfigurationNode node = loader.createNode();
node.set(configuration); save(configuration, (Class<T>) configuration.getClass(), node);
loader.save(node); loader.save(node);
} catch (ConfigurateException e) { } catch (ConfigurateException e) {
throw new ConfigException("Failed to load configuration", e); throw new ConfigException("Failed to load configuration", e);
} }
} }
protected void save(T config, Class<T> clazz, CommentedConfigurationNode node) throws SerializationException {
configObjectMapper().get(clazz).save(config, node);
}
} }

View File

@ -31,7 +31,8 @@ import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import java.net.URL; import java.net.URL;
public abstract class TranslatedConfigManager<T extends Config, LT extends AbstractConfigurationLoader<CommentedConfigurationNode>> extends ConfigurateConfigManager<T, LT> { public abstract class TranslatedConfigManager<T extends Config, LT extends AbstractConfigurationLoader<CommentedConfigurationNode>>
extends ConfigurateConfigManager<T, LT> {
public TranslatedConfigManager(DiscordSRV discordSRV) { public TranslatedConfigManager(DiscordSRV discordSRV) {
super(discordSRV); super(discordSRV);
@ -55,6 +56,7 @@ public abstract class TranslatedConfigManager<T extends Config, LT extends Abstr
return translation; return translation;
} }
@SuppressWarnings("unchecked")
public void translate() throws ConfigException { public void translate() throws ConfigException {
T config = config(); T config = config();
if (config == null) { if (config == null) {
@ -70,7 +72,8 @@ public abstract class TranslatedConfigManager<T extends Config, LT extends Abstr
translation = translation.node(config.getFileName()); translation = translation.node(config.getFileName());
CommentedConfigurationNode node = loader().createNode(); CommentedConfigurationNode node = loader().createNode();
node.set(config); save(config, (Class<T>) config.getClass(), node);
//node.set(config);
translateNode(node, translation, translation.node("_comments")); translateNode(node, translation, translation.node("_comments"));
} catch (ConfigurateException e) { } catch (ConfigurateException e) {
throw new ConfigException(e); throw new ConfigException(e);

View File

@ -45,7 +45,9 @@ public class SendableDiscordMessageSerializer implements TypeSerializer<Sendable
ConfigurationNode webhook = node.node("Webhook"); ConfigurationNode webhook = node.node("Webhook");
String webhookUsername = webhook.node("Username").getString(); String webhookUsername = webhook.node("Username").getString();
if (webhook.node("Enabled").getBoolean(webhook.node("Enable").getBoolean(webhookUsername != null))) { if (webhook.node("Enabled").getBoolean(
webhook.node("Enable").getBoolean(
webhookUsername != null))) {
builder.setWebhookUsername(webhookUsername); builder.setWebhookUsername(webhookUsername);
builder.setWebhookAvatarUrl(webhook.node("AvatarUrl").getString()); builder.setWebhookAvatarUrl(webhook.node("AvatarUrl").getString());
} }
@ -82,7 +84,9 @@ public class SendableDiscordMessageSerializer implements TypeSerializer<Sendable
List<DiscordMessageEmbed.Builder> embedBuilders = new ArrayList<>(); List<DiscordMessageEmbed.Builder> embedBuilders = new ArrayList<>();
obj.getEmbeds().forEach(embed -> embedBuilders.add(embed.toBuilder())); obj.getEmbeds().forEach(embed -> embedBuilders.add(embed.toBuilder()));
node.setList(DiscordMessageEmbed.Builder.class, embedBuilders); if (!embedBuilders.isEmpty()) {
node.node("Embeds").setList(DiscordMessageEmbed.Builder.class, embedBuilders);
}
node.node("Content").set(obj.getContent()); node.node("Content").set(obj.getContent());
} }

View File

@ -25,18 +25,22 @@ import com.discordsrv.api.discord.api.entity.channel.DiscordDMChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel; import com.discordsrv.api.discord.api.entity.channel.DiscordMessageChannel;
import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel; import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel;
import com.discordsrv.api.discord.api.entity.guild.DiscordGuild; import com.discordsrv.api.discord.api.entity.guild.DiscordGuild;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.api.discord.api.entity.user.DiscordUser; import com.discordsrv.api.discord.api.entity.user.DiscordUser;
import com.discordsrv.api.discord.api.exception.NotReadyException; import com.discordsrv.api.discord.api.exception.NotReadyException;
import com.discordsrv.api.discord.api.exception.UnknownChannelException; import com.discordsrv.api.discord.api.exception.UnknownChannelException;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.config.main.channels.BaseChannelConfig; import com.discordsrv.common.config.main.channels.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.ChannelConfig; import com.discordsrv.common.config.main.channels.ChannelConfig;
import com.discordsrv.common.config.main.channels.ChannelConfigHolder;
import com.discordsrv.common.discord.api.channel.DiscordDMChannelImpl; import com.discordsrv.common.discord.api.channel.DiscordDMChannelImpl;
import com.discordsrv.common.discord.api.channel.DiscordTextChannelImpl; import com.discordsrv.common.discord.api.channel.DiscordTextChannelImpl;
import com.discordsrv.common.discord.api.guild.DiscordGuildImpl; import com.discordsrv.common.discord.api.guild.DiscordGuildImpl;
import com.discordsrv.common.discord.api.message.SendableDiscordMessageFormatterImpl;
import com.discordsrv.common.discord.api.user.DiscordUserImpl; import com.discordsrv.common.discord.api.user.DiscordUserImpl;
import com.github.benmanes.caffeine.cache.*; import com.github.benmanes.caffeine.cache.AsyncCacheLoader;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Expiry;
import com.github.benmanes.caffeine.cache.RemovalListener;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
@ -72,6 +76,11 @@ public class DiscordAPIImpl implements DiscordAPI {
return cachedClients.get(channelId); return cachedClients.get(channelId);
} }
@Override
public SendableDiscordMessage.Formatter format(SendableDiscordMessage.Builder message) {
return new SendableDiscordMessageFormatterImpl(discordSRV, message);
}
@Override @Override
public @NotNull Optional<? extends DiscordMessageChannel> getMessageChannelById(@NotNull String id) { public @NotNull Optional<? extends DiscordMessageChannel> getMessageChannelById(@NotNull String id) {
Optional<DiscordTextChannel> textChannel = getTextChannelById(id); Optional<DiscordTextChannel> textChannel = getTextChannelById(id);
@ -163,8 +172,7 @@ public class DiscordAPIImpl implements DiscordAPI {
private class WebhookCacheExpiry implements Expiry<String, WebhookClient> { private class WebhookCacheExpiry implements Expiry<String, WebhookClient> {
private boolean isConfiguredChannel(String channelId) { private boolean isConfiguredChannel(String channelId) {
for (ChannelConfigHolder value : discordSRV.config().channels.values()) { for (BaseChannelConfig config : discordSRV.config().channels.values()) {
BaseChannelConfig config = value.get();
if (config instanceof ChannelConfig if (config instanceof ChannelConfig
&& ((ChannelConfig) config).channelIds.contains(channelId)) { && ((ChannelConfig) config).channelIds.contains(channelId)) {
return true; return true;

View File

@ -23,11 +23,11 @@ import club.minnced.discord.webhook.receive.ReadonlyMessage;
import club.minnced.discord.webhook.receive.ReadonlyUser; import club.minnced.discord.webhook.receive.ReadonlyUser;
import club.minnced.discord.webhook.send.WebhookEmbed; import club.minnced.discord.webhook.send.WebhookEmbed;
import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel; import com.discordsrv.api.discord.api.entity.channel.DiscordTextChannel;
import com.discordsrv.api.discord.api.exception.UnknownChannelException;
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed; import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage; import com.discordsrv.api.discord.api.entity.message.ReceivedDiscordMessage;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage; import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.api.discord.api.entity.message.impl.SendableDiscordMessageImpl; import com.discordsrv.api.discord.api.entity.message.impl.SendableDiscordMessageImpl;
import com.discordsrv.api.discord.api.exception.UnknownChannelException;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.MessageEmbed;

View File

@ -0,0 +1,79 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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 <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.discord.api.message;
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
import com.discordsrv.api.placeholder.PlaceholderService;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.placeholder.converter.ComponentResultConverter;
import com.discordsrv.common.string.util.Placeholders;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SendableDiscordMessageFormatterImpl implements SendableDiscordMessage.Formatter {
private final Set<Object> context = new HashSet<>();
private final Map<Pattern, Function<Matcher, Object>> replacements = new HashMap<>();
private final DiscordSRV discordSRV;
private final SendableDiscordMessage.Builder builder;
public SendableDiscordMessageFormatterImpl(DiscordSRV discordSRV, SendableDiscordMessage.Builder builder) {
this.discordSRV = discordSRV;
this.builder = builder;
}
@Override
public SendableDiscordMessage.Formatter addContext(Object... context) {
this.context.addAll(Arrays.asList(context));
return this;
}
@Override
public SendableDiscordMessage.Formatter addReplacement(Pattern target, Function<Matcher, Object> replacement) {
this.replacements.put(target, replacement);
return this;
}
@Override
public SendableDiscordMessage build() {
Function<String, String> placeholders = input -> {
if (input == null) {
return null;
}
return new Placeholders(input)
.addAll(replacements)
.replaceAll(PlaceholderService.PATTERN,
matcher -> discordSRV.placeholderService().getResultAsString(matcher, context))
.get();
};
ComponentResultConverter.plain(() ->
builder.setWebhookUsername(placeholders.apply(builder.getWebhookUsername())));
builder.setContent(placeholders.apply(builder.getContent()));
// TODO: rest of the content, escaping unwanted characters
return builder.build();
}
}

View File

@ -28,8 +28,9 @@ import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil; import com.discordsrv.common.component.util.ComponentUtil;
import com.discordsrv.common.config.main.channels.BaseChannelConfig; import com.discordsrv.common.config.main.channels.BaseChannelConfig;
import com.discordsrv.common.config.main.channels.ChannelConfig; import com.discordsrv.common.config.main.channels.ChannelConfig;
import com.discordsrv.common.config.main.channels.minecraftodiscord.MinecraftToDiscordChatConfig;
import com.discordsrv.common.function.OrDefault; import com.discordsrv.common.function.OrDefault;
import com.discordsrv.common.player.util.PlayerUtil; import dev.vankka.mcdiscordreserializer.discord.DiscordSerializer;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import java.util.Collections; import java.util.Collections;
@ -49,23 +50,23 @@ public class DefaultChatListener extends AbstractListener {
GameChannel gameChannel = event.getGameChannel(); GameChannel gameChannel = event.getGameChannel();
Component message = ComponentUtil.fromAPI(event.message()); Component message = ComponentUtil.fromAPI(event.message());
Component displayName = PlayerUtil.displayName(event.getPlayer());
OrDefault<BaseChannelConfig> channelConfig = discordSRV.channelConfig().orDefault(gameChannel); OrDefault<BaseChannelConfig> channelConfig = discordSRV.channelConfig().orDefault(gameChannel);
OrDefault<MinecraftToDiscordChatConfig> chatConfig = channelConfig.map(cfg -> cfg.minecraftToDiscord);
// Component discordMessage = EnhancedLegacyText.get().buildComponent(channelConfig.map(cfg -> cfg.minecraftToDiscord).get(cfg -> cfg.messageFormat)) SendableDiscordMessage.Builder builder = chatConfig.get(cfg -> cfg.messageFormat);
// .replace("%message%", message) if (builder == null) {
// .replace("%player_display_name%", displayName) return;
// .build(); }
//
// String username = new Placeholders(channelConfig.map(cfg -> cfg.minecraftToDiscord).get(cfg -> cfg.usernameFormat)) SendableDiscordMessage discordMessage = discordSRV.discordAPI().format(builder)
// .replace("%player_display_name%", () -> PlainTextComponentSerializer.plainText().serialize(displayName)) .addContext(event.getPlayer())
// .get(); .addReplacement("%message%", DiscordSerializer.INSTANCE.serialize(message))
.build();
discordSRV.eventBus().publish( discordSRV.eventBus().publish(
new ChatMessageSendEvent( new ChatMessageSendEvent(
null, discordMessage,
null,
gameChannel gameChannel
) )
); );
@ -86,13 +87,7 @@ public class DefaultChatListener extends AbstractListener {
for (String channelId : channelIds) { for (String channelId : channelIds) {
discordSRV.discordAPI().getTextChannelById(channelId).ifPresent(textChannel -> discordSRV.discordAPI().getTextChannelById(channelId).ifPresent(textChannel ->
textChannel.sendMessage( textChannel.sendMessage(event.getDiscordMessage()));
SendableDiscordMessage.builder()
.setWebhookUsername(event.getDiscordUsername())
.setContent(event.getDiscordMessage())
.build()
)
);
} }
} }
} }

View File

@ -21,6 +21,7 @@ package com.discordsrv.common.placeholder;
import com.discordsrv.api.event.events.placeholder.PlaceholderLookupEvent; import com.discordsrv.api.event.events.placeholder.PlaceholderLookupEvent;
import com.discordsrv.api.placeholder.Placeholder; import com.discordsrv.api.placeholder.Placeholder;
import com.discordsrv.api.placeholder.PlaceholderLookupResult; import com.discordsrv.api.placeholder.PlaceholderLookupResult;
import com.discordsrv.api.placeholder.PlaceholderResultConverter;
import com.discordsrv.api.placeholder.PlaceholderService; import com.discordsrv.api.placeholder.PlaceholderService;
import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.placeholder.provider.AnnotationPlaceholderProvider; import com.discordsrv.common.placeholder.provider.AnnotationPlaceholderProvider;
@ -29,11 +30,13 @@ import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.LoadingCache; import com.github.benmanes.caffeine.cache.LoadingCache;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.*; import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -42,6 +45,7 @@ public class PlaceholderServiceImpl implements PlaceholderService {
private final DiscordSRV discordSRV; private final DiscordSRV discordSRV;
private final LoadingCache<Class<?>, Set<PlaceholderProvider>> classProviders; private final LoadingCache<Class<?>, Set<PlaceholderProvider>> classProviders;
private final Set<PlaceholderResultConverter> converters = new CopyOnWriteArraySet<>();
public PlaceholderServiceImpl(DiscordSRV discordSRV) { public PlaceholderServiceImpl(DiscordSRV discordSRV) {
this.discordSRV = discordSRV; this.discordSRV = discordSRV;
@ -58,13 +62,20 @@ public class PlaceholderServiceImpl implements PlaceholderService {
} }
@Override @Override
public PlaceholderLookupResult lookupPlaceholder(String placeholder, Object... context) { public PlaceholderLookupResult lookupPlaceholder(@NotNull String placeholder, Object... context) {
return lookupPlaceholder(placeholder, getArrayAsSet(context)); return lookupPlaceholder(placeholder, getArrayAsSet(context));
} }
@Override @Override
public PlaceholderLookupResult lookupPlaceholder(String placeholder, Set<Object> context) { public PlaceholderLookupResult lookupPlaceholder(@NotNull String placeholder, @NotNull Set<Object> context) {
for (Object o : context) { for (Object o : context) {
if (o instanceof PlaceholderProvider) {
PlaceholderLookupResult result = ((PlaceholderProvider) o).lookup(placeholder, context);
if (result.getType() != PlaceholderLookupResult.Type.UNKNOWN_PLACEHOLDER) {
return result;
}
}
Set<PlaceholderProvider> providers = classProviders.get(o.getClass()); Set<PlaceholderProvider> providers = classProviders.get(o.getClass());
if (providers == null) { if (providers == null) {
continue; continue;
@ -88,35 +99,108 @@ public class PlaceholderServiceImpl implements PlaceholderService {
} }
@Override @Override
public String replacePlaceholders(String input, Object... context) { public String replacePlaceholders(@NotNull String input, Object... context) {
return replacePlaceholders(input, getArrayAsSet(context)); return replacePlaceholders(input, getArrayAsSet(context));
} }
@Override @Override
public String replacePlaceholders(String input, Set<Object> context) { public void addResultConverter(@NotNull PlaceholderResultConverter resultConverter) {
return processReplacement(PATTERN, input, context); converters.add(resultConverter);
} }
private String processReplacement(Pattern pattern, String input, Set<Object> context) { @Override
public void removeResultConverter(@NotNull PlaceholderResultConverter resultConverter) {
converters.remove(resultConverter);
}
@Override
public String replacePlaceholders(@NotNull String input, @NotNull Set<Object> context) {
return getReplacement(PATTERN, input, context);
}
private String getReplacement(Pattern pattern, String input, Set<Object> context) {
Matcher matcher = pattern.matcher(input); Matcher matcher = pattern.matcher(input);
String output = input; String output = input;
while (matcher.find()) { while (matcher.find()) {
String placeholder = matcher.group(2); String placeholder = matcher.group(2);
String originalPlaceholder = placeholder; PlaceholderLookupResult result = resolve(placeholder, context);
output = updateContent(result, placeholder, matcher, output);
// Recursive
placeholder = processReplacement(RECURSIVE_PATTERN, placeholder, context);
PlaceholderLookupResult result = lookupPlaceholder(placeholder, context);
output = updateBasedOnResult(result, input, originalPlaceholder, matcher);
} }
return output; return output;
} }
private String updateBasedOnResult( @Override
PlaceholderLookupResult result, String input, String originalPlaceholder, Matcher matcher) { public Object getResult(@NotNull Matcher matcher, @NotNull Set<Object> context) {
String output = input; if (matcher.groupCount() < 3) {
throw new IllegalStateException("Matcher must have atleast 3 groups");
}
String placeholder = matcher.group(2);
PlaceholderLookupResult result = resolve(matcher, context);
return getResultRepresentation(result, placeholder, matcher);
}
@Override
public String getResultAsString(@NotNull Matcher matcher, @NotNull Set<Object> context) {
Object result = getResult(matcher, context);
return getResultAsString(result);
}
private String getResultAsString(Object result) {
if (result == null) {
return "null";
} else if (result instanceof String) {
return (String) result;
}
String output = null;
for (PlaceholderResultConverter converter : converters) {
output = converter.convertPlaceholderResult(result);
if (output != null) {
break;
}
}
if (output == null) {
output = String.valueOf(result);
}
return output;
}
public PlaceholderLookupResult resolve(Matcher matcher, Set<Object> context) {
return resolve(matcher.group(2), context);
}
private PlaceholderLookupResult resolve(String placeholder, Set<Object> context) {
// Recursive
placeholder = getReplacement(RECURSIVE_PATTERN, placeholder, context);
return lookupPlaceholder(placeholder, context);
}
private String updateContent(
PlaceholderLookupResult result, String placeholder, Matcher matcher, String input) {
Object representation = getResultRepresentation(result, placeholder, matcher);
String output = getResultAsString(representation);
for (PlaceholderResultConverter converter : converters) {
output = converter.convertPlaceholderResult(representation);
if (output != null) {
break;
}
}
if (output == null) {
output = String.valueOf(representation);
}
return Pattern.compile(
matcher.group(1) + placeholder + matcher.group(3),
Pattern.LITERAL
)
.matcher(input)
.replaceFirst(output);
}
private Object getResultRepresentation(PlaceholderLookupResult result, String placeholder, Matcher matcher) {
while (result != null) { while (result != null) {
PlaceholderLookupResult.Type type = result.getType(); PlaceholderLookupResult.Type type = result.getType();
if (type == PlaceholderLookupResult.Type.UNKNOWN_PLACEHOLDER) { if (type == PlaceholderLookupResult.Type.UNKNOWN_PLACEHOLDER) {
@ -124,7 +208,7 @@ public class PlaceholderServiceImpl implements PlaceholderService {
} }
boolean newLookup = false; boolean newLookup = false;
String replacement = null; Object replacement = null;
switch (type) { switch (type) {
case SUCCESS: case SUCCESS:
replacement = result.getValue(); replacement = result.getValue();
@ -136,27 +220,18 @@ public class PlaceholderServiceImpl implements PlaceholderService {
replacement = "Error"; replacement = "Error";
break; break;
case NEW_LOOKUP: case NEW_LOOKUP:
// prevent infinite recursion result = lookupPlaceholder((String) result.getValue(), result.getExtras());
if (result.getValue().equals(originalPlaceholder)) {
break;
}
result = lookupPlaceholder(result.getValue(), result.getExtras());
newLookup = true; newLookup = true;
break; break;
} }
if (replacement != null) { if (replacement != null) {
output = Pattern.compile( return replacement;
matcher.group(1)
+ originalPlaceholder
+ matcher.group(3),
Pattern.LITERAL
).matcher(output).replaceFirst(replacement);
} }
if (!newLookup) { if (!newLookup) {
break; break;
} }
} }
return output; return matcher.group(1) + placeholder + matcher.group(3);
} }
private static class ClassProviderLoader implements CacheLoader<Class<?>, Set<PlaceholderProvider>> { private static class ClassProviderLoader implements CacheLoader<Class<?>, Set<PlaceholderProvider>> {

View File

@ -0,0 +1,56 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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 <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.placeholder.converter;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.placeholder.PlaceholderResultConverter;
import com.discordsrv.common.component.util.ComponentUtil;
import dev.vankka.mcdiscordreserializer.discord.DiscordSerializer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.jetbrains.annotations.NotNull;
public class ComponentResultConverter implements PlaceholderResultConverter {
private static final ThreadLocal<Boolean> PLAIN_CONTEXT = new ThreadLocal<>();
public static void plain(Runnable runnable) {
PLAIN_CONTEXT.set(true);
runnable.run();
PLAIN_CONTEXT.set(false);
}
@Override
public String convertPlaceholderResult(@NotNull Object result) {
if (result instanceof MinecraftComponent) {
result = ComponentUtil.fromAPI((MinecraftComponent) result);
}
if (result instanceof Component) {
Component component = (Component) result;
if (PLAIN_CONTEXT.get()) {
return PlainTextComponentSerializer.plainText()
.serialize(component);
} else {
return DiscordSerializer.INSTANCE
.serialize(component);
}
}
return null;
}
}

View File

@ -22,7 +22,6 @@ import com.discordsrv.api.placeholder.Placeholder;
import com.discordsrv.api.player.DiscordSRVPlayer; import com.discordsrv.api.player.DiscordSRVPlayer;
import com.discordsrv.common.command.game.sender.ICommandSender; import com.discordsrv.common.command.game.sender.ICommandSender;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -40,12 +39,8 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende
return identity().uuid(); return identity().uuid();
} }
@NotNull
@Placeholder("player_display_name")
Component displayName(); Component displayName();
@ApiStatus.NonExtendable
@Placeholder("player_display_name")
default String plainDisplayName() {
return PlainTextComponentSerializer.plainText()
.serialize(displayName());
}
} }

View File

@ -1,36 +0,0 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2021 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 <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.player.util;
import com.discordsrv.api.player.DiscordSRVPlayer;
import com.discordsrv.common.player.IPlayer;
import net.kyori.adventure.text.Component;
public final class PlayerUtil {
private PlayerUtil() {}
public static Component displayName(DiscordSRVPlayer player) {
if (player instanceof IPlayer) {
return ((IPlayer) player).displayName();
} else {
return Component.text(player.getUsername());
}
}
}

View File

@ -18,9 +18,9 @@
package com.discordsrv.common.string.util; package com.discordsrv.common.string.util;
import javax.annotation.CheckReturnValue;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -28,54 +28,55 @@ import java.util.regex.Pattern;
public class Placeholders { public class Placeholders {
private final String inputText; private final String inputText;
private final Map<Pattern, Supplier<String>> replacements = new HashMap<>(); private final Map<Pattern, Function<Matcher, Object>> replacements = new HashMap<>();
public Placeholders(String inputText) { public Placeholders(String inputText) {
this.inputText = inputText; this.inputText = inputText;
} }
@CheckReturnValue public Placeholders addAll(Map<Pattern, Function<Matcher, Object>> replacements) {
public Placeholders replace(String replace, String replacement) { replacements.forEach(this.replacements::put);
return replace(replace, () -> replacement); return this;
} }
@CheckReturnValue public Placeholders replace(String target, Object replacement) {
public Placeholders replaceAll(String regex, String replacement) { return replace(target, matcher -> replacement);
return replaceAll(regex, () -> replacement);
} }
@CheckReturnValue public Placeholders replaceAll(Pattern pattern, Object replacement) {
public Placeholders replaceAll(Pattern pattern, String replacement) { return replaceAll(pattern, matcher -> replacement);
return replaceAll(pattern, () -> replacement);
} }
@CheckReturnValue public Placeholders replace(String target, Supplier<Object> replacement) {
public Placeholders replace(String replace, Supplier<String> replacement) { return replaceAll(Pattern.compile(target, Pattern.LITERAL), matcher -> replacement);
return replaceAll(Pattern.compile(replace, Pattern.LITERAL), replacement);
} }
@CheckReturnValue public Placeholders replaceAll(Pattern pattern, Supplier<Object> replacement) {
public Placeholders replaceAll(String regex, Supplier<String> replacement) { return replaceAll(pattern, matcher -> replacement);
return replaceAll(Pattern.compile(regex), replacement);
} }
@CheckReturnValue public Placeholders replace(String target, Function<Matcher, Object> replacement) {
public Placeholders replaceAll(Pattern pattern, Supplier<String> replacement) { return replaceAll(Pattern.compile(target, Pattern.LITERAL), replacement);
replacements.put(pattern, replacement); }
public Placeholders replaceAll(Pattern pattern, Function<Matcher, Object> replacement) {
this.replacements.put(pattern, replacement);
return this; return this;
} }
public String get() { public String get() {
String input = inputText; String input = inputText;
for (Map.Entry<Pattern, Supplier<String>> entry : replacements.entrySet()) { for (Map.Entry<Pattern, Function<Matcher, Object>> entry : replacements.entrySet()) {
Pattern pattern = entry.getKey(); Pattern pattern = entry.getKey();
Matcher matcher = pattern.matcher(input); Matcher matcher = pattern.matcher(input);
if (!matcher.find()) { if (!matcher.find()) {
continue; continue;
} }
Supplier<String> replacement = entry.getValue(); Function<Matcher, Object> replacement = entry.getValue();
input = matcher.replaceAll(replacement.get()); Object value = replacement.apply(matcher);
input = matcher.replaceAll(String.valueOf(value));
} }
return input; return input;
} }

View File

@ -22,7 +22,7 @@ import com.discordsrv.bukkit.config.connection.BukkitConnectionConfig;
import com.discordsrv.bukkit.config.main.BukkitConfig; import com.discordsrv.bukkit.config.main.BukkitConfig;
import com.discordsrv.common.config.Config; import com.discordsrv.common.config.Config;
import com.discordsrv.common.config.annotation.Untranslated; import com.discordsrv.common.config.annotation.Untranslated;
import com.discordsrv.common.config.main.channels.ChannelConfigHolder; import com.discordsrv.common.config.main.channels.BaseChannelConfig;
import org.spongepowered.configurate.CommentedConfigurationNode; import org.spongepowered.configurate.CommentedConfigurationNode;
import org.spongepowered.configurate.ConfigurateException; import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationNode; import org.spongepowered.configurate.ConfigurationNode;
@ -75,9 +75,9 @@ public final class DiscordSRVTranslation {
.build(); .build();
ChannelConfigHolder.Serializer channelSerializer = new ChannelConfigHolder.Serializer(objectMapper); BaseChannelConfig.Serializer channelSerializer = new BaseChannelConfig.Serializer(objectMapper);
CommentedConfigurationNode node = CommentedConfigurationNode.root(ConfigurationOptions.defaults() CommentedConfigurationNode node = CommentedConfigurationNode.root(ConfigurationOptions.defaults()
.serializers(builder -> builder.register(ChannelConfigHolder.class, channelSerializer))); .serializers(builder -> builder.register(BaseChannelConfig.class, channelSerializer)));
for (Config config : CONFIG_INSTANCES) { for (Config config : CONFIG_INSTANCES) {
ConfigurationNode section = node.node(config.getFileName()); ConfigurationNode section = node.node(config.getFileName());
ConfigurationNode configSection = section.copy(); ConfigurationNode configSection = section.copy();

View File

@ -22,6 +22,7 @@ import com.discordsrv.common.player.IPlayer;
import com.discordsrv.sponge.SpongeDiscordSRV; import com.discordsrv.sponge.SpongeDiscordSRV;
import net.kyori.adventure.identity.Identity; import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.command.exception.CommandException; import org.spongepowered.api.command.exception.CommandException;
import org.spongepowered.api.entity.living.player.server.ServerPlayer; import org.spongepowered.api.entity.living.player.server.ServerPlayer;
@ -54,7 +55,7 @@ public class SpongePlayer extends SpongeOfflinePlayer implements IPlayer {
} }
@Override @Override
public Component displayName() { public @NotNull Component displayName() {
return player.displayName().get(); return player.displayName().get();
} }
} }

View File

@ -61,7 +61,7 @@ public class VelocityPlayer implements IPlayer {
} }
@Override @Override
public Component displayName() { public @NotNull Component displayName() {
// Use Adventure's Pointer, otherwise username // Use Adventure's Pointer, otherwise username
return player.getOrDefaultFrom( return player.getOrDefaultFrom(
Identity.DISPLAY_NAME, Identity.DISPLAY_NAME,