Channelcon&chat&other fixes

#58 fixed
Made checkmarks stored separately per channel
#55 fixed
Correctly handling interrupt events, a local bug might have been fixed where the server didn't always stop.
Added a reset command

Needs testing
This commit is contained in:
Norbi Peti 2018-07-19 00:39:17 +02:00
parent e718fafd52
commit de857fef0b
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
5 changed files with 121 additions and 55 deletions

View file

@ -1,6 +1,5 @@
package buttondevteam.discordplugin; package buttondevteam.discordplugin;
import buttondevteam.lib.TBMCCoreAPI;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import sx.blah.discord.util.EmbedBuilder; import sx.blah.discord.util.EmbedBuilder;
import sx.blah.discord.util.RequestBuffer; import sx.blah.discord.util.RequestBuffer;
@ -9,6 +8,7 @@ import sx.blah.discord.util.RequestBuffer.IVoidRequest;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public final class DPUtils { public final class DPUtils {
@ -39,18 +39,13 @@ public final class DPUtils {
* Performs Discord actions, retrying when ratelimited. May return null if action fails too many times or in safe mode. * Performs Discord actions, retrying when ratelimited. May return null if action fails too many times or in safe mode.
*/ */
@Nullable @Nullable
public static <T> T perform(IRequest<T> action, long timeout, TimeUnit unit) { public static <T> T perform(IRequest<T> action, long timeout, TimeUnit unit) throws TimeoutException, InterruptedException {
if (DiscordPlugin.SafeMode) if (DiscordPlugin.SafeMode)
return null; return null;
if (Thread.currentThread() == DiscordPlugin.mainThread) // TODO: Ignore shutdown message <-- if (Thread.currentThread() == DiscordPlugin.mainThread) // TODO: Ignore shutdown message <--
// throw new RuntimeException("Tried to wait for a Discord request on the main thread. This could cause lag."); // throw new RuntimeException("Tried to wait for a Discord request on the main thread. This could cause lag.");
Bukkit.getLogger().warning("Waiting for a Discord request on the main thread!"); Bukkit.getLogger().warning("Waiting for a Discord request on the main thread!");
try { return RequestBuffer.request(action).get(timeout, unit); // Let the pros handle this
return RequestBuffer.request(action).get(timeout, unit); // Let the pros handle this
} catch (Exception e) {
TBMCCoreAPI.SendException("Couldn't perform Discord action!", e);
return null;
}
} }
/** /**

View file

@ -15,6 +15,7 @@ import com.google.gson.JsonParser;
import lombok.val; import lombok.val;
import net.milkbowl.vault.permission.Permission; import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@ -35,6 +36,7 @@ import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> { public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> {
@ -210,16 +212,8 @@ public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> {
TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), this); TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), this);
TBMCChatAPI.AddCommands(this, DiscordMCCommandBase.class); TBMCChatAPI.AddCommands(this, DiscordMCCommandBase.class);
TBMCCoreAPI.RegisterUserClass(DiscordPlayer.class); TBMCCoreAPI.RegisterUserClass(DiscordPlayer.class);
new Thread(this::AnnouncementGetterThreadMethod).start(); new Thread(this::AnnouncementGetterThreadMethod).start(); //TODO: Handle relogging (test)
setupProviders(); setupProviders();
/*
* IDiscordOAuth doa = new DiscordOAuthBuilder(dc).withClientID("226443037893591041") .withClientSecret(getConfig().getString("appsecret")) .withRedirectUrl("https://" +
* (TBMCCoreAPI.IsTestServer() ? "localhost" : "server.figytuna.com") + ":8081/callback") .withScopes(Scope.IDENTIFY).withHttpServerOptions(new HttpServerOptions().setPort(8081))
* .withSuccessHandler((rc, user) -> { rc.response().headers().add("Location", "https://" + (TBMCCoreAPI.IsTestServer() ? "localhost" : "server.figytuna.com") + ":8080/login?type=discord&"
* + rc.request().query()); rc.response().setStatusCode(303); rc.response().end("Redirecting"); rc.response().close(); }).withFailureHandler(rc -> { rc.response().headers().add("Location",
* "https://" + (TBMCCoreAPI.IsTestServer() ? "localhost" : "server.figytuna.com") + ":8080/login?type=discord&" + rc.request().query()); rc.response().setStatusCode(303);
* rc.response().end("Redirecting"); rc.response().close(); }).build(); getLogger().info("Auth URL: " + doa.buildAuthUrl());
*/
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while enabling DiscordPlugin!", e); TBMCCoreAPI.SendException("An error occured while enabling DiscordPlugin!", e);
} }
@ -260,22 +254,28 @@ public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> {
} }
saveConfig(); saveConfig();
MCChatListener.forAllMCChat(ch -> DiscordPlugin.sendMessageToChannelWait(ch, "", MCChatListener.forAllMCChat(ch -> {
new EmbedBuilder().withColor(Restart ? Color.ORANGE : Color.RED) try {
.withTitle(Restart ? "Server restarting" : "Server stopping") DiscordPlugin.sendMessageToChannelWait(ch, "",
.withDescription( new EmbedBuilder().withColor(Restart ? Color.ORANGE : Color.RED)
Bukkit.getOnlinePlayers().size() > 0 .withTitle(Restart ? "Server restarting" : "Server stopping")
? (DPUtils .withDescription(
.sanitizeString(Bukkit.getOnlinePlayers().stream() Bukkit.getOnlinePlayers().size() > 0
.map(p -> p.getDisplayName()).collect(Collectors.joining(", "))) ? (DPUtils
+ (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ") .sanitizeString(Bukkit.getOnlinePlayers().stream()
+ "asked *politely* to leave the server for a bit.") .map(Player::getDisplayName).collect(Collectors.joining(", ")))
: "") + (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ")
.build(), 5, TimeUnit.SECONDS)); + "asked *politely* to leave the server for a bit.")
: "")
.build(), 5, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException e) {
e.printStackTrace();
}
});
ChromaBot.getInstance().updatePlayerList(); ChromaBot.getInstance().updatePlayerList();
try { try {
SafeMode = true; // Stop interacting with Discord SafeMode = true; // Stop interacting with Discord
MCChatListener.stop(); MCChatListener.stop(true);
ChromaBot.delete(); ChromaBot.delete();
dc.changePresence(StatusType.IDLE, ActivityType.PLAYING, "Chromacraft"); //No longer using the same account for testing dc.changePresence(StatusType.IDLE, ActivityType.PLAYING, "Chromacraft"); //No longer using the same account for testing
dc.logout(); dc.logout();
@ -359,26 +359,30 @@ public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> {
} }
public static void sendMessageToChannel(IChannel channel, String message, EmbedObject embed) { public static void sendMessageToChannel(IChannel channel, String message, EmbedObject embed) {
sendMessageToChannel(channel, message, embed, false); try {
sendMessageToChannel(channel, message, embed, false);
} catch (TimeoutException | InterruptedException e) {
e.printStackTrace(); //Shouldn't happen, as we're not waiting on the result
}
} }
public static IMessage sendMessageToChannelWait(IChannel channel, String message) { public static IMessage sendMessageToChannelWait(IChannel channel, String message) throws TimeoutException, InterruptedException {
return sendMessageToChannelWait(channel, message, null); return sendMessageToChannelWait(channel, message, null);
} }
public static IMessage sendMessageToChannelWait(IChannel channel, String message, EmbedObject embed) { public static IMessage sendMessageToChannelWait(IChannel channel, String message, EmbedObject embed) throws TimeoutException, InterruptedException {
return sendMessageToChannel(channel, message, embed, true); return sendMessageToChannel(channel, message, embed, true);
} }
public static IMessage sendMessageToChannelWait(IChannel channel, String message, EmbedObject embed, long timeout, TimeUnit unit) { public static IMessage sendMessageToChannelWait(IChannel channel, String message, EmbedObject embed, long timeout, TimeUnit unit) throws TimeoutException, InterruptedException {
return sendMessageToChannel(channel, message, embed, true, timeout, unit); return sendMessageToChannel(channel, message, embed, true, timeout, unit);
} }
private static IMessage sendMessageToChannel(IChannel channel, String message, EmbedObject embed, boolean wait) { private static IMessage sendMessageToChannel(IChannel channel, String message, EmbedObject embed, boolean wait) throws TimeoutException, InterruptedException {
return sendMessageToChannel(channel, message, embed, wait, -1, null); return sendMessageToChannel(channel, message, embed, wait, -1, null);
} }
private static IMessage sendMessageToChannel(IChannel channel, String message, EmbedObject embed, boolean wait, long timeout, TimeUnit unit) { private static IMessage sendMessageToChannel(IChannel channel, String message, EmbedObject embed, boolean wait, long timeout, TimeUnit unit) throws TimeoutException, InterruptedException {
if (message.length() > 1980) { if (message.length() > 1980) {
message = message.substring(0, 1980); message = message.substring(0, 1980);
Bukkit.getLogger() Bukkit.getLogger()
@ -404,6 +408,8 @@ public class DiscordPlugin extends JavaPlugin implements IListener<ReadyEvent> {
DPUtils.performNoWait(r); DPUtils.performNoWait(r);
return null; return null;
} }
} catch (TimeoutException | InterruptedException e) {
throw e;
} catch (Exception e) { } catch (Exception e) {
Bukkit.getLogger().warning( Bukkit.getLogger().warning(
"Failed to deliver message to Discord! Channel: " + channel.getName() + " Message: " + message); "Failed to deliver message to Discord! Channel: " + channel.getName() + " Message: " + message);

View file

@ -27,7 +27,7 @@ public class ConnectCommand extends DiscordCommandBase {
@Override @Override
public boolean run(IMessage message, String args) { public boolean run(IMessage message, String args) {
if (args.length() == 0) if (args.length() == 0)
return true; return false;
if (args.contains(" ")) { if (args.contains(" ")) {
DiscordPlugin.sendMessageToChannel(message.getChannel(), DiscordPlugin.sendMessageToChannel(message.getChannel(),
"Too many arguments.\nUsage: connect <Minecraftname>"); "Too many arguments.\nUsage: connect <Minecraftname>");

View file

@ -12,6 +12,7 @@ import buttondevteam.lib.chat.ChatRoom;
import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCChatAPI;
import buttondevteam.lib.player.TBMCPlayer; import buttondevteam.lib.player.TBMCPlayer;
import com.vdurmont.emoji.EmojiParser; import com.vdurmont.emoji.EmojiParser;
import io.netty.util.collection.LongObjectHashMap;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.val; import lombok.val;
@ -38,6 +39,7 @@ import java.time.Instant;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -53,7 +55,7 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
@EventHandler // Minecraft @EventHandler // Minecraft
public void onMCChat(TBMCChatEvent ev) { public void onMCChat(TBMCChatEvent ev) {
if (ev.isCancelled()) if (DiscordPlugin.SafeMode || ev.isCancelled()) //SafeMode: Needed so it doesn't restart after server shutdown
return; return;
sendevents.add(new AbstractMap.SimpleEntry<>(ev, Instant.now())); sendevents.add(new AbstractMap.SimpleEntry<>(ev, Instant.now()));
if (sendtask != null) if (sendtask != null)
@ -71,15 +73,10 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
try { try {
TBMCChatEvent e; TBMCChatEvent e;
Instant time; Instant time;
try { val se = sendevents.take(); // Wait until an element is available
val se = sendevents.take(); // Wait until an element is available e = se.getKey();
e = se.getKey(); time = se.getValue();
time = se.getValue();
} catch (InterruptedException ex) {
sendtask.cancel();
sendtask = null;
return;
}
final String authorPlayer = "[" + DPUtils.sanitizeString(e.getChannel().DisplayName) + "] " // final String authorPlayer = "[" + DPUtils.sanitizeString(e.getChannel().DisplayName) + "] " //
+ (e.getSender() instanceof DiscordSenderBase ? "[D]" : "") // + (e.getSender() instanceof DiscordSenderBase ? "[D]" : "") //
+ (DPUtils.sanitizeString(e.getSender() instanceof Player // + (DPUtils.sanitizeString(e.getSender() instanceof Player //
@ -102,7 +99,7 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
// embed.withFooterText(e.getChannel().DisplayName); // embed.withFooterText(e.getChannel().DisplayName);
embed.withTimestamp(time); embed.withTimestamp(time);
final long nanoTime = System.nanoTime(); final long nanoTime = System.nanoTime();
Consumer<LastMsgData> doit = lastmsgdata -> { InterruptibleConsumer<LastMsgData> doit = lastmsgdata -> {
final EmbedObject embedObject = embed.build(); final EmbedObject embedObject = embed.build();
if (lastmsgdata.message == null || lastmsgdata.message.isDeleted() if (lastmsgdata.message == null || lastmsgdata.message.isDeleted()
|| !authorPlayer.equals(lastmsgdata.message.getEmbeds().get(0).getAuthor().getName()) || !authorPlayer.equals(lastmsgdata.message.getEmbeds().get(0).getAuthor().getName())
@ -144,14 +141,19 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
while (iterator.hasNext()) { //TODO: Add cmd to fix mcchat while (iterator.hasNext()) { //TODO: Add cmd to fix mcchat
val lmd = iterator.next(); val lmd = iterator.next();
if ((e.isFromcmd() || isdifferentchannel.test(lmd.channel)) //Test if msg is from Discord if ((e.isFromcmd() || isdifferentchannel.test(lmd.channel)) //Test if msg is from Discord
&& e.getChannel().ID.equals(lmd.mcchannel.ID)) //If it's from a command, the command msg has been deleted, so we need to send it && e.getChannel().ID.equals(lmd.mcchannel.ID) //If it's from a command, the command msg has been deleted, so we need to send it
if (e.shouldSendTo(lmd.dcp) && e.getGroupID().equals(lmd.groupID)) //Check original user's permissions && e.getGroupID().equals(lmd.groupID)) { //Check if this is the group we want to test - #58
if (e.shouldSendTo(lmd.dcp)) //Check original user's permissions
doit.accept(lmd); doit.accept(lmd);
else { else {
iterator.remove(); //If the user no longer has permission, remove the connection iterator.remove(); //If the user no longer has permission, remove the connection
DiscordPlugin.sendMessageToChannel(lmd.channel, "The user no longer has permission to view the channel, connection removed."); DiscordPlugin.sendMessageToChannel(lmd.channel, "The user no longer has permission to view the channel, connection removed.");
} }
}
} }
} catch (InterruptedException ex) { //Stop if interrupted anywhere
sendtask.cancel();
sendtask = null;
} catch (Exception ex) { } catch (Exception ex) {
TBMCCoreAPI.SendException("Error while sending message to Discord!", ex); TBMCCoreAPI.SendException("Error while sending message to Discord!", ex);
} }
@ -216,6 +218,7 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
* Used for town or nation chats or anything else * Used for town or nation chats or anything else
*/ */
private static ArrayList<CustomLMD> lastmsgCustom = new ArrayList<>(); private static ArrayList<CustomLMD> lastmsgCustom = new ArrayList<>();
private static LongObjectHashMap<IMessage> lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks
public static boolean privateMCChat(IChannel channel, boolean start, IUser user, DiscordPlayer dp) { public static boolean privateMCChat(IChannel channel, boolean start, IUser user, DiscordPlayer dp) {
TBMCPlayer mcp = dp.getAs(TBMCPlayer.class); TBMCPlayer mcp = dp.getAs(TBMCPlayer.class);
@ -233,6 +236,8 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
MCListener.callEventExcludingSome(new PlayerQuitEvent(sender, "")); MCListener.callEventExcludingSome(new PlayerQuitEvent(sender, ""));
} }
} }
if (!start)
lastmsgfromd.remove(channel.getLongID());
return start // return start //
? lastmsgPerUser.add(new LastMsgData(channel, user, dp)) // Doesn't support group DMs ? lastmsgPerUser.add(new LastMsgData(channel, user, dp)) // Doesn't support group DMs
: lastmsgPerUser.removeIf(lmd -> lmd.channel.getLongID() == channel.getLongID()); : lastmsgPerUser.removeIf(lmd -> lmd.channel.getLongID() == channel.getLongID());
@ -273,6 +278,7 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
} }
public static boolean removeCustomChat(IChannel channel) { public static boolean removeCustomChat(IChannel channel) {
lastmsgfromd.remove(channel.getLongID());
return lastmsgCustom.removeIf(lmd -> lmd.channel.getLongID() == channel.getLongID()); return lastmsgCustom.removeIf(lmd -> lmd.channel.getLongID() == channel.getLongID());
} }
@ -337,14 +343,32 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
.map(data -> data.channel).forEach(action); .map(data -> data.channel).forEach(action);
} }
public static void stop() { /**
* Stop the listener. Any calls to onMCChat will restart it as long as we're not in safe mode.
*
* @param wait Wait 5 seconds for the threads to stop
*/
public static void stop(boolean wait) {
if (sendthread != null) sendthread.interrupt(); if (sendthread != null) sendthread.interrupt();
if (recthread != null) recthread.interrupt(); if (recthread != null) recthread.interrupt();
try {
if (sendthread != null) {
sendthread.interrupt();
if (wait)
sendthread.join(5000);
}
if (recthread != null) {
recthread.interrupt();
if (wait)
recthread.join(5000);
}
} catch (InterruptedException e) {
e.printStackTrace(); //This thread shouldn't be interrupted
}
} }
private BukkitTask rectask; private BukkitTask rectask;
private LinkedBlockingQueue<MessageReceivedEvent> recevents = new LinkedBlockingQueue<>(); private LinkedBlockingQueue<MessageReceivedEvent> recevents = new LinkedBlockingQueue<>();
private IMessage lastmsgfromd; // Last message sent by a Discord user, used for clearing checkmarks
private Runnable recrun; private Runnable recrun;
private static Thread recthread; private static Thread recthread;
@ -507,14 +531,15 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
} }
if (react) { if (react) {
try { try {
if (lastmsgfromd != null) { val lmfd = lastmsgfromd.get(event.getChannel().getLongID());
DPUtils.perform(() -> lastmsgfromd.removeReaction(DiscordPlugin.dc.getOurUser(), if (lmfd != null) {
DPUtils.perform(() -> lmfd.removeReaction(DiscordPlugin.dc.getOurUser(),
DiscordPlugin.DELIVERED_REACTION)); // Remove it no matter what, we know it's there 99.99% of the time DiscordPlugin.DELIVERED_REACTION)); // Remove it no matter what, we know it's there 99.99% of the time
} }
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e); TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e);
} }
lastmsgfromd = event.getMessage(); lastmsgfromd.put(event.getChannel().getLongID(), event.getMessage());
DPUtils.perform(() -> event.getMessage().addReaction(DiscordPlugin.DELIVERED_REACTION)); DPUtils.perform(() -> event.getMessage().addReaction(DiscordPlugin.DELIVERED_REACTION));
} }
} catch (Exception e) { } catch (Exception e) {
@ -573,4 +598,9 @@ public class MCChatListener implements Listener, IListener<MessageReceivedEvent>
return Optional.of(dsender); return Optional.of(dsender);
}).map(Supplier::get).filter(Optional::isPresent).map(Optional::get).findFirst().get(); }).map(Supplier::get).filter(Optional::isPresent).map(Optional::get).findFirst().get();
} }
@FunctionalInterface
private interface InterruptibleConsumer<T> {
void accept(T value) throws TimeoutException, InterruptedException;
}
} }

View file

@ -0,0 +1,35 @@
package buttondevteam.discordplugin.mccommands;
import buttondevteam.discordplugin.DiscordPlugin;
import buttondevteam.discordplugin.listeners.MCChatListener;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.TBMCCommandBase;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@CommandClass(path = "discord reset", modOnly = true)
public class ResetMCCommand extends TBMCCommandBase { //Not player-only, so not using DiscordMCCommandBase
@Override
public boolean OnCommand(CommandSender sender, String s, String[] strings) {
Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, () -> {
sender.sendMessage("§bStopping MCChatListener...");
DiscordPlugin.SafeMode = true;
MCChatListener.stop(true);
if (DiscordPlugin.dc.isLoggedIn()) {
sender.sendMessage("§bLogging out...");
DiscordPlugin.dc.logout();
} else
sender.sendMessage("§bWe're not logged in.");
sender.sendMessage("§bLogging in...");
DiscordPlugin.dc.login();
DiscordPlugin.SafeMode = false;
sender.sendMessage("§bChromaBot has been reset!");
});
return false;
}
@Override
public String[] GetHelpText(String s) {
return new String[0];
}
}