mirror of
https://github.com/DiscordSRV/Ascension.git
synced 2024-11-22 11:55:54 +01:00
Merge branch 'linking-commands'
This commit is contained in:
commit
6d4adebfd3
@ -4,9 +4,9 @@ import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public final class PlayerLocaleProvider {
|
||||
public final class PaperPlayer {
|
||||
|
||||
private PlayerLocaleProvider() {}
|
||||
private PaperPlayer() {}
|
||||
|
||||
private static final boolean localeMethodExists;
|
||||
private static final boolean getLocaleMethodExists;
|
@ -0,0 +1,45 @@
|
||||
package com.discordsrv.bukkit.player;
|
||||
|
||||
import com.discordsrv.common.player.provider.model.SkinInfo;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.profile.PlayerTextures;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class SpigotPlayer {
|
||||
|
||||
private SpigotPlayer() {}
|
||||
|
||||
private static final boolean playerProfileExists;
|
||||
|
||||
static {
|
||||
Class<?> playerClass = Player.class;
|
||||
|
||||
boolean playerProfile = false;
|
||||
try {
|
||||
playerClass.getMethod("getPlayerProfile");
|
||||
playerProfile = true;
|
||||
} catch (ReflectiveOperationException ignored) {}
|
||||
playerProfileExists = playerProfile;
|
||||
}
|
||||
|
||||
public static SkinInfo getSkinInfo(Player player) {
|
||||
if (!playerProfileExists) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PlayerTextures textures = player.getPlayerProfile().getTextures();
|
||||
|
||||
URL skinUrl = textures.getSkin();
|
||||
if (skinUrl == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String skinUrlPlain = skinUrl.toString();
|
||||
return new SkinInfo(
|
||||
skinUrlPlain.substring(skinUrlPlain.lastIndexOf('/') + 1),
|
||||
textures.getSkinModel().toString().toLowerCase(Locale.ROOT)
|
||||
);
|
||||
}
|
||||
}
|
@ -21,9 +21,11 @@ package com.discordsrv.bukkit.player;
|
||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.player.IOfflinePlayer;
|
||||
import com.discordsrv.common.player.provider.model.SkinInfo;
|
||||
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 {
|
||||
|
||||
@ -47,6 +49,11 @@ public class BukkitOfflinePlayer implements IOfflinePlayer {
|
||||
return offlinePlayer.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SkinInfo skinInfo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Identity identity() {
|
||||
return identity;
|
||||
|
@ -24,10 +24,12 @@ import com.discordsrv.bukkit.component.PaperComponentHandle;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.discordsrv.common.player.provider.model.SkinInfo;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@ -62,9 +64,14 @@ public class BukkitPlayer extends BukkitCommandSender implements IPlayer {
|
||||
return player.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SkinInfo skinInfo() {
|
||||
return SpigotPlayer.getSkinInfo(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale locale() {
|
||||
return PlayerLocaleProvider.getLocale(player);
|
||||
return PaperPlayer.getLocale(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -20,6 +20,7 @@ package com.discordsrv.bukkit.player;
|
||||
|
||||
import com.discordsrv.bukkit.BukkitDiscordSRV;
|
||||
import com.discordsrv.common.player.IOfflinePlayer;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.discordsrv.common.player.ServerPlayerProvider;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -87,13 +88,23 @@ public class BukkitPlayerProvider extends ServerPlayerProvider<BukkitPlayer, Buk
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<IOfflinePlayer> offlinePlayer(UUID uuid) {
|
||||
public CompletableFuture<IOfflinePlayer> lookupOfflinePlayer(UUID uuid) {
|
||||
IPlayer player = player(uuid);
|
||||
if (player != null) {
|
||||
return CompletableFuture.completedFuture(player);
|
||||
}
|
||||
|
||||
return getFuture(() -> discordSRV.server().getOfflinePlayer(uuid));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Shut up, I know
|
||||
@Override
|
||||
public CompletableFuture<IOfflinePlayer> offlinePlayer(String username) {
|
||||
public CompletableFuture<IOfflinePlayer> lookupOfflinePlayer(String username) {
|
||||
IPlayer player = player(username);
|
||||
if (player != null) {
|
||||
return CompletableFuture.completedFuture(player);
|
||||
}
|
||||
|
||||
return getFuture(() -> discordSRV.server().getOfflinePlayer(username));
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ import com.discordsrv.bungee.command.game.sender.BungeeCommandSender;
|
||||
import com.discordsrv.bungee.component.util.BungeeComponentUtil;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.discordsrv.common.player.provider.model.SkinInfo;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
@ -52,6 +53,11 @@ public class BungeePlayer extends BungeeCommandSender implements IPlayer {
|
||||
return commandSender.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SkinInfo skinInfo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Locale locale() {
|
||||
return player.getLocale();
|
||||
|
@ -101,6 +101,7 @@ import java.lang.reflect.Constructor;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@ -669,7 +670,7 @@ public abstract class AbstractDiscordSRV<
|
||||
scheduler().run(() -> updateChecker.check(true));
|
||||
}
|
||||
if (initial) {
|
||||
scheduler().runAtFixedRate(() -> updateChecker.check(false), 6L, TimeUnit.HOURS);
|
||||
scheduler().runAtFixedRate(() -> updateChecker.check(false), Duration.ofHours(6));
|
||||
}
|
||||
|
||||
if (flags.contains(ReloadFlag.LINKED_ACCOUNT_PROVIDER)) {
|
||||
|
@ -32,6 +32,7 @@ import net.dv8tion.jda.api.managers.channel.ChannelManager;
|
||||
import net.dv8tion.jda.api.managers.channel.concrete.TextChannelManager;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -79,9 +80,8 @@ public class TimedUpdaterModule extends AbstractModule<DiscordSRV> {
|
||||
|
||||
futures.add(discordSRV.scheduler().runAtFixedRate(
|
||||
() -> update(updaterConfig),
|
||||
firstReload ? 0 : time,
|
||||
time,
|
||||
TimeUnit.SECONDS
|
||||
firstReload ? Duration.ZERO : Duration.ofSeconds(time),
|
||||
Duration.ofSeconds(time)
|
||||
));
|
||||
}
|
||||
firstReload = false;
|
||||
|
@ -1,14 +1,26 @@
|
||||
package com.discordsrv.common.command.combined.abstraction;
|
||||
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.events.interaction.command.DiscordChatInputInteractionEvent;
|
||||
import com.discordsrv.api.discord.events.interaction.command.DiscordCommandAutoCompleteInteractionEvent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandSuggester;
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class CombinedCommand implements GameCommandExecutor, Consumer<DiscordChatInputInteractionEvent> {
|
||||
public abstract class CombinedCommand
|
||||
implements
|
||||
GameCommandExecutor,
|
||||
GameCommandSuggester,
|
||||
Consumer<DiscordChatInputInteractionEvent>,
|
||||
DiscordCommand.AutoCompleteHandler
|
||||
{
|
||||
|
||||
protected final DiscordSRV discordSRV;
|
||||
|
||||
@ -18,7 +30,7 @@ public abstract class CombinedCommand implements GameCommandExecutor, Consumer<D
|
||||
|
||||
@Override
|
||||
public void execute(ICommandSender sender, GameCommandArguments arguments, String label) {
|
||||
execute(new GameCommandExecution(discordSRV, sender, arguments));
|
||||
execute(new GameCommandExecution(discordSRV, sender, arguments, label));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -28,4 +40,18 @@ public abstract class CombinedCommand implements GameCommandExecutor, Consumer<D
|
||||
|
||||
public abstract void execute(CommandExecution execution);
|
||||
|
||||
@Override
|
||||
public List<String> suggestValues(ICommandSender sender, GameCommandArguments previousArguments, String currentInput) {
|
||||
return suggest(new GameCommandExecution(discordSRV, sender, previousArguments, null), currentInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void autoComplete(DiscordCommandAutoCompleteInteractionEvent event) {
|
||||
List<String> suggestions = suggest(new DiscordCommandExecution(discordSRV, event), null);
|
||||
suggestions.forEach(suggestion -> event.addChoice(suggestion, suggestion));
|
||||
}
|
||||
|
||||
public List<String> suggest(CommandExecution execution, @Nullable String input) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,18 @@
|
||||
package com.discordsrv.common.command.combined.abstraction;
|
||||
|
||||
import com.discordsrv.common.config.messages.MessagesConfig;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
|
||||
public interface CommandExecution {
|
||||
|
||||
Locale locale();
|
||||
MessagesConfig messages();
|
||||
|
||||
void setEphemeral(boolean ephemeral);
|
||||
|
||||
String getArgument(String label);
|
||||
@ -20,5 +27,7 @@ public interface CommandExecution {
|
||||
|
||||
void send(Collection<Text> texts, Collection<Text> extra);
|
||||
|
||||
void send(Component minecraft, String discord);
|
||||
|
||||
void runAsync(Runnable runnable);
|
||||
}
|
||||
|
@ -1,26 +1,57 @@
|
||||
package com.discordsrv.common.command.combined.abstraction;
|
||||
|
||||
import com.discordsrv.api.discord.events.interaction.command.DiscordChatInputInteractionEvent;
|
||||
import com.discordsrv.api.discord.events.interaction.command.DiscordCommandAutoCompleteInteractionEvent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.config.messages.MessagesConfig;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.events.interaction.GenericInteractionCreateEvent;
|
||||
import net.dv8tion.jda.api.interactions.InteractionHook;
|
||||
import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback;
|
||||
import net.dv8tion.jda.api.interactions.commands.CommandInteractionPayload;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class DiscordCommandExecution implements CommandExecution {
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final DiscordChatInputInteractionEvent event;
|
||||
|
||||
private final GenericInteractionCreateEvent createEvent;
|
||||
private final CommandInteractionPayload interactionPayload;
|
||||
private final IReplyCallback replyCallback;
|
||||
|
||||
private final AtomicBoolean isEphemeral = new AtomicBoolean(true);
|
||||
private final AtomicReference<InteractionHook> hook = new AtomicReference<>();
|
||||
|
||||
public DiscordCommandExecution(DiscordSRV discordSRV, DiscordChatInputInteractionEvent event) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.event = event;
|
||||
this.createEvent = event.asJDA();
|
||||
this.interactionPayload = event.asJDA();
|
||||
this.replyCallback = event.asJDA();
|
||||
}
|
||||
|
||||
public DiscordCommandExecution(DiscordSRV discordSRV, DiscordCommandAutoCompleteInteractionEvent event) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.createEvent = event.asJDA();
|
||||
this.interactionPayload = event.asJDA();
|
||||
this.replyCallback = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale locale() {
|
||||
return createEvent.getUserLocale().toLocale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessagesConfig messages() {
|
||||
return discordSRV.messagesConfig(locale());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -30,7 +61,7 @@ public class DiscordCommandExecution implements CommandExecution {
|
||||
|
||||
@Override
|
||||
public String getArgument(String label) {
|
||||
OptionMapping mapping = event.asJDA().getOption(label);
|
||||
OptionMapping mapping = interactionPayload.getOption(label);
|
||||
return mapping != null ? mapping.getAsString() : null;
|
||||
}
|
||||
|
||||
@ -52,12 +83,25 @@ public class DiscordCommandExecution implements CommandExecution {
|
||||
verifyStyle(builder, formats, null);
|
||||
}
|
||||
|
||||
sendResponse(builder.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Component minecraft, String discord) {
|
||||
sendResponse(discord);
|
||||
}
|
||||
|
||||
private void sendResponse(String content) {
|
||||
if (replyCallback == null) {
|
||||
throw new IllegalStateException("May not be used on auto completions");
|
||||
}
|
||||
|
||||
InteractionHook interactionHook = hook.get();
|
||||
boolean ephemeral = isEphemeral.get();
|
||||
if (interactionHook != null) {
|
||||
interactionHook.sendMessage(builder.toString()).setEphemeral(ephemeral).queue();
|
||||
interactionHook.sendMessage(content).setEphemeral(ephemeral).queue();
|
||||
} else {
|
||||
event.asJDA().reply(builder.toString()).setEphemeral(ephemeral).queue();
|
||||
replyCallback.reply(content).setEphemeral(ephemeral).queue();
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,9 +127,13 @@ public class DiscordCommandExecution implements CommandExecution {
|
||||
|
||||
@Override
|
||||
public void runAsync(Runnable runnable) {
|
||||
event.asJDA().deferReply(isEphemeral.get()).queue(ih -> {
|
||||
replyCallback.deferReply(isEphemeral.get()).queue(ih -> {
|
||||
hook.set(ih);
|
||||
discordSRV.scheduler().run(runnable);
|
||||
});
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return createEvent.getUser();
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package com.discordsrv.common.command.combined.abstraction;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import com.discordsrv.common.config.messages.MessagesConfig;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TextReplacementConfig;
|
||||
@ -10,6 +12,7 @@ import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class GameCommandExecution implements CommandExecution {
|
||||
@ -25,11 +28,23 @@ public class GameCommandExecution implements CommandExecution {
|
||||
private final DiscordSRV discordSRV;
|
||||
private final ICommandSender sender;
|
||||
private final GameCommandArguments arguments;
|
||||
private final String label;
|
||||
|
||||
public GameCommandExecution(DiscordSRV discordSRV, ICommandSender sender, GameCommandArguments arguments) {
|
||||
public GameCommandExecution(DiscordSRV discordSRV, ICommandSender sender, GameCommandArguments arguments, String label) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.sender = sender;
|
||||
this.arguments = arguments;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale locale() {
|
||||
return sender instanceof IPlayer ? ((IPlayer) sender).locale() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessagesConfig messages() {
|
||||
return discordSRV.messagesConfig(locale());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -51,6 +66,11 @@ public class GameCommandExecution implements CommandExecution {
|
||||
sender.sendMessage(builder.build().replaceText(URL_REPLACEMENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Component minecraft, String discord) {
|
||||
sender.sendMessage(minecraft);
|
||||
}
|
||||
|
||||
private TextComponent.Builder render(Collection<Text> texts) {
|
||||
TextComponent.Builder builder = Component.text();
|
||||
for (Text text : texts) {
|
||||
@ -67,4 +87,12 @@ public class GameCommandExecution implements CommandExecution {
|
||||
public void runAsync(Runnable runnable) {
|
||||
discordSRV.scheduler().run(runnable);
|
||||
}
|
||||
|
||||
public ICommandSender getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,8 @@
|
||||
|
||||
package com.discordsrv.common.command.combined.commands;
|
||||
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.CommandOption;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.combined.abstraction.CombinedCommand;
|
||||
@ -31,6 +31,7 @@ import com.discordsrv.common.paste.Paste;
|
||||
import com.discordsrv.common.paste.PasteService;
|
||||
import com.discordsrv.common.paste.service.AESEncryptedPasteService;
|
||||
import com.discordsrv.common.paste.service.BytebinPasteService;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@ -53,7 +54,7 @@ public class DebugCommand extends CombinedCommand {
|
||||
if (GAME == null) {
|
||||
DebugCommand command = getInstance(discordSRV);
|
||||
GAME = GameCommand.literal("debug")
|
||||
.requiredPermission("discordsrv.admin.debug")
|
||||
.requiredPermission(Permission.COMMAND_DEBUG)
|
||||
.executor(command)
|
||||
.then(
|
||||
GameCommand.stringWord("format")
|
||||
|
@ -0,0 +1,262 @@
|
||||
package com.discordsrv.common.command.combined.commands;
|
||||
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.CommandOption;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
|
||||
import com.discordsrv.api.placeholder.provider.SinglePlaceholder;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.combined.abstraction.CombinedCommand;
|
||||
import com.discordsrv.common.command.combined.abstraction.CommandExecution;
|
||||
import com.discordsrv.common.command.combined.abstraction.GameCommandExecution;
|
||||
import com.discordsrv.common.command.combined.abstraction.Text;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommand;
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import com.discordsrv.common.command.util.CommandUtil;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.config.messages.MessagesConfig;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.linking.LinkProvider;
|
||||
import com.discordsrv.common.linking.LinkStore;
|
||||
import com.discordsrv.common.logging.Logger;
|
||||
import com.discordsrv.common.logging.NamedLogger;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
import com.discordsrv.common.player.IOfflinePlayer;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class LinkInitCommand extends CombinedCommand {
|
||||
|
||||
private static DebugCommand INSTANCE;
|
||||
private static GameCommand GAME;
|
||||
private static DiscordCommand DISCORD;
|
||||
|
||||
private static DebugCommand getInstance(DiscordSRV discordSRV) {
|
||||
return INSTANCE != null ? INSTANCE : (INSTANCE = new DebugCommand(discordSRV));
|
||||
}
|
||||
|
||||
public static GameCommand getGame(DiscordSRV discordSRV) {
|
||||
if (GAME == null) {
|
||||
LinkInitCommand command = new LinkInitCommand(discordSRV);
|
||||
GAME = GameCommand.literal("link")
|
||||
.then(
|
||||
GameCommand.stringWord("player")
|
||||
.then(
|
||||
GameCommand.stringWord("user")
|
||||
.requiredPermission(Permission.COMMAND_LINK_OTHER)
|
||||
.executor(command)
|
||||
)
|
||||
)
|
||||
.requiredPermission(Permission.COMMAND_LINK)
|
||||
.executor(command);
|
||||
}
|
||||
|
||||
return GAME;
|
||||
}
|
||||
|
||||
public static DiscordCommand getDiscord(DiscordSRV discordSRV) {
|
||||
if (DISCORD == null) {
|
||||
DebugCommand command = getInstance(discordSRV);
|
||||
DISCORD = DiscordCommand.chatInput(ComponentIdentifier.of("DiscordSRV", "link"), "link", "Link players")
|
||||
.addOption(
|
||||
CommandOption.builder(CommandOption.Type.STRING, "player", "The player to link")
|
||||
.setRequired(true)
|
||||
.build()
|
||||
)
|
||||
.addOption(
|
||||
CommandOption.builder(CommandOption.Type.USER, "user", "The user to link")
|
||||
.setRequired(true)
|
||||
.build()
|
||||
)
|
||||
.setAutoCompleteHandler(command)
|
||||
.setEventHandler(command)
|
||||
.build();
|
||||
}
|
||||
|
||||
return DISCORD;
|
||||
}
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final Logger logger;
|
||||
private final Cache<UUID, Boolean> linkCheckRateLimit;
|
||||
|
||||
public LinkInitCommand(DiscordSRV discordSRV) {
|
||||
super(discordSRV);
|
||||
this.discordSRV = discordSRV;
|
||||
this.logger = new NamedLogger(discordSRV, "LINK_COMMAND");
|
||||
this.linkCheckRateLimit = discordSRV.caffeineBuilder()
|
||||
.expireAfterWrite(LinkStore.LINKING_CODE_RATE_LIMIT)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandExecution execution) {
|
||||
String playerArgument = execution.getArgument("player");
|
||||
String userArgument = execution.getArgument("user");
|
||||
if (execution instanceof GameCommandExecution) {
|
||||
ICommandSender sender = ((GameCommandExecution) execution).getSender();
|
||||
|
||||
if (StringUtils.isEmpty(playerArgument)) {
|
||||
if (sender instanceof IPlayer) {
|
||||
startLinking((IPlayer) sender, ((GameCommandExecution) execution).getLabel());
|
||||
} else {
|
||||
sender.sendMessage(discordSRV.messagesConfig(sender).pleaseSpecifyPlayerAndUserToLink.asComponent());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sender.hasPermission(Permission.COMMAND_LINK_OTHER)) {
|
||||
sender.sendMessage(discordSRV.messagesConfig(sender).noPermission.asComponent());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LinkProvider linkProvider = discordSRV.linkProvider();
|
||||
if (!(linkProvider instanceof LinkStore)) {
|
||||
execution.send(new Text("Cannot create links using this link provider").withGameColor(NamedTextColor.DARK_RED));
|
||||
return;
|
||||
}
|
||||
|
||||
CompletableFuture<UUID> playerUUIDFuture = CommandUtil.lookupPlayer(discordSRV, logger, execution, false, playerArgument, null);
|
||||
CompletableFuture<Long> userIdFuture = CommandUtil.lookupUser(discordSRV, logger, execution, false, userArgument, null);
|
||||
|
||||
playerUUIDFuture.whenComplete((playerUUID, __) -> userIdFuture.whenComplete((userId, ___) -> {
|
||||
if (playerUUID == null) {
|
||||
execution.send(
|
||||
execution.messages().minecraft.playerNotFound.asComponent(),
|
||||
execution.messages().discord.playerNotFound
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (userId == null) {
|
||||
execution.send(
|
||||
execution.messages().minecraft.userNotFound.asComponent(),
|
||||
execution.messages().discord.userNotFound
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
CompletableFuture<IOfflinePlayer> playerFuture = CompletableFutureUtil.timeout(
|
||||
discordSRV,
|
||||
discordSRV.playerProvider().lookupOfflinePlayer(playerUUID),
|
||||
Duration.ofSeconds(5)
|
||||
);
|
||||
CompletableFuture<DiscordUser> userFuture = CompletableFutureUtil.timeout(
|
||||
discordSRV,
|
||||
discordSRV.discordAPI().retrieveUserById(userId),
|
||||
Duration.ofSeconds(5)
|
||||
);
|
||||
|
||||
linkProvider.queryUserId(playerUUID).whenComplete((linkedUser, t) -> {
|
||||
if (t != null) {
|
||||
logger.error("Failed to check linking status", t);
|
||||
execution.send(
|
||||
execution.messages().minecraft.unableToCheckLinkingStatus.asComponent(),
|
||||
execution.messages().discord.unableToCheckLinkingStatus
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (linkedUser.isPresent()) {
|
||||
execution.send(
|
||||
execution.messages().minecraft.playerAlreadyLinked3rd.asComponent(),
|
||||
execution.messages().discord.playerAlreadyLinked3rd
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
linkProvider.queryPlayerUUID(userId).whenComplete((linkedPlayer, t2) -> {
|
||||
if (t2 != null) {
|
||||
logger.error("Failed to check linking status", t2);
|
||||
execution.send(
|
||||
execution.messages().minecraft.unableToCheckLinkingStatus.asComponent(),
|
||||
execution.messages().discord.unableToCheckLinkingStatus
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (linkedPlayer.isPresent()) {
|
||||
execution.send(
|
||||
execution.messages().minecraft.userAlreadyLinked3rd.asComponent(),
|
||||
execution.messages().discord.userAlreadyLinked3rd
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
((LinkStore) linkProvider).createLink(playerUUID, userId).whenComplete((v, t3) -> {
|
||||
if (t3 != null) {
|
||||
logger.error("Failed to check linking status", t3);
|
||||
execution.send(
|
||||
execution.messages().minecraft.unableToLinkAtThisTime.asComponent(),
|
||||
execution.messages().discord.unableToCheckLinkingStatus
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
userFuture.whenComplete((user, ____) -> playerFuture.whenComplete((player, _____) -> execution.send(
|
||||
ComponentUtil.fromAPI(
|
||||
execution.messages().minecraft.nowLinked3rd.textBuilder()
|
||||
.applyPlaceholderService()
|
||||
.addContext(user, player)
|
||||
.addPlaceholder("user_id", userId)
|
||||
.addPlaceholder("player_uuid", playerUUID)
|
||||
.build()
|
||||
),
|
||||
discordSRV.placeholderService().replacePlaceholders(
|
||||
execution.messages().discord.nowLinked3rd,
|
||||
user,
|
||||
player,
|
||||
new SinglePlaceholder("user_id", userId),
|
||||
new SinglePlaceholder("player_uuid", playerUUID)
|
||||
)
|
||||
)));
|
||||
});
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
private void startLinking(IPlayer player, String label) {
|
||||
MessagesConfig.Minecraft messages = discordSRV.messagesConfig(player);
|
||||
|
||||
LinkProvider linkProvider = discordSRV.linkProvider();
|
||||
if (linkProvider.getCachedUserId(player.uniqueId()).isPresent()) {
|
||||
player.sendMessage(messages.alreadyLinked1st.asComponent());
|
||||
return;
|
||||
}
|
||||
|
||||
if (linkCheckRateLimit.getIfPresent(player.uniqueId()) != null) {
|
||||
player.sendMessage(messages.pleaseWaitBeforeRunningThatCommandAgain.asComponent());
|
||||
return;
|
||||
}
|
||||
linkCheckRateLimit.put(player.uniqueId(), true);
|
||||
|
||||
player.sendMessage(discordSRV.messagesConfig(player).checkingLinkStatus.asComponent());
|
||||
linkProvider.queryUserId(player.uniqueId(), true).whenComplete((userId, t1) -> {
|
||||
if (t1 != null) {
|
||||
logger.error("Failed to check linking status", t1);
|
||||
player.sendMessage(messages.unableToLinkAtThisTime.asComponent());
|
||||
return;
|
||||
}
|
||||
if (userId.isPresent()) {
|
||||
player.sendMessage(messages.nowLinked1st.asComponent());
|
||||
return;
|
||||
}
|
||||
|
||||
linkProvider.getLinkingInstructions(player, label).whenComplete((comp, t2) -> {
|
||||
if (t2 != null) {
|
||||
logger.error("Failed to link account", t2);
|
||||
player.sendMessage(messages.unableToLinkAtThisTime.asComponent());
|
||||
return;
|
||||
}
|
||||
|
||||
player.sendMessage(ComponentUtil.fromAPI(comp));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
package com.discordsrv.common.command.combined.commands;
|
||||
|
||||
import com.discordsrv.api.discord.entity.DiscordUser;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.CommandOption;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
|
||||
import com.discordsrv.api.placeholder.provider.SinglePlaceholder;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.combined.abstraction.CombinedCommand;
|
||||
import com.discordsrv.common.command.combined.abstraction.CommandExecution;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommand;
|
||||
import com.discordsrv.common.command.util.CommandUtil;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.logging.Logger;
|
||||
import com.discordsrv.common.logging.NamedLogger;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
import com.discordsrv.common.player.IOfflinePlayer;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class LinkedCommand extends CombinedCommand {
|
||||
|
||||
private static LinkedCommand INSTANCE;
|
||||
private static GameCommand GAME;
|
||||
private static DiscordCommand DISCORD;
|
||||
|
||||
private static LinkedCommand getInstance(DiscordSRV discordSRV) {
|
||||
return INSTANCE != null ? INSTANCE : (INSTANCE = new LinkedCommand(discordSRV));
|
||||
}
|
||||
|
||||
public static GameCommand getGame(DiscordSRV discordSRV) {
|
||||
if (GAME == null) {
|
||||
LinkedCommand command = getInstance(discordSRV);
|
||||
GAME = GameCommand.literal("linked")
|
||||
.then(
|
||||
GameCommand.stringGreedy("target")
|
||||
.requiredPermission(Permission.COMMAND_LINKED_OTHER)
|
||||
.executor(command)
|
||||
)
|
||||
.requiredPermission(Permission.COMMAND_LINKED)
|
||||
.executor(command);
|
||||
}
|
||||
|
||||
return GAME;
|
||||
}
|
||||
|
||||
public static DiscordCommand getDiscord(DiscordSRV discordSRV) {
|
||||
if (DISCORD == null) {
|
||||
LinkedCommand command = getInstance(discordSRV);
|
||||
DISCORD = DiscordCommand.chatInput(ComponentIdentifier.of("DiscordSRV", "linked"), "linked", "Get the linking status of a given user")
|
||||
.addOption(CommandOption.builder(
|
||||
CommandOption.Type.USER,
|
||||
"user",
|
||||
"The Discord user to check the linking status of"
|
||||
).setRequired(false).build())
|
||||
.addOption(CommandOption.builder(
|
||||
CommandOption.Type.STRING,
|
||||
"player",
|
||||
"The Minecraft player username or UUID to check the linking status of"
|
||||
).setRequired(false).build())
|
||||
.setEventHandler(command)
|
||||
.build();
|
||||
}
|
||||
|
||||
return DISCORD;
|
||||
}
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
public LinkedCommand(DiscordSRV discordSRV) {
|
||||
super(discordSRV);
|
||||
this.logger = new NamedLogger(discordSRV, "LINKED_COMMAND");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandExecution execution) {
|
||||
execution.setEphemeral(true);
|
||||
|
||||
execution.runAsync(() -> CommandUtil.lookupTarget(discordSRV, logger, execution, true, Permission.COMMAND_LINKED_OTHER)
|
||||
.whenComplete((result, t) -> {
|
||||
if (t != null) {
|
||||
logger.error("Failed to execute linked command", t);
|
||||
return;
|
||||
}
|
||||
if (result.isValid()) {
|
||||
processResult(result, execution);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private void processResult(CommandUtil.TargetLookupResult result, CommandExecution execution) {
|
||||
if (result.isPlayer()) {
|
||||
UUID playerUUID = result.getPlayerUUID();
|
||||
CompletableFuture<IOfflinePlayer> playerFuture = CompletableFutureUtil.timeout(
|
||||
discordSRV,
|
||||
discordSRV.playerProvider().lookupOfflinePlayer(playerUUID),
|
||||
Duration.ofSeconds(5)
|
||||
);
|
||||
|
||||
discordSRV.linkProvider().getUserId(playerUUID).whenComplete((optUserId, t) -> {
|
||||
if (t != null) {
|
||||
logger.error("Failed to check linking status during linked command", t);
|
||||
execution.send(
|
||||
execution.messages().minecraft.unableToCheckLinkingStatus.asComponent(),
|
||||
execution.messages().discord.unableToCheckLinkingStatus
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!optUserId.isPresent()) {
|
||||
playerFuture.whenComplete((player, ___) -> execution.send(
|
||||
ComponentUtil.fromAPI(
|
||||
execution.messages().minecraft.minecraftPlayerUnlinked
|
||||
.textBuilder()
|
||||
.applyPlaceholderService()
|
||||
.addContext(player)
|
||||
.addPlaceholder("player_uuid", playerUUID)
|
||||
.build()
|
||||
),
|
||||
discordSRV.placeholderService().replacePlaceholders(
|
||||
execution.messages().discord.minecraftPlayerUnlinked,
|
||||
player,
|
||||
new SinglePlaceholder("player_uuid", playerUUID)
|
||||
)
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
long userId = optUserId.get();
|
||||
CompletableFuture<DiscordUser> userFuture = CompletableFutureUtil.timeout(
|
||||
discordSRV,
|
||||
discordSRV.discordAPI().retrieveUserById(userId),
|
||||
Duration.ofSeconds(5)
|
||||
);
|
||||
|
||||
playerFuture.whenComplete((player, __) -> userFuture.whenComplete((user, ___) -> execution.send(
|
||||
ComponentUtil.fromAPI(
|
||||
execution.messages().minecraft.minecraftPlayerLinkedTo
|
||||
.textBuilder()
|
||||
.applyPlaceholderService()
|
||||
.addContext(player, user)
|
||||
.addPlaceholder("player_uuid", playerUUID)
|
||||
.addPlaceholder("user_id", userId)
|
||||
.build()
|
||||
),
|
||||
discordSRV.placeholderService().replacePlaceholders(
|
||||
execution.messages().discord.minecraftPlayerLinkedTo,
|
||||
player,
|
||||
user,
|
||||
new SinglePlaceholder("player_uuid", playerUUID),
|
||||
new SinglePlaceholder("user_id", userId)
|
||||
)
|
||||
)));
|
||||
});
|
||||
} else {
|
||||
long userId = result.getUserId();
|
||||
CompletableFuture<DiscordUser> userFuture = CompletableFutureUtil.timeout(
|
||||
discordSRV,
|
||||
discordSRV.discordAPI().retrieveUserById(userId),
|
||||
Duration.ofSeconds(5)
|
||||
);
|
||||
|
||||
discordSRV.linkProvider().getPlayerUUID(userId).whenComplete((optPlayerUUID, t) -> {
|
||||
if (t != null) {
|
||||
logger.error("Failed to check linking status during linked command", t);
|
||||
execution.send(
|
||||
execution.messages().minecraft.unableToCheckLinkingStatus.asComponent(),
|
||||
execution.messages().discord.unableToCheckLinkingStatus
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!optPlayerUUID.isPresent()) {
|
||||
userFuture.whenComplete((user, ___) -> execution.send(
|
||||
ComponentUtil.fromAPI(
|
||||
execution.messages().minecraft.discordUserUnlinked
|
||||
.textBuilder()
|
||||
.applyPlaceholderService()
|
||||
.addContext(user)
|
||||
.addPlaceholder("user_id", userId)
|
||||
.build()
|
||||
),
|
||||
discordSRV.placeholderService().replacePlaceholders(
|
||||
execution.messages().discord.discordUserUnlinked,
|
||||
user,
|
||||
new SinglePlaceholder("user_id", userId)
|
||||
)
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
UUID playerUUID = optPlayerUUID.get();
|
||||
CompletableFuture<IOfflinePlayer> playerFuture = CompletableFutureUtil.timeout(
|
||||
discordSRV,
|
||||
discordSRV.playerProvider().lookupOfflinePlayer(playerUUID),
|
||||
Duration.ofSeconds(5)
|
||||
);
|
||||
|
||||
userFuture.whenComplete((user, __) -> playerFuture.whenComplete((player, ___) -> execution.send(
|
||||
ComponentUtil.fromAPI(
|
||||
execution.messages().minecraft.discordUserLinkedTo
|
||||
.textBuilder()
|
||||
.applyPlaceholderService()
|
||||
.addContext(user, player)
|
||||
.addPlaceholder("user_id", userId)
|
||||
.addPlaceholder("player_uuid", playerUUID)
|
||||
.build()
|
||||
),
|
||||
discordSRV.placeholderService().replacePlaceholders(
|
||||
execution.messages().discord.discordUserLinkedTo,
|
||||
user,
|
||||
player,
|
||||
new SinglePlaceholder("user_id", userId),
|
||||
new SinglePlaceholder("player_uuid", playerUUID)
|
||||
)
|
||||
)));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.groupsync.GroupSyncModule;
|
||||
import com.discordsrv.common.groupsync.enums.GroupSyncCause;
|
||||
import com.discordsrv.common.groupsync.enums.GroupSyncResult;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
@ -34,7 +35,7 @@ public class ResyncCommand extends CombinedCommand {
|
||||
if (GAME == null) {
|
||||
ResyncCommand command = getInstance(discordSRV);
|
||||
GAME = GameCommand.literal("resync")
|
||||
.requiredPermission("discordsrv.admin.resync")
|
||||
.requiredPermission(Permission.COMMAND_RESYNC)
|
||||
.executor(command);
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ import com.discordsrv.common.command.combined.abstraction.CommandExecution;
|
||||
import com.discordsrv.common.command.combined.abstraction.Text;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommand;
|
||||
import com.discordsrv.common.debug.data.VersionInfo;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -48,7 +49,7 @@ public class VersionCommand extends CombinedCommand {
|
||||
if (GAME == null) {
|
||||
VersionCommand command = getInstance(discordSRV);
|
||||
GAME = GameCommand.literal("version")
|
||||
.requiredPermission("discordsrv.admin.version")
|
||||
.requiredPermission(Permission.COMMAND_VERSION)
|
||||
.executor(command);
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,10 @@ package com.discordsrv.common.command.discord.commands;
|
||||
import com.discordsrv.api.discord.entity.interaction.command.DiscordCommand;
|
||||
import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifier;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.combined.commands.DebugCommand;
|
||||
import com.discordsrv.common.command.combined.commands.ResyncCommand;
|
||||
import com.discordsrv.common.command.combined.commands.VersionCommand;
|
||||
import com.discordsrv.common.command.combined.commands.*;
|
||||
import com.discordsrv.common.command.discord.commands.subcommand.ExecuteCommand;
|
||||
import com.discordsrv.common.config.main.DiscordCommandConfig;
|
||||
import com.discordsrv.common.linking.LinkStore;
|
||||
|
||||
public class DiscordSRVDiscordCommand {
|
||||
|
||||
@ -22,11 +21,15 @@ public class DiscordSRVDiscordCommand {
|
||||
DiscordCommand.ChatInputBuilder builder = DiscordCommand.chatInput(IDENTIFIER, "discordsrv", "DiscordSRV related commands")
|
||||
.addSubCommand(DebugCommand.getDiscord(discordSRV))
|
||||
.addSubCommand(VersionCommand.getDiscord(discordSRV))
|
||||
.addSubCommand(ResyncCommand.getDiscord(discordSRV));
|
||||
.addSubCommand(ResyncCommand.getDiscord(discordSRV))
|
||||
.addSubCommand(LinkedCommand.getDiscord(discordSRV));
|
||||
|
||||
if (config.execute.enabled) {
|
||||
builder = builder.addSubCommand(ExecuteCommand.get(discordSRV));
|
||||
}
|
||||
if (discordSRV.linkProvider() instanceof LinkStore) {
|
||||
builder = builder.addSubCommand(LinkInitCommand.getDiscord(discordSRV));
|
||||
}
|
||||
|
||||
INSTANCE = builder
|
||||
.setGuildOnly(false)
|
||||
|
@ -18,6 +18,7 @@ import net.dv8tion.jda.api.interactions.InteractionHook;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Consumer;
|
||||
@ -207,7 +208,7 @@ public class ExecuteCommand implements Consumer<DiscordChatInputInteractionEvent
|
||||
synchronized (queued) {
|
||||
queued.offer(component);
|
||||
if (future == null) {
|
||||
future = discordSRV.scheduler().runLater(this::send, 500);
|
||||
future = discordSRV.scheduler().runLater(this::send, Duration.ofMillis(500));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,9 +20,9 @@ package com.discordsrv.common.command.game;
|
||||
|
||||
import com.discordsrv.api.DiscordSRVApi;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.combined.commands.LinkInitCommand;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommand;
|
||||
import com.discordsrv.common.command.game.commands.DiscordSRVGameCommand;
|
||||
import com.discordsrv.common.command.game.commands.subcommand.LinkCommand;
|
||||
import com.discordsrv.common.command.game.handler.ICommandHandler;
|
||||
import com.discordsrv.common.config.main.GameCommandConfig;
|
||||
import com.discordsrv.common.module.type.AbstractModule;
|
||||
@ -43,7 +43,7 @@ public class GameCommandModule extends AbstractModule<DiscordSRV> {
|
||||
super(discordSRV);
|
||||
this.primaryCommand = DiscordSRVGameCommand.get(discordSRV, "discordsrv");
|
||||
this.discordAlias = DiscordSRVGameCommand.get(discordSRV, "discord");
|
||||
this.linkCommand = LinkCommand.get(discordSRV);
|
||||
this.linkCommand = LinkInitCommand.getGame(discordSRV);
|
||||
|
||||
registerCommand(primaryCommand);
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ package com.discordsrv.common.command.game.abstraction;
|
||||
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import com.discordsrv.common.function.CheckedFunction;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
@ -176,6 +177,10 @@ public class GameCommand {
|
||||
return redirection;
|
||||
}
|
||||
|
||||
public GameCommand requiredPermission(Permission permission) {
|
||||
return requiredPermission(permission.permission());
|
||||
}
|
||||
|
||||
public GameCommand requiredPermission(String permission) {
|
||||
if (redirection != null) {
|
||||
throw new IllegalStateException("Cannot required permissions on a node with a redirection");
|
||||
|
@ -20,17 +20,15 @@ package com.discordsrv.common.command.game.commands;
|
||||
|
||||
import com.discordsrv.api.component.MinecraftComponent;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.combined.commands.DebugCommand;
|
||||
import com.discordsrv.common.command.combined.commands.ResyncCommand;
|
||||
import com.discordsrv.common.command.combined.commands.VersionCommand;
|
||||
import com.discordsrv.common.command.combined.commands.*;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommand;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
|
||||
import com.discordsrv.common.command.game.commands.subcommand.BroadcastCommand;
|
||||
import com.discordsrv.common.command.game.commands.subcommand.LinkCommand;
|
||||
import com.discordsrv.common.command.game.commands.subcommand.reload.ReloadCommand;
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@ -46,13 +44,14 @@ public class DiscordSRVGameCommand implements GameCommandExecutor {
|
||||
}
|
||||
return INSTANCES.computeIfAbsent(alias, key ->
|
||||
GameCommand.literal(alias)
|
||||
.requiredPermission("discordsrv.player.command")
|
||||
.requiredPermission(Permission.COMMAND_ROOT)
|
||||
.executor(COMMAND)
|
||||
.then(BroadcastCommand.discord(discordSRV))
|
||||
.then(BroadcastCommand.minecraft(discordSRV))
|
||||
.then(BroadcastCommand.json(discordSRV))
|
||||
.then(DebugCommand.getGame(discordSRV))
|
||||
.then(LinkCommand.get(discordSRV))
|
||||
.then(LinkInitCommand.getGame(discordSRV))
|
||||
.then(LinkedCommand.getGame(discordSRV))
|
||||
.then(ReloadCommand.get(discordSRV))
|
||||
.then(ResyncCommand.getGame(discordSRV))
|
||||
.then(VersionCommand.getGame(discordSRV))
|
||||
|
@ -31,6 +31,7 @@ import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.IChannelConfig;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
@ -69,7 +70,7 @@ public abstract class BroadcastCommand implements GameCommandExecutor, GameComma
|
||||
BroadcastCommand command = executor.get();
|
||||
consumer.accept(
|
||||
GameCommand.literal(label)
|
||||
.requiredPermission("discordsrv.admin.broadcast")
|
||||
.requiredPermission(Permission.COMMAND_BROADCAST)
|
||||
.then(
|
||||
GameCommand.string("channel")
|
||||
.suggester(command)
|
||||
|
@ -1,101 +0,0 @@
|
||||
/*
|
||||
* This file is part of DiscordSRV, licensed under the GPLv3 License
|
||||
* Copyright (c) 2016-2023 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.command.game.commands.subcommand;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommand;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.linking.LinkProvider;
|
||||
import com.discordsrv.common.linking.LinkStore;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class LinkCommand implements GameCommandExecutor {
|
||||
|
||||
private static GameCommand INSTANCE;
|
||||
|
||||
public static GameCommand get(DiscordSRV discordSRV) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = GameCommand.literal("link")
|
||||
.requiredPermission("discordsrv.player.link")
|
||||
.executor(new LinkCommand(discordSRV));
|
||||
}
|
||||
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final Cache<UUID, Boolean> linkCheckRateLimit;
|
||||
|
||||
public LinkCommand(DiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.linkCheckRateLimit = discordSRV.caffeineBuilder()
|
||||
.expireAfterWrite(LinkStore.LINKING_CODE_RATE_LIMIT)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(ICommandSender sender, GameCommandArguments arguments, String label) {
|
||||
if (!(sender instanceof IPlayer)) {
|
||||
sender.sendMessage(Component.text("Player only command").color(NamedTextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
IPlayer player = (IPlayer) sender;
|
||||
LinkProvider linkProvider = discordSRV.linkProvider();
|
||||
if (linkProvider.getCachedUserId(player.uniqueId()).isPresent()) {
|
||||
player.sendMessage(discordSRV.messagesConfig(player).alreadyLinked.asComponent());
|
||||
return;
|
||||
}
|
||||
|
||||
if (linkCheckRateLimit.getIfPresent(player.uniqueId()) != null) {
|
||||
player.sendMessage(discordSRV.messagesConfig(player).pleaseWaitBeforeRunningThatCommandAgain.asComponent());
|
||||
return;
|
||||
}
|
||||
linkCheckRateLimit.put(player.uniqueId(), true);
|
||||
|
||||
sender.sendMessage(discordSRV.messagesConfig(player).checkingLinkStatus.asComponent());
|
||||
linkProvider.queryUserId(player.uniqueId()).whenComplete((userId, t) -> {
|
||||
if (t != null) {
|
||||
sender.sendMessage(discordSRV.messagesConfig(player).unableToLinkAtThisTime.asComponent());
|
||||
return;
|
||||
}
|
||||
if (userId.isPresent()) {
|
||||
sender.sendMessage(discordSRV.messagesConfig(player).youAreNowLinked.asComponent());
|
||||
return;
|
||||
}
|
||||
|
||||
linkProvider.getLinkingInstructions(player, label).whenComplete((comp, t2) -> {
|
||||
if (t2 != null) {
|
||||
sender.sendMessage(discordSRV.messagesConfig(player).unableToLinkAtThisTime.asComponent());
|
||||
return;
|
||||
}
|
||||
|
||||
sender.sendMessage(ComponentUtil.fromAPI(comp));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import com.discordsrv.common.command.game.abstraction.GameCommandArguments;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandExecutor;
|
||||
import com.discordsrv.common.command.game.abstraction.GameCommandSuggester;
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
@ -44,7 +45,7 @@ public class ReloadCommand implements GameCommandExecutor, GameCommandSuggester
|
||||
if (INSTANCE == null) {
|
||||
ReloadCommand cmd = new ReloadCommand(discordSRV);
|
||||
INSTANCE = GameCommand.literal("reload")
|
||||
.requiredPermission("discordsrv.admin.reload")
|
||||
.requiredPermission(Permission.COMMAND_RELOAD)
|
||||
.executor(cmd)
|
||||
.then(
|
||||
GameCommand.stringGreedy("flags")
|
||||
|
@ -19,10 +19,15 @@
|
||||
package com.discordsrv.common.command.game.sender;
|
||||
|
||||
import com.discordsrv.common.command.game.executor.CommandExecutor;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
import net.kyori.adventure.audience.ForwardingAudience;
|
||||
|
||||
public interface ICommandSender extends ForwardingAudience.Single, CommandExecutor {
|
||||
|
||||
default boolean hasPermission(Permission permission) {
|
||||
return hasPermission(permission.permission());
|
||||
}
|
||||
|
||||
boolean hasPermission(String permission);
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,241 @@
|
||||
package com.discordsrv.common.command.util;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.command.combined.abstraction.CommandExecution;
|
||||
import com.discordsrv.common.command.combined.abstraction.DiscordCommandExecution;
|
||||
import com.discordsrv.common.command.combined.abstraction.GameCommandExecution;
|
||||
import com.discordsrv.common.command.game.sender.ICommandSender;
|
||||
import com.discordsrv.common.config.messages.MessagesConfig;
|
||||
import com.discordsrv.common.logging.Logger;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.discordsrv.common.uuid.util.UUIDUtil;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.utils.MiscUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public final class CommandUtil {
|
||||
|
||||
private CommandUtil() {}
|
||||
|
||||
public static CompletableFuture<UUID> lookupPlayer(
|
||||
DiscordSRV discordSRV,
|
||||
Logger logger,
|
||||
CommandExecution execution,
|
||||
boolean selfPermitted,
|
||||
String target,
|
||||
@Nullable Permission otherPermission
|
||||
) {
|
||||
return lookupTarget(discordSRV, logger, execution, target, selfPermitted, true, false, otherPermission)
|
||||
.thenApply((result) -> {
|
||||
if (result != null && result.isValid()) {
|
||||
return result.getPlayerUUID();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static CompletableFuture<Long> lookupUser(
|
||||
DiscordSRV discordSRV,
|
||||
Logger logger,
|
||||
CommandExecution execution,
|
||||
boolean selfPermitted,
|
||||
String target,
|
||||
@Nullable Permission otherPermission
|
||||
) {
|
||||
return lookupTarget(discordSRV, logger, execution, target, selfPermitted, false, true, otherPermission)
|
||||
.thenApply(result -> {
|
||||
if (result != null && result.isValid()) {
|
||||
return result.getUserId();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static CompletableFuture<TargetLookupResult> lookupTarget(
|
||||
DiscordSRV discordSRV,
|
||||
Logger logger,
|
||||
CommandExecution execution,
|
||||
boolean selfPermitted,
|
||||
@Nullable Permission otherPermission
|
||||
) {
|
||||
String target = execution.getArgument("target");
|
||||
if (target == null) {
|
||||
target = execution.getArgument("user");
|
||||
}
|
||||
if (target == null) {
|
||||
target = execution.getArgument("player");
|
||||
}
|
||||
return lookupTarget(discordSRV, logger, execution, target, selfPermitted, true, true, otherPermission);
|
||||
}
|
||||
|
||||
private static CompletableFuture<TargetLookupResult> lookupTarget(
|
||||
DiscordSRV discordSRV,
|
||||
Logger logger,
|
||||
CommandExecution execution,
|
||||
String target,
|
||||
boolean selfPermitted,
|
||||
boolean lookupPlayer,
|
||||
boolean lookupUser,
|
||||
@Nullable Permission otherPermission
|
||||
) {
|
||||
MessagesConfig messages = discordSRV.messagesConfig(execution.locale());
|
||||
|
||||
if (execution instanceof GameCommandExecution) {
|
||||
ICommandSender sender = ((GameCommandExecution) execution).getSender();
|
||||
if (target != null) {
|
||||
if (otherPermission != null && !sender.hasPermission(Permission.COMMAND_LINKED_OTHER)) {
|
||||
sender.sendMessage(discordSRV.messagesConfig(sender).noPermission.asComponent());
|
||||
return CompletableFuture.completedFuture(TargetLookupResult.INVALID);
|
||||
}
|
||||
} else if (sender instanceof IPlayer && selfPermitted && lookupPlayer) {
|
||||
target = ((IPlayer) sender).uniqueId().toString();
|
||||
}
|
||||
} else if (execution instanceof DiscordCommandExecution) {
|
||||
if (target == null) {
|
||||
if (selfPermitted && lookupUser) {
|
||||
target = Long.toUnsignedString(((DiscordCommandExecution) execution).getUser().getIdLong());
|
||||
} else {
|
||||
execution.send(
|
||||
messages.minecraft.pleaseSpecifyUser.asComponent(),
|
||||
messages.discord.pleaseSpecifyUser
|
||||
);
|
||||
return CompletableFuture.completedFuture(TargetLookupResult.INVALID);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected CommandExecution");
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
return CompletableFuture.completedFuture(requireTarget(execution, lookupUser, lookupPlayer, messages));
|
||||
}
|
||||
|
||||
if (lookupUser) {
|
||||
if (target.matches("\\d{17,22}")) {
|
||||
// Discord user id
|
||||
long id;
|
||||
try {
|
||||
id = MiscUtil.parseLong(target);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
execution.send(
|
||||
messages.minecraft.userNotFound.asComponent(),
|
||||
messages.discord.userNotFound
|
||||
);
|
||||
return CompletableFuture.completedFuture(TargetLookupResult.INVALID);
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(new TargetLookupResult(true, null, id));
|
||||
} else if (target.startsWith("@")) {
|
||||
// Discord username
|
||||
String username = target.substring(1);
|
||||
JDA jda = discordSRV.jda();
|
||||
if (jda != null) {
|
||||
List<User> users = jda.getUsersByName(username, true);
|
||||
|
||||
if (users.size() == 1) {
|
||||
return CompletableFuture.completedFuture(new TargetLookupResult(true, null, users.get(0).getIdLong()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lookupPlayer) {
|
||||
UUID uuid;
|
||||
boolean shortUUID;
|
||||
if ((shortUUID = target.length() == 32) || target.length() == 36) {
|
||||
// Player UUID
|
||||
try {
|
||||
if (shortUUID) {
|
||||
uuid = UUIDUtil.fromShort(target);
|
||||
} else {
|
||||
uuid = UUID.fromString(target);
|
||||
}
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
execution.send(
|
||||
messages.minecraft.playerNotFound.asComponent(),
|
||||
messages.discord.playerNotFound
|
||||
);
|
||||
return CompletableFuture.completedFuture(TargetLookupResult.INVALID);
|
||||
}
|
||||
return CompletableFuture.completedFuture(new TargetLookupResult(true, uuid, 0L));
|
||||
} else if (target.matches("[a-zA-Z0-9_]{1,16}")) {
|
||||
// Player name
|
||||
IPlayer playerByName = discordSRV.playerProvider().player(target);
|
||||
if (playerByName != null) {
|
||||
uuid = playerByName.uniqueId();
|
||||
} else {
|
||||
return discordSRV.playerProvider().lookupOfflinePlayer(target)
|
||||
.thenApply(offlinePlayer -> new TargetLookupResult(true, offlinePlayer.uniqueId(), 0L))
|
||||
.exceptionally(t -> {
|
||||
logger.error("Failed to lookup offline player by username", t);
|
||||
return TargetLookupResult.INVALID;
|
||||
});
|
||||
}
|
||||
return CompletableFuture.completedFuture(new TargetLookupResult(true, uuid, 0L));
|
||||
}
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(requireTarget(execution, lookupUser, lookupPlayer, messages));
|
||||
}
|
||||
|
||||
private static TargetLookupResult requireTarget(CommandExecution execution, boolean lookupUser, boolean lookupPlayer, MessagesConfig messages) {
|
||||
if (lookupPlayer && lookupUser) {
|
||||
execution.send(
|
||||
messages.minecraft.pleaseSpecifyPlayerOrUser.asComponent(),
|
||||
messages.discord.pleaseSpecifyPlayerOrUser
|
||||
);
|
||||
return TargetLookupResult.INVALID;
|
||||
} else if (lookupPlayer) {
|
||||
execution.send(
|
||||
messages.minecraft.pleaseSpecifyPlayer.asComponent(),
|
||||
messages.discord.pleaseSpecifyPlayer
|
||||
);
|
||||
return TargetLookupResult.INVALID;
|
||||
} else if (lookupUser) {
|
||||
execution.send(
|
||||
messages.minecraft.pleaseSpecifyUser.asComponent(),
|
||||
messages.discord.pleaseSpecifyUser
|
||||
);
|
||||
return TargetLookupResult.INVALID;
|
||||
} else {
|
||||
throw new IllegalStateException("lookupPlayer & lookupUser are false");
|
||||
}
|
||||
}
|
||||
|
||||
public static class TargetLookupResult {
|
||||
|
||||
public static TargetLookupResult INVALID = new TargetLookupResult(false, null, 0L);
|
||||
|
||||
private final boolean valid;
|
||||
private final UUID playerUUID;
|
||||
private final long userId;
|
||||
|
||||
public TargetLookupResult(boolean valid, UUID playerUUID, long userId) {
|
||||
this.valid = valid;
|
||||
this.playerUUID = playerUUID;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public boolean isPlayer() {
|
||||
return playerUUID != null;
|
||||
}
|
||||
|
||||
public UUID getPlayerUUID() {
|
||||
return playerUUID;
|
||||
}
|
||||
|
||||
public long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,8 +2,10 @@ package com.discordsrv.common.config.messages;
|
||||
|
||||
import com.discordsrv.common.config.Config;
|
||||
import com.discordsrv.common.config.configurate.annotation.Constants;
|
||||
import com.discordsrv.common.config.configurate.annotation.Untranslated;
|
||||
import com.discordsrv.common.config.helper.MinecraftMessage;
|
||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Comment;
|
||||
|
||||
@ConfigSerializable
|
||||
public class MessagesConfig implements Config {
|
||||
@ -19,30 +21,94 @@ public class MessagesConfig implements Config {
|
||||
|
||||
@ConfigSerializable
|
||||
public static class Minecraft {
|
||||
private static final String ERROR_COLOR = "&c";
|
||||
private static final String SUCCESS_COLOR = "&a";
|
||||
private static final String NEUTRAL_COLOR = "&b";
|
||||
|
||||
private MinecraftMessage make(String rawFormat) {
|
||||
return new MinecraftMessage(rawFormat);
|
||||
}
|
||||
|
||||
@Constants("&c")
|
||||
@Comment("Generic")
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage noPermission = make("%1Sorry, but you do not have permission to use that command");
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage pleaseSpecifyPlayer = make("%1Please specify the Minecraft player");
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage pleaseSpecifyUser = make("%1Please specify the Discord user");
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage pleaseSpecifyPlayerOrUser = make("%1Please specify the Minecraft player or Discord user");
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage playerNotFound = make("%1Minecraft player not found");
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage userNotFound = make("%1Discord user not found");
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage unableToCheckLinkingStatus = make("%1Unable to check linking status, please try again later");
|
||||
@Constants("&c")
|
||||
public MinecraftMessage alreadyLinked = make("%1You are already linked");
|
||||
@Constants("&c")
|
||||
public MinecraftMessage pleaseWaitBeforeRunningThatCommandAgain = make("%1Please wait before running that command again");
|
||||
@Constants("&c")
|
||||
public MinecraftMessage unableToLinkAtThisTime = make("%1Unable to check linking status, please try again later");
|
||||
@Constants("&b")
|
||||
public MinecraftMessage checkingLinkStatus = make("%1Checking linking status...");
|
||||
|
||||
@Constants("&b")
|
||||
public MinecraftMessage youAreNowLinked = make("%1You are now linked!");
|
||||
@Untranslated(Untranslated.Type.COMMENT)
|
||||
@Comment("/discord link")
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage alreadyLinked1st = make("%1You are already linked");
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage pleaseSpecifyPlayerAndUserToLink = make("%1Please specify the Minecraft player and the Discord user to link");
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage playerAlreadyLinked3rd = make("%1That player is already linked");
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage userAlreadyLinked3rd = make("%1That player is already linked");
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage pleaseWaitBeforeRunningThatCommandAgain = make("%1Please wait before running that command again");
|
||||
@Constants(ERROR_COLOR)
|
||||
public MinecraftMessage unableToLinkAtThisTime = make("%1Unable to check linking status, please try again later");
|
||||
@Constants(NEUTRAL_COLOR)
|
||||
public MinecraftMessage checkingLinkStatus = make("%1Checking linking status...");
|
||||
@Constants(SUCCESS_COLOR)
|
||||
public MinecraftMessage nowLinked1st = make("%1You are now linked!");
|
||||
@Constants({
|
||||
SUCCESS_COLOR,
|
||||
NEUTRAL_COLOR,
|
||||
SUCCESS_COLOR + "[hover:show_text:%player_uuid%][click:copy_to_clipboard:%player_uuid%]%player_name|text:'<Unknown>'%[click][hover]" + NEUTRAL_COLOR,
|
||||
SUCCESS_COLOR + "[hover:show_text:%user_id%][click:copy_to_clipboard:%user_id%]@%user_name%[click][hover]" + NEUTRAL_COLOR
|
||||
})
|
||||
public MinecraftMessage nowLinked3rd = make("%1Link created successfully %2(%3 and %4)");
|
||||
@Constants({
|
||||
NEUTRAL_COLOR,
|
||||
"&f[click:open_url:%minecraftauth_link%][hover:show_text:Click to open]%minecraftauth_link_simple%[click]" + NEUTRAL_COLOR,
|
||||
"&fMinecraftAuth"
|
||||
})
|
||||
public MinecraftMessage minecraftAuthLinking = make("%1Please visit %2 to link your account through %4");
|
||||
|
||||
@Untranslated(Untranslated.Type.COMMENT)
|
||||
@Comment("/discord linked")
|
||||
@Constants({
|
||||
SUCCESS_COLOR + "[hover:show_text:%user_id%][click:copy_to_clipboard:%user_id%]@%user_name%[click][hover]",
|
||||
NEUTRAL_COLOR,
|
||||
SUCCESS_COLOR + "[hover:show_text:%player_uuid%][click:copy_to_clipboard:%player_uuid%]%player_name|text:'<Unknown>'%[click][hover]"
|
||||
})
|
||||
public MinecraftMessage discordUserLinkedTo = make("%1 %2is linked to %3");
|
||||
|
||||
@Untranslated(Untranslated.Type.COMMENT)
|
||||
@Comment("/discord linked")
|
||||
@Constants({
|
||||
SUCCESS_COLOR + "[hover:show_text:%user_id%][click:copy_to_clipboard:%user_id%]@%user_name%[click][hover]",
|
||||
NEUTRAL_COLOR,
|
||||
ERROR_COLOR
|
||||
})
|
||||
public MinecraftMessage discordUserUnlinked = make("%1 %2is %3unlinked");
|
||||
|
||||
@Constants({
|
||||
"&b",
|
||||
"&7[click:open_url:%minecraftauth_link%][hover:show_text:Click to open]%minecraftauth_link_simple%[click]&b",
|
||||
"&7MinecraftAuth"
|
||||
SUCCESS_COLOR + "[hover:show_text:%player_uuid%][click:copy_to_clipboard:%player_uuid%]%player_name|text:'<Unknown>'%[click][hover]",
|
||||
NEUTRAL_COLOR,
|
||||
SUCCESS_COLOR + "[hover:show_text:%user_id%][click:copy_to_clipboard:%user_id%]@%user_name%[click][hover]"
|
||||
})
|
||||
public MinecraftMessage minecraftAuthLinking = make("%1Please visit %2 to link your account through %3");
|
||||
public MinecraftMessage minecraftPlayerLinkedTo = make("%1 %2is linked to %3");
|
||||
|
||||
@Constants({
|
||||
SUCCESS_COLOR + "[hover:show_text:%player_uuid%][click:copy_to_clipboard:%player_uuid%]%player_name|text:'<Unknown>'%[click][hover]",
|
||||
NEUTRAL_COLOR,
|
||||
ERROR_COLOR
|
||||
})
|
||||
public MinecraftMessage minecraftPlayerUnlinked = make("%1 %2is %3unlinked");
|
||||
|
||||
}
|
||||
|
||||
public Discord discord = new Discord();
|
||||
@ -50,7 +116,65 @@ public class MessagesConfig implements Config {
|
||||
@ConfigSerializable
|
||||
public static class Discord {
|
||||
|
||||
}
|
||||
private static final String SUCCESS_PREFIX = "✅ ";
|
||||
private static final String INPUT_ERROR_PREFIX = "\uD83D\uDDD2️ ";
|
||||
private static final String ERROR_PREFIX = "❌ ";
|
||||
|
||||
@Comment("Generic")
|
||||
@Constants(INPUT_ERROR_PREFIX)
|
||||
public String pleaseSpecifyPlayer = "%1Please specify the Minecraft player";
|
||||
@Constants(INPUT_ERROR_PREFIX)
|
||||
public String pleaseSpecifyUser = "%1Please specify the Discord user";
|
||||
@Constants(INPUT_ERROR_PREFIX)
|
||||
public String pleaseSpecifyPlayerOrUser = "%1Please specify the Minecraft player or Discord user";
|
||||
@Constants(ERROR_PREFIX)
|
||||
public String playerNotFound = "%1Minecraft player not found";
|
||||
@Constants(ERROR_PREFIX)
|
||||
public String userNotFound = "%1Discord user not found";
|
||||
@Constants(ERROR_PREFIX)
|
||||
public String unableToCheckLinkingStatus = "%1Unable to check linking status, please try again later";
|
||||
|
||||
@Untranslated(Untranslated.Type.COMMENT)
|
||||
@Comment("/discord link")
|
||||
@Constants(ERROR_PREFIX)
|
||||
public String playerAlreadyLinked3rd = "%1That Minecraft player is already linked";
|
||||
@Constants(ERROR_PREFIX)
|
||||
public String userAlreadyLinked3rd = "%1That Discord user is already linked";
|
||||
@Constants({
|
||||
SUCCESS_PREFIX,
|
||||
"**%player_name%** (%player_uuid%)",
|
||||
"**%user_name%** (%user_id%)"
|
||||
})
|
||||
public String nowLinked3rd = "%1Link created successfully\n%2 and %3";
|
||||
|
||||
@Untranslated(Untranslated.Type.COMMENT)
|
||||
@Comment("/discord linked")
|
||||
@Constants({
|
||||
SUCCESS_PREFIX,
|
||||
"**%user_name%** (<@%user_id%>)",
|
||||
"**%player_name%** (%player_uuid%)"
|
||||
})
|
||||
public String discordUserLinkedTo = "%1%2 is linked to %3";
|
||||
|
||||
@Untranslated(Untranslated.Type.COMMENT)
|
||||
@Comment("/discord linked")
|
||||
@Constants({
|
||||
ERROR_PREFIX,
|
||||
"**%user_name%** (%user_id%)"
|
||||
})
|
||||
public String discordUserUnlinked = "%1%2 is __unlinked__";
|
||||
|
||||
@Constants({
|
||||
SUCCESS_PREFIX,
|
||||
"**%player_name%** (%player_uuid%)",
|
||||
"**%user_name%** (<@%user_id%>)"
|
||||
})
|
||||
public String minecraftPlayerLinkedTo = "%1%2 is linked to %3";
|
||||
|
||||
@Constants({
|
||||
ERROR_PREFIX,
|
||||
"**%player_name%** (%player_uuid%)"
|
||||
})
|
||||
public String minecraftPlayerUnlinked = "%1%2 is __unlinked__";
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import net.dv8tion.jda.api.entities.Message;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
@ -196,7 +197,7 @@ public class SingleConsoleHandler {
|
||||
if (config.appender.outputMode == ConsoleConfig.OutputMode.OFF) {
|
||||
return;
|
||||
}
|
||||
this.queueProcessingFuture = discordSRV.scheduler().runLater(this::processQueue, 2, TimeUnit.SECONDS);
|
||||
this.queueProcessingFuture = discordSRV.scheduler().runLater(this::processQueue, Duration.ofSeconds(2));
|
||||
}
|
||||
|
||||
private void processQueue() {
|
||||
|
@ -69,6 +69,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.InterruptedIOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
@ -303,9 +304,8 @@ public class JDAConnectionManager implements DiscordConnectionManager {
|
||||
);
|
||||
this.failureCallbackFuture = discordSRV.scheduler().runAtFixedRate(
|
||||
this::checkDefaultFailureCallback,
|
||||
30,
|
||||
120,
|
||||
TimeUnit.SECONDS
|
||||
Duration.ofSeconds(30),
|
||||
Duration.ofSeconds(120)
|
||||
);
|
||||
|
||||
MemberCachingConfig memberCachingConfig = discordSRV.config().memberCaching;
|
||||
|
@ -18,10 +18,15 @@
|
||||
|
||||
package com.discordsrv.common.future.util;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public final class CompletableFutureUtil {
|
||||
|
||||
@ -58,4 +63,17 @@ public final class CompletableFutureUtil {
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
public static <T> CompletableFuture<T> timeout(DiscordSRV discordSRV, CompletableFuture<T> future, Duration timeout) {
|
||||
ScheduledFuture<?> scheduledFuture = discordSRV.scheduler().runLater(() -> {
|
||||
if (!future.isDone()) {
|
||||
future.completeExceptionally(new TimeoutException());
|
||||
}
|
||||
}, timeout);
|
||||
return future.whenComplete((__, t) -> {
|
||||
if (t == null) {
|
||||
scheduledFuture.cancel(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import com.github.benmanes.caffeine.cache.Cache;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@ -127,9 +128,8 @@ public class GroupSyncModule extends AbstractModule<DiscordSRV> {
|
||||
int cycleTime = timer.cycleTime;
|
||||
future = discordSRV.scheduler().runAtFixedRate(
|
||||
() -> resyncPair(pair, GroupSyncCause.TIMER),
|
||||
cycleTime,
|
||||
cycleTime,
|
||||
TimeUnit.MINUTES
|
||||
Duration.ofMinutes(cycleTime),
|
||||
Duration.ofMinutes(cycleTime)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,45 @@
|
||||
package com.discordsrv.common.http.util;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.exception.MessageException;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public final class HttpUtil {
|
||||
|
||||
private HttpUtil() {}
|
||||
|
||||
public static ResponseBody checkIfResponseSuccessful(Request request, Response response) throws IOException {
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody == null || !response.isSuccessful()) {
|
||||
String responseString = responseBody == null
|
||||
? "response body is null"
|
||||
: StringUtils.substring(responseBody.string(), 0, 500);
|
||||
throw new MessageException("Request to " + request.url().host() + " failed: " + response.code() + ": " + responseString);
|
||||
}
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
public static <T> CompletableFuture<T> readJson(DiscordSRV discordSRV, Request request, Class<T> type) {
|
||||
CompletableFuture<T> future = new CompletableFuture<>();
|
||||
discordSRV.scheduler().run(() -> {
|
||||
try (Response response = discordSRV.httpClient().newCall(request).execute()) {
|
||||
ResponseBody responseBody = checkIfResponseSuccessful(request, response);
|
||||
|
||||
T result = discordSRV.json().readValue(responseBody.byteStream(), type);
|
||||
if (result == null) {
|
||||
throw new MessageException("Response json cannot be parsed");
|
||||
}
|
||||
future.complete(result);
|
||||
} catch (Throwable t) {
|
||||
future.completeExceptionally(t);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
}
|
@ -30,7 +30,11 @@ import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface LinkProvider {
|
||||
|
||||
CompletableFuture<Optional<Long>> queryUserId(@NotNull UUID playerUUID);
|
||||
default CompletableFuture<Optional<Long>> queryUserId(@NotNull UUID playerUUID) {
|
||||
return queryUserId(playerUUID, false);
|
||||
}
|
||||
|
||||
CompletableFuture<Optional<Long>> queryUserId(@NotNull UUID playerUUID, boolean canCauseLink);
|
||||
|
||||
default CompletableFuture<Optional<Long>> getUserId(@NotNull UUID playerUUID) {
|
||||
Optional<Long> userId = getCachedUserId(playerUUID);
|
||||
@ -44,7 +48,11 @@ public interface LinkProvider {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
CompletableFuture<Optional<UUID>> queryPlayerUUID(long userId);
|
||||
default CompletableFuture<Optional<UUID>> queryPlayerUUID(long userId) {
|
||||
return queryPlayerUUID(userId, false);
|
||||
}
|
||||
|
||||
CompletableFuture<Optional<UUID>> queryPlayerUUID(long userId, boolean canCauseLink);
|
||||
|
||||
default CompletableFuture<Optional<UUID>> getPlayerUUID(long userId) {
|
||||
Optional<UUID> playerUUID = getCachedPlayerUUID(userId);
|
||||
|
@ -22,14 +22,19 @@ import com.discordsrv.api.event.bus.Subscribe;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.linking.LinkProvider;
|
||||
import com.discordsrv.common.player.event.PlayerConnectedEvent;
|
||||
import com.github.benmanes.caffeine.cache.*;
|
||||
import com.github.benmanes.caffeine.cache.AsyncCacheLoader;
|
||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Expiry;
|
||||
import org.checkerframework.checker.index.qual.NonNegative;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -41,6 +46,7 @@ public abstract class CachedLinkProvider implements LinkProvider {
|
||||
protected final DiscordSRV discordSRV;
|
||||
private final Cache<Long, UUID> userToPlayer;
|
||||
private final AsyncLoadingCache<UUID, Long> playerToUser;
|
||||
private final Set<UUID> linkingAllowed = new CopyOnWriteArraySet<>();
|
||||
|
||||
public CachedLinkProvider(DiscordSRV discordSRV) {
|
||||
this.discordSRV = discordSRV;
|
||||
@ -86,7 +92,7 @@ public abstract class CachedLinkProvider implements LinkProvider {
|
||||
.buildAsync(new AsyncCacheLoader<UUID, Long>() {
|
||||
@Override
|
||||
public @NonNull CompletableFuture<Long> asyncLoad(@NonNull UUID key, @NonNull Executor executor) {
|
||||
return queryUserId(key).thenApply(opt -> opt.orElse(UNLINKED_USER));
|
||||
return queryUserId(key, linkingAllowed.remove(key)).thenApply(opt -> opt.orElse(UNLINKED_USER));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -150,6 +156,9 @@ public abstract class CachedLinkProvider implements LinkProvider {
|
||||
@Subscribe
|
||||
public void onPlayerConnected(PlayerConnectedEvent event) {
|
||||
// Cache logged in players
|
||||
playerToUser.get(event.player().uniqueId());
|
||||
UUID uuid = event.player().uniqueId();
|
||||
linkingAllowed.add(uuid);
|
||||
playerToUser.get(uuid);
|
||||
linkingAllowed.remove(uuid);
|
||||
}
|
||||
}
|
||||
|
@ -58,8 +58,9 @@ public class MinecraftAuthenticationLinker extends CachedLinkProvider implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<Long>> queryUserId(@NotNull UUID playerUUID) {
|
||||
public CompletableFuture<Optional<Long>> queryUserId(@NotNull UUID playerUUID, boolean canCauseLink) {
|
||||
return query(
|
||||
canCauseLink,
|
||||
() -> AuthService.lookup(AccountType.MINECRAFT, playerUUID.toString(), AccountType.DISCORD)
|
||||
.map(account -> (DiscordAccount) account)
|
||||
.map(discord -> Long.parseUnsignedLong(discord.getUserId())),
|
||||
@ -73,8 +74,9 @@ public class MinecraftAuthenticationLinker extends CachedLinkProvider implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<UUID>> queryPlayerUUID(long userId) {
|
||||
public CompletableFuture<Optional<UUID>> queryPlayerUUID(long userId, boolean canCauseLink) {
|
||||
return query(
|
||||
canCauseLink,
|
||||
() -> AuthService.lookup(AccountType.DISCORD, Long.toUnsignedString(userId), AccountType.MINECRAFT)
|
||||
.map(account -> (MinecraftAccount) account)
|
||||
.map(MinecraftAccount::getUUID),
|
||||
@ -163,6 +165,7 @@ public class MinecraftAuthenticationLinker extends CachedLinkProvider implements
|
||||
}
|
||||
|
||||
private <T> CompletableFuture<Optional<T>> query(
|
||||
boolean canCauseLink,
|
||||
CheckedSupplier<Optional<T>> authSupplier,
|
||||
Supplier<CompletableFuture<Optional<T>>> storageSupplier,
|
||||
Consumer<T> linked,
|
||||
@ -177,6 +180,9 @@ public class MinecraftAuthenticationLinker extends CachedLinkProvider implements
|
||||
authService.completeExceptionally(t);
|
||||
}
|
||||
});
|
||||
if (!canCauseLink) {
|
||||
return authService;
|
||||
}
|
||||
|
||||
CompletableFuture<Optional<T>> storageFuture = storageSupplier.get();
|
||||
return CompletableFutureUtil.combine(authService, storageFuture).thenApply(results -> {
|
||||
|
@ -40,7 +40,7 @@ public class StorageLinker extends CachedLinkProvider implements LinkProvider, L
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<Long>> queryUserId(@NotNull UUID playerUUID) {
|
||||
public CompletableFuture<Optional<Long>> queryUserId(@NotNull UUID playerUUID, boolean canCauseLink) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
Long value = discordSRV.storage().getUserId(playerUUID);
|
||||
return Optional.ofNullable(value);
|
||||
@ -48,7 +48,7 @@ public class StorageLinker extends CachedLinkProvider implements LinkProvider, L
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<UUID>> queryPlayerUUID(long userId) {
|
||||
public CompletableFuture<Optional<UUID>> queryPlayerUUID(long userId, boolean canCauseLink) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
UUID value = discordSRV.storage().getPlayerUUID(userId);
|
||||
return Optional.ofNullable(value);
|
||||
|
@ -79,7 +79,7 @@ public abstract class ServerRequireLinkingModule<T extends DiscordSRV> extends R
|
||||
return CompletableFuture.completedFuture(message);
|
||||
}
|
||||
|
||||
return linkProvider.queryUserId(playerUUID)
|
||||
return linkProvider.queryUserId(playerUUID, true)
|
||||
.thenCompose(opt -> {
|
||||
if (!opt.isPresent()) {
|
||||
// User is not linked
|
||||
|
@ -37,13 +37,13 @@ import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class DiscordSRVLogger implements Logger {
|
||||
|
||||
@ -111,7 +111,7 @@ public class DiscordSRVLogger implements Logger {
|
||||
}
|
||||
return logs;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
doLog("LOGGING", LogLevel.ERROR, "Failed to rotate log", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -180,7 +180,7 @@ public class DiscordSRVLogger implements Logger {
|
||||
linesToWrite.add(entry);
|
||||
synchronized (lineProcessingLock) {
|
||||
if (lineProcessingFuture == null || lineProcessingFuture.isDone()) {
|
||||
lineProcessingFuture = discordSRV.scheduler().runLater(this::processLines, TimeUnit.SECONDS.toMillis(2));
|
||||
lineProcessingFuture = discordSRV.scheduler().runLater(this::processLines, Duration.ofSeconds(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ import net.kyori.adventure.text.Component;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
@ -133,7 +134,7 @@ public class DiscordChatMessageModule extends AbstractModule<DiscordSRV> {
|
||||
MessageSend send = new MessageSend(message, gameChannel, config);
|
||||
|
||||
sends.put(key, send);
|
||||
send.setFuture(discordSRV.scheduler().runLater(() -> processSend(key), delayMillis));
|
||||
send.setFuture(discordSRV.scheduler().runLater(() -> processSend(key), Duration.ofMillis(delayMillis)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,7 @@ import com.discordsrv.common.config.main.channels.MinecraftToDiscordChatConfig;
|
||||
import com.discordsrv.common.config.main.channels.base.BaseChannelConfig;
|
||||
import com.discordsrv.common.future.util.CompletableFutureUtil;
|
||||
import com.discordsrv.common.messageforwarding.game.AbstractGameMessageModule;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
@ -126,7 +127,7 @@ public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<Mine
|
||||
MentionCachingModule mentionCaching = discordSRV.getModule(MentionCachingModule.class);
|
||||
|
||||
if (mentionCaching != null && mentionConfig.users && mentionConfig.uncachedUsers
|
||||
&& player.hasPermission("discordsrv.mention.lookup.user")) {
|
||||
&& player.hasPermission(Permission.MENTION_USER_LOOKUP)) {
|
||||
List<CompletableFuture<List<MentionCachingModule.CachedMention>>> futures = new ArrayList<>();
|
||||
|
||||
String messageContent = discordSRV.componentFactory().plainSerializer().serialize(message);
|
||||
@ -184,23 +185,23 @@ public class MinecraftToDiscordChatModule extends AbstractGameMessageModule<Mine
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<AllowedMention> allowedMentions = new ArrayList<>();
|
||||
if (mentionConfig.users && player.hasPermission("discordsrv.mention.user")) {
|
||||
if (mentionConfig.users && player.hasPermission(Permission.MENTION_USER)) {
|
||||
allowedMentions.add(AllowedMention.ALL_USERS);
|
||||
}
|
||||
if (mentionConfig.roles) {
|
||||
if (player.hasPermission("discordsrv.mention.roles.mentionable")) {
|
||||
if (player.hasPermission(Permission.MENTION_ROLE_MENTIONABLE)) {
|
||||
for (Role role : guild.getRoles()) {
|
||||
if (role.isMentionable()) {
|
||||
allowedMentions.add(AllowedMention.role(role.getIdLong()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (player.hasPermission("discordsrv.mention.roles.all")) {
|
||||
if (player.hasPermission(Permission.MENTION_ROLE_ALL)) {
|
||||
allowedMentions.add(AllowedMention.ALL_ROLES);
|
||||
}
|
||||
}
|
||||
|
||||
boolean everyone = mentionConfig.everyone && player.hasPermission("discordsrv.mention.everyone");
|
||||
boolean everyone = mentionConfig.everyone && player.hasPermission(Permission.MENTION_EVERYONE);
|
||||
if (everyone) {
|
||||
allowedMentions.add(AllowedMention.EVERYONE);
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
package com.discordsrv.common.permission;
|
||||
|
||||
public enum Permission {
|
||||
|
||||
// Commands
|
||||
// Admin
|
||||
COMMAND_DEBUG("command.admin.debug"),
|
||||
COMMAND_RELOAD("command.admin.reload"),
|
||||
COMMAND_BROADCAST("command.admin.broadcast"),
|
||||
COMMAND_RESYNC("command.admin.resync"),
|
||||
COMMAND_VERSION("command.admin.version"),
|
||||
// Player
|
||||
COMMAND_ROOT("command.player.root"),
|
||||
COMMAND_LINK("command.player.link.base"),
|
||||
COMMAND_LINK_OTHER("command.player.link.other"),
|
||||
COMMAND_LINKED("command.player.linked.base"),
|
||||
COMMAND_LINKED_OTHER("command.player.linked.other"),
|
||||
|
||||
// Mentions
|
||||
MENTION_USER("mention.user.base"),
|
||||
MENTION_USER_LOOKUP("mention.user.lookup"),
|
||||
MENTION_ROLE_MENTIONABLE("mention.role.mentionable"),
|
||||
MENTION_ROLE_ALL("mention.role.all"),
|
||||
MENTION_EVERYONE("mention.everyone"),
|
||||
|
||||
// Misc
|
||||
UPDATE_NOTIFICATION("updatenotification"),
|
||||
;
|
||||
|
||||
private final String permission;
|
||||
|
||||
Permission(String permission) {
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
public String permission() {
|
||||
return "discordsrv." + permission;
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ package com.discordsrv.common.player;
|
||||
|
||||
import com.discordsrv.api.placeholder.annotation.Placeholder;
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.player.provider.model.SkinInfo;
|
||||
import com.discordsrv.common.profile.Profile;
|
||||
import net.kyori.adventure.identity.Identified;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@ -48,4 +49,21 @@ public interface IOfflinePlayer extends Identified {
|
||||
default UUID uniqueId() {
|
||||
return identity().uuid();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
SkinInfo skinInfo();
|
||||
|
||||
@Placeholder("player_skin_texture_id")
|
||||
@Nullable
|
||||
default String skinTextureId() {
|
||||
SkinInfo info = skinInfo();
|
||||
return info != null ? info.textureId() : null;
|
||||
}
|
||||
|
||||
@Placeholder("player_skin_model")
|
||||
@Nullable
|
||||
default String skinModel() {
|
||||
SkinInfo info = skinInfo();
|
||||
return info != null ? info.model() : null;
|
||||
}
|
||||
}
|
||||
|
@ -63,11 +63,6 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende
|
||||
return uniqueId().toString().replace("-", "");
|
||||
}
|
||||
|
||||
@Placeholder("player_texture")
|
||||
default @Nullable String textureId() {
|
||||
return null; // TODO: implement
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Placeholder("player_display_name")
|
||||
Component displayName();
|
||||
@ -81,7 +76,7 @@ public interface IPlayer extends DiscordSRVPlayer, IOfflinePlayer, ICommandSende
|
||||
|
||||
if (avatarConfig.autoDecideAvatarUrl) {
|
||||
// Offline mode
|
||||
if (uniqueId().version() == 3) avatarUrlTemplate = "https://cravatar.eu/helmavatar/%player_name%/128.png#%texture%";
|
||||
if (uniqueId().version() == 3) avatarUrlTemplate = "https://cravatar.eu/helmavatar/%player_name%/128.png#%player_skin_texture_id%";
|
||||
// Bedrock
|
||||
else if (uniqueId().getLeastSignificantBits() == 0) avatarUrlTemplate = "https://api.tydiumcraft.net/skin?uuid=%player_uuid_nodashes%&type=avatar&size=128";
|
||||
}
|
||||
|
@ -0,0 +1,44 @@
|
||||
package com.discordsrv.common.player;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.player.provider.model.SkinInfo;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class OfflinePlayer implements IOfflinePlayer {
|
||||
|
||||
private final DiscordSRV discordSRV;
|
||||
private final String username;
|
||||
private final Identity identity;
|
||||
private final SkinInfo skinInfo;
|
||||
|
||||
public OfflinePlayer(DiscordSRV discordSRV, String username, UUID uuid, SkinInfo skinInfo) {
|
||||
this.discordSRV = discordSRV;
|
||||
this.username = username;
|
||||
this.identity = Identity.identity(uuid);
|
||||
this.skinInfo = skinInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscordSRV discordSRV() {
|
||||
return discordSRV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String username() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Identity identity() {
|
||||
return identity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SkinInfo skinInfo() {
|
||||
return skinInfo;
|
||||
}
|
||||
}
|
@ -30,6 +30,14 @@ public abstract class ServerPlayerProvider<T extends IPlayer, DT extends Discord
|
||||
super(discordSRV);
|
||||
}
|
||||
|
||||
public abstract CompletableFuture<IOfflinePlayer> offlinePlayer(UUID uuid);
|
||||
public abstract CompletableFuture<IOfflinePlayer> offlinePlayer(String username);
|
||||
@Override
|
||||
public CompletableFuture<UUID> lookupUUIDForUsername(String username) {
|
||||
return lookupOfflinePlayer(username).thenApply(IOfflinePlayer::uniqueId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract CompletableFuture<IOfflinePlayer> lookupOfflinePlayer(String username);
|
||||
|
||||
@Override
|
||||
public abstract CompletableFuture<IOfflinePlayer> lookupOfflinePlayer(UUID uuid);
|
||||
}
|
||||
|
@ -19,9 +19,18 @@
|
||||
package com.discordsrv.common.player.provider;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.http.util.HttpUtil;
|
||||
import com.discordsrv.common.player.IOfflinePlayer;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.discordsrv.common.player.OfflinePlayer;
|
||||
import com.discordsrv.common.player.event.PlayerConnectedEvent;
|
||||
import com.discordsrv.common.player.event.PlayerDisconnectedEvent;
|
||||
import com.discordsrv.common.player.provider.model.GameProfileResponse;
|
||||
import com.discordsrv.common.player.provider.model.SkinInfo;
|
||||
import com.discordsrv.common.player.provider.model.Textures;
|
||||
import com.discordsrv.common.player.provider.model.UUIDResponse;
|
||||
import com.discordsrv.common.uuid.util.UUIDUtil;
|
||||
import okhttp3.Request;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -29,12 +38,17 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public abstract class AbstractPlayerProvider<T extends IPlayer, DT extends DiscordSRV> implements PlayerProvider<T> {
|
||||
|
||||
private static final String MOJANG_API_URL = "https://api.mojang.com";
|
||||
private static final String USERNAME_TO_UUID_URL = MOJANG_API_URL + "/users/profiles/minecraft/%s";
|
||||
private static final String UUID_TO_PROFILE_URL = MOJANG_API_URL + "/session/minecraft/profile/%s";
|
||||
|
||||
private final Map<UUID, T> players = new ConcurrentHashMap<>();
|
||||
private final List<T> allPlayers = new CopyOnWriteArrayList<>();
|
||||
protected final DT discordSRV;
|
||||
@ -88,4 +102,47 @@ public abstract class AbstractPlayerProvider<T extends IPlayer, DT extends Disco
|
||||
public @NotNull Collection<T> allPlayers() {
|
||||
return allPlayers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<UUID> lookupUUIDForUsername(String username) {
|
||||
IPlayer player = player(username);
|
||||
if (player != null) {
|
||||
return CompletableFuture.completedFuture(player.uniqueId());
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(String.format(USERNAME_TO_UUID_URL, username))
|
||||
.get()
|
||||
.build();
|
||||
|
||||
return HttpUtil.readJson(discordSRV, request, UUIDResponse.class)
|
||||
.thenApply(response -> UUIDUtil.fromShort(response.id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<IOfflinePlayer> lookupOfflinePlayer(UUID uuid) {
|
||||
IPlayer player = player(uuid);
|
||||
if (player != null) {
|
||||
return CompletableFuture.completedFuture(player);
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(String.format(UUID_TO_PROFILE_URL, uuid))
|
||||
.get()
|
||||
.build();
|
||||
|
||||
return HttpUtil.readJson(discordSRV, request, GameProfileResponse.class).thenApply(response -> {
|
||||
SkinInfo skinInfo = null;
|
||||
for (GameProfileResponse.Property property : response.properties) {
|
||||
if (!Textures.KEY.equals(property.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Textures textures = Textures.getFromBase64(discordSRV, property.value);
|
||||
skinInfo = textures.getSkinInfo();
|
||||
}
|
||||
|
||||
return new OfflinePlayer(discordSRV, response.name, uuid, skinInfo);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -20,12 +20,14 @@ package com.discordsrv.common.player.provider;
|
||||
|
||||
import com.discordsrv.api.player.DiscordSRVPlayer;
|
||||
import com.discordsrv.api.player.IPlayerProvider;
|
||||
import com.discordsrv.common.player.IOfflinePlayer;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface PlayerProvider<T extends IPlayer> extends IPlayerProvider {
|
||||
|
||||
@ -51,4 +53,11 @@ public interface PlayerProvider<T extends IPlayer> extends IPlayerProvider {
|
||||
*/
|
||||
@NotNull
|
||||
Collection<T> allPlayers();
|
||||
|
||||
CompletableFuture<UUID> lookupUUIDForUsername(String username);
|
||||
|
||||
default CompletableFuture<IOfflinePlayer> lookupOfflinePlayer(String username) {
|
||||
return lookupUUIDForUsername(username).thenCompose(this::lookupOfflinePlayer);
|
||||
}
|
||||
CompletableFuture<IOfflinePlayer> lookupOfflinePlayer(UUID uuid);
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
package com.discordsrv.common.player.provider.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GameProfileResponse {
|
||||
|
||||
public String id;
|
||||
public String name;
|
||||
public List<Property> properties;
|
||||
|
||||
public static class Property {
|
||||
public String name;
|
||||
public String value;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.discordsrv.common.player.provider.model;
|
||||
|
||||
public class SkinInfo {
|
||||
|
||||
private final String textureId;
|
||||
private final String model;
|
||||
|
||||
public SkinInfo(String textureId, String model) {
|
||||
this.textureId = textureId;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public String textureId() {
|
||||
return textureId;
|
||||
}
|
||||
|
||||
public String model() {
|
||||
return model;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.discordsrv.common.player.provider.model;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
|
||||
public class Textures {
|
||||
|
||||
public static String KEY = "textures";
|
||||
|
||||
public String profileId;
|
||||
public String profileName;
|
||||
public boolean signatureRequired;
|
||||
public Map<String, Texture> textures;
|
||||
|
||||
public static class Texture {
|
||||
public String url;
|
||||
public Map<String, Object> metadata;
|
||||
}
|
||||
public SkinInfo getSkinInfo() {
|
||||
Textures.Texture texture = textures.get("SKIN");
|
||||
if (texture == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String url = texture.url;
|
||||
Map<String, Object> metadata = texture.metadata;
|
||||
|
||||
String textureId = url.substring(url.lastIndexOf("/") + 1);
|
||||
return new SkinInfo(textureId, metadata != null ? (String) metadata.get("model") : null);
|
||||
}
|
||||
|
||||
public static Textures getFromBase64(DiscordSRV discordSRV, String base64) {
|
||||
byte[] bytes = Base64.getDecoder().decode(base64);
|
||||
Textures textures;
|
||||
try {
|
||||
textures = discordSRV.json().readValue(bytes, Textures.class);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
return textures;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.discordsrv.common.player.provider.model;
|
||||
|
||||
public class UUIDResponse {
|
||||
|
||||
public String name;
|
||||
public String id;
|
||||
}
|
@ -21,6 +21,7 @@ package com.discordsrv.common.scheduler;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@SuppressWarnings({"UnusedReturnValue", "unused"}) // API
|
||||
@ -62,36 +63,23 @@ public interface Scheduler {
|
||||
*/
|
||||
Future<?> run(@NotNull Runnable task);
|
||||
|
||||
/**
|
||||
* Schedules the given task to run after the provided time in the provided {@link TimeUnit}.
|
||||
*
|
||||
* @param task the task
|
||||
* @param time the amount of time in the provided unit
|
||||
* @param unit the unit for the time
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
default ScheduledFuture<?> runLater(@NotNull Runnable task, long time, @NotNull TimeUnit unit) {
|
||||
return runLater(task, unit.toMillis(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the given task after the provided amount of milliseconds.
|
||||
*
|
||||
* @param task the task
|
||||
* @param timeMillis the delay before executing the task
|
||||
* @param delay the delay before executing the task
|
||||
*/
|
||||
ScheduledFuture<?> runLater(Runnable task, long timeMillis);
|
||||
ScheduledFuture<?> runLater(Runnable task, Duration delay);
|
||||
|
||||
/**
|
||||
* Schedules the given task at the given rate.
|
||||
*
|
||||
* @param task the task
|
||||
* @param rate the rate in the given unit
|
||||
* @param unit the unit for the rate
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
default ScheduledFuture<?> runAtFixedRate(@NotNull Runnable task, long rate, @NotNull TimeUnit unit) {
|
||||
return runAtFixedRate(task, rate, rate, unit);
|
||||
default ScheduledFuture<?> runAtFixedRate(@NotNull Runnable task, Duration rate) {
|
||||
return runAtFixedRate(task, rate, rate);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,21 +88,8 @@ public interface Scheduler {
|
||||
* @param task the task
|
||||
* @param initialDelay the initial delay in the provided unit
|
||||
* @param rate the rate to run the task at in the given unit
|
||||
* @param unit the unit for the initial delay and rate
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
default ScheduledFuture<?> runAtFixedRate(@NotNull Runnable task, long initialDelay, long rate, @NotNull TimeUnit unit) {
|
||||
return runAtFixedRate(task, unit.toMillis(initialDelay), unit.toMillis(rate));
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a task to run at the given rate after the initial delay.
|
||||
*
|
||||
* @param task the task
|
||||
* @param initialDelayMillis the initial delay in milliseconds
|
||||
* @param rateMillis the rate in milliseconds
|
||||
*/
|
||||
ScheduledFuture<?> runAtFixedRate(@NotNull Runnable task, long initialDelayMillis, long rateMillis);
|
||||
|
||||
ScheduledFuture<?> runAtFixedRate(@NotNull Runnable task, Duration initialDelay, Duration rate);
|
||||
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import com.discordsrv.common.scheduler.threadfactory.CountingForkJoinWorkerThrea
|
||||
import com.discordsrv.common.scheduler.threadfactory.CountingThreadFactory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class StandardScheduler implements Scheduler {
|
||||
@ -124,13 +125,13 @@ public class StandardScheduler implements Scheduler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledFuture<?> runLater(Runnable task, long timeMillis) {
|
||||
return scheduledExecutorService.schedule(wrap(task), timeMillis, TimeUnit.MILLISECONDS);
|
||||
public ScheduledFuture<?> runLater(Runnable task, Duration delay) {
|
||||
return scheduledExecutorService.schedule(wrap(task), delay.toMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledFuture<?> runAtFixedRate(@NotNull Runnable task, long initialDelayMillis, long rateMillis) {
|
||||
return scheduledExecutorService.scheduleAtFixedRate(wrap(task), initialDelayMillis, rateMillis, TimeUnit.MILLISECONDS);
|
||||
public ScheduledFuture<?> runAtFixedRate(@NotNull Runnable task, Duration initialDelay, Duration rate) {
|
||||
return scheduledExecutorService.scheduleAtFixedRate(wrap(task), initialDelay.toMillis(), rate.toMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public class ExceptionHandlingExecutor implements Executor {
|
||||
|
@ -24,7 +24,9 @@ import com.discordsrv.common.config.connection.ConnectionConfig;
|
||||
import com.discordsrv.common.config.connection.UpdateConfig;
|
||||
import com.discordsrv.common.debug.data.VersionInfo;
|
||||
import com.discordsrv.common.exception.MessageException;
|
||||
import com.discordsrv.common.http.util.HttpUtil;
|
||||
import com.discordsrv.common.logging.NamedLogger;
|
||||
import com.discordsrv.common.permission.Permission;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.discordsrv.common.player.event.PlayerConnectedEvent;
|
||||
import com.discordsrv.common.update.github.GitHubCompareResponse;
|
||||
@ -143,16 +145,6 @@ public class UpdateChecker {
|
||||
return true;
|
||||
}
|
||||
|
||||
private ResponseBody checkResponse(Request request, Response response, ResponseBody responseBody) throws IOException {
|
||||
if (responseBody == null || !response.isSuccessful()) {
|
||||
String responseString = responseBody == null
|
||||
? "response body is null"
|
||||
: StringUtils.substring(responseBody.string(), 0, 500);
|
||||
throw new MessageException("Request to " + request.url().host() + " failed: " + response.code() + ": " + responseString);
|
||||
}
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code null} for preventing shutdown
|
||||
*/
|
||||
@ -166,7 +158,7 @@ public class UpdateChecker {
|
||||
|
||||
String responseString;
|
||||
try (Response response = discordSRV.httpClient().newCall(request).execute()) {
|
||||
ResponseBody responseBody = checkResponse(request, response, response.body());
|
||||
ResponseBody responseBody = HttpUtil.checkIfResponseSuccessful(request, response);
|
||||
responseString = responseBody.string();
|
||||
}
|
||||
|
||||
@ -204,7 +196,7 @@ public class UpdateChecker {
|
||||
.get().build();
|
||||
|
||||
try (Response response = discordSRV.httpClient().newCall(request).execute()) {
|
||||
ResponseBody responseBody = checkResponse(request, response, response.body());
|
||||
ResponseBody responseBody = HttpUtil.checkIfResponseSuccessful(request, response);
|
||||
GitHubCompareResponse compare = discordSRV.json().readValue(responseBody.byteStream(), GitHubCompareResponse.class);
|
||||
|
||||
VersionCheck versionCheck = new VersionCheck();
|
||||
@ -236,7 +228,7 @@ public class UpdateChecker {
|
||||
.get().build();
|
||||
|
||||
try (Response response = discordSRV.httpClient().newCall(request).execute()) {
|
||||
ResponseBody responseBody = checkResponse(request, response, response.body());
|
||||
ResponseBody responseBody = HttpUtil.checkIfResponseSuccessful(request, response);
|
||||
List<GithubRelease> releases = discordSRV.json().readValue(responseBody.byteStream(), new TypeReference<List<GithubRelease>>() {});
|
||||
|
||||
for (GithubRelease release : releases) {
|
||||
@ -303,7 +295,7 @@ public class UpdateChecker {
|
||||
}
|
||||
|
||||
IPlayer player = event.player();
|
||||
if (!player.hasPermission("discordsrv.updatenotification")) {
|
||||
if (!player.hasPermission(Permission.UPDATE_NOTIFICATION)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,37 @@
|
||||
package com.discordsrv.common.uuid.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public final class UUIDUtil {
|
||||
|
||||
private UUIDUtil() {}
|
||||
|
||||
public static UUID fromShortOrFull(@NotNull String uuidString) {
|
||||
int length = uuidString.length();
|
||||
if (length == 32) {
|
||||
return fromShort(uuidString);
|
||||
} else if (length == 36) {
|
||||
return UUID.fromString(uuidString);
|
||||
}
|
||||
throw new IllegalArgumentException("Not a valid 36 or 32 character long UUID");
|
||||
}
|
||||
|
||||
public static UUID fromShort(@NotNull String shortUUID) {
|
||||
if (shortUUID.length() != 32) {
|
||||
throw new IllegalArgumentException("Short uuids are 32 characters long");
|
||||
}
|
||||
|
||||
String fullLengthUUID = shortUUID.substring(0, 8)
|
||||
+ "-" + shortUUID.substring(8, 12)
|
||||
+ "-" + shortUUID.substring(12, 16)
|
||||
+ "-" + shortUUID.substring(16, 20)
|
||||
+ "-" + shortUUID.substring(20);
|
||||
return UUID.fromString(fullLengthUUID);
|
||||
}
|
||||
|
||||
public static String toShort(@NotNull UUID uuid) {
|
||||
return uuid.toString().replace("-", "");
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ import com.discordsrv.common.MockDiscordSRV;
|
||||
import com.discordsrv.common.channel.GlobalChannel;
|
||||
import com.discordsrv.common.component.util.ComponentUtil;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.discordsrv.common.player.provider.model.SkinInfo;
|
||||
import com.discordsrv.common.testing.TestHelper;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
@ -73,6 +74,11 @@ public class MinecraftToDiscordChatMessageTest {
|
||||
return "Vankka";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SkinInfo skinInfo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Locale locale() {
|
||||
return Locale.getDefault();
|
||||
|
@ -20,10 +20,14 @@ package com.discordsrv.sponge.player;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.player.IOfflinePlayer;
|
||||
import com.discordsrv.common.player.provider.model.SkinInfo;
|
||||
import com.discordsrv.common.player.provider.model.Textures;
|
||||
import com.discordsrv.sponge.SpongeDiscordSRV;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.api.entity.living.player.User;
|
||||
import org.spongepowered.api.profile.property.ProfileProperty;
|
||||
|
||||
public class SpongeOfflinePlayer implements IOfflinePlayer {
|
||||
|
||||
@ -45,6 +49,19 @@ public class SpongeOfflinePlayer implements IOfflinePlayer {
|
||||
return user.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SkinInfo skinInfo() {
|
||||
for (ProfileProperty property : user.profile().properties()) {
|
||||
if (!Textures.KEY.equals(property.name())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Textures textures = Textures.getFromBase64(discordSRV, property.value());
|
||||
return textures.getSkinInfo();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Identity identity() {
|
||||
return user.profile();
|
||||
|
@ -20,6 +20,8 @@ package com.discordsrv.sponge.player;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.discordsrv.common.player.provider.model.SkinInfo;
|
||||
import com.discordsrv.common.player.provider.model.Textures;
|
||||
import com.discordsrv.sponge.SpongeDiscordSRV;
|
||||
import com.discordsrv.sponge.command.game.sender.SpongeCommandSender;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
@ -51,6 +53,13 @@ public class SpongePlayer extends SpongeCommandSender implements IPlayer {
|
||||
return player.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SkinInfo skinInfo() {
|
||||
String texturesRaw = player.skinProfile().get().value();
|
||||
Textures textures = Textures.getFromBase64(discordSRV, texturesRaw);
|
||||
return textures.getSkinInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Locale locale() {
|
||||
return player.locale();
|
||||
|
@ -19,6 +19,7 @@
|
||||
package com.discordsrv.sponge.player;
|
||||
|
||||
import com.discordsrv.common.player.IOfflinePlayer;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.discordsrv.common.player.ServerPlayerProvider;
|
||||
import com.discordsrv.sponge.SpongeDiscordSRV;
|
||||
import org.spongepowered.api.entity.living.player.User;
|
||||
@ -77,14 +78,24 @@ public class SpongePlayerProvider extends ServerPlayerProvider<SpongePlayer, Spo
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<IOfflinePlayer> offlinePlayer(UUID uuid) {
|
||||
public CompletableFuture<IOfflinePlayer> lookupOfflinePlayer(UUID uuid) {
|
||||
IPlayer player = player(uuid);
|
||||
if (player != null) {
|
||||
return CompletableFuture.completedFuture(player);
|
||||
}
|
||||
|
||||
return discordSRV.game().server().userManager()
|
||||
.load(uuid)
|
||||
.thenApply(optional -> optional.map(this::convert).orElse(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<IOfflinePlayer> offlinePlayer(String username) {
|
||||
public CompletableFuture<IOfflinePlayer> lookupOfflinePlayer(String username) {
|
||||
IPlayer player = player(username);
|
||||
if (player != null) {
|
||||
return CompletableFuture.completedFuture(player);
|
||||
}
|
||||
|
||||
return discordSRV.game().server().userManager()
|
||||
.load(username)
|
||||
.thenApply(optional -> optional.map(this::convert).orElse(null));
|
||||
|
@ -20,9 +20,12 @@ package com.discordsrv.velocity.player;
|
||||
|
||||
import com.discordsrv.common.DiscordSRV;
|
||||
import com.discordsrv.common.player.IPlayer;
|
||||
import com.discordsrv.common.player.provider.model.SkinInfo;
|
||||
import com.discordsrv.common.player.provider.model.Textures;
|
||||
import com.discordsrv.velocity.VelocityDiscordSRV;
|
||||
import com.discordsrv.velocity.command.game.sender.VelocityCommandSender;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import net.kyori.adventure.identity.Identity;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -49,6 +52,19 @@ public class VelocityPlayer extends VelocityCommandSender implements IPlayer {
|
||||
return player.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable SkinInfo skinInfo() {
|
||||
for (GameProfile.Property property : player.getGameProfile().getProperties()) {
|
||||
if (!Textures.KEY.equals(property.getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Textures textures = Textures.getFromBase64(discordSRV, property.getValue());
|
||||
return textures.getSkinInfo();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Locale locale() {
|
||||
return player.getPlayerSettings().getLocale();
|
||||
|
Loading…
Reference in New Issue
Block a user