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
This commit is contained in:
Norbi Peti 2017-07-13 00:58:05 +02:00
parent a501d9d457
commit fd14bf1954
9 changed files with 230 additions and 57 deletions

View file

@ -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
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

10
deploy.sh Normal file
View file

@ -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

View file

@ -150,6 +150,12 @@
<version>1.12-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId>
<version>1.12-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.austinv11</groupId>
<artifactId>Discord4j</artifactId>

View file

@ -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<DiscordConnectedPlayer> {
private static int nextEntityId = 10000;
private @Getter VanillaCommandListener<DiscordConnectedPlayer> vanillaCmdListener;
public DiscordConnectedPlayer(IUser user, IChannel channel, UUID uuid) {
super(user, channel, nextEntityId++, uuid);
vanillaCmdListener = new VanillaCommandListener<>(this);
}
}

View file

@ -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<DiscordPlayerSender> {
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<DiscordPlayerSender> vanillaCmdListener;
public DiscordPlayerSender(IUser user, IChannel channel, Player player) {
super(user, channel);
this.player = player;
vanillaCmdListener = new VanillaCommandListener<DiscordPlayerSender>(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

View file

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

View file

@ -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<MessageReceivedEvent>
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<LastMsgData> 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<LastMsgData> 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<MessageReceivedEvent>
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<MessageReceivedEvent>
val key = (channel.isPrivate() ? "" : "P") + author.getStringID();
return Stream.<Supplier<Optional<DiscordSenderBase>>>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);

View file

@ -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

View file

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