Error handling, 1.14 vanilla command support

Error handling (not today)
Added support for vanilla commands on 1.14
This commit is contained in:
Norbi Peti 2019-08-14 00:53:51 +02:00
parent 7a9e7de138
commit e88684a564
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
14 changed files with 178 additions and 22 deletions

View file

@ -183,6 +183,12 @@
<version>1.12.2-R0.1-SNAPSHOT</version> <version>1.12.2-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.spigotmc.</groupId>
<artifactId>spigot</artifactId>
<version>1.14.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.discord4j/Discord4J --> <!-- https://mvnrepository.com/artifact/com.discord4j/Discord4J -->
<dependency> <dependency>
<groupId>com.discord4j</groupId> <groupId>com.discord4j</groupId>

View file

@ -163,4 +163,8 @@ public final class DPUtils {
return getMessageChannel(config.getPath(), config.get()); return getMessageChannel(config.getPath(), config.get());
} }
public static <T> Mono<T> ignoreError(Mono<T> mono) {
return mono.onErrorResume(t -> Mono.empty());
}
} }

View file

@ -2,7 +2,6 @@ package buttondevteam.discordplugin;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import buttondevteam.discordplugin.playerfaker.VCMDWrapper; import buttondevteam.discordplugin.playerfaker.VCMDWrapper;
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User; import discord4j.core.object.entity.User;
import lombok.Getter; import lombok.Getter;
@ -21,7 +20,7 @@ import java.util.HashSet;
import java.util.UUID; import java.util.UUID;
public abstract class DiscordConnectedPlayer extends DiscordSenderBase implements IMCPlayer<DiscordConnectedPlayer> { public abstract class DiscordConnectedPlayer extends DiscordSenderBase implements IMCPlayer<DiscordConnectedPlayer> {
private @Getter VCMDWrapper<DiscordConnectedPlayer> vanillaCmdListener; private @Getter VCMDWrapper vanillaCmdListener;
@Getter @Getter
@Setter @Setter
private boolean loggedIn = false; private boolean loggedIn = false;
@ -56,7 +55,9 @@ public abstract class DiscordConnectedPlayer extends DiscordSenderBase implement
uniqueId = uuid; uniqueId = uuid;
displayName = mcname; displayName = mcname;
try { 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) { } catch (NoClassDefFoundError e) {
DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error."); DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error.");
} }

View file

