From e88684a5647497b61f93caf349c9201ac82fa194 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Wed, 14 Aug 2019 00:53:51 +0200 Subject: [PATCH] Error handling, 1.14 vanilla command support Error handling (not today) Added support for vanilla commands on 1.14 --- pom.xml | 6 + .../buttondevteam/discordplugin/DPUtils.java | 4 + .../discordplugin/DiscordConnectedPlayer.java | 7 +- .../discordplugin/DiscordPlayerSender.java | 7 +- .../discordplugin/DiscordSender.java | 4 +- .../discordplugin/IMCPlayer.java | 4 +- .../discordplugin/commands/DebugCommand.java | 2 +- .../discordplugin/listeners/MCListener.java | 2 +- .../discordplugin/mcchat/MCChatListener.java | 16 ++- .../discordplugin/mcchat/MCListener.java | 4 +- .../playerfaker/DiscordFakePlayer.java | 5 +- .../playerfaker/VCMDWrapper.java | 31 ++++- .../playerfaker/VanillaCommandListener.java | 2 +- .../playerfaker/VanillaCommandListener14.java | 106 ++++++++++++++++++ 14 files changed, 178 insertions(+), 22 deletions(-) create mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.java diff --git a/pom.xml b/pom.xml index 6d68328..c9d1eb8 100755 --- a/pom.xml +++ b/pom.xml @@ -183,6 +183,12 @@ 1.12.2-R0.1-SNAPSHOT provided + + org.spigotmc. + spigot + 1.14.4-R0.1-SNAPSHOT + provided + com.discord4j diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.java b/src/main/java/buttondevteam/discordplugin/DPUtils.java index 616b750..84d2ced 100755 --- a/src/main/java/buttondevteam/discordplugin/DPUtils.java +++ b/src/main/java/buttondevteam/discordplugin/DPUtils.java @@ -163,4 +163,8 @@ public final class DPUtils { return getMessageChannel(config.getPath(), config.get()); } + public static Mono ignoreError(Mono mono) { + return mono.onErrorResume(t -> Mono.empty()); + } + } diff --git a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java index da5ccd4..b5d7da4 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java @@ -2,7 +2,6 @@ package buttondevteam.discordplugin; import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.discordplugin.playerfaker.VCMDWrapper; -import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.User; import lombok.Getter; @@ -21,7 +20,7 @@ import java.util.HashSet; import java.util.UUID; public abstract class DiscordConnectedPlayer extends DiscordSenderBase implements IMCPlayer { - private @Getter VCMDWrapper vanillaCmdListener; + private @Getter VCMDWrapper vanillaCmdListener; @Getter @Setter private boolean loggedIn = false; @@ -56,7 +55,9 @@ public abstract class DiscordConnectedPlayer extends DiscordSenderBase implement uniqueId = uuid; displayName = mcname; try { - vanillaCmdListener = new VCMDWrapper<>(new VanillaCommandListener<>(this)); + vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this)); + if (vanillaCmdListener.getListener() == null) + DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error."); } catch (NoClassDefFoundError e) { DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error."); } diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java index 62949c5..1e94b14 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java @@ -1,7 +1,6 @@ package buttondevteam.discordplugin; import buttondevteam.discordplugin.playerfaker.VCMDWrapper; -import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.User; import lombok.Getter; @@ -37,13 +36,15 @@ import java.util.*; public class DiscordPlayerSender extends DiscordSenderBase implements IMCPlayer { protected Player player; - private @Getter VCMDWrapper vanillaCmdListener; + private @Getter VCMDWrapper vanillaCmdListener; public DiscordPlayerSender(User user, MessageChannel channel, Player player) { super(user, channel); this.player = player; try { - vanillaCmdListener = new VCMDWrapper<>(new VanillaCommandListener(this, player)); + vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, player)); + if (vanillaCmdListener.getListener() == null) + DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error."); } catch (NoClassDefFoundError e) { DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error."); } diff --git a/src/main/java/buttondevteam/discordplugin/DiscordSender.java b/src/main/java/buttondevteam/discordplugin/DiscordSender.java index 9eda278..0381e66 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordSender.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordSender.java @@ -12,6 +12,7 @@ import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionAttachment; import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.plugin.Plugin; +import reactor.core.publisher.Mono; import java.util.Set; @@ -23,7 +24,8 @@ public class DiscordSender extends DiscordSenderBase implements CommandSender { public DiscordSender(User user, MessageChannel channel) { super(user, channel); val def = "Discord user"; - name = user == null ? def : user.asMember(DiscordPlugin.mainServer.getId()).blockOptional().map(Member::getDisplayName).orElse(def); + name = user == null ? def : user.asMember(DiscordPlugin.mainServer.getId()) + .onErrorResume(t -> Mono.empty()).blockOptional().map(Member::getDisplayName).orElse(def); } public DiscordSender(User user, MessageChannel channel, String name) { diff --git a/src/main/java/buttondevteam/discordplugin/IMCPlayer.java b/src/main/java/buttondevteam/discordplugin/IMCPlayer.java index 7943f21..c2ee28e 100755 --- a/src/main/java/buttondevteam/discordplugin/IMCPlayer.java +++ b/src/main/java/buttondevteam/discordplugin/IMCPlayer.java @@ -3,6 +3,6 @@ package buttondevteam.discordplugin; import buttondevteam.discordplugin.playerfaker.VCMDWrapper; import org.bukkit.entity.Player; -public interface IMCPlayer> extends Player { - VCMDWrapper getVanillaCmdListener(); +public interface IMCPlayer extends Player { + VCMDWrapper getVanillaCmdListener(); } diff --git a/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.java b/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.java index e1c0686..65e0663 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.java +++ b/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.java @@ -19,7 +19,7 @@ public class DebugCommand extends ICommand2DC { .flatMap(m -> DiscordPlugin.plugin.modRole().get() .map(mr -> m.getRoleIds().stream().anyMatch(r -> r.equals(mr.getId()))) .switchIfEmpty(Mono.fromSupplier(() -> DiscordPlugin.mainServer.getOwnerId().asLong() == m.getId().asLong()))) //Role not found - .subscribe(success -> { + .onErrorReturn(false).subscribe(success -> { if (success) sender.sendMessage("debug " + (CommonListeners.debug() ? "enabled" : "disabled")); else diff --git a/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java b/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java index 6c9acd6..3afc038 100755 --- a/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java +++ b/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java @@ -38,7 +38,7 @@ public class MCListener implements Listener { if (!userOpt.isPresent()) return; User user = userOpt.get(); e.addInfo("Discord tag: " + user.getUsername() + "#" + user.getDiscriminator()); - val memberOpt = user.asMember(DiscordPlugin.mainServer.getId()).blockOptional(); + val memberOpt = user.asMember(DiscordPlugin.mainServer.getId()).onErrorResume(t -> Mono.empty()).blockOptional(); if (!memberOpt.isPresent()) return; Member member = memberOpt.get(); val prOpt = member.getPresence().blockOptional(); diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java index 790edfb..4ff36cb 100755 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java @@ -10,6 +10,7 @@ import buttondevteam.discordplugin.DiscordSenderBase; import buttondevteam.discordplugin.listeners.CommandListener; import buttondevteam.discordplugin.listeners.CommonListeners; import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; +import buttondevteam.discordplugin.playerfaker.VanillaCommandListener14; import buttondevteam.discordplugin.util.Timings; import buttondevteam.lib.*; import buttondevteam.lib.chat.ChatMessage; @@ -277,10 +278,11 @@ public class MCChatListener implements Listener { for (User u : event.getMessage().getUserMentions().toIterable()) { //TODO: Role mentions dmessage = dmessage.replace(u.getMention(), "@" + u.getUsername()); // TODO: IG Formatting - val m = u.asMember(DiscordPlugin.mainServer.getId()).block(); - if (m != null) { - final String nick = m.getDisplayName(); - dmessage = dmessage.replace(m.getNicknameMention(), "@" + nick); + val m = u.asMember(DiscordPlugin.mainServer.getId()).onErrorResume(t -> Mono.empty()).blockOptional(); + if (m.isPresent()) { + val mm = m.get(); + final String nick = mm.getDisplayName(); + dmessage = dmessage.replace(mm.getNicknameMention(), "@" + nick); } } for (GuildChannel ch : event.getGuild().flux().flatMap(Guild::getChannels).toIterable()) { @@ -340,7 +342,11 @@ public class MCChatListener implements Listener { channel.set(clmd.mcchannel); //Hack to send command in the channel } //TODO: Permcheck isn't implemented for commands try { - VanillaCommandListener.runBukkitOrVanillaCommand(dsender, cmd); + String mcpackage = Bukkit.getServer().getClass().getPackage().getName(); + if (mcpackage.contains("1_12")) + VanillaCommandListener.runBukkitOrVanillaCommand(dsender, cmd); + else if (mcpackage.contains("1_14")) + VanillaCommandListener14.runBukkitOrVanillaCommand(dsender, cmd); } catch (NoClassDefFoundError e) { Bukkit.dispatchCommand(dsender, cmd); } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java index 0880bd9..c3ad14a 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java @@ -118,7 +118,7 @@ class MCListener implements Listener { final DiscordPlayer p = TBMCPlayerBase.getPlayer(source.getPlayer().getUniqueId(), TBMCPlayer.class) .getAs(DiscordPlayer.class); if (p == null) return; - DiscordPlugin.dc.getUserById(Snowflake.of(p.getDiscordID())) + DPUtils.ignoreError(DiscordPlugin.dc.getUserById(Snowflake.of(p.getDiscordID())) .flatMap(user -> user.asMember(DiscordPlugin.mainServer.getId())) .flatMap(user -> role.flatMap(r -> { if (e.getValue()) @@ -131,7 +131,7 @@ class MCListener implements Listener { if (modlog != null) return modlog.flatMap(ch -> ch.createMessage(msg)); return Mono.empty(); - })).subscribe(); + }))).subscribe(); } @EventHandler diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordFakePlayer.java b/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordFakePlayer.java index 1902516..25b505e 100755 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordFakePlayer.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordFakePlayer.java @@ -1,7 +1,9 @@ package buttondevteam.discordplugin.playerfaker; +import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.mcchat.MinecraftChatModule; +import discord4j.core.object.entity.Member; import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.User; import lombok.Getter; @@ -143,7 +145,8 @@ public class DiscordFakePlayer extends DiscordHumanEntity implements Player { @Override public String getDisplayName() { - return Objects.requireNonNull(user.asMember(DiscordPlugin.mainServer.getId()).block()).getDisplayName(); + return DPUtils.ignoreError(user.asMember(DiscordPlugin.mainServer.getId())).blockOptional() + .map(Member::getDisplayName).orElse(name); } @Override diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java index 95a7811..4bcf5eb 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java @@ -4,9 +4,36 @@ import buttondevteam.discordplugin.DiscordSenderBase; import buttondevteam.discordplugin.IMCPlayer; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; @RequiredArgsConstructor -public class VCMDWrapper> { +public class VCMDWrapper { @Getter //Needed to mock the player - private final VanillaCommandListener listener; + private final Object listener; + + /** + * This constructor will only send raw vanilla messages to the sender in plain text. + * + * @param player The Discord sender player (the wrapper) + */ + public static > Object createListener(T player) { + return createListener(player, null); + } + + /** + * This constructor will send both raw vanilla messages to the sender in plain text and forward the raw message to the provided player. + * + * @param player The Discord sender player (the wrapper) + * @param bukkitplayer The Bukkit player to send the raw message to + */ + public static > Object createListener(T player, Player bukkitplayer) { + String mcpackage = Bukkit.getServer().getClass().getPackage().getName(); + if (mcpackage.contains("1_12")) + return bukkitplayer == null ? new VanillaCommandListener<>(player) : new VanillaCommandListener<>(player, bukkitplayer); + else if (mcpackage.contains("1_14")) + return bukkitplayer == null ? new VanillaCommandListener14<>(player) : new VanillaCommandListener14<>(player, bukkitplayer); + else + return null; + } } diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java index 9f2db22..c12a308 100755 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java @@ -87,7 +87,7 @@ public class VanillaCommandListener> if (!vcmd.testPermission(sender)) return true; - ICommandListener icommandlistener = sender.getVanillaCmdListener().getListener(); + ICommandListener icommandlistener = (ICommandListener) sender.getVanillaCmdListener().getListener(); String[] args = cmdstr.split(" "); args = Arrays.copyOfRange(args, 1, args.length); try { diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.java new file mode 100644 index 0000000..fbaf958 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.java @@ -0,0 +1,106 @@ +package buttondevteam.discordplugin.playerfaker; + +import buttondevteam.discordplugin.DiscordSenderBase; +import buttondevteam.discordplugin.IMCPlayer; +import lombok.Getter; +import lombok.val; +import net.minecraft.server.v1_14_R1.*; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.v1_14_R1.CraftServer; +import org.bukkit.craftbukkit.v1_14_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_14_R1.command.ProxiedNativeCommandSender; +import org.bukkit.craftbukkit.v1_14_R1.command.VanillaCommandWrapper; +import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import java.util.Arrays; + +public class VanillaCommandListener14> implements ICommandListener { + private @Getter T player; + private Player bukkitplayer; + + /** + * This constructor will only send raw vanilla messages to the sender in plain text. + * + * @param player The Discord sender player (the wrapper) + */ + public VanillaCommandListener14(T player) { + this.player = player; + this.bukkitplayer = null; + } + + /** + * This constructor will send both raw vanilla messages to the sender in plain text and forward the raw message to the provided player. + * + * @param player The Discord sender player (the wrapper) + * @param bukkitplayer The Bukkit player to send the raw message to + */ + public VanillaCommandListener14(T player, Player bukkitplayer) { + this.player = player; + this.bukkitplayer = bukkitplayer; + if (!(bukkitplayer instanceof CraftPlayer)) + throw new ClassCastException("bukkitplayer must be a Bukkit player!"); + } + + @Override + public void sendMessage(IChatBaseComponent arg0) { + player.sendMessage(arg0.getString()); + if (bukkitplayer != null) + ((CraftPlayer) bukkitplayer).getHandle().sendMessage(arg0); + } + + @Override + public boolean shouldSendSuccess() { + return true; + } + + @Override + public boolean shouldSendFailure() { + return true; + } + + @Override + public boolean shouldBroadcastCommands() { + return true; //Broadcast to in-game admins + } + + @Override + public CommandSender getBukkitSender(CommandListenerWrapper commandListenerWrapper) { + return player; + } + + public static boolean runBukkitOrVanillaCommand(DiscordSenderBase dsender, String cmdstr) { + val cmd = ((CraftServer) Bukkit.getServer()).getCommandMap().getCommand(cmdstr.split(" ")[0].toLowerCase()); + if (!(dsender instanceof Player) || !(cmd instanceof VanillaCommandWrapper)) + return Bukkit.dispatchCommand(dsender, cmdstr); // Unconnected users are treated well in vanilla cmds + + if (!(dsender instanceof IMCPlayer)) + throw new ClassCastException( + "dsender needs to implement IMCPlayer to use vanilla commands as it implements Player."); + + IMCPlayer sender = (IMCPlayer) dsender; // Don't use val on recursive interfaces :P + + val vcmd = (VanillaCommandWrapper) cmd; + if (!vcmd.testPermission(sender)) + return true; + + val world = ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle(); + ICommandListener icommandlistener = (ICommandListener) sender.getVanillaCmdListener().getListener(); + val wrapper = new CommandListenerWrapper(icommandlistener, new Vec3D(0, 0, 0), + new Vec2F(0, 0), world, 0, sender.getName(), + new ChatComponentText(sender.getName()), world.getMinecraftServer(), null); + val pncs = new ProxiedNativeCommandSender(wrapper, sender, sender); + String[] args = cmdstr.split(" "); + args = Arrays.copyOfRange(args, 1, args.length); + try { + return vcmd.execute(pncs, cmd.getLabel(), args); + } catch (CommandException commandexception) { + // Taken from CommandHandler + ChatMessage chatmessage = new ChatMessage(commandexception.getMessage(), commandexception.a()); + chatmessage.getChatModifier().setColor(EnumChatFormat.RED); + icommandlistener.sendMessage(chatmessage); + } + return true; + } +}