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