From fd14bf195483a9e781a8997402a27a03d2fc8475 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Thu, 13 Jul 2017 00:58:05 +0200 Subject: [PATCH] FINISHED mcchat! #12 #13 Vanilla commands are supported as well Now it'll send the command output to the player as well, if ran on Discord Minecraft chat sending to Discord made async Started using CraftBukkit Updated Travis config according to that --- .travis.yml | 24 +++- deploy.sh | 10 ++ pom.xml | 6 + .../discordplugin/DiscordConnectedPlayer.java | 8 +- .../discordplugin/DiscordPlayerSender.java | 21 +++- .../discordplugin/IMCPlayer.java | 9 ++ .../listeners/MCChatListener.java | 92 ++++++++------- .../playerfaker/DiscordFakePlayer.java | 7 +- .../playerfaker/VanillaCommandListener.java | 110 ++++++++++++++++++ 9 files changed, 230 insertions(+), 57 deletions(-) create mode 100644 deploy.sh create mode 100644 src/main/java/buttondevteam/discordplugin/IMCPlayer.java create mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java diff --git a/.travis.yml b/.travis.yml index bf018d2..8e4a89e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,26 @@ +cache: + directories: + - $HOME/.m2/repository/org/bukkit/craftbukkit +before_install: | # Wget BuildTools and run if cached folder not found + if [ ! -d "$HOME/.m2/repository/org/bukkit/craftbukkit/1.12-R0.1-SNAPSHOT" ]; then + wget -O BuildTools.jar https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar +# grep so that download counts don't appear in log files + - java -jar BuildTools.jar --rev 1.12 | grep -vE "[^/ ]*/[^/ ]*\s*KB\s*$" | grep -v "^\s*$" + fi language: java jdk: - oraclejdk8 - \ No newline at end of file +sudo: true +deploy: + # deploy develop to the staging environment + - provider: script + script: chmod +x deploy.sh && sh deploy.sh staging + on: + branch: dev + skip_cleanup: true + # deploy master to production + - provider: script + script: chmod +x deploy.sh && sh deploy.sh production + on: + branch: master + skip_cleanup: true diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..606423b --- /dev/null +++ b/deploy.sh @@ -0,0 +1,10 @@ +#!/bin/sh +FILENAME=$(find target/ -maxdepth 1 ! -name '*original*' -name '*.jar') +echo Found file: $FILENAME + +if [ $1 = 'production' ]; then +echo Production mode +echo $UPLOAD_KEY > upload_key +chmod 400 upload_key +yes | scp -B -i upload_key -o StrictHostKeyChecking=no $FILENAME travis@server.figytuna.com:/minecraft/main/plugins +fi diff --git a/pom.xml b/pom.xml index a326fcd..c6ea957 100644 --- a/pom.xml +++ b/pom.xml @@ -150,6 +150,12 @@ 1.12-R0.1-SNAPSHOT provided + + org.spigotmc + spigot + 1.12-R0.1-SNAPSHOT + provided + com.github.austinv11 Discord4j diff --git a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java index a669d0d..c10f13b 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java @@ -3,14 +3,18 @@ package buttondevteam.discordplugin; import java.util.UUID; import buttondevteam.discordplugin.playerfaker.DiscordFakePlayer; +import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; +import lombok.Getter; import sx.blah.discord.handle.obj.IChannel; import sx.blah.discord.handle.obj.IUser; -public class DiscordConnectedPlayer extends DiscordFakePlayer { - private static int nextEntityId = 0; +public class DiscordConnectedPlayer extends DiscordFakePlayer implements IMCPlayer { + private static int nextEntityId = 10000; + private @Getter VanillaCommandListener vanillaCmdListener; public DiscordConnectedPlayer(IUser user, IChannel channel, UUID uuid) { super(user, channel, nextEntityId++, uuid); + vanillaCmdListener = new VanillaCommandListener<>(this); } } diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java index 3edf995..97c3aef 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java @@ -31,20 +31,33 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.scoreboard.Scoreboard; import org.bukkit.util.Vector; +import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; +import lombok.Getter; import sx.blah.discord.handle.obj.IChannel; import sx.blah.discord.handle.obj.IUser; @SuppressWarnings("deprecation") -public class DiscordPlayerSender extends DiscordSenderBase implements Player { +public class DiscordPlayerSender extends DiscordSenderBase implements IMCPlayer { protected Player player; - // protected @Delegate(excludes = ProjectileSource.class) Player player; - // protected @Delegate(excludes = { ProjectileSource.class, Permissible.class }) Player player; - // protected @Delegate(excludes = { ProjectileSource.class, CommandSender.class }) Player player; + private @Getter VanillaCommandListener vanillaCmdListener; public DiscordPlayerSender(IUser user, IChannel channel, Player player) { super(user, channel); this.player = player; + vanillaCmdListener = new VanillaCommandListener(this); + } + + @Override + public void sendMessage(String message) { + player.sendMessage(message); + super.sendMessage(message); + } + + @Override + public void sendMessage(String[] messages) { + player.sendMessage(messages); + super.sendMessage(messages); } @Override diff --git a/src/main/java/buttondevteam/discordplugin/IMCPlayer.java b/src/main/java/buttondevteam/discordplugin/IMCPlayer.java new file mode 100644 index 0000000..e52539d --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/IMCPlayer.java @@ -0,0 +1,9 @@ +package buttondevteam.discordplugin; + +import org.bukkit.entity.Player; + +import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; + +public interface IMCPlayer> extends Player { + VanillaCommandListener getVanillaCmdListener(); +} diff --git a/src/main/java/buttondevteam/discordplugin/listeners/MCChatListener.java b/src/main/java/buttondevteam/discordplugin/listeners/MCChatListener.java index dfeb248..5160f05 100644 --- a/src/main/java/buttondevteam/discordplugin/listeners/MCChatListener.java +++ b/src/main/java/buttondevteam/discordplugin/listeners/MCChatListener.java @@ -18,6 +18,7 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import buttondevteam.discordplugin.*; +import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; import buttondevteam.lib.*; import buttondevteam.lib.chat.Channel; import buttondevteam.lib.chat.TBMCChatAPI; @@ -36,50 +37,53 @@ public class MCChatListener implements Listener, IListener return; if (e.getSender() instanceof DiscordSender || e.getSender() instanceof DiscordPlayerSender) return; - synchronized (this) { - final String authorPlayer = DiscordPlugin.sanitizeString(e.getSender() instanceof Player // - ? ((Player) e.getSender()).getDisplayName() // - : e.getSender().getName()); - final EmbedBuilder embed = new EmbedBuilder().withAuthorName(authorPlayer).withDescription(e.getMessage()) - .withColor(new Color(e.getChannel().color.getRed(), e.getChannel().color.getGreen(), - e.getChannel().color.getBlue())); - if (e.getSender() instanceof Player) - embed.withAuthorIcon("https://minotar.net/avatar/" + ((Player) e.getSender()).getName() + "/32.png"); - final long nanoTime = System.nanoTime(); - Consumer doit = lastmsgdata -> { - final EmbedObject embedObject = embed.build(); - String msg = lastmsgdata.channel.isPrivate() ? DiscordPlugin.sanitizeString(e.getChannel().DisplayName) - : ""; - if (lastmsgdata.message == null || lastmsgdata.message.isDeleted() - || !authorPlayer.equals(lastmsgdata.message.getEmbeds().get(0).getAuthor().getName()) - || lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120 - || !lastmsgdata.mcchannel.ID.equals(e.getChannel().ID)) { - lastmsgdata.message = DiscordPlugin.sendMessageToChannel(lastmsgdata.channel, msg, embedObject); - lastmsgdata.time = nanoTime; - lastmsgdata.mcchannel = e.getChannel(); - lastmsgdata.content = embedObject.description; - } else - try { - lastmsgdata.content = embedObject.description = lastmsgdata.content + "\n" - + embedObject.description;// The message object doesn't get updated - final LastMsgData _lastmsgdata = lastmsgdata; - DiscordPlugin.perform(() -> _lastmsgdata.message.edit(msg, embedObject)); - } catch (MissingPermissionsException | DiscordException e1) { - TBMCCoreAPI.SendException("An error occured while editing chat message!", e1); - } - }; - if (e.getChannel().equals(Channel.GlobalChat)) - doit.accept( - lastmsgdata == null ? lastmsgdata = new LastMsgData(DiscordPlugin.chatchannel) : lastmsgdata); + Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, () -> { + synchronized (this) { + final String authorPlayer = DiscordPlugin.sanitizeString(e.getSender() instanceof Player // + ? ((Player) e.getSender()).getDisplayName() // + : e.getSender().getName()); + final EmbedBuilder embed = new EmbedBuilder().withAuthorName(authorPlayer) + .withDescription(e.getMessage()).withColor(new Color(e.getChannel().color.getRed(), + e.getChannel().color.getGreen(), e.getChannel().color.getBlue())); + if (e.getSender() instanceof Player) + embed.withAuthorIcon( + "https://minotar.net/avatar/" + ((Player) e.getSender()).getName() + "/32.png"); + final long nanoTime = System.nanoTime(); + Consumer doit = lastmsgdata -> { + final EmbedObject embedObject = embed.build(); + String msg = lastmsgdata.channel.isPrivate() + ? DiscordPlugin.sanitizeString(e.getChannel().DisplayName) : ""; + if (lastmsgdata.message == null || lastmsgdata.message.isDeleted() + || !authorPlayer.equals(lastmsgdata.message.getEmbeds().get(0).getAuthor().getName()) + || lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120 + || !lastmsgdata.mcchannel.ID.equals(e.getChannel().ID)) { + lastmsgdata.message = DiscordPlugin.sendMessageToChannel(lastmsgdata.channel, msg, embedObject); + lastmsgdata.time = nanoTime; + lastmsgdata.mcchannel = e.getChannel(); + lastmsgdata.content = embedObject.description; + } else + try { + lastmsgdata.content = embedObject.description = lastmsgdata.content + "\n" + + embedObject.description;// The message object doesn't get updated + final LastMsgData _lastmsgdata = lastmsgdata; + DiscordPlugin.perform(() -> _lastmsgdata.message.edit(msg, embedObject)); + } catch (MissingPermissionsException | DiscordException e1) { + TBMCCoreAPI.SendException("An error occured while editing chat message!", e1); + } + }; + if (e.getChannel().equals(Channel.GlobalChat)) + doit.accept(lastmsgdata == null ? lastmsgdata = new LastMsgData(DiscordPlugin.chatchannel) + : lastmsgdata); - for (LastMsgData data : lastmsgPerUser) { - final IUser iUser = data.channel.getUsersHere().stream() - .filter(u -> u.getLongID() != u.getClient().getOurUser().getLongID()).findFirst().get(); // Doesn't support group DMs - final DiscordPlayer user = DiscordPlayer.getUser(iUser.getStringID(), DiscordPlayer.class); - if (user.isMinecraftChatEnabled() && e.shouldSendTo(getSender(data.channel, iUser, user))) - doit.accept(data); + for (LastMsgData data : lastmsgPerUser) { + final IUser iUser = data.channel.getUsersHere().stream() + .filter(u -> u.getLongID() != u.getClient().getOurUser().getLongID()).findFirst().get(); // Doesn't support group DMs + final DiscordPlayer user = DiscordPlayer.getUser(iUser.getStringID(), DiscordPlayer.class); + if (user.isMinecraftChatEnabled() && e.shouldSendTo(getSender(data.channel, iUser, user))) + doit.accept(data); + } } - } // TODO: Author URL + }); // TODO: Author URL } private static class LastMsgData { @@ -235,7 +239,7 @@ public class MCChatListener implements Listener, IListener dsender.sendMessage("Stop it. You know the answer."); lastlist = 0; } else - Bukkit.dispatchCommand(dsender, cmd); + VanillaCommandListener.runBukkitOrVanillaCommand(dsender, cmd); lastlistp = (short) Bukkit.getOnlinePlayers().size(); } else { if (dmessage.length() == 0 && event.getMessage().getAttachments().size() == 0) @@ -273,7 +277,7 @@ public class MCChatListener implements Listener, IListener val key = (channel.isPrivate() ? "" : "P") + author.getStringID(); return Stream.>>of( // https://stackoverflow.com/a/28833677/2703239 () -> Optional.ofNullable(OnlineSenders.get(key)), // Find first non-null - () -> Optional.ofNullable(ConnectedSenders.get(author.getStringID())), // This doesn't support it + () -> Optional.ofNullable(ConnectedSenders.get(key)), // This doesn't support the public chat, but it'll always return null for it () -> Optional.ofNullable(UnconnectedSenders.get(key)), () -> { val dsender = new DiscordSender(author, channel); UnconnectedSenders.put(key, dsender); diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordFakePlayer.java b/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordFakePlayer.java index 729c873..69e8126 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordFakePlayer.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordFakePlayer.java @@ -11,13 +11,10 @@ import org.bukkit.entity.*; import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.map.MapView; import org.bukkit.permissions.PermissibleBase; -import org.bukkit.permissions.ServerOperator; import org.bukkit.plugin.Plugin; import org.bukkit.scoreboard.Scoreboard; import buttondevteam.discordplugin.DiscordPlugin; -import lombok.Getter; -import lombok.Setter; import lombok.experimental.Delegate; import sx.blah.discord.handle.obj.IChannel; import sx.blah.discord.handle.obj.IUser; @@ -25,9 +22,7 @@ import sx.blah.discord.handle.obj.IUser; public class DiscordFakePlayer extends DiscordHumanEntity implements Player { protected DiscordFakePlayer(IUser user, IChannel channel, int entityId, UUID uuid) { super(user, channel, entityId, uuid); - perm = new PermissibleBase(new ServerOperator() { - private @Getter @Setter boolean op; - }); + perm = new PermissibleBase(Bukkit.getOfflinePlayer(uuid)); } @Delegate diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java new file mode 100644 index 0000000..3355478 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java @@ -0,0 +1,110 @@ +package buttondevteam.discordplugin.playerfaker; + +import java.util.Arrays; + +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_12_R1.CraftServer; +import org.bukkit.craftbukkit.v1_12_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_12_R1.command.VanillaCommandWrapper; +import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; + +import buttondevteam.discordplugin.DiscordSenderBase; +import buttondevteam.discordplugin.IMCPlayer; +import lombok.Getter; +import lombok.val; +import net.minecraft.server.v1_12_R1.ChatMessage; +import net.minecraft.server.v1_12_R1.CommandException; +import net.minecraft.server.v1_12_R1.EnumChatFormat; +import net.minecraft.server.v1_12_R1.IChatBaseComponent; +import net.minecraft.server.v1_12_R1.ICommandListener; +import net.minecraft.server.v1_12_R1.MinecraftServer; +import net.minecraft.server.v1_12_R1.World; + +public class VanillaCommandListener> 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 VanillaCommandListener(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 VanillaCommandListener(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 MinecraftServer C_() { + return ((CraftServer) Bukkit.getServer()).getServer(); + } + + @Override + public boolean a(int oplevel, String cmd) { + // return oplevel <= 2; // Value from CommandBlockListenerAbstract, found what it is in EntityPlayer - Wait, that'd always allow OP commands + return oplevel == 0 || player.isOp(); + } + + @Override + public String getName() { + return player.getName(); + } + + @Override + public World getWorld() { + return ((CraftWorld) player.getWorld()).getHandle(); + } + + @Override + public void sendMessage(IChatBaseComponent arg0) { + player.sendMessage(arg0.toPlainText()); + if (bukkitplayer != null) + ((CraftPlayer) bukkitplayer).getHandle().sendMessage(arg0); + } + + 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; + + ICommandListener icommandlistener = sender.getVanillaCmdListener(); + String[] args = cmdstr.split(" "); + args = Arrays.copyOfRange(args, 1, args.length); + try { + vcmd.dispatchVanillaCommand(sender, icommandlistener, args); + } catch (CommandException commandexception) { + // Taken from CommandHandler + ChatMessage chatmessage = new ChatMessage(commandexception.getMessage(), commandexception.getArgs()); + chatmessage.getChatModifier().setColor(EnumChatFormat.RED); + icommandlistener.sendMessage(chatmessage); + } + return true; + } +}