mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-11-01 08:39:31 +01:00
Placeholder service, misc bug fixes & improvements
This commit is contained in:
parent
2c3031dc75
commit
7d14a92158
@ -27,6 +27,7 @@ import com.discordsrv.api.component.MinecraftComponentFactory;
|
||||
import com.discordsrv.api.discord.api.DiscordAPI;
|
||||
import com.discordsrv.api.discord.connection.DiscordConnectionDetails;
|
||||
import com.discordsrv.api.event.bus.EventBus;
|
||||
import com.discordsrv.api.placeholder.PlaceholderService;
|
||||
import com.discordsrv.api.player.DiscordSRVPlayer;
|
||||
import com.discordsrv.api.player.IPlayerProvider;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
@ -57,6 +58,12 @@ public interface DiscordSRVApi {
|
||||
@NotNull
|
||||
EventBus eventBus();
|
||||
|
||||
/**
|
||||
* DiscordSRV's own placeholder service.
|
||||
* @return the {@link PlaceholderService} instance.
|
||||
*/
|
||||
PlaceholderService placeholderService();
|
||||
|
||||
/**
|
||||
* A provider for {@link com.discordsrv.api.component.MinecraftComponent}s.
|
||||
* @return the {@link com.discordsrv.api.component.MinecraftComponentFactory} instance.
|
||||
|
@ -28,10 +28,10 @@ import com.discordsrv.api.event.events.Processable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
*
|
||||
* This event is used to lookup {@link GameChannel}s by their name (and optionally plugin name).
|
||||
* This is also used to determine which plugin's channel should take priority when multiple plugins
|
||||
* define channels with the same name ({@link com.discordsrv.api.event.bus.EventPriority}).
|
||||
*/
|
||||
public class GameChannelLookupEvent implements Processable {
|
||||
|
||||
@ -76,9 +76,12 @@ public class GameChannelLookupEvent implements Processable {
|
||||
/**
|
||||
* Provides a {@link GameChannel} for the provided channel name ({@link #getChannelName()}).
|
||||
* @param channel the channel
|
||||
* @throws IllegalStateException if the event is already processed
|
||||
*/
|
||||
public void process(GameChannel channel) {
|
||||
Objects.requireNonNull(channel, "channel");
|
||||
public void process(@NotNull GameChannel channel) {
|
||||
if (processed) {
|
||||
throw new IllegalStateException("Already processed");
|
||||
}
|
||||
if (pluginName != null && !pluginName.equalsIgnoreCase(channel.getOwnerName())) {
|
||||
// Not the plugin we're looking for, ignore
|
||||
return;
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.event.events.placeholder;
|
||||
|
||||
import com.discordsrv.api.event.events.Event;
|
||||
import com.discordsrv.api.event.events.Processable;
|
||||
import com.discordsrv.api.placeholder.PlaceholderLookupResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class PlaceholderLookupEvent implements Event, Processable {
|
||||
|
||||
private final String placeholder;
|
||||
private final Set<Object> context;
|
||||
|
||||
private boolean processed;
|
||||
private PlaceholderLookupResult result;
|
||||
|
||||
public PlaceholderLookupEvent(String placeholder, Set<Object> context) {
|
||||
this.placeholder = placeholder;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public String getPlaceholder() {
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
public Set<Object> getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProcessed() {
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PlaceholderLookupResult} from a {@link #process(PlaceholderLookupResult)} matching required criteria.
|
||||
* @return the placeholder lookup result provided by a listener
|
||||
* @throws IllegalStateException if {@link #isProcessed()} doesn't return true
|
||||
*/
|
||||
@NotNull
|
||||
public PlaceholderLookupResult getResultFromProcessing() {
|
||||
if (!processed) {
|
||||
throw new IllegalStateException("This event has not been successfully processed yet, no result is available");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a {@link PlaceholderLookupResult} for the provided {@link #getPlaceholder()} and {@link #getContext()}.
|
||||
* @param result the result
|
||||
* @throws IllegalStateException if the event is already processed
|
||||
*/
|
||||
public void process(@NotNull PlaceholderLookupResult result) {
|
||||
if (processed) {
|
||||
throw new IllegalStateException("Already processed");
|
||||
}
|
||||
if (result.getType() == PlaceholderLookupResult.Type.UNKNOWN_PLACEHOLDER) {
|
||||
// Ignore unknown
|
||||
return;
|
||||
}
|
||||
|
||||
this.result = result;
|
||||
this.processed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void markAsProcessed() {
|
||||
throw new RuntimeException("Please use process(PlaceholderLookupResult) instead");
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates a Placeholder for DiscordSRV's {@link PlaceholderService}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
public @interface Placeholder {
|
||||
|
||||
/**
|
||||
* The name of the Placeholder.
|
||||
* @return the placeholder's name, may contain any character besides {@code %}.
|
||||
*/
|
||||
String value();
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 java.util.Set;
|
||||
|
||||
public class PlaceholderLookupResult {
|
||||
|
||||
public static final PlaceholderLookupResult LOOKUP_FAILED = new PlaceholderLookupResult(Type.LOOKUP_FAILED);
|
||||
public static final PlaceholderLookupResult DATA_NOT_AVAILABLE = new PlaceholderLookupResult(Type.DATA_NOT_AVAILABLE);
|
||||
public static final PlaceholderLookupResult UNKNOWN_PLACEHOLDER = new PlaceholderLookupResult(Type.UNKNOWN_PLACEHOLDER);
|
||||
|
||||
public static PlaceholderLookupResult success(Object result) {
|
||||
return new PlaceholderLookupResult(String.valueOf(result));
|
||||
}
|
||||
|
||||
public static PlaceholderLookupResult newLookup(String placeholder, Set<Object> extras) {
|
||||
return new PlaceholderLookupResult(placeholder, extras);
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final String value;
|
||||
private final Set<Object> extras;
|
||||
|
||||
protected PlaceholderLookupResult(Type type) {
|
||||
this.type = type;
|
||||
this.value = null;
|
||||
this.extras = null;
|
||||
}
|
||||
|
||||
protected PlaceholderLookupResult(String value) {
|
||||
this.type = Type.SUCCESS;
|
||||
this.value = value;
|
||||
this.extras = null;
|
||||
}
|
||||
|
||||
protected PlaceholderLookupResult(String placeholder, Set<Object> extras) {
|
||||
this.type = Type.NEW_LOOKUP;
|
||||
this.value = placeholder;
|
||||
this.extras = extras;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public Set<Object> getExtras() {
|
||||
return extras;
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
|
||||
SUCCESS,
|
||||
NEW_LOOKUP,
|
||||
|
||||
LOOKUP_FAILED,
|
||||
DATA_NOT_AVAILABLE,
|
||||
UNKNOWN_PLACEHOLDER
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public interface PlaceholderService {
|
||||
|
||||
/**
|
||||
* The primary pattern used by DiscordSRV to find placeholders.
|
||||
*/
|
||||
Pattern PATTERN = Pattern.compile("(%)([^%]+)(%)");
|
||||
|
||||
/**
|
||||
* The pattern DiscordSRV uses to find recursive placeholders.
|
||||
*/
|
||||
Pattern RECURSIVE_PATTERN = Pattern.compile("(\\{)(.+)(})");
|
||||
|
||||
PlaceholderLookupResult lookupPlaceholder(String placeholder, Set<Object> context);
|
||||
PlaceholderLookupResult lookupPlaceholder(String placeholder, Object... context);
|
||||
|
||||
String replacePlaceholders(String placeholder, Set<Object> context);
|
||||
String replacePlaceholders(String placeholder, Object... context);
|
||||
}
|
@ -23,6 +23,7 @@
|
||||
|
||||
package com.discordsrv.api.player;
|
||||
|
||||
import com.discordsrv.api.placeholder.Placeholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
@ -36,6 +37,7 @@ public interface DiscordSRVPlayer {
|
||||
* The username of the player.
|
||||
* @return the player's username
|
||||
*/
|
||||
@Placeholder("player_name")
|
||||
@NotNull
|
||||
String getUsername();
|
||||
|
||||
@ -43,6 +45,7 @@ public interface DiscordSRVPlayer {
|
||||
* The {@link UUID} of the player.
|
||||
* @return the player's unique id
|
||||
*/
|
||||
@Placeholder("player_uuid")
|
||||
@NotNull
|
||||
UUID uuid();
|
||||
|
||||
|
@ -23,7 +23,6 @@ import com.discordsrv.common.player.IOfflinePlayer;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BukkitOfflinePlayer implements IOfflinePlayer {
|
||||
|
||||
@ -37,8 +36,9 @@ public class BukkitOfflinePlayer implements IOfflinePlayer {
|
||||
this.identity = Identity.identity(offlinePlayer.getUniqueId());
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullabilityProblems")
|
||||
@Override
|
||||
public @Nullable String getUsername() {
|
||||
public String getUsername() {
|
||||
return offlinePlayer.getName();
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ package com.discordsrv.common;
|
||||
import com.discordsrv.api.discord.connection.DiscordConnectionDetails;
|
||||
import com.discordsrv.api.event.bus.EventBus;
|
||||
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
|
||||
import com.discordsrv.api.placeholder.PlaceholderService;
|
||||
import com.discordsrv.common.api.util.ApiInstanceUtil;
|
||||
import com.discordsrv.common.channel.ChannelConfig;
|
||||
import com.discordsrv.common.channel.DefaultGlobalChannel;
|
||||
@ -39,6 +40,7 @@ import com.discordsrv.common.listener.DefaultChannelLookupListener;
|
||||
import com.discordsrv.common.listener.DefaultChatListener;
|
||||
import com.discordsrv.common.logging.DependencyLoggingFilter;
|
||||
import com.discordsrv.common.logging.logger.backend.LoggingBackend;
|
||||
import com.discordsrv.common.placeholder.PlaceholderServiceImpl;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -54,8 +56,9 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
|
||||
// DiscordSRVApi
|
||||
private final EventBus eventBus;
|
||||
private PlaceholderService placeholderService;
|
||||
private final ComponentFactory componentFactory;
|
||||
private final DiscordAPIImpl discordAPI;
|
||||
private DiscordAPIImpl discordAPI;
|
||||
private final DiscordConnectionDetails discordConnectionDetails;
|
||||
|
||||
// DiscordSRV
|
||||
@ -70,7 +73,6 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
ApiInstanceUtil.setInstance(this);
|
||||
this.eventBus = new EventBusImpl(this);
|
||||
this.componentFactory = new ComponentFactory();
|
||||
this.discordAPI = new DiscordAPIImpl(this);
|
||||
this.discordConnectionDetails = new DiscordConnectionDetailsImpl(this);
|
||||
}
|
||||
|
||||
@ -86,6 +88,11 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
return eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PlaceholderService placeholderService() {
|
||||
return placeholderService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ComponentFactory componentFactory() {
|
||||
return componentFactory;
|
||||
@ -183,6 +190,10 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
||||
|
||||
@OverridingMethodsMustInvokeSuper
|
||||
protected void enable() throws Throwable {
|
||||
// API Stuff
|
||||
this.placeholderService = new PlaceholderServiceImpl(this);
|
||||
this.discordAPI = new DiscordAPIImpl(this);
|
||||
|
||||
// Config
|
||||
try {
|
||||
connectionConfigManager().load();
|
||||
|
@ -32,6 +32,8 @@ import com.discordsrv.common.discord.connection.DiscordConnectionManager;
|
||||
import com.discordsrv.common.logging.logger.Logger;
|
||||
import com.discordsrv.common.player.provider.AbstractPlayerProvider;
|
||||
import com.discordsrv.common.scheduler.Scheduler;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@ -50,11 +52,11 @@ public interface DiscordSRV extends DiscordSRVApi {
|
||||
// DiscordSRVApi
|
||||
@Override
|
||||
@NotNull
|
||||
AbstractPlayerProvider<?> playerProvider();
|
||||
ComponentFactory componentFactory();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
ComponentFactory componentFactory();
|
||||
AbstractPlayerProvider<?> playerProvider();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@ -74,6 +76,13 @@ public interface DiscordSRV extends DiscordSRVApi {
|
||||
Locale locale();
|
||||
void setStatus(Status status);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ApiStatus.NonExtendable
|
||||
default <K, V> Caffeine<K, V> caffeineBuilder() {
|
||||
return (Caffeine<K, V>) Caffeine.newBuilder()
|
||||
.executor(scheduler().forkExecutor());
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
CompletableFuture<Void> invokeEnable();
|
||||
CompletableFuture<Void> invokeDisable();
|
||||
|
@ -25,7 +25,6 @@ import com.discordsrv.common.config.main.channels.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.ChannelConfigHolder;
|
||||
import com.discordsrv.common.function.OrDefault;
|
||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
@ -36,23 +35,24 @@ import java.util.concurrent.TimeUnit;
|
||||
public class ChannelConfig {
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final LoadingCache<String, GameChannel> CHANNELS = Caffeine.newBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build(new CacheLoader<String, GameChannel>() {
|
||||
@Override
|
||||
public @Nullable GameChannel load(@NonNull String channelName) {
|
||||
GameChannelLookupEvent event = new GameChannelLookupEvent(null, channelName);
|
||||
discordSRV.eventBus().publish(event);
|
||||
if (!event.isProcessed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return event.getChannelFromProcessing();
|
||||
}
|
||||
});
|
||||
private final LoadingCache<String, GameChannel> channels;
|
||||
|
||||
public ChannelConfig(DiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.channels = discordSRV.caffeineBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build(new CacheLoader<String, GameChannel>() {
|
||||
@Override
|
||||
public @Nullable GameChannel load(@NonNull String channelName) {
|
||||
GameChannelLookupEvent event = new GameChannelLookupEvent(null, channelName);
|
||||
discordSRV.eventBus().publish(event);
|
||||
if (!event.isProcessed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return event.getChannelFromProcessing();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, ChannelConfigHolder> channels() {
|
||||
@ -84,7 +84,7 @@ public class ChannelConfig {
|
||||
return config.get();
|
||||
}
|
||||
|
||||
GameChannel gameChannel = CHANNELS.get(channelName);
|
||||
GameChannel gameChannel = channels.get(channelName);
|
||||
if (gameChannel != null && gameChannel.getOwnerName().equals(ownerName)) {
|
||||
config = channels().get(channelName);
|
||||
return config != null ? config.get() : null;
|
||||
@ -92,7 +92,7 @@ public class ChannelConfig {
|
||||
return null;
|
||||
}
|
||||
|
||||
GameChannel gameChannel = CHANNELS.get(channelName);
|
||||
GameChannel gameChannel = channels.get(channelName);
|
||||
return gameChannel != null ? get(gameChannel) : null;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import java.lang.annotation.Target;
|
||||
/**
|
||||
* Prevents the annotated options from being (partially) merged into existing configs (only being added to new configs).
|
||||
*/
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface DefaultOnly {
|
||||
|
||||
|
@ -26,7 +26,7 @@ import java.lang.annotation.Target;
|
||||
/**
|
||||
* Specifies that the given option will be partially or completely undocumented.
|
||||
*/
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Untranslated {
|
||||
|
||||
|
@ -18,17 +18,17 @@
|
||||
|
||||
package com.discordsrv.common.config.main.channels.minecraftodiscord;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
|
||||
@ConfigSerializable
|
||||
public class MinecraftToDiscordChatConfig {
|
||||
|
||||
@Setting("UsernameFormat")
|
||||
public String usernameFormat = "%player_display_name%";
|
||||
|
||||
@Setting("MessageFormat")
|
||||
public String messageFormat = "%message%";
|
||||
@Setting("Format")
|
||||
public SendableDiscordMessage.Builder messageFormat = SendableDiscordMessage.builder()
|
||||
.setWebhookUsername("%player_display_name%")
|
||||
.setContent("%player_message%");// TODO
|
||||
|
||||
@Setting("UseWebhooks")
|
||||
public boolean useWebhooks = false;
|
||||
|
@ -25,12 +25,15 @@ 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.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 java.awt.Color;
|
||||
|
||||
public abstract class MainConfigManager<C extends MainConfig>
|
||||
extends TranslatedConfigManager<C, YamlConfigurationLoader>
|
||||
implements YamlConfigLoaderProvider {
|
||||
@ -46,11 +49,13 @@ public abstract class MainConfigManager<C extends MainConfig>
|
||||
|
||||
@Override
|
||||
public ConfigurationOptions defaultOptions() {
|
||||
return YamlConfigLoaderProvider.super.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());
|
||||
});
|
||||
}
|
||||
|
@ -43,18 +43,18 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
|
||||
|
||||
protected final DiscordSRV discordSRV;
|
||||
private final Path filePath;
|
||||
private final LT loader;
|
||||
private final ObjectMapper.Factory configObjectMapper;
|
||||
private final ObjectMapper.Factory defaultObjectMapper;
|
||||
private final LT loader;
|
||||
|
||||
protected T configuration;
|
||||
|
||||
public ConfigurateConfigManager(DiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.filePath = new File(discordSRV.dataDirectory().toFile(), fileName()).toPath();
|
||||
this.loader = createLoader(filePath, configNodeOptions());
|
||||
this.configObjectMapper = configObjectMapperBuilder().build();
|
||||
this.defaultObjectMapper = defaultObjectMapperBuilder().build();
|
||||
this.loader = createLoader(filePath, configNodeOptions());
|
||||
}
|
||||
|
||||
public Path filePath() {
|
||||
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.serializer;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.serialize.TypeSerializer;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class ColorSerializer implements TypeSerializer<Color> {
|
||||
|
||||
@Override
|
||||
public Color deserialize(Type type, ConfigurationNode node) {
|
||||
String hexColor = node.getString();
|
||||
int length;
|
||||
if (hexColor != null && ((length = hexColor.length()) == 6 || (length == 7 && hexColor.startsWith("#")))) {
|
||||
if (length == 7) {
|
||||
hexColor = hexColor.substring(1);
|
||||
}
|
||||
|
||||
try {
|
||||
int r = Integer.parseInt(hexColor.substring(0, 2), 16);
|
||||
int g = Integer.parseInt(hexColor.substring(2, 4), 16);
|
||||
int b = Integer.parseInt(hexColor.substring(4, 6), 16);
|
||||
|
||||
return new Color(r, g, b);
|
||||
} catch (NumberFormatException ignored) {}
|
||||
} else {
|
||||
int intColor = node.getInt(Integer.MIN_VALUE);
|
||||
if (intColor != Integer.MIN_VALUE) {
|
||||
return new Color(intColor);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Type type, @Nullable Color obj, ConfigurationNode node) throws SerializationException {
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
node.set(String.format("#%02x%02x%02x", obj.getRed(), obj.getGreen(), obj.getBlue()));
|
||||
}
|
||||
}
|
@ -1,23 +1,146 @@
|
||||
/*
|
||||
* 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.serializer;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.serialize.TypeSerializer;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
|
||||
public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMessageEmbed.Builder> {
|
||||
|
||||
@Override
|
||||
public DiscordMessageEmbed.Builder deserialize(Type type, ConfigurationNode node) throws SerializationException {
|
||||
// TODO
|
||||
return null;
|
||||
if (!node.node("Enabled").getBoolean(node.node("Enable").getBoolean(true))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DiscordMessageEmbed.Builder builder = DiscordMessageEmbed.builder();
|
||||
|
||||
Color color = node.node("Color").get(Color.class);
|
||||
builder.setColor(color != null ? color.getRGB() : Role.DEFAULT_COLOR_RAW);
|
||||
|
||||
ConfigurationNode author = node.node("Author");
|
||||
builder.setAuthor(
|
||||
author.node("Name").getString(),
|
||||
author.node("Url").getString(),
|
||||
author.node("ImageUrl").getString());
|
||||
|
||||
ConfigurationNode title = node.node("Title");
|
||||
builder.setTitle(
|
||||
title.node("Text").getString(),
|
||||
title.node("Url").getString());
|
||||
|
||||
builder.setDescription(node.node("Description").getString());
|
||||
for (DiscordMessageEmbed.Field field : node.getList(DiscordMessageEmbed.Field.class, Collections.emptyList())) {
|
||||
builder.addField(field);
|
||||
}
|
||||
|
||||
builder.setThumbnailUrl(node.node("ThumbnailUrl").getString());
|
||||
builder.setImageUrl(node.node("ImageUrl").getString());
|
||||
|
||||
// TODO: timestamp
|
||||
|
||||
ConfigurationNode footer = node.node("Footer");
|
||||
builder.setFooter(
|
||||
footer.node("Text").getString(),
|
||||
footer.node("ImageUrl").getString(footer.node("IconUrl").getString()));
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Type type, DiscordMessageEmbed.@Nullable Builder obj, ConfigurationNode node) throws SerializationException {
|
||||
// TODO
|
||||
public void serialize(Type type, DiscordMessageEmbed.@Nullable Builder obj, ConfigurationNode node)
|
||||
throws SerializationException {
|
||||
if (obj == null) {
|
||||
node.set(null);
|
||||
return;
|
||||
}
|
||||
|
||||
node.node("Color").set(new Color(obj.getColor()));
|
||||
|
||||
ConfigurationNode author = node.node("Author");
|
||||
author.node("Name").set(obj.getAuthorName());
|
||||
author.node("Url").set(obj.getAuthorUrl());
|
||||
author.node("ImageUrl").set(obj.getAuthorImageUrl());
|
||||
|
||||
ConfigurationNode title = node.node("Title");
|
||||
title.node("Text").set(obj.getTitle());
|
||||
title.node("Url").set(obj.getTitleUrl());
|
||||
|
||||
node.node("Description").set(obj.getDescription());
|
||||
node.node("Fields").setList(DiscordMessageEmbed.Field.class, obj.getFields());
|
||||
|
||||
node.node("ThumbnailUrl").set(obj.getThumbnailUrl());
|
||||
node.node("ImageUrl").set(obj.getImageUrl());
|
||||
|
||||
ConfigurationNode footer = node.node("Footer");
|
||||
footer.node("Text").set(obj.getFooter());
|
||||
footer.node("ImageUrl").set(obj.getFooterImageUrl());
|
||||
}
|
||||
|
||||
public static class FieldSerializer implements TypeSerializer<DiscordMessageEmbed.Field> {
|
||||
|
||||
@Override
|
||||
public DiscordMessageEmbed.Field deserialize(Type type, ConfigurationNode node) {
|
||||
// v1 compat
|
||||
String footerString = node.getString();
|
||||
if (footerString != null) {
|
||||
if (footerString.contains(";")) {
|
||||
String[] parts = footerString.split(";", 3);
|
||||
if (parts.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean inline = parts.length < 3 || Boolean.parseBoolean(parts[2]);
|
||||
return new DiscordMessageEmbed.Field(parts[0], parts[1], inline);
|
||||
} else {
|
||||
boolean inline = Boolean.parseBoolean(footerString);
|
||||
return new DiscordMessageEmbed.Field("\u200e", "\u200e", inline);
|
||||
}
|
||||
}
|
||||
|
||||
return new DiscordMessageEmbed.Field(
|
||||
node.node("Title").getString(),
|
||||
node.node("Value").getString(),
|
||||
node.node("Inline").getBoolean()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Type type, DiscordMessageEmbed.@Nullable Field obj, ConfigurationNode node)
|
||||
throws SerializationException {
|
||||
if (obj == null) {
|
||||
node.set(null);
|
||||
return;
|
||||
}
|
||||
|
||||
node.node("Title").set(obj.getTitle());
|
||||
node.node("Value").set(obj.getValue());
|
||||
node.node("Inline").set(obj.isInline());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
* 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.serializer;
|
||||
|
||||
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
|
||||
@ -9,13 +27,14 @@ import org.spongepowered.configurate.serialize.TypeSerializer;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SendableDiscordMessageSerializer implements TypeSerializer<SendableDiscordMessage.Builder> {
|
||||
|
||||
@Override
|
||||
public SendableDiscordMessage.Builder deserialize(Type type, ConfigurationNode node) throws SerializationException {
|
||||
public SendableDiscordMessage.Builder deserialize(Type type, ConfigurationNode node)
|
||||
throws SerializationException {
|
||||
String contentOnly = node.getString();
|
||||
if (contentOnly != null) {
|
||||
return SendableDiscordMessage.builder()
|
||||
@ -25,16 +44,21 @@ public class SendableDiscordMessageSerializer implements TypeSerializer<Sendable
|
||||
SendableDiscordMessage.Builder builder = SendableDiscordMessage.builder();
|
||||
|
||||
ConfigurationNode webhook = node.node("Webhook");
|
||||
if (webhook.node("Enabled").getBoolean(false)) {
|
||||
builder.setWebhookUsername(webhook.node("Username").getString());
|
||||
String webhookUsername = webhook.node("Username").getString();
|
||||
if (webhook.node("Enabled").getBoolean(webhook.node("Enable").getBoolean(webhookUsername != null))) {
|
||||
builder.setWebhookUsername(webhookUsername);
|
||||
builder.setWebhookAvatarUrl(webhook.node("AvatarUrl").getString());
|
||||
}
|
||||
|
||||
List<DiscordMessageEmbed.Builder> embeds = node.node("Embeds").getList(DiscordMessageEmbed.Builder.class);
|
||||
if (embeds != null) {
|
||||
for (DiscordMessageEmbed.Builder embed : embeds) {
|
||||
builder.addEmbed(embed.build());
|
||||
}
|
||||
// v1 compat
|
||||
DiscordMessageEmbed.Builder singleEmbed = node.node("Embed").get(
|
||||
DiscordMessageEmbed.Builder.class);
|
||||
List<DiscordMessageEmbed.Builder> embedList = singleEmbed != null
|
||||
? Collections.singletonList(singleEmbed) : Collections.emptyList();
|
||||
|
||||
for (DiscordMessageEmbed.Builder embed : node.node("Embeds")
|
||||
.getList(DiscordMessageEmbed.Builder.class, embedList)) {
|
||||
builder.addEmbed(embed.build());
|
||||
}
|
||||
|
||||
builder.setContent(node.node("Content").getString());
|
||||
@ -42,7 +66,8 @@ public class SendableDiscordMessageSerializer implements TypeSerializer<Sendable
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Type type, SendableDiscordMessage.@Nullable Builder obj, ConfigurationNode node) throws SerializationException {
|
||||
public void serialize(Type type, SendableDiscordMessage.@Nullable Builder obj, ConfigurationNode node)
|
||||
throws SerializationException {
|
||||
if (obj == null) {
|
||||
node.set(null);
|
||||
return;
|
||||
@ -52,7 +77,7 @@ public class SendableDiscordMessageSerializer implements TypeSerializer<Sendable
|
||||
if (webhookUsername != null) {
|
||||
ConfigurationNode webhook = node.node("Webhook");
|
||||
webhook.node("Username").set(webhookUsername);
|
||||
webhook.node("AvatarUrl").set(Optional.ofNullable(obj.getWebhookAvatarUrl()).orElse(""));
|
||||
webhook.node("AvatarUrl").set(obj.getWebhookAvatarUrl());
|
||||
}
|
||||
|
||||
List<DiscordMessageEmbed.Builder> embedBuilders = new ArrayList<>();
|
||||
|
@ -31,6 +31,9 @@ import java.util.concurrent.ForkJoinPool;
|
||||
|
||||
/**
|
||||
* TODO: revamp
|
||||
* - run DiscordSRV#load() after DiscordSRV is initialized
|
||||
* - catch exceptions, so they don't go missing
|
||||
* - make the whenComplete stuff less janky
|
||||
*/
|
||||
public class InitialDependencyLoader {
|
||||
|
||||
|
@ -54,17 +54,18 @@ public class DiscordAPIImpl implements DiscordAPI {
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
|
||||
private final AsyncLoadingCache<String, WebhookClient> cachedClients = Caffeine.newBuilder()
|
||||
.removalListener((RemovalListener<String, WebhookClient>) (id, client, cause) -> {
|
||||
if (client != null) {
|
||||
client.close();
|
||||
}
|
||||
})
|
||||
.expireAfter(new WebhookCacheExpiry())
|
||||
.buildAsync(new WebhookCacheLoader());
|
||||
private final AsyncLoadingCache<String, WebhookClient> cachedClients;
|
||||
|
||||
public DiscordAPIImpl(DiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.cachedClients = discordSRV.caffeineBuilder()
|
||||
.removalListener((RemovalListener<String, WebhookClient>) (id, client, cause) -> {
|
||||
if (client != null) {
|
||||
client.close();
|
||||
}
|
||||
})
|
||||
.expireAfter(new WebhookCacheExpiry())
|
||||
.buildAsync(new WebhookCacheLoader());
|
||||
}
|
||||
|
||||
public CompletableFuture<WebhookClient> queryWebhookClient(String channelId) {
|
||||
|
@ -22,8 +22,17 @@ import com.discordsrv.api.discord.connection.DiscordConnectionDetails;
|
||||
import com.discordsrv.api.event.bus.EventPriority;
|
||||
import com.discordsrv.api.event.bus.Subscribe;
|
||||
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
|
||||
import com.discordsrv.api.event.events.placeholder.PlaceholderLookupEvent;
|
||||
import com.discordsrv.api.placeholder.PlaceholderLookupResult;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||
import com.discordsrv.common.discord.api.channel.DiscordDMChannelImpl;
|
||||
import com.discordsrv.common.discord.api.channel.DiscordTextChannelImpl;
|
||||
import com.discordsrv.common.discord.api.guild.DiscordGuildImpl;
|
||||
import com.discordsrv.common.discord.api.guild.DiscordGuildMemberImpl;
|
||||
import com.discordsrv.common.discord.api.guild.DiscordRoleImpl;
|
||||
import com.discordsrv.common.discord.api.message.ReceivedDiscordMessageImpl;
|
||||
import com.discordsrv.common.discord.api.user.DiscordUserImpl;
|
||||
import com.discordsrv.common.discord.connection.DiscordConnectionManager;
|
||||
import com.discordsrv.common.scheduler.Scheduler;
|
||||
import com.discordsrv.common.scheduler.threadfactory.CountingThreadFactory;
|
||||
@ -31,6 +40,7 @@ import com.neovisionaries.ws.client.WebSocketFactory;
|
||||
import com.neovisionaries.ws.client.WebSocketFrame;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.entities.*;
|
||||
import net.dv8tion.jda.api.events.DisconnectEvent;
|
||||
import net.dv8tion.jda.api.events.ShutdownEvent;
|
||||
import net.dv8tion.jda.api.events.StatusChangeEvent;
|
||||
@ -39,12 +49,14 @@ import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||
import net.dv8tion.jda.api.requests.RestAction;
|
||||
import net.dv8tion.jda.api.utils.AllowedMentions;
|
||||
import net.dv8tion.jda.api.utils.MemberCachePolicy;
|
||||
import net.dv8tion.jda.internal.entities.ReceivedMessage;
|
||||
import net.dv8tion.jda.internal.hooks.EventManagerProxy;
|
||||
import net.dv8tion.jda.internal.utils.IOUtil;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
import javax.security.auth.login.LoginException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@ -79,6 +91,33 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
shutdown().join();
|
||||
}
|
||||
|
||||
@Subscribe(priority = EventPriority.EARLIEST)
|
||||
public void onPlaceholderLookup(PlaceholderLookupEvent event) {
|
||||
Set<Object> newContext = new HashSet<>();
|
||||
for (Object o : event.getContext()) {
|
||||
Object converted;
|
||||
if (o instanceof PrivateChannel) {
|
||||
converted = new DiscordDMChannelImpl(discordSRV, (PrivateChannel) o);
|
||||
} else if (o instanceof TextChannel) {
|
||||
converted = new DiscordTextChannelImpl(discordSRV, (TextChannel) o);
|
||||
} else if (o instanceof Guild) {
|
||||
converted = new DiscordGuildImpl(discordSRV, (Guild) o);
|
||||
} else if (o instanceof Member) {
|
||||
converted = new DiscordGuildMemberImpl((Member) o);
|
||||
} else if (o instanceof Role) {
|
||||
converted = new DiscordRoleImpl((Role) o);
|
||||
} else if (o instanceof ReceivedMessage) {
|
||||
converted = ReceivedDiscordMessageImpl.fromJDA(discordSRV, (Message) o);
|
||||
} else if (o instanceof User) {
|
||||
converted = new DiscordUserImpl((User) o);
|
||||
} else {
|
||||
converted = o;
|
||||
}
|
||||
newContext.add(converted);
|
||||
}
|
||||
event.process(PlaceholderLookupResult.newLookup(event.getPlaceholder(), newContext));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onStatusChange(StatusChangeEvent event) {
|
||||
DiscordSRV.Status currentStatus = discordSRV.status();
|
||||
|
@ -30,11 +30,7 @@ import com.discordsrv.common.config.main.channels.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.ChannelConfig;
|
||||
import com.discordsrv.common.function.OrDefault;
|
||||
import com.discordsrv.common.player.util.PlayerUtil;
|
||||
import com.discordsrv.common.string.util.Placeholders;
|
||||
import dev.vankka.enhancedlegacytext.EnhancedLegacyText;
|
||||
import dev.vankka.mcdiscordreserializer.discord.DiscordSerializer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -57,19 +53,19 @@ public class DefaultChatListener extends AbstractListener {
|
||||
|
||||
OrDefault<BaseChannelConfig> channelConfig = discordSRV.channelConfig().orDefault(gameChannel);
|
||||
|
||||
Component discordMessage = EnhancedLegacyText.get().buildComponent(channelConfig.map(cfg -> cfg.minecraftToDiscord).get(cfg -> cfg.messageFormat))
|
||||
.replace("%message%", message)
|
||||
.replace("%player_display_name%", displayName)
|
||||
.build();
|
||||
|
||||
String username = new Placeholders(channelConfig.map(cfg -> cfg.minecraftToDiscord).get(cfg -> cfg.usernameFormat))
|
||||
.replace("%player_display_name%", () -> PlainTextComponentSerializer.plainText().serialize(displayName))
|
||||
.get();
|
||||
// Component discordMessage = EnhancedLegacyText.get().buildComponent(channelConfig.map(cfg -> cfg.minecraftToDiscord).get(cfg -> cfg.messageFormat))
|
||||
// .replace("%message%", message)
|
||||
// .replace("%player_display_name%", displayName)
|
||||
// .build();
|
||||
//
|
||||
// String username = new Placeholders(channelConfig.map(cfg -> cfg.minecraftToDiscord).get(cfg -> cfg.usernameFormat))
|
||||
// .replace("%player_display_name%", () -> PlainTextComponentSerializer.plainText().serialize(displayName))
|
||||
// .get();
|
||||
|
||||
discordSRV.eventBus().publish(
|
||||
new ChatMessageSendEvent(
|
||||
DiscordSerializer.INSTANCE.serialize(discordMessage),
|
||||
username,
|
||||
null,
|
||||
null,
|
||||
gameChannel
|
||||
)
|
||||
);
|
||||
|
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.discordsrv.api.event.events.placeholder.PlaceholderLookupEvent;
|
||||
import com.discordsrv.api.placeholder.Placeholder;
|
||||
import com.discordsrv.api.placeholder.PlaceholderLookupResult;
|
||||
import com.discordsrv.api.placeholder.PlaceholderService;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.placeholder.provider.AnnotationPlaceholderProvider;
|
||||
import com.discordsrv.common.placeholder.provider.PlaceholderProvider;
|
||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class PlaceholderServiceImpl implements PlaceholderService {
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final LoadingCache<Class<?>, Set<PlaceholderProvider>> classProviders;
|
||||
|
||||
public PlaceholderServiceImpl(DiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.classProviders = discordSRV.caffeineBuilder()
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.expireAfterWrite(15, TimeUnit.MINUTES)
|
||||
.build(new ClassProviderLoader());
|
||||
}
|
||||
|
||||
private static Set<Object> getArrayAsSet(Object[] array) {
|
||||
return array.length == 0
|
||||
? Collections.emptySet()
|
||||
: new HashSet<>(Arrays.asList(array));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaceholderLookupResult lookupPlaceholder(String placeholder, Object... context) {
|
||||
return lookupPlaceholder(placeholder, getArrayAsSet(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaceholderLookupResult lookupPlaceholder(String placeholder, Set<Object> context) {
|
||||
for (Object o : context) {
|
||||
Set<PlaceholderProvider> providers = classProviders.get(o.getClass());
|
||||
if (providers == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (PlaceholderProvider provider : providers) {
|
||||
PlaceholderLookupResult result = provider.lookup(placeholder, context);
|
||||
if (result.getType() != PlaceholderLookupResult.Type.UNKNOWN_PLACEHOLDER) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only go through this if a placeholder couldn't be looked up from the context
|
||||
PlaceholderLookupEvent lookupEvent = new PlaceholderLookupEvent(placeholder, context);
|
||||
discordSRV.eventBus().publish(lookupEvent);
|
||||
|
||||
return lookupEvent.isProcessed()
|
||||
? lookupEvent.getResultFromProcessing()
|
||||
: PlaceholderLookupResult.UNKNOWN_PLACEHOLDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String replacePlaceholders(String input, Object... context) {
|
||||
return replacePlaceholders(input, getArrayAsSet(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String replacePlaceholders(String input, Set<Object> context) {
|
||||
return processReplacement(PATTERN, input, context);
|
||||
}
|
||||
|
||||
private String processReplacement(Pattern pattern, String input, Set<Object> context) {
|
||||
Matcher matcher = pattern.matcher(input);
|
||||
|
||||
String output = input;
|
||||
while (matcher.find()) {
|
||||
String placeholder = matcher.group(2);
|
||||
String originalPlaceholder = placeholder;
|
||||
|
||||
// Recursive
|
||||
placeholder = processReplacement(RECURSIVE_PATTERN, placeholder, context);
|
||||
|
||||
PlaceholderLookupResult result = lookupPlaceholder(placeholder, context);
|
||||
output = updateBasedOnResult(result, input, originalPlaceholder, matcher);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
private String updateBasedOnResult(
|
||||
PlaceholderLookupResult result, String input, String originalPlaceholder, Matcher matcher) {
|
||||
String output = input;
|
||||
while (result != null) {
|
||||
PlaceholderLookupResult.Type type = result.getType();
|
||||
if (type == PlaceholderLookupResult.Type.UNKNOWN_PLACEHOLDER) {
|
||||
break;
|
||||
}
|
||||
|
||||
boolean newLookup = false;
|
||||
String replacement = null;
|
||||
switch (type) {
|
||||
case SUCCESS:
|
||||
replacement = result.getValue();
|
||||
break;
|
||||
case DATA_NOT_AVAILABLE:
|
||||
replacement = "Unavailable";
|
||||
break;
|
||||
case LOOKUP_FAILED:
|
||||
replacement = "Error";
|
||||
break;
|
||||
case NEW_LOOKUP:
|
||||
// prevent infinite recursion
|
||||
if (result.getValue().equals(originalPlaceholder)) {
|
||||
break;
|
||||
}
|
||||
result = lookupPlaceholder(result.getValue(), result.getExtras());
|
||||
newLookup = true;
|
||||
break;
|
||||
}
|
||||
if (replacement != null) {
|
||||
output = Pattern.compile(
|
||||
matcher.group(1)
|
||||
+ originalPlaceholder
|
||||
+ matcher.group(3),
|
||||
Pattern.LITERAL
|
||||
).matcher(output).replaceFirst(replacement);
|
||||
}
|
||||
if (!newLookup) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
private static class ClassProviderLoader implements CacheLoader<Class<?>, Set<PlaceholderProvider>> {
|
||||
|
||||
@Override
|
||||
public @Nullable Set<PlaceholderProvider> load(@NonNull Class<?> key) {
|
||||
Set<PlaceholderProvider> providers = new HashSet<>();
|
||||
|
||||
Class<?> currentClass = key;
|
||||
do {
|
||||
List<Class<?>> classes = new ArrayList<>(Arrays.asList(currentClass.getInterfaces()));
|
||||
classes.add(currentClass);
|
||||
|
||||
for (Class<?> clazz : classes) {
|
||||
for (Method method : clazz.getMethods()) {
|
||||
Placeholder annotation = method.getAnnotation(Placeholder.class);
|
||||
if (annotation == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isStatic = Modifier.isStatic(method.getModifiers());
|
||||
providers.add(new AnnotationPlaceholderProvider(annotation, isStatic ? null : clazz, method));
|
||||
}
|
||||
for (Field field : clazz.getFields()) {
|
||||
Placeholder annotation = field.getAnnotation(Placeholder.class);
|
||||
if (annotation == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isStatic = Modifier.isStatic(field.getModifiers());
|
||||
providers.add(new AnnotationPlaceholderProvider(annotation, isStatic ? null : clazz, field));
|
||||
}
|
||||
}
|
||||
|
||||
currentClass = currentClass.getSuperclass();
|
||||
} while (currentClass != null);
|
||||
|
||||
return providers;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import com.discordsrv.api.placeholder.Placeholder;
|
||||
import com.discordsrv.api.placeholder.PlaceholderLookupResult;
|
||||
import com.discordsrv.common.placeholder.provider.util.PlaceholderMethodUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
public class AnnotationPlaceholderProvider implements PlaceholderProvider {
|
||||
|
||||
private final Placeholder annotation;
|
||||
|
||||
private final Class<?> type;
|
||||
private final Method method;
|
||||
private final Field field;
|
||||
|
||||
public AnnotationPlaceholderProvider(Placeholder annotation, Class<?> type, Method method) {
|
||||
this.annotation = annotation;
|
||||
this.type = type;
|
||||
this.method = method;
|
||||
this.field = null;
|
||||
}
|
||||
|
||||
public AnnotationPlaceholderProvider(Placeholder annotation, Class<?> type, Field field) {
|
||||
this.annotation = annotation;
|
||||
this.type = type;
|
||||
this.method = null;
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PlaceholderLookupResult lookup(@NotNull String placeholder, @NotNull Set<Object> context) {
|
||||
if (!annotation.value().equals(placeholder) || (type != null && context.isEmpty())) {
|
||||
return PlaceholderLookupResult.UNKNOWN_PLACEHOLDER;
|
||||
}
|
||||
|
||||
Object instance = null;
|
||||
if (type != null) {
|
||||
for (Object o : context) {
|
||||
if (type.isAssignableFrom(o.getClass())) {
|
||||
instance = o;
|
||||
}
|
||||
}
|
||||
if (instance == null) {
|
||||
return PlaceholderLookupResult.UNKNOWN_PLACEHOLDER;
|
||||
}
|
||||
}
|
||||
|
||||
if (field != null) {
|
||||
try {
|
||||
return PlaceholderLookupResult.success(field.get(instance));
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace(); // TODO
|
||||
return PlaceholderLookupResult.LOOKUP_FAILED;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
assert method != null;
|
||||
return PlaceholderMethodUtil.lookup(method, instance, context);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
e.printStackTrace(); // TODO
|
||||
return PlaceholderLookupResult.LOOKUP_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import com.discordsrv.api.placeholder.PlaceholderLookupResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A placeholder provider used internally by DiscordSRV for {@link com.discordsrv.api.placeholder.Placeholder}.
|
||||
* API users should use the {@link com.discordsrv.api.event.events.placeholder.PlaceholderLookupEvent} instead.
|
||||
*/
|
||||
public interface PlaceholderProvider {
|
||||
|
||||
@NotNull
|
||||
PlaceholderLookupResult lookup(@NotNull String placeholder, @NotNull Set<Object> context);
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.provider.util;
|
||||
|
||||
import com.discordsrv.api.placeholder.PlaceholderLookupResult;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
public final class PlaceholderMethodUtil {
|
||||
|
||||
private PlaceholderMethodUtil() {}
|
||||
|
||||
public static PlaceholderLookupResult lookup(Method method, Object instance, Set<Object> context)
|
||||
throws InvocationTargetException, IllegalAccessException {
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
Object[] parameters = new Object[parameterTypes.length];
|
||||
|
||||
for (Object o : context) {
|
||||
Class<?> objectType = o.getClass();
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
Class<?> parameterType = parameterTypes[i];
|
||||
if (parameterType == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parameterType.isAssignableFrom(objectType)) {
|
||||
parameters[i] = o;
|
||||
parameterTypes[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Class<?> parameterType : parameterTypes) {
|
||||
if (parameterType != null) {
|
||||
return PlaceholderLookupResult.UNKNOWN_PLACEHOLDER;
|
||||
}
|
||||
}
|
||||
|
||||
return PlaceholderLookupResult.success(
|
||||
method.invoke(instance, parameters)
|
||||
);
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
|
||||
package com.discordsrv.common.player;
|
||||
|
||||
import com.discordsrv.api.placeholder.Placeholder;
|
||||
import net.kyori.adventure.identity.Identified;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -27,10 +28,12 @@ import java.util.UUID;
|
||||
|
||||
public interface IOfflinePlayer extends Identified {
|
||||
|
||||
@Placeholder("player_name")
|
||||
@Nullable
|
||||
String getUsername();
|
||||
|
||||
@ApiStatus.NonExtendable
|
||||
@Placeholder("player_uuid")
|
||||
@NotNull
|
||||
default UUID uuid() {
|
||||
return identity().uuid();
|
||||
|
@ -18,9 +18,11 @@
|
||||
|
||||
package com.discordsrv.common.player;
|
||||
|
||||
import com.discordsrv.api.placeholder.Placeholder;
|
||||
import com.discordsrv.api.player.DiscordSRVPlayer;
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -28,7 +30,6 @@ import java.util.UUID;
|
||||
|
||||
public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSender {
|
||||
|
||||
@SuppressWarnings("NullableProblems") // IOfflinePlayer != IPlayer
|
||||
@Override
|
||||
@NotNull
|
||||
String getUsername();
|
||||
@ -40,4 +41,11 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende
|
||||
}
|
||||
|
||||
Component displayName();
|
||||
|
||||
@ApiStatus.NonExtendable
|
||||
@Placeholder("player_display_name")
|
||||
default String plainDisplayName() {
|
||||
return PlainTextComponentSerializer.plainText()
|
||||
.serialize(displayName());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user