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; + } +}