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.api.DiscordAPI;
|
||||||
import com.discordsrv.api.discord.connection.DiscordConnectionDetails;
|
import com.discordsrv.api.discord.connection.DiscordConnectionDetails;
|
||||||
import com.discordsrv.api.event.bus.EventBus;
|
import com.discordsrv.api.event.bus.EventBus;
|
||||||
|
import com.discordsrv.api.placeholder.PlaceholderService;
|
||||||
import com.discordsrv.api.player.DiscordSRVPlayer;
|
import com.discordsrv.api.player.DiscordSRVPlayer;
|
||||||
import com.discordsrv.api.player.IPlayerProvider;
|
import com.discordsrv.api.player.IPlayerProvider;
|
||||||
import net.dv8tion.jda.api.JDA;
|
import net.dv8tion.jda.api.JDA;
|
||||||
@ -57,6 +58,12 @@ public interface DiscordSRVApi {
|
|||||||
@NotNull
|
@NotNull
|
||||||
EventBus eventBus();
|
EventBus eventBus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DiscordSRV's own placeholder service.
|
||||||
|
* @return the {@link PlaceholderService} instance.
|
||||||
|
*/
|
||||||
|
PlaceholderService placeholderService();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A provider for {@link com.discordsrv.api.component.MinecraftComponent}s.
|
* A provider for {@link com.discordsrv.api.component.MinecraftComponent}s.
|
||||||
* @return the {@link com.discordsrv.api.component.MinecraftComponentFactory} instance.
|
* @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.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
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 {
|
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()}).
|
* Provides a {@link GameChannel} for the provided channel name ({@link #getChannelName()}).
|
||||||
* @param channel the channel
|
* @param channel the channel
|
||||||
|
* @throws IllegalStateException if the event is already processed
|
||||||
*/
|
*/
|
||||||
public void process(GameChannel channel) {
|
public void process(@NotNull GameChannel channel) {
|
||||||
Objects.requireNonNull(channel, "channel");
|
if (processed) {
|
||||||
|
throw new IllegalStateException("Already processed");
|
||||||
|
}
|
||||||
if (pluginName != null && !pluginName.equalsIgnoreCase(channel.getOwnerName())) {
|
if (pluginName != null && !pluginName.equalsIgnoreCase(channel.getOwnerName())) {
|
||||||
// Not the plugin we're looking for, ignore
|
// Not the plugin we're looking for, ignore
|
||||||
return;
|
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;
|
package com.discordsrv.api.player;
|
||||||
|
|
||||||
|
import com.discordsrv.api.placeholder.Placeholder;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -36,6 +37,7 @@ public interface DiscordSRVPlayer {
|
|||||||
* The username of the player.
|
* The username of the player.
|
||||||
* @return the player's username
|
* @return the player's username
|
||||||
*/
|
*/
|
||||||
|
@Placeholder("player_name")
|
||||||
@NotNull
|
@NotNull
|
||||||
String getUsername();
|
String getUsername();
|
||||||
|
|
||||||
@ -43,6 +45,7 @@ public interface DiscordSRVPlayer {
|
|||||||
* The {@link UUID} of the player.
|
* The {@link UUID} of the player.
|
||||||
* @return the player's unique id
|
* @return the player's unique id
|
||||||
*/
|
*/
|
||||||
|
@Placeholder("player_uuid")
|
||||||
@NotNull
|
@NotNull
|
||||||
UUID uuid();
|
UUID uuid();
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ import com.discordsrv.common.player.IOfflinePlayer;
|
|||||||
import net.kyori.adventure.identity.Identity;
|
import net.kyori.adventure.identity.Identity;
|
||||||
import org.bukkit.OfflinePlayer;
|
import org.bukkit.OfflinePlayer;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
public class BukkitOfflinePlayer implements IOfflinePlayer {
|
public class BukkitOfflinePlayer implements IOfflinePlayer {
|
||||||
|
|
||||||
@ -37,8 +36,9 @@ public class BukkitOfflinePlayer implements IOfflinePlayer {
|
|||||||
this.identity = Identity.identity(offlinePlayer.getUniqueId());
|
this.identity = Identity.identity(offlinePlayer.getUniqueId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("NullabilityProblems")
|
||||||
@Override
|
@Override
|
||||||
public @Nullable String getUsername() {
|
public String getUsername() {
|
||||||
return offlinePlayer.getName();
|
return offlinePlayer.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ package com.discordsrv.common;
|
|||||||
import com.discordsrv.api.discord.connection.DiscordConnectionDetails;
|
import com.discordsrv.api.discord.connection.DiscordConnectionDetails;
|
||||||
import com.discordsrv.api.event.bus.EventBus;
|
import com.discordsrv.api.event.bus.EventBus;
|
||||||
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
|
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.api.util.ApiInstanceUtil;
|
||||||
import com.discordsrv.common.channel.ChannelConfig;
|
import com.discordsrv.common.channel.ChannelConfig;
|
||||||
import com.discordsrv.common.channel.DefaultGlobalChannel;
|
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.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 net.dv8tion.jda.api.JDA;
|
import net.dv8tion.jda.api.JDA;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@ -54,8 +56,9 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
|||||||
|
|
||||||
// DiscordSRVApi
|
// DiscordSRVApi
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
|
private PlaceholderService placeholderService;
|
||||||
private final ComponentFactory componentFactory;
|
private final ComponentFactory componentFactory;
|
||||||
private final DiscordAPIImpl discordAPI;
|
private DiscordAPIImpl discordAPI;
|
||||||
private final DiscordConnectionDetails discordConnectionDetails;
|
private final DiscordConnectionDetails discordConnectionDetails;
|
||||||
|
|
||||||
// DiscordSRV
|
// DiscordSRV
|
||||||
@ -70,7 +73,6 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
|||||||
ApiInstanceUtil.setInstance(this);
|
ApiInstanceUtil.setInstance(this);
|
||||||
this.eventBus = new EventBusImpl(this);
|
this.eventBus = new EventBusImpl(this);
|
||||||
this.componentFactory = new ComponentFactory();
|
this.componentFactory = new ComponentFactory();
|
||||||
this.discordAPI = new DiscordAPIImpl(this);
|
|
||||||
this.discordConnectionDetails = new DiscordConnectionDetailsImpl(this);
|
this.discordConnectionDetails = new DiscordConnectionDetailsImpl(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +88,11 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
|||||||
return eventBus;
|
return eventBus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull PlaceholderService placeholderService() {
|
||||||
|
return placeholderService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull ComponentFactory componentFactory() {
|
public @NotNull ComponentFactory componentFactory() {
|
||||||
return componentFactory;
|
return componentFactory;
|
||||||
@ -183,6 +190,10 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
|
|||||||
|
|
||||||
@OverridingMethodsMustInvokeSuper
|
@OverridingMethodsMustInvokeSuper
|
||||||
protected void enable() throws Throwable {
|
protected void enable() throws Throwable {
|
||||||
|
// API Stuff
|
||||||
|
this.placeholderService = new PlaceholderServiceImpl(this);
|
||||||
|
this.discordAPI = new DiscordAPIImpl(this);
|
||||||
|
|
||||||
// Config
|
// Config
|
||||||
try {
|
try {
|
||||||
connectionConfigManager().load();
|
connectionConfigManager().load();
|
||||||
|
@ -32,6 +32,8 @@ import com.discordsrv.common.discord.connection.DiscordConnectionManager;
|
|||||||
import com.discordsrv.common.logging.logger.Logger;
|
import com.discordsrv.common.logging.logger.Logger;
|
||||||
import com.discordsrv.common.player.provider.AbstractPlayerProvider;
|
import com.discordsrv.common.player.provider.AbstractPlayerProvider;
|
||||||
import com.discordsrv.common.scheduler.Scheduler;
|
import com.discordsrv.common.scheduler.Scheduler;
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -50,11 +52,11 @@ public interface DiscordSRV extends DiscordSRVApi {
|
|||||||
// DiscordSRVApi
|
// DiscordSRVApi
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
AbstractPlayerProvider<?> playerProvider();
|
ComponentFactory componentFactory();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
ComponentFactory componentFactory();
|
AbstractPlayerProvider<?> playerProvider();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
@ -74,6 +76,13 @@ public interface DiscordSRV extends DiscordSRVApi {
|
|||||||
Locale locale();
|
Locale locale();
|
||||||
void setStatus(Status status);
|
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
|
// Lifecycle
|
||||||
CompletableFuture<Void> invokeEnable();
|
CompletableFuture<Void> invokeEnable();
|
||||||
CompletableFuture<Void> invokeDisable();
|
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.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.Caffeine;
|
|
||||||
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;
|
||||||
@ -36,23 +35,24 @@ import java.util.concurrent.TimeUnit;
|
|||||||
public class ChannelConfig {
|
public class ChannelConfig {
|
||||||
|
|
||||||
private final DiscordSRV discordSRV;
|
private final DiscordSRV discordSRV;
|
||||||
private final LoadingCache<String, GameChannel> CHANNELS = Caffeine.newBuilder()
|
private final LoadingCache<String, GameChannel> channels;
|
||||||
.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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
public ChannelConfig(DiscordSRV discordSRV) {
|
public ChannelConfig(DiscordSRV discordSRV) {
|
||||||
this.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() {
|
private Map<String, ChannelConfigHolder> channels() {
|
||||||
@ -84,7 +84,7 @@ public class ChannelConfig {
|
|||||||
return config.get();
|
return config.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
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 != null ? config.get() : null;
|
||||||
@ -92,7 +92,7 @@ public class ChannelConfig {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameChannel gameChannel = CHANNELS.get(channelName);
|
GameChannel gameChannel = channels.get(channelName);
|
||||||
return gameChannel != null ? get(gameChannel) : null;
|
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).
|
* 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)
|
@Target(ElementType.FIELD)
|
||||||
public @interface DefaultOnly {
|
public @interface DefaultOnly {
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import java.lang.annotation.Target;
|
|||||||
/**
|
/**
|
||||||
* Specifies that the given option will be partially or completely undocumented.
|
* Specifies that the given option will be partially or completely undocumented.
|
||||||
*/
|
*/
|
||||||
@Retention(value = RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.FIELD)
|
@Target(ElementType.FIELD)
|
||||||
public @interface Untranslated {
|
public @interface Untranslated {
|
||||||
|
|
||||||
|
@ -18,17 +18,17 @@
|
|||||||
|
|
||||||
package com.discordsrv.common.config.main.channels.minecraftodiscord;
|
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.ConfigSerializable;
|
||||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||||
|
|
||||||
@ConfigSerializable
|
@ConfigSerializable
|
||||||
public class MinecraftToDiscordChatConfig {
|
public class MinecraftToDiscordChatConfig {
|
||||||
|
|
||||||
@Setting("UsernameFormat")
|
@Setting("Format")
|
||||||
public String usernameFormat = "%player_display_name%";
|
public SendableDiscordMessage.Builder messageFormat = SendableDiscordMessage.builder()
|
||||||
|
.setWebhookUsername("%player_display_name%")
|
||||||
@Setting("MessageFormat")
|
.setContent("%player_message%");// TODO
|
||||||
public String messageFormat = "%message%";
|
|
||||||
|
|
||||||
@Setting("UseWebhooks")
|
@Setting("UseWebhooks")
|
||||||
public boolean useWebhooks = false;
|
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.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.DiscordMessageEmbedSerializer;
|
||||||
import com.discordsrv.common.config.serializer.SendableDiscordMessageSerializer;
|
import com.discordsrv.common.config.serializer.SendableDiscordMessageSerializer;
|
||||||
import org.spongepowered.configurate.ConfigurationOptions;
|
import org.spongepowered.configurate.ConfigurationOptions;
|
||||||
import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
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,11 +49,13 @@ public abstract class MainConfigManager<C extends MainConfig>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConfigurationOptions defaultOptions() {
|
public ConfigurationOptions defaultOptions() {
|
||||||
return YamlConfigLoaderProvider.super.defaultOptions()
|
return super.defaultOptions()
|
||||||
.serializers(builder -> {
|
.serializers(builder -> {
|
||||||
ObjectMapper.Factory objectMapper = defaultObjectMapper();
|
ObjectMapper.Factory objectMapper = defaultObjectMapper();
|
||||||
|
builder.register(Color.class, new ColorSerializer());
|
||||||
builder.register(ChannelConfigHolder.class, new ChannelConfigHolder.Serializer(objectMapper));
|
builder.register(ChannelConfigHolder.class, new ChannelConfigHolder.Serializer(objectMapper));
|
||||||
builder.register(DiscordMessageEmbed.Builder.class, new DiscordMessageEmbedSerializer());
|
builder.register(DiscordMessageEmbed.Builder.class, new DiscordMessageEmbedSerializer());
|
||||||
|
builder.register(DiscordMessageEmbed.Field.class, new DiscordMessageEmbedSerializer.FieldSerializer());
|
||||||
builder.register(SendableDiscordMessage.Builder.class, new SendableDiscordMessageSerializer());
|
builder.register(SendableDiscordMessage.Builder.class, new SendableDiscordMessageSerializer());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -43,18 +43,18 @@ public abstract class ConfigurateConfigManager<T, LT extends AbstractConfigurati
|
|||||||
|
|
||||||
protected final DiscordSRV discordSRV;
|
protected final DiscordSRV discordSRV;
|
||||||
private final Path filePath;
|
private final Path filePath;
|
||||||
private final LT loader;
|
|
||||||
private final ObjectMapper.Factory configObjectMapper;
|
private final ObjectMapper.Factory configObjectMapper;
|
||||||
private final ObjectMapper.Factory defaultObjectMapper;
|
private final ObjectMapper.Factory defaultObjectMapper;
|
||||||
|
private final LT loader;
|
||||||
|
|
||||||
protected T configuration;
|
protected T configuration;
|
||||||
|
|
||||||
public ConfigurateConfigManager(DiscordSRV discordSRV) {
|
public ConfigurateConfigManager(DiscordSRV discordSRV) {
|
||||||
this.discordSRV = discordSRV;
|
this.discordSRV = discordSRV;
|
||||||
this.filePath = new File(discordSRV.dataDirectory().toFile(), fileName()).toPath();
|
this.filePath = new File(discordSRV.dataDirectory().toFile(), fileName()).toPath();
|
||||||
this.loader = createLoader(filePath, configNodeOptions());
|
|
||||||
this.configObjectMapper = configObjectMapperBuilder().build();
|
this.configObjectMapper = configObjectMapperBuilder().build();
|
||||||
this.defaultObjectMapper = defaultObjectMapperBuilder().build();
|
this.defaultObjectMapper = defaultObjectMapperBuilder().build();
|
||||||
|
this.loader = createLoader(filePath, configNodeOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path filePath() {
|
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;
|
package com.discordsrv.common.config.serializer;
|
||||||
|
|
||||||
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
|
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.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.spongepowered.configurate.ConfigurationNode;
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
import org.spongepowered.configurate.serialize.SerializationException;
|
import org.spongepowered.configurate.serialize.SerializationException;
|
||||||
import org.spongepowered.configurate.serialize.TypeSerializer;
|
import org.spongepowered.configurate.serialize.TypeSerializer;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMessageEmbed.Builder> {
|
public class DiscordMessageEmbedSerializer implements TypeSerializer<DiscordMessageEmbed.Builder> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DiscordMessageEmbed.Builder deserialize(Type type, ConfigurationNode node) throws SerializationException {
|
public DiscordMessageEmbed.Builder deserialize(Type type, ConfigurationNode node) throws SerializationException {
|
||||||
// TODO
|
if (!node.node("Enabled").getBoolean(node.node("Enable").getBoolean(true))) {
|
||||||
return null;
|
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
|
@Override
|
||||||
public void serialize(Type type, DiscordMessageEmbed.@Nullable Builder obj, ConfigurationNode node) throws SerializationException {
|
public void serialize(Type type, DiscordMessageEmbed.@Nullable Builder obj, ConfigurationNode node)
|
||||||
// TODO
|
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;
|
package com.discordsrv.common.config.serializer;
|
||||||
|
|
||||||
import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed;
|
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.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class SendableDiscordMessageSerializer implements TypeSerializer<SendableDiscordMessage.Builder> {
|
public class SendableDiscordMessageSerializer implements TypeSerializer<SendableDiscordMessage.Builder> {
|
||||||
|
|
||||||
@Override
|
@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();
|
String contentOnly = node.getString();
|
||||||
if (contentOnly != null) {
|
if (contentOnly != null) {
|
||||||
return SendableDiscordMessage.builder()
|
return SendableDiscordMessage.builder()
|
||||||
@ -25,16 +44,21 @@ public class SendableDiscordMessageSerializer implements TypeSerializer<Sendable
|
|||||||
SendableDiscordMessage.Builder builder = SendableDiscordMessage.builder();
|
SendableDiscordMessage.Builder builder = SendableDiscordMessage.builder();
|
||||||
|
|
||||||
ConfigurationNode webhook = node.node("Webhook");
|
ConfigurationNode webhook = node.node("Webhook");
|
||||||
if (webhook.node("Enabled").getBoolean(false)) {
|
String webhookUsername = webhook.node("Username").getString();
|
||||||
builder.setWebhookUsername(webhook.node("Username").getString());
|
if (webhook.node("Enabled").getBoolean(webhook.node("Enable").getBoolean(webhookUsername != null))) {
|
||||||
|
builder.setWebhookUsername(webhookUsername);
|
||||||
builder.setWebhookAvatarUrl(webhook.node("AvatarUrl").getString());
|
builder.setWebhookAvatarUrl(webhook.node("AvatarUrl").getString());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DiscordMessageEmbed.Builder> embeds = node.node("Embeds").getList(DiscordMessageEmbed.Builder.class);
|
// v1 compat
|
||||||
if (embeds != null) {
|
DiscordMessageEmbed.Builder singleEmbed = node.node("Embed").get(
|
||||||
for (DiscordMessageEmbed.Builder embed : embeds) {
|
DiscordMessageEmbed.Builder.class);
|
||||||
builder.addEmbed(embed.build());
|
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());
|
builder.setContent(node.node("Content").getString());
|
||||||
@ -42,7 +66,8 @@ public class SendableDiscordMessageSerializer implements TypeSerializer<Sendable
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) {
|
if (obj == null) {
|
||||||
node.set(null);
|
node.set(null);
|
||||||
return;
|
return;
|
||||||
@ -52,7 +77,7 @@ public class SendableDiscordMessageSerializer implements TypeSerializer<Sendable
|
|||||||
if (webhookUsername != null) {
|
if (webhookUsername != null) {
|
||||||
ConfigurationNode webhook = node.node("Webhook");
|
ConfigurationNode webhook = node.node("Webhook");
|
||||||
webhook.node("Username").set(webhookUsername);
|
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<>();
|
List<DiscordMessageEmbed.Builder> embedBuilders = new ArrayList<>();
|
||||||
|
@ -31,6 +31,9 @@ import java.util.concurrent.ForkJoinPool;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: revamp
|
* 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 {
|
public class InitialDependencyLoader {
|
||||||
|
|
||||||
|
@ -54,17 +54,18 @@ public class DiscordAPIImpl implements DiscordAPI {
|
|||||||
|
|
||||||
private final DiscordSRV discordSRV;
|
private final DiscordSRV discordSRV;
|
||||||
|
|
||||||
private final AsyncLoadingCache<String, WebhookClient> cachedClients = Caffeine.newBuilder()
|
private final AsyncLoadingCache<String, WebhookClient> cachedClients;
|
||||||
.removalListener((RemovalListener<String, WebhookClient>) (id, client, cause) -> {
|
|
||||||
if (client != null) {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.expireAfter(new WebhookCacheExpiry())
|
|
||||||
.buildAsync(new WebhookCacheLoader());
|
|
||||||
|
|
||||||
public DiscordAPIImpl(DiscordSRV discordSRV) {
|
public DiscordAPIImpl(DiscordSRV discordSRV) {
|
||||||
this.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) {
|
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.EventPriority;
|
||||||
import com.discordsrv.api.event.bus.Subscribe;
|
import com.discordsrv.api.event.bus.Subscribe;
|
||||||
import com.discordsrv.api.event.events.lifecycle.DiscordSRVShuttingDownEvent;
|
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.DiscordSRV;
|
||||||
import com.discordsrv.common.config.connection.ConnectionConfig;
|
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.discord.connection.DiscordConnectionManager;
|
||||||
import com.discordsrv.common.scheduler.Scheduler;
|
import com.discordsrv.common.scheduler.Scheduler;
|
||||||
import com.discordsrv.common.scheduler.threadfactory.CountingThreadFactory;
|
import com.discordsrv.common.scheduler.threadfactory.CountingThreadFactory;
|
||||||
@ -31,6 +40,7 @@ import com.neovisionaries.ws.client.WebSocketFactory;
|
|||||||
import com.neovisionaries.ws.client.WebSocketFrame;
|
import com.neovisionaries.ws.client.WebSocketFrame;
|
||||||
import net.dv8tion.jda.api.JDA;
|
import net.dv8tion.jda.api.JDA;
|
||||||
import net.dv8tion.jda.api.JDABuilder;
|
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.DisconnectEvent;
|
||||||
import net.dv8tion.jda.api.events.ShutdownEvent;
|
import net.dv8tion.jda.api.events.ShutdownEvent;
|
||||||
import net.dv8tion.jda.api.events.StatusChangeEvent;
|
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.requests.RestAction;
|
||||||
import net.dv8tion.jda.api.utils.AllowedMentions;
|
import net.dv8tion.jda.api.utils.AllowedMentions;
|
||||||
import net.dv8tion.jda.api.utils.MemberCachePolicy;
|
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.hooks.EventManagerProxy;
|
||||||
import net.dv8tion.jda.internal.utils.IOUtil;
|
import net.dv8tion.jda.internal.utils.IOUtil;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
import javax.security.auth.login.LoginException;
|
import javax.security.auth.login.LoginException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
@ -79,6 +91,33 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
|||||||
shutdown().join();
|
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
|
@Subscribe
|
||||||
public void onStatusChange(StatusChangeEvent event) {
|
public void onStatusChange(StatusChangeEvent event) {
|
||||||
DiscordSRV.Status currentStatus = discordSRV.status();
|
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.config.main.channels.ChannelConfig;
|
||||||
import com.discordsrv.common.function.OrDefault;
|
import com.discordsrv.common.function.OrDefault;
|
||||||
import com.discordsrv.common.player.util.PlayerUtil;
|
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.Component;
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -57,19 +53,19 @@ public class DefaultChatListener extends AbstractListener {
|
|||||||
|
|
||||||
OrDefault<BaseChannelConfig> channelConfig = discordSRV.channelConfig().orDefault(gameChannel);
|
OrDefault<BaseChannelConfig> channelConfig = discordSRV.channelConfig().orDefault(gameChannel);
|
||||||
|
|
||||||
Component discordMessage = EnhancedLegacyText.get().buildComponent(channelConfig.map(cfg -> cfg.minecraftToDiscord).get(cfg -> cfg.messageFormat))
|
// Component discordMessage = EnhancedLegacyText.get().buildComponent(channelConfig.map(cfg -> cfg.minecraftToDiscord).get(cfg -> cfg.messageFormat))
|
||||||
.replace("%message%", message)
|
// .replace("%message%", message)
|
||||||
.replace("%player_display_name%", displayName)
|
// .replace("%player_display_name%", displayName)
|
||||||
.build();
|
// .build();
|
||||||
|
//
|
||||||
String username = new Placeholders(channelConfig.map(cfg -> cfg.minecraftToDiscord).get(cfg -> cfg.usernameFormat))
|
// String username = new Placeholders(channelConfig.map(cfg -> cfg.minecraftToDiscord).get(cfg -> cfg.usernameFormat))
|
||||||
.replace("%player_display_name%", () -> PlainTextComponentSerializer.plainText().serialize(displayName))
|
// .replace("%player_display_name%", () -> PlainTextComponentSerializer.plainText().serialize(displayName))
|
||||||
.get();
|
// .get();
|
||||||
|
|
||||||
discordSRV.eventBus().publish(
|
discordSRV.eventBus().publish(
|
||||||
new ChatMessageSendEvent(
|
new ChatMessageSendEvent(
|
||||||
DiscordSerializer.INSTANCE.serialize(discordMessage),
|
null,
|
||||||
username,
|
null,
|
||||||
gameChannel
|
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;
|
package com.discordsrv.common.player;
|
||||||
|
|
||||||
|
import com.discordsrv.api.placeholder.Placeholder;
|
||||||
import net.kyori.adventure.identity.Identified;
|
import net.kyori.adventure.identity.Identified;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -27,10 +28,12 @@ import java.util.UUID;
|
|||||||
|
|
||||||
public interface IOfflinePlayer extends Identified {
|
public interface IOfflinePlayer extends Identified {
|
||||||
|
|
||||||
|
@Placeholder("player_name")
|
||||||
@Nullable
|
@Nullable
|
||||||
String getUsername();
|
String getUsername();
|
||||||
|
|
||||||
@ApiStatus.NonExtendable
|
@ApiStatus.NonExtendable
|
||||||
|
@Placeholder("player_uuid")
|
||||||
@NotNull
|
@NotNull
|
||||||
default UUID uuid() {
|
default UUID uuid() {
|
||||||
return identity().uuid();
|
return identity().uuid();
|
||||||
|
@ -18,9 +18,11 @@
|
|||||||
|
|
||||||
package com.discordsrv.common.player;
|
package com.discordsrv.common.player;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
@ -28,7 +30,6 @@ import java.util.UUID;
|
|||||||
|
|
||||||
public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSender {
|
public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSender {
|
||||||
|
|
||||||
@SuppressWarnings("NullableProblems") // IOfflinePlayer != IPlayer
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
String getUsername();
|
String getUsername();
|
||||||
@ -40,4 +41,11 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component displayName();
|
Component displayName();
|
||||||
|
|
||||||
|
@ApiStatus.NonExtendable
|
||||||
|
@Placeholder("player_display_name")
|
||||||
|
default String plainDisplayName() {
|
||||||
|
return PlainTextComponentSerializer.plainText()
|
||||||
|
.serialize(displayName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user