@ -1,7 +1,6 @@
package buttondevteam.discordplugin; package buttondevteam.discordplugin;
import buttondevteam.discordplugin.playerfaker.VCMDWrapper; import buttondevteam.discordplugin.playerfaker.VCMDWrapper;
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User; import discord4j.core.object.entity.User;
import lombok.Getter; import lombok.Getter;
@ -37,13 +36,15 @@ import java.util.*;
public class DiscordPlayerSender extends DiscordSenderBase implements IMCPlayer<DiscordPlayerSender> { public class DiscordPlayerSender extends DiscordSenderBase implements IMCPlayer<DiscordPlayerSender> {
protected Player player; protected Player player;
private @Getter VCMDWrapper<DiscordPlayerSender> vanillaCmdListener; private @Getter VCMDWrapper vanillaCmdListener;
public DiscordPlayerSender(User user, MessageChannel channel, Player player) { public DiscordPlayerSender(User user, MessageChannel channel, Player player) {
super(user, channel); super(user, channel);
this.player = player; this.player = player;
try { try {
vanillaCmdListener = new VCMDWrapper<>(new VanillaCommandListener<DiscordPlayerSender>(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) { } catch (NoClassDefFoundError e) {
DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error."); DPUtils.getLogger().warning("Vanilla commands won't be available from Discord due to a compatibility error.");
} }

View file

@ -12,6 +12,7 @@ import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment; import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import reactor.core.publisher.Mono;
import java.util.Set; import java.util.Set;
@ -23,7 +24,8 @@ public class DiscordSender extends DiscordSenderBase implements CommandSender {
public DiscordSender(User user, MessageChannel channel) { public DiscordSender(User user, MessageChannel channel) {
super(user, channel); super(user, channel);
val def = "Discord user"; 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) { public DiscordSender(User user, MessageChannel channel, String name) {

View file

@ -3,6 +3,6 @@ package buttondevteam.discordplugin;
import buttondevteam.discordplugin.playerfaker.VCMDWrapper; import buttondevteam.discordplugin.playerfaker.VCMDWrapper;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public interface IMCPlayer<T extends DiscordSenderBase & IMCPlayer<T>> extends Player { public interface IMCPlayer<T> extends Player {
VCMDWrapper<T> getVanillaCmdListener(); VCMDWrapper getVanillaCmdListener();
} }

View file

@ -19,7 +19,7 @@ public class DebugCommand extends ICommand2DC {
.flatMap(m -> DiscordPlugin.plugin.modRole().get() .flatMap(m -> DiscordPlugin.plugin.modRole().get()
.map(mr -> m.getRoleIds().stream().anyMatch(r -> r.equals(mr.getId()))) .map(mr -> m.getRoleIds().stream().anyMatch(r -> r.equals(mr.getId())))
.switchIfEmpty(Mono.fromSupplier(() -> DiscordPlugin.mainServer.getOwnerId().asLong() == m.getId().asLong()))) //Role not found .switchIfEmpty(Mono.fromSupplier(() -> DiscordPlugin.mainServer.getOwnerId().asLong() == m.getId().asLong()))) //Role not found
.subscribe(success -> { .onErrorReturn(false).subscribe(success -> {
if (success) if (success)
sender.sendMessage("debug " + (CommonListeners.debug() ? "enabled" : "disabled")); sender.sendMessage("debug " + (CommonListeners.debug() ? "enabled" : "disabled"));
else else

View file

@ -38,7 +38,7 @@ public class MCListener implements Listener {
if (!userOpt.isPresent()) return; if (!userOpt.isPresent()) return;
User user = userOpt.get(); User user = userOpt.get();
e.addInfo("Discord tag: " + user.getUsername() + "#" + user.getDiscriminator()); 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; if (!memberOpt.isPresent()) return;
Member member = memberOpt.get(); Member member = memberOpt.get();
val prOpt = member.getPresence().blockOptional(); val prOpt = member.getPresence().blockOptional();

View file

@ -10,6 +10,7 @@ import buttondevteam.discordplugin.DiscordSenderBase;
import buttondevteam.discordplugin.listeners.CommandListener; import buttondevteam.discordplugin.listeners.CommandListener;
import buttondevteam.discordplugin.listeners.CommonListeners; import buttondevteam.discordplugin.listeners.CommonListeners;
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener14;
import buttondevteam.discordplugin.util.Timings; import buttondevteam.discordplugin.util.Timings;
import buttondevteam.lib.*; import buttondevteam.lib.*;
import buttondevteam.lib.chat.ChatMessage; import buttondevteam.lib.chat.ChatMessage;
@ -277,10 +278,11 @@ public class MCChatListener implements Listener {
for (User u : event.getMessage().getUserMentions().toIterable()) { //TODO: Role mentions for (User u : event.getMessage().getUserMentions().toIterable()) { //TODO: Role mentions
dmessage = dmessage.replace(u.getMention(), "@" + u.getUsername()); // TODO: IG Formatting dmessage = dmessage.replace(u.getMention(), "@" + u.getUsername()); // TODO: IG Formatting
val m = u.asMember(DiscordPlugin.mainServer.getId()).block(); val m = u.asMember(DiscordPlugin.mainServer.getId()).onErrorResume(t -> Mono.empty()).blockOptional();
if (m != null) { if (m.isPresent()) {
final String nick = m.getDisplayName(); val mm = m.get();
dmessage = dmessage.replace(m.getNicknameMention(), "@" + nick); final String nick = mm.getDisplayName();
dmessage = dmessage.replace(mm.getNicknameMention(), "@" + nick);
} }
} }
for (GuildChannel ch : event.getGuild().flux().flatMap(Guild::getChannels).toIterable()) { 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 channel.set(clmd.mcchannel); //Hack to send command in the channel
} //TODO: Permcheck isn't implemented for commands } //TODO: Permcheck isn't implemented for commands
try { 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) { } catch (NoClassDefFoundError e) {
Bukkit.dispatchCommand(dsender, cmd); Bukkit.dispatchCommand(dsender, cmd);
} }

View file

@ -118,7 +118,7 @@ class MCListener implements Listener {
final DiscordPlayer p = TBMCPlayerBase.getPlayer(source.getPlayer().getUniqueId(), TBMCPlayer.class) final DiscordPlayer p = TBMCPlayerBase.getPlayer(source.getPlayer().getUniqueId(), TBMCPlayer.class)
.getAs(DiscordPlayer.class); .getAs(DiscordPlayer.class);
if (p == null) return; 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 -> user.asMember(DiscordPlugin.mainServer.getId()))
.flatMap(user -> role.flatMap(r -> { .flatMap(user -> role.flatMap(r -> {
if (e.getValue()) if (e.getValue())
@ -131,7 +131,7 @@ class MCListener implements Listener {
if (modlog != null) if (modlog != null)
return modlog.flatMap(ch -> ch.createMessage(msg)); return modlog.flatMap(ch -> ch.createMessage(msg));
return Mono.empty(); return Mono.empty();
})).subscribe(); }))).subscribe();
} }
@EventHandler @EventHandler

View file

@ -1,7 +1,9 @@
package buttondevteam.discordplugin.playerfaker; package buttondevteam.discordplugin.playerfaker;
import buttondevteam.discordplugin.DPUtils;
import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import discord4j.core.object.entity.Member;
import discord4j.core.object.entity.MessageChannel; import discord4j.core.object.entity.MessageChannel;
import discord4j.core.object.entity.User; import discord4j.core.object.entity.User;
import lombok.Getter; import lombok.Getter;
@ -143,7 +145,8 @@ public class DiscordFakePlayer extends DiscordHumanEntity implements Player {
@Override @Override
public String getDisplayName() { 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 @Override

View file

@ -4,9 +4,36 @@ import buttondevteam.discordplugin.DiscordSenderBase;
import buttondevteam.discordplugin.IMCPlayer; import buttondevteam.discordplugin.IMCPlayer;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@RequiredArgsConstructor @RequiredArgsConstructor
public class VCMDWrapper<T extends DiscordSenderBase & IMCPlayer<T>> { public class VCMDWrapper {
@Getter //Needed to mock the player @Getter //Needed to mock the player
private final VanillaCommandListener<T> 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 <T extends DiscordSenderBase & IMCPlayer<T>> 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 <T extends DiscordSenderBase & IMCPlayer<T>> 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;
}
} }

View file

@ -87,7 +87,7 @@ public class VanillaCommandListener<T extends DiscordSenderBase & IMCPlayer<T>>
if (!vcmd.testPermission(sender)) if (!vcmd.testPermission(sender))
return true; return true;
ICommandListener icommandlistener = sender.getVanillaCmdListener().getListener(); ICommandListener icommandlistener = (ICommandListener) sender.getVanillaCmdListener().getListener();
String[] args = cmdstr.split(" "); String[] args = cmdstr.split(" ");
args = Arrays.copyOfRange(args, 1, args.length); args = Arrays.copyOfRange(args, 1, args.length);
try { try {

View file

@ -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<T extends DiscordSenderBase & IMCPlayer<T>> 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;
}
}