) event -> { //Role update event
+ if (!DiscordPlugin.plugin.isGameRole(event.getNewRole())) {
+ if (DiscordPlugin.GameRoles.remove(event.getOldRole().getName()))
+ DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Removed " + event.getOldRole().getName() + " as a game role because it's color changed.");
+ } else {
+ if (DiscordPlugin.GameRoles.contains(event.getOldRole().getName()) && event.getOldRole().getName().equals(event.getNewRole().getName()))
+ return;
+ boolean removed = DiscordPlugin.GameRoles.remove(event.getOldRole().getName()); //Regardless of whether it was a game role
+ DiscordPlugin.GameRoles.add(event.getNewRole().getName()); //Add it because it has no color
+ if (removed)
+ DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Changed game role from " + event.getOldRole().getName() + " to " + event.getNewRole().getName() + ".");
+ else
+ DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, "Added " + event.getNewRole().getName() + " as game role because it has the default color.");
+ }
+ }};
+ }
+
+ private static boolean debug = false;
+
+ public static void debug(String debug) {
+ if (CommonListeners.debug) //Debug
+ DPUtils.getLogger().info(debug);
+ }
+
+ public static boolean debug() {
+ return debug = !debug;
+ }
+}
diff --git a/src/main/java/buttondevteam/discordplugin/listeners/DiscordListener.java b/src/main/java/buttondevteam/discordplugin/listeners/DiscordListener.java
new file mode 100644
index 0000000..292a1a1
--- /dev/null
+++ b/src/main/java/buttondevteam/discordplugin/listeners/DiscordListener.java
@@ -0,0 +1,4 @@
+package buttondevteam.discordplugin.listeners;
+
+public interface DiscordListener {
+}
diff --git a/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java b/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java
index 6f9b3c9..a39d5fc 100755
--- a/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java
+++ b/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java
@@ -1,98 +1,24 @@
package buttondevteam.discordplugin.listeners;
-import buttondevteam.discordplugin.*;
-import buttondevteam.discordplugin.commands.ConnectCommand;
-import buttondevteam.lib.TBMCCoreAPI;
-import buttondevteam.lib.TBMCSystemChatEvent;
-import buttondevteam.lib.player.*;
-import com.earth2me.essentials.CommandSource;
+import buttondevteam.discordplugin.DiscordPlayer;
+import buttondevteam.discordplugin.DiscordPlugin;
+import buttondevteam.lib.player.TBMCPlayerGetInfoEvent;
import lombok.val;
-import net.ess3.api.events.AfkStatusChangeEvent;
-import net.ess3.api.events.MuteStatusChangeEvent;
import org.bukkit.Bukkit;
-import org.bukkit.entity.Player;
-import org.bukkit.event.*;
-import org.bukkit.event.entity.PlayerDeathEvent;
-import org.bukkit.event.player.PlayerJoinEvent;
-import org.bukkit.event.player.PlayerKickEvent;
-import org.bukkit.event.player.PlayerLoginEvent;
-import org.bukkit.event.player.PlayerLoginEvent.Result;
-import org.bukkit.event.player.PlayerQuitEvent;
-import org.bukkit.event.server.BroadcastMessageEvent;
+import org.bukkit.event.Event;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerCommandEvent;
import org.bukkit.plugin.AuthorNagException;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredListener;
-import sx.blah.discord.handle.obj.IRole;
import sx.blah.discord.handle.obj.IUser;
-import sx.blah.discord.util.DiscordException;
-import sx.blah.discord.util.MissingPermissionsException;
import java.util.Arrays;
import java.util.logging.Level;
public class MCListener implements Listener {
- @EventHandler(priority = EventPriority.HIGHEST)
- public void onPlayerLogin(PlayerLoginEvent e) {
- if (e.getResult() != Result.ALLOWED)
- return;
- MCChatListener.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) //Only private mcchat should be in ConnectedSenders
- .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
- .ifPresent(dcp -> callEventExcludingSome(new PlayerQuitEvent(dcp, "")));
- }
-
- @EventHandler(priority = EventPriority.LOWEST)
- public void onPlayerJoin(TBMCPlayerJoinEvent e) {
- if (e.getPlayer() instanceof DiscordConnectedPlayer)
- return; // Don't show the joined message for the fake player
- Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, () -> {
- final Player p = e.getPlayer();
- DiscordPlayer dp = e.GetPlayer().getAs(DiscordPlayer.class);
- if (dp != null) {
- val user = DiscordPlugin.dc.getUserByID(Long.parseLong(dp.getDiscordID()));
- MCChatListener.addSender(MCChatListener.OnlineSenders, dp.getDiscordID(),
- new DiscordPlayerSender(user, user.getOrCreatePMChannel(), p));
- MCChatListener.addSender(MCChatListener.OnlineSenders, dp.getDiscordID(),
- new DiscordPlayerSender(user, DiscordPlugin.chatchannel, p)); //Stored per-channel
- }
- if (ConnectCommand.WaitingToConnect.containsKey(e.GetPlayer().PlayerName().get())) {
- IUser user = DiscordPlugin.dc
- .getUserByID(Long.parseLong(ConnectCommand.WaitingToConnect.get(e.GetPlayer().PlayerName().get())));
- p.sendMessage("§bTo connect with the Discord account @" + user.getName() + "#" + user.getDiscriminator()
- + " do /discord accept");
- p.sendMessage("§bIf it wasn't you, do /discord decline");
- }
- final String message = e.GetPlayer().PlayerName().get() + " joined the game";
- MCChatListener.forAllowedCustomAndAllMCChat(MCChatListener.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true);
- //System.out.println("Does this appear more than once?"); //No
- MCChatListener.ListC = 0;
- ChromaBot.getInstance().updatePlayerList();
- });
- }
-
- @EventHandler(priority = EventPriority.HIGHEST)
- public void onPlayerLeave(TBMCPlayerQuitEvent e) {
- if (e.getPlayer() instanceof DiscordConnectedPlayer)
- return; // Only care about real users
- MCChatListener.OnlineSenders.entrySet()
- .removeIf(entry -> entry.getValue().entrySet().stream().anyMatch(p -> p.getValue().getUniqueId().equals(e.getPlayer().getUniqueId())));
- Bukkit.getScheduler().runTask(DiscordPlugin.plugin,
- () -> MCChatListener.ConnectedSenders.values().stream().flatMap(v -> v.values().stream())
- .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
- .ifPresent(dcp -> callEventExcludingSome(new PlayerJoinEvent(dcp, ""))));
- Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin,
- ChromaBot.getInstance()::updatePlayerList, 5);
- final String message = e.GetPlayer().PlayerName().get() + " left the game";
- MCChatListener.forAllowedCustomAndAllMCChat(MCChatListener.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true);
- }
-
- @EventHandler(priority = EventPriority.HIGHEST)
- public void onPlayerKick(PlayerKickEvent e) {
- /*if (!DiscordPlugin.hooked && !e.getReason().equals("The server is restarting")
- && !e.getReason().equals("Server closed")) // The leave messages errored with the previous setup, I could make it wait since I moved it here, but instead I have a special
- MCChatListener.forAllowedCustomAndAllMCChat(e.getPlayer().getName() + " left the game"); // message for this - Oh wait this doesn't even send normally because of the hook*/
- }
-
@EventHandler
public void onGetInfo(TBMCPlayerGetInfoEvent e) {
if (DiscordPlugin.SafeMode)
@@ -107,125 +33,71 @@ public class MCListener implements Listener {
e.addInfo(user.getPresence().getActivity().get() + ": " + user.getPresence().getText().get());
}
- @EventHandler(priority = EventPriority.LOW)
- public void onPlayerDeath(PlayerDeathEvent e) {
- MCChatListener.forAllowedCustomAndAllMCChat(MCChatListener.send(e.getDeathMessage()), e.getEntity(), ChannelconBroadcast.DEATH, true);
- }
-
- @EventHandler
- public void onPlayerAFK(AfkStatusChangeEvent e) {
- final Player base = e.getAffected().getBase();
- if (e.isCancelled() || !base.isOnline())
- return;
- final String msg = base.getDisplayName()
- + " is " + (e.getValue() ? "now" : "no longer") + " AFK.";
- MCChatListener.forAllowedCustomAndAllMCChat(MCChatListener.send(msg), base, ChannelconBroadcast.AFK, false);
- }
-
@EventHandler
public void onServerCommand(ServerCommandEvent e) {
DiscordPlugin.Restart = !e.getCommand().equalsIgnoreCase("stop"); // The variable is always true except if stopped
}
- @EventHandler
- public void onPlayerMute(MuteStatusChangeEvent e) {
- try {
- DPUtils.performNoWait(() -> {
- final IRole role = DiscordPlugin.dc.getRoleByID(164090010461667328L);
- final CommandSource source = e.getAffected().getSource();
- if (!source.isPlayer())
- return;
- final IUser user = DiscordPlugin.dc.getUserByID(
- Long.parseLong(TBMCPlayerBase.getPlayer(source.getPlayer().getUniqueId(), TBMCPlayer.class)
- .getAs(DiscordPlayer.class).getDiscordID()));
- if (e.getValue())
- user.addRole(role);
- else
- user.removeRole(role);
- });
- } catch (DiscordException | MissingPermissionsException ex) {
- TBMCCoreAPI.SendException("Failed to give/take Muted role to player " + e.getAffected().getName() + "!",
- ex);
- }
- }
+ private static final String[] EXCLUDED_PLUGINS = {"ProtocolLib", "LibsDisguises", "JourneyMapServer"}; //TODO: Make configurable
- @EventHandler
- public void onChatSystemMessage(TBMCSystemChatEvent event) {
- MCChatListener.forAllowedMCChat(MCChatListener.send(event.getMessage()), event);
- }
+ public static void callEventExcludingSome(Event event) {
+ callEventExcluding(event, false, EXCLUDED_PLUGINS);
+ }
- @EventHandler
- public void onBroadcastMessage(BroadcastMessageEvent event) {
- MCChatListener.forCustomAndAllMCChat(MCChatListener.send(event.getMessage()), ChannelconBroadcast.BROADCAST, false);
- }
+ /**
+ * Calls an event with the given details.
+ *
+ * This method only synchronizes when the event is not asynchronous.
+ *
+ * @param event Event details
+ * @param only Flips the operation and includes the listed plugins
+ * @param plugins The plugins to exclude. Not case sensitive.
+ */
+ public static void callEventExcluding(Event event, boolean only, String... plugins) { // Copied from Spigot-API and modified a bit
+ if (event.isAsynchronous()) {
+ if (Thread.holdsLock(Bukkit.getPluginManager())) {
+ throw new IllegalStateException(
+ event.getEventName() + " cannot be triggered asynchronously from inside synchronized code.");
+ }
+ if (Bukkit.getServer().isPrimaryThread()) {
+ throw new IllegalStateException(
+ event.getEventName() + " cannot be triggered asynchronously from primary server thread.");
+ }
+ fireEventExcluding(event, only, plugins);
+ } else {
+ synchronized (Bukkit.getPluginManager()) {
+ fireEventExcluding(event, only, plugins);
+ }
+ }
+ }
- @EventHandler
- public void onYEEHAW(TBMCYEEHAWEvent event) { //TODO: Inherit from the chat event base to have channel support
- String name = event.getSender() instanceof Player ? ((Player) event.getSender()).getDisplayName()
- : event.getSender().getName();
- //Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO
- MCChatListener.forAllMCChat(MCChatListener.send(name + " <:YEEHAW:" + DiscordPlugin.mainServer.getEmojiByName("YEEHAW").getStringID() + ">s"));
- }
+ private static void fireEventExcluding(Event event, boolean only, String... plugins) {
+ HandlerList handlers = event.getHandlers(); // Code taken from SimplePluginManager in Spigot-API
+ RegisteredListener[] listeners = handlers.getRegisteredListeners();
+ val server = Bukkit.getServer();
- private static final String[] EXCLUDED_PLUGINS = {"ProtocolLib", "LibsDisguises"};
+ for (RegisteredListener registration : listeners) {
+ if (!registration.getPlugin().isEnabled()
+ || Arrays.stream(plugins).anyMatch(p -> only ^ p.equalsIgnoreCase(registration.getPlugin().getName())))
+ continue; // Modified to exclude plugins
- public static void callEventExcludingSome(Event event) {
- callEventExcluding(event, EXCLUDED_PLUGINS);
- }
+ try {
+ registration.callEvent(event);
+ } catch (AuthorNagException ex) {
+ Plugin plugin = registration.getPlugin();
- /**
- * Calls an event with the given details.
- *
- * This method only synchronizes when the event is not asynchronous.
- *
- * @param event Event details
- * @param plugins The plugins to exclude. Not case sensitive.
- */
- private static void callEventExcluding(Event event, String... plugins) { // Copied from Spigot-API and modified a bit
- if (event.isAsynchronous()) {
- if (Thread.holdsLock(Bukkit.getPluginManager())) {
- throw new IllegalStateException(
- event.getEventName() + " cannot be triggered asynchronously from inside synchronized code.");
- }
- if (Bukkit.getServer().isPrimaryThread()) {
- throw new IllegalStateException(
- event.getEventName() + " cannot be triggered asynchronously from primary server thread.");
- }
- fireEventExcluding(event, plugins);
- } else {
- synchronized (Bukkit.getPluginManager()) {
- fireEventExcluding(event, plugins);
- }
- }
- }
+ if (plugin.isNaggable()) {
+ plugin.setNaggable(false);
- private static void fireEventExcluding(Event event, String... plugins) {
- HandlerList handlers = event.getHandlers(); // Code taken from SimplePluginManager in Spigot-API
- RegisteredListener[] listeners = handlers.getRegisteredListeners();
- val server = Bukkit.getServer();
-
- for (RegisteredListener registration : listeners) {
- if (!registration.getPlugin().isEnabled()
- || Arrays.stream(plugins).anyMatch(p -> p.equalsIgnoreCase(registration.getPlugin().getName())))
- continue; // Modified to exclude plugins
-
- try {
- registration.callEvent(event);
- } catch (AuthorNagException ex) {
- Plugin plugin = registration.getPlugin();
-
- if (plugin.isNaggable()) {
- plugin.setNaggable(false);
-
- server.getLogger().log(Level.SEVERE,
- String.format("Nag author(s): '%s' of '%s' about the following: %s",
- plugin.getDescription().getAuthors(), plugin.getDescription().getFullName(),
- ex.getMessage()));
- }
- } catch (Throwable ex) {
- server.getLogger().log(Level.SEVERE, "Could not pass event " + event.getEventName() + " to "
- + registration.getPlugin().getDescription().getFullName(), ex);
- }
- }
- }
+ server.getLogger().log(Level.SEVERE,
+ String.format("Nag author(s): '%s' of '%s' about the following: %s",
+ plugin.getDescription().getAuthors(), plugin.getDescription().getFullName(),
+ ex.getMessage()));
+ }
+ } catch (Throwable ex) {
+ server.getLogger().log(Level.SEVERE, "Could not pass event " + event.getEventName() + " to "
+ + registration.getPlugin().getDescription().getFullName(), ex);
+ }
+ }
+ }
}
diff --git a/src/main/java/buttondevteam/discordplugin/commands/MCChatCommand.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java
similarity index 65%
rename from src/main/java/buttondevteam/discordplugin/commands/MCChatCommand.java
rename to src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java
index e2ca2ca..3c05f02 100755
--- a/src/main/java/buttondevteam/discordplugin/commands/MCChatCommand.java
+++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java
@@ -1,8 +1,8 @@
-package buttondevteam.discordplugin.commands;
+package buttondevteam.discordplugin.mcchat;
import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.DiscordPlugin;
-import buttondevteam.discordplugin.listeners.MCChatListener;
+import buttondevteam.discordplugin.commands.DiscordCommandBase;
import buttondevteam.lib.TBMCCoreAPI;
import sx.blah.discord.handle.obj.IMessage;
@@ -13,7 +13,7 @@ public class MCChatCommand extends DiscordCommandBase {
return "mcchat";
}
- @Override
+ @Override //TODO: Only register if module is enabled
public boolean run(IMessage message, String args) {
if (!message.getChannel().isPrivate()) {
DiscordPlugin.sendMessageToChannel(message.getChannel(),
@@ -22,10 +22,10 @@ public class MCChatCommand extends DiscordCommandBase {
}
try (final DiscordPlayer user = DiscordPlayer.getUser(message.getAuthor().getStringID(), DiscordPlayer.class)) {
boolean mcchat = !user.isMinecraftChatEnabled();
- MCChatListener.privateMCChat(message.getChannel(), mcchat, message.getAuthor(), user);
+ MCChatPrivate.privateMCChat(message.getChannel(), mcchat, message.getAuthor(), user);
DiscordPlugin.sendMessageToChannel(message.getChannel(),
"Minecraft chat " + (mcchat //
- ? "enabled. Use '/mcchat' again to turn it off." //
+ ? "enabled. Use '" + DiscordPlugin.getPrefix() + "mcchat' again to turn it off." //
: "disabled."));
} catch (Exception e) {
TBMCCoreAPI.SendException("Error while setting mcchat for user" + message.getAuthor().getName(), e);
@@ -36,8 +36,9 @@ public class MCChatCommand extends DiscordCommandBase {
@Override
public String[] getHelpText() {
return new String[] { //
- "mcchat enables or disables the Minecraft chat in private messages.", //
- "It can be useful if you don't want your messages to be visible, for example when talking a private channel." //
+ DiscordPlugin.getPrefix() + "mcchat enables or disables the Minecraft chat in private messages.", //
+ "It can be useful if you don't want your messages to be visible, for example when talking in a private channel.", //
+ "You can also run all of the ingame commands you have access to using this command, if you have your accounts connected." //
}; // TODO: Pin channel switching to indicate the current channel
}
diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java
new file mode 100644
index 0000000..74979f8
--- /dev/null
+++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java
@@ -0,0 +1,59 @@
+package buttondevteam.discordplugin.mcchat;
+
+import buttondevteam.discordplugin.DiscordConnectedPlayer;
+import buttondevteam.lib.chat.Channel;
+import lombok.NonNull;
+import lombok.val;
+import sx.blah.discord.handle.obj.IChannel;
+import sx.blah.discord.handle.obj.IUser;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class MCChatCustom {
+ /**
+ * Used for town or nation chats or anything else
+ */
+ static ArrayList lastmsgCustom = new ArrayList<>();
+
+ public static void addCustomChat(IChannel channel, String groupid, Channel mcchannel, IUser user, DiscordConnectedPlayer dcp, int toggles) {
+ val lmd = new CustomLMD(channel, user, groupid, mcchannel, dcp, toggles);
+ lastmsgCustom.add(lmd);
+ }
+
+ public static boolean hasCustomChat(IChannel channel) {
+ return lastmsgCustom.stream().anyMatch(lmd -> lmd.channel.getLongID() == channel.getLongID());
+ }
+
+ @Nullable
+ public static CustomLMD getCustomChat(IChannel channel) {
+ return lastmsgCustom.stream().filter(lmd -> lmd.channel.getLongID() == channel.getLongID()).findAny().orElse(null);
+ }
+
+ public static boolean removeCustomChat(IChannel channel) {
+ MCChatUtils.lastmsgfromd.remove(channel.getLongID());
+ return lastmsgCustom.removeIf(lmd -> lmd.channel.getLongID() == channel.getLongID());
+ }
+
+ public static List getCustomChats() {
+ return Collections.unmodifiableList(lastmsgCustom);
+ }
+
+ public static class CustomLMD extends MCChatUtils.LastMsgData {
+ public final String groupID;
+ public final Channel mcchannel;
+ public final DiscordConnectedPlayer dcp;
+ public int toggles;
+
+ private CustomLMD(@NonNull IChannel channel, @NonNull IUser user,
+ @NonNull String groupid, @NonNull Channel mcchannel, @NonNull DiscordConnectedPlayer dcp, int toggles) {
+ super(channel, user);
+ groupID = groupid;
+ this.mcchannel = mcchannel;
+ this.dcp = dcp;
+ this.toggles = toggles;
+ }
+ }
+}
diff --git a/src/main/java/buttondevteam/discordplugin/listeners/MCChatListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java
similarity index 56%
rename from src/main/java/buttondevteam/discordplugin/listeners/MCChatListener.java
rename to src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java
index 4d73c83..c2ec714 100755
--- a/src/main/java/buttondevteam/discordplugin/listeners/MCChatListener.java
+++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java
@@ -1,56 +1,48 @@
-package buttondevteam.discordplugin.listeners;
+package buttondevteam.discordplugin.mcchat;
-import buttondevteam.discordplugin.*;
+import buttondevteam.core.ComponentManager;
+import buttondevteam.discordplugin.DPUtils;
+import buttondevteam.discordplugin.DiscordPlugin;
+import buttondevteam.discordplugin.DiscordSender;
+import buttondevteam.discordplugin.DiscordSenderBase;
+import buttondevteam.discordplugin.listeners.CommandListener;
import buttondevteam.discordplugin.playerfaker.VanillaCommandListener;
import buttondevteam.lib.TBMCChatEvent;
import buttondevteam.lib.TBMCChatPreprocessEvent;
import buttondevteam.lib.TBMCCoreAPI;
-import buttondevteam.lib.TBMCSystemChatEvent;
import buttondevteam.lib.chat.Channel;
import buttondevteam.lib.chat.ChatMessage;
import buttondevteam.lib.chat.ChatRoom;
import buttondevteam.lib.chat.TBMCChatAPI;
import buttondevteam.lib.player.TBMCPlayer;
import com.vdurmont.emoji.EmojiParser;
-import io.netty.util.collection.LongObjectHashMap;
-import lombok.NonNull;
-import lombok.RequiredArgsConstructor;
-import lombok.experimental.var;
import lombok.val;
import org.bukkit.Bukkit;
-import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerJoinEvent;
-import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.scheduler.BukkitTask;
-import sx.blah.discord.api.events.IListener;
import sx.blah.discord.api.internal.json.objects.EmbedObject;
import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent;
import sx.blah.discord.handle.obj.IChannel;
import sx.blah.discord.handle.obj.IMessage;
-import sx.blah.discord.handle.obj.IPrivateChannel;
import sx.blah.discord.handle.obj.IUser;
import sx.blah.discord.util.DiscordException;
import sx.blah.discord.util.EmbedBuilder;
import sx.blah.discord.util.MissingPermissionsException;
-import javax.annotation.Nullable;
import java.awt.*;
import java.time.Instant;
-import java.util.*;
-import java.util.List;
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.Optional;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException;
-import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.function.Supplier;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
-public class MCChatListener implements Listener, IListener {
+public class MCChatListener implements Listener {
private BukkitTask sendtask;
private LinkedBlockingQueue> sendevents = new LinkedBlockingQueue<>();
private Runnable sendrunnable;
@@ -58,7 +50,7 @@ public class MCChatListener implements Listener, IListener
@EventHandler // Minecraft
public void onMCChat(TBMCChatEvent ev) {
- if (DiscordPlugin.SafeMode || ev.isCancelled()) //SafeMode: Needed so it doesn't restart after server shutdown
+ if (!ComponentManager.isEnabled(MinecraftChatModule.class) || ev.isCancelled()) //SafeMode: Needed so it doesn't restart after server shutdown
return;
sendevents.add(new AbstractMap.SimpleEntry<>(ev, Instant.now()));
if (sendtask != null)
@@ -81,7 +73,7 @@ public class MCChatListener implements Listener, IListener
time = se.getValue();
final String authorPlayer = "[" + DPUtils.sanitizeStringNoEscape(e.getChannel().DisplayName) + "] " //
- + (e.getSender() instanceof DiscordSenderBase ? "[D]" : "") //
+ + ("Minecraft".equals(e.getOrigin()) ? "" : "[" + e.getOrigin().substring(0, 1) + "]") //
+ (DPUtils.sanitizeStringNoEscape(e.getSender() instanceof Player //
? ((Player) e.getSender()).getDisplayName() //
: e.getSender().getName()));
@@ -102,7 +94,7 @@ public class MCChatListener implements Listener, IListener
// embed.withFooterText(e.getChannel().DisplayName);
embed.withTimestamp(time);
final long nanoTime = System.nanoTime();
- InterruptibleConsumer doit = lastmsgdata -> {
+ InterruptibleConsumer doit = lastmsgdata -> {
final EmbedObject embedObject = embed.build();
if (lastmsgdata.message == null || lastmsgdata.message.isDeleted()
|| !authorPlayer.equals(lastmsgdata.message.getEmbeds().get(0).getAuthor().getName())
@@ -117,7 +109,7 @@ public class MCChatListener implements Listener, IListener
try {
lastmsgdata.content = embedObject.description = lastmsgdata.content + "\n"
+ embedObject.description;// The message object doesn't get updated
- final LastMsgData _lastmsgdata = lastmsgdata;
+ final MCChatUtils.LastMsgData _lastmsgdata = lastmsgdata;
DPUtils.perform(() -> _lastmsgdata.message.edit("", embedObject));
} catch (MissingPermissionsException | DiscordException e1) {
TBMCCoreAPI.SendException("An error occurred while editing chat message!", e1);
@@ -129,21 +121,21 @@ public class MCChatListener implements Listener, IListener
|| ((DiscordSenderBase) e.getSender()).getChannel().getLongID() != ch.getLongID();
if (e.getChannel().isGlobal()
- && (e.isFromcmd() || isdifferentchannel.test(DiscordPlugin.chatchannel)))
- doit.accept(lastmsgdata == null
- ? lastmsgdata = new LastMsgData(DiscordPlugin.chatchannel, null)
- : lastmsgdata);
+ && (e.isFromCommand() || isdifferentchannel.test(DiscordPlugin.chatchannel)))
+ doit.accept(MCChatUtils.lastmsgdata == null
+ ? MCChatUtils.lastmsgdata = new MCChatUtils.LastMsgData(DiscordPlugin.chatchannel, null)
+ : MCChatUtils.lastmsgdata);
- for (LastMsgData data : lastmsgPerUser) {
- if ((e.isFromcmd() || isdifferentchannel.test(data.channel))
- && e.shouldSendTo(getSender(data.channel, data.user)))
+ for (MCChatUtils.LastMsgData data : MCChatPrivate.lastmsgPerUser) {
+ if ((e.isFromCommand() || isdifferentchannel.test(data.channel))
+ && e.shouldSendTo(MCChatUtils.getSender(data.channel, data.user)))
doit.accept(data);
}
- val iterator = lastmsgCustom.iterator();
+ val iterator = MCChatCustom.lastmsgCustom.iterator();
while (iterator.hasNext()) {
val lmd = iterator.next();
- if ((e.isFromcmd() || isdifferentchannel.test(lmd.channel)) //Test if msg is from Discord
+ if ((e.isFromCommand() || 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.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
@@ -162,32 +154,6 @@ public class MCChatListener implements Listener, IListener
}
}
- @RequiredArgsConstructor
- public static class LastMsgData {
- public IMessage message;
- public long time;
- public String content;
- public final IChannel channel;
- public Channel mcchannel;
- public final IUser user;
- }
-
- public static class CustomLMD extends LastMsgData {
- public final String groupID;
- public final Channel mcchannel;
- public final DiscordConnectedPlayer dcp;
- public int toggles;
-
- private CustomLMD(@NonNull IChannel channel, @NonNull IUser user,
- @NonNull String groupid, @NonNull Channel mcchannel, @NonNull DiscordConnectedPlayer dcp, int toggles) {
- super(channel, user);
- groupID = groupid;
- this.mcchannel = mcchannel;
- this.dcp = dcp;
- this.toggles = toggles;
- }
- }
-
@EventHandler
public void onChatPreprocess(TBMCChatPreprocessEvent event) {
int start = -1;
@@ -211,72 +177,8 @@ public class MCChatListener implements Listener, IListener
private static final String[] UnconnectedCmds = new String[]{"list", "u", "shrug", "tableflip", "unflip", "mwiki",
"yeehaw", "lenny", "rp", "plugins"};
- private static LastMsgData lastmsgdata;
private static short lastlist = 0;
private static short lastlistp = 0;
- /**
- * Used for messages in PMs (mcchat).
- */
- private static ArrayList lastmsgPerUser = new ArrayList();
- /**
- * Used for town or nation chats or anything else
- */
- private static ArrayList lastmsgCustom = new ArrayList<>();
- private static LongObjectHashMap 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) {
- TBMCPlayer mcp = dp.getAs(TBMCPlayer.class);
- if (mcp != null) { // If the accounts aren't connected, can't make a connected sender
- val p = Bukkit.getPlayer(mcp.getUUID());
- val op = Bukkit.getOfflinePlayer(mcp.getUUID());
- if (start) {
- val sender = new DiscordConnectedPlayer(user, channel, mcp.getUUID(), op.getName());
- addSender(ConnectedSenders, user, sender);
- if (p == null)// Player is offline - If the player is online, that takes precedence
- MCListener.callEventExcludingSome(new PlayerJoinEvent(sender, ""));
- } else {
- val sender = removeSender(ConnectedSenders, channel, user);
- if (p == null)// Player is offline - If the player is online, that takes precedence
- MCListener.callEventExcludingSome(new PlayerQuitEvent(sender, ""));
- }
- }
- if (!start)
- lastmsgfromd.remove(channel.getLongID());
- return start //
- ? lastmsgPerUser.add(new LastMsgData(channel, user)) // Doesn't support group DMs
- : lastmsgPerUser.removeIf(lmd -> lmd.channel.getLongID() == channel.getLongID());
- }
-
- public static T addSender(HashMap> senders,
- IUser user, T sender) {
- return addSender(senders, user.getStringID(), sender);
- }
-
- public static T addSender(HashMap> senders,
- String did, T sender) {
- var map = senders.get(did);
- if (map == null)
- map = new HashMap<>();
- map.put(sender.getChannel(), sender);
- senders.put(did, map);
- return sender;
- }
-
- public static T getSender(HashMap> senders,
- IChannel channel, IUser user) {
- var map = senders.get(user.getStringID());
- if (map != null)
- return map.get(channel);
- return null;
- }
-
- public static T removeSender(HashMap> senders,
- IChannel channel, IUser user) {
- var map = senders.get(user.getStringID());
- if (map != null)
- return map.remove(channel);
- return null;
- }
// ......................DiscordSender....DiscordConnectedPlayer.DiscordPlayerSender
// Offline public chat......x............................................
@@ -289,143 +191,8 @@ public class MCChatListener implements Listener, IListener
// If online and disabling private chat, don't logout
// The maps may not contain the senders for UnconnectedSenders
- public static boolean isMinecraftChatEnabled(DiscordPlayer dp) {
- return isMinecraftChatEnabled(dp.getDiscordID());
- }
-
- public static boolean isMinecraftChatEnabled(String did) { // Don't load the player data just for this
- return lastmsgPerUser.stream()
- .anyMatch(lmd -> ((IPrivateChannel) lmd.channel).getRecipient().getStringID().equals(did));
- }
-
- public static void addCustomChat(IChannel channel, String groupid, Channel mcchannel, IUser user, DiscordConnectedPlayer dcp, int toggles) {
- val lmd = new CustomLMD(channel, user, groupid, mcchannel, dcp, toggles);
- lastmsgCustom.add(lmd);
- }
-
- public static boolean hasCustomChat(IChannel channel) {
- return lastmsgCustom.stream().anyMatch(lmd -> lmd.channel.getLongID() == channel.getLongID());
- }
-
- @Nullable
- public static CustomLMD getCustomChat(IChannel channel) {
- return lastmsgCustom.stream().filter(lmd -> lmd.channel.getLongID() == channel.getLongID()).findAny().orElse(null);
- }
-
- public static boolean removeCustomChat(IChannel channel) {
- lastmsgfromd.remove(channel.getLongID());
- return lastmsgCustom.removeIf(lmd -> lmd.channel.getLongID() == channel.getLongID());
- }
-
- public static List getCustomChats() {
- return Collections.unmodifiableList(lastmsgCustom);
- }
-
- /**
- * May contain P<DiscordID> as key for public chat
- */
- public static final HashMap> UnconnectedSenders = new HashMap<>();
- public static final HashMap> ConnectedSenders = new HashMap<>();
- /**
- * May contain P<DiscordID> as key for public chat
- */
- public static final HashMap> OnlineSenders = new HashMap<>();
public static short ListC = 0;
- /**
- * Resets the last message, so it will start a new one instead of appending to it.
- * This is used when someone (even the bot) sends a message to the channel.
- *
- * @param channel The channel to reset in - the process is slightly different for the public, private and custom chats
- */
- public static void resetLastMessage(IChannel channel) {
- if (channel.getLongID() == DiscordPlugin.chatchannel.getLongID()) {
- (lastmsgdata == null ? lastmsgdata = new LastMsgData(DiscordPlugin.chatchannel, null)
- : lastmsgdata).message = null;
- return;
- } // Don't set the whole object to null, the player and channel information should be preserved
- for (LastMsgData data : channel.isPrivate() ? lastmsgPerUser : lastmsgCustom) {
- if (data.channel.getLongID() == channel.getLongID()) {
- data.message = null;
- return;
- }
- }
- //If it gets here, it's sending a message to a non-chat channel
- }
-
- public static void forAllMCChat(Consumer action) {
- action.accept(DiscordPlugin.chatchannel);
- for (LastMsgData data : lastmsgPerUser)
- action.accept(data.channel);
- // lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat
- }
-
- /**
- * For custom and all MC chat
- *
- * @param action The action to act
- * @param toggle The toggle to check
- * @param hookmsg Whether the message is also sent from the hook
- */
- public static void forCustomAndAllMCChat(Consumer action, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
- if (!DiscordPlugin.hooked || !hookmsg)
- forAllMCChat(action);
- final Consumer customLMDConsumer = cc -> action.accept(cc.channel);
- if (toggle == null)
- lastmsgCustom.forEach(customLMDConsumer);
- else
- lastmsgCustom.stream().filter(cc -> (cc.toggles & toggle.flag) != 0).forEach(customLMDConsumer);
- }
-
- /**
- * Do the {@code action} for each custom chat the {@code sender} have access to and has that broadcast type enabled.
- *
- * @param action The action to do
- * @param sender The sender to check perms of or null to send to all that has it toggled
- * @param toggle The toggle to check or null to send to all allowed
- */
- public static void forAllowedCustomMCChat(Consumer action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle) {
- lastmsgCustom.stream().filter(clmd -> {
- //new TBMCChannelConnectFakeEvent(sender, clmd.mcchannel).shouldSendTo(clmd.dcp) - Thought it was this simple hehe - Wait, it *should* be this simple
- if (toggle != null && (clmd.toggles & toggle.flag) == 0)
- return false; //If null then allow
- if (sender == null)
- return true;
- return clmd.groupID.equals(clmd.mcchannel.getGroupID(sender));
- }).forEach(cc -> action.accept(cc.channel)); //TODO: Send error messages on channel connect
- }
-
- /**
- * Do the {@code action} for each custom chat the {@code sender} have access to and has that broadcast type enabled.
- *
- * @param action The action to do
- * @param sender The sender to check perms of or null to send to all that has it toggled
- * @param toggle The toggle to check or null to send to all allowed
- * @param hookmsg Whether the message is also sent from the hook
- */
- public static void forAllowedCustomAndAllMCChat(Consumer action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
- if (!DiscordPlugin.hooked || !hookmsg)
- forAllMCChat(action);
- forAllowedCustomMCChat(action, sender, toggle);
- }
-
- public static Consumer send(String message) {
- return ch -> DiscordPlugin.sendMessageToChannel(ch, DPUtils.sanitizeString(message));
- }
-
- public static void forAllowedMCChat(Consumer action, TBMCSystemChatEvent event) {
- if (event.getChannel().isGlobal())
- action.accept(DiscordPlugin.chatchannel);
- for (LastMsgData data : lastmsgPerUser)
- if (event.shouldSendTo(getSender(data.channel, data.user)))
- action.accept(data.channel);
- lastmsgCustom.stream().filter(clmd -> {
- if ((clmd.toggles & ChannelconBroadcast.BROADCAST.flag) == 0)
- return false;
- return event.shouldSendTo(clmd.dcp);
- }).map(clmd -> clmd.channel).forEach(action);
- }
-
/**
* Stop the listener. Any calls to onMCChat will restart it as long as we're not in safe mode.
*
@@ -445,13 +212,13 @@ public class MCChatListener implements Listener, IListener
if (wait)
recthread.join(5000);
}
- lastmsgdata = null;
- lastmsgPerUser.clear();
- lastmsgCustom.clear();
- lastmsgfromd.clear();
- ConnectedSenders.clear();
+ MCChatUtils.lastmsgdata = null;
+ MCChatPrivate.lastmsgPerUser.clear();
+ MCChatCustom.lastmsgCustom.clear();
+ MCChatUtils.lastmsgfromd.clear();
+ MCChatUtils.ConnectedSenders.clear();
lastlist = lastlistp = ListC = 0;
- UnconnectedSenders.clear();
+ MCChatUtils.UnconnectedSenders.clear();
recthread = sendthread = null;
} catch (InterruptedException e) {
e.printStackTrace(); //This thread shouldn't be interrupted
@@ -463,27 +230,28 @@ public class MCChatListener implements Listener, IListener
private Runnable recrun;
private static Thread recthread;
- @Override // Discord
- public void handle(MessageReceivedEvent ev) {
- if (DiscordPlugin.SafeMode)
- return;
+ // Discord
+ public boolean handleDiscord(MessageReceivedEvent ev) {
+ if (!ComponentManager.isEnabled(MinecraftChatModule.class))
+ return false;
val author = ev.getMessage().getAuthor();
- if (author.isBot())
- return;
- final boolean hasCustomChat = hasCustomChat(ev.getChannel());
- if (!ev.getMessage().getChannel().getStringID().equals(DiscordPlugin.chatchannel.getStringID())
- && !(ev.getMessage().getChannel().isPrivate() && isMinecraftChatEnabled(author.getStringID()))
+ final boolean hasCustomChat = MCChatCustom.hasCustomChat(ev.getChannel());
+ if (ev.getMessage().getChannel().getLongID() != DiscordPlugin.chatchannel.getLongID()
+ && !(ev.getMessage().getChannel().isPrivate() && MCChatPrivate.isMinecraftChatEnabled(author.getStringID()))
&& !hasCustomChat)
- return;
- if (ev.getMessage().getContent().equalsIgnoreCase("mcchat"))
- return; // Race condition: If it gets here after it enabled mcchat it says it - I might as well allow disabling with this (CommandListener)
+ return false; //Chat isn't enabled on this channel
+ if (ev.getMessage().getChannel().isPrivate() //Only in private chat
+ && ev.getMessage().getContent().length() < "/mcchat<>".length()
+ && ev.getMessage().getContent().replace("/", "")
+ .equalsIgnoreCase("mcchat")) //Either mcchat or /mcchat
+ return false; //Allow disabling the chat if needed
if (CommandListener.runCommand(ev.getMessage(), true))
- return;
- resetLastMessage(ev.getChannel());
+ return true; //Allow running commands in chat channels
+ MCChatUtils.resetLastMessage(ev.getChannel());
lastlist++;
recevents.add(ev);
if (rectask != null)
- return;
+ return true;
recrun = () -> { //Don't return in a while loop next time
recthread = Thread.currentThread();
processDiscordToMC();
@@ -491,6 +259,7 @@ public class MCChatListener implements Listener, IListener
rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Continue message processing
};
rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Start message processing
+ return true;
}
private void processDiscordToMC() {
@@ -505,7 +274,7 @@ public class MCChatListener implements Listener, IListener
val sender = event.getMessage().getAuthor();
String dmessage = event.getMessage().getContent();
try {
- final DiscordSenderBase dsender = getSender(event.getMessage().getChannel(), sender);
+ final DiscordSenderBase dsender = MCChatUtils.getSender(event.getMessage().getChannel(), sender);
val user = dsender.getChromaUser();
for (IUser u : event.getMessage().getMentions()) {
@@ -525,7 +294,7 @@ public class MCChatListener implements Listener, IListener
.getAttachments().stream().map(IMessage.Attachment::getUrl).collect(Collectors.joining("\n"))
: "");
- CustomLMD clmd = getCustomChat(event.getChannel());
+ MCChatCustom.CustomLMD clmd = MCChatCustom.getCustomChat(event.getChannel());
boolean react = false;
@@ -570,18 +339,13 @@ public class MCChatListener implements Listener, IListener
() -> { //TODO: Better handling...
val channel = user.channel();
val chtmp = channel.get();
- //System.out.println("1: "+chtmp.ID);
- //System.out.println("clmd: "+clmd);
if (clmd != null) {
channel.set(clmd.mcchannel); //Hack to send command in the channel
- //System.out.println("clmd chan: "+clmd.mcchannel.ID);
} //TODO: Permcheck isn't implemented for commands
- //System.out.println("2: "+channel.get().ID);
VanillaCommandListener.runBukkitOrVanillaCommand(dsender, cmd);
Bukkit.getLogger().info(dsender.getName() + " issued command from Discord: /" + cmdlowercased);
if (clmd != null)
channel.set(chtmp);
- //System.out.println("3: "+channel.get().ID); - TODO: Remove
});
else {
Channel chc = ch.get();
@@ -619,8 +383,9 @@ public class MCChatListener implements Listener, IListener
} else {// Not a command
if (dmessage.length() == 0 && event.getMessage().getAttachments().size() == 0
&& !event.getChannel().isPrivate() && event.getMessage().isSystemMessage()) {
- val rtr = clmd != null ? clmd.mcchannel.filteranderrormsg.apply(clmd.dcp) : dsender.getChromaUser().channel().get().filteranderrormsg.apply(dsender);
- TBMCChatAPI.SendSystemMessage(clmd != null ? clmd.mcchannel : dsender.getChromaUser().channel().get(), rtr.score, rtr.groupID,
+ val rtr = clmd != null ? clmd.mcchannel.getRTR(clmd.dcp)
+ : dsender.getChromaUser().channel().get().getRTR(dsender);
+ TBMCChatAPI.SendSystemMessage(clmd != null ? clmd.mcchannel : dsender.getChromaUser().channel().get(), rtr,
(dsender instanceof Player ? ((Player) dsender).getDisplayName()
: dsender.getName()) + " pinned a message on Discord.");
}
@@ -635,7 +400,7 @@ public class MCChatListener implements Listener, IListener
}
if (react) {
try {
- val lmfd = lastmsgfromd.get(event.getChannel().getLongID());
+ val lmfd = MCChatUtils.lastmsgfromd.get(event.getChannel().getLongID());
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
@@ -643,7 +408,7 @@ public class MCChatListener implements Listener, IListener
} catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e);
}
- lastmsgfromd.put(event.getChannel().getLongID(), event.getMessage());
+ MCChatUtils.lastmsgfromd.put(event.getChannel().getLongID(), event.getMessage());
DPUtils.perform(() -> event.getMessage().addReaction(DiscordPlugin.DELIVERED_REACTION));
}
} catch (Exception e) {
@@ -651,19 +416,6 @@ public class MCChatListener implements Listener, IListener
}
}
- /**
- * This method will find the best sender to use: if the player is online, use that, if not but connected then use that etc.
- */
- private static DiscordSenderBase getSender(IChannel channel, final IUser author) {
- //noinspection OptionalGetWithoutIsPresent
- return Stream.>>of( // https://stackoverflow.com/a/28833677/2703239
- () -> Optional.ofNullable(getSender(OnlineSenders, channel, author)), // Find first non-null
- () -> Optional.ofNullable(getSender(ConnectedSenders, channel, author)), // This doesn't support the public chat, but it'll always return null for it
- () -> Optional.ofNullable(getSender(OnlineSenders, channel, author)), //
- () -> Optional.of(addSender(UnconnectedSenders, author,
- new DiscordSender(author, channel)))).map(Supplier::get).filter(Optional::isPresent).map(Optional::get).findFirst().get();
- }
-
@FunctionalInterface
private interface InterruptibleConsumer {
void accept(T value) throws TimeoutException, InterruptedException;
diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java
new file mode 100644
index 0000000..99c1740
--- /dev/null
+++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java
@@ -0,0 +1,69 @@
+package buttondevteam.discordplugin.mcchat;
+
+import buttondevteam.discordplugin.DiscordConnectedPlayer;
+import buttondevteam.discordplugin.DiscordPlayer;
+import buttondevteam.discordplugin.DiscordPlugin;
+import buttondevteam.lib.player.TBMCPlayer;
+import lombok.val;
+import org.bukkit.Bukkit;
+import org.bukkit.event.Event;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import sx.blah.discord.handle.obj.IChannel;
+import sx.blah.discord.handle.obj.IPrivateChannel;
+import sx.blah.discord.handle.obj.IUser;
+
+import java.util.ArrayList;
+
+import static buttondevteam.discordplugin.listeners.MCListener.callEventExcludingSome;
+
+public class MCChatPrivate {
+
+ /**
+ * Used for messages in PMs (mcchat).
+ */
+ static ArrayList lastmsgPerUser = new ArrayList<>();
+
+ public static boolean privateMCChat(IChannel channel, boolean start, IUser user, DiscordPlayer dp) {
+ TBMCPlayer mcp = dp.getAs(TBMCPlayer.class);
+ if (mcp != null) { // If the accounts aren't connected, can't make a connected sender
+ val p = Bukkit.getPlayer(mcp.getUUID());
+ val op = Bukkit.getOfflinePlayer(mcp.getUUID());
+ if (start) {
+ val sender = new DiscordConnectedPlayer(user, channel, mcp.getUUID(), op.getName());
+ MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender);
+ if (p == null)// Player is offline - If the player is online, that takes precedence
+ callEventSync(new PlayerJoinEvent(sender, ""));
+ } else {
+ val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel, user);
+ if (p == null)// Player is offline - If the player is online, that takes precedence
+ callEventSync(new PlayerQuitEvent(sender, ""));
+ }
+ } // ---- PermissionsEx warning is normal on logout ----
+ if (!start)
+ MCChatUtils.lastmsgfromd.remove(channel.getLongID());
+ return start //
+ ? lastmsgPerUser.add(new MCChatUtils.LastMsgData(channel, user)) // Doesn't support group DMs
+ : lastmsgPerUser.removeIf(lmd -> lmd.channel.getLongID() == channel.getLongID());
+ }
+
+ public static boolean isMinecraftChatEnabled(DiscordPlayer dp) {
+ return isMinecraftChatEnabled(dp.getDiscordID());
+ }
+
+ public static boolean isMinecraftChatEnabled(String did) { // Don't load the player data just for this
+ return lastmsgPerUser.stream()
+ .anyMatch(lmd -> ((IPrivateChannel) lmd.channel).getRecipient().getStringID().equals(did));
+ }
+
+ public static void logoutAll() {
+ for (val entry : MCChatUtils.ConnectedSenders.entrySet())
+ for (val valueEntry : entry.getValue().entrySet())
+ callEventExcludingSome(new PlayerQuitEvent(valueEntry.getValue(), "")); //This is sync
+ MCChatUtils.ConnectedSenders.clear();
+ }
+
+ private static void callEventSync(Event event) {
+ Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> callEventExcludingSome(event));
+ }
+}
diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java
new file mode 100644
index 0000000..6826f6e
--- /dev/null
+++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java
@@ -0,0 +1,218 @@
+package buttondevteam.discordplugin.mcchat;
+
+import buttondevteam.core.ComponentManager;
+import buttondevteam.discordplugin.*;
+import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule;
+import buttondevteam.lib.TBMCSystemChatEvent;
+import buttondevteam.lib.chat.Channel;
+import io.netty.util.collection.LongObjectHashMap;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.var;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import sx.blah.discord.handle.obj.IChannel;
+import sx.blah.discord.handle.obj.IMessage;
+import sx.blah.discord.handle.obj.IUser;
+
+import javax.annotation.Nullable;
+import java.util.HashMap;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class MCChatUtils {
+ /**
+ * May contain P<DiscordID> as key for public chat
+ */
+ public static final HashMap> UnconnectedSenders = new HashMap<>();
+ public static final HashMap> ConnectedSenders = new HashMap<>();
+ /**
+ * May contain P<DiscordID> as key for public chat
+ */
+ public static final HashMap> OnlineSenders = new HashMap<>();
+ static @Nullable LastMsgData lastmsgdata;
+ static LongObjectHashMap lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks
+
+ public static void updatePlayerList() {
+ if (notEnabled()) return;
+ DPUtils.performNoWait(() -> {
+ if (lastmsgdata != null)
+ updatePL(lastmsgdata);
+ MCChatCustom.lastmsgCustom.forEach(MCChatUtils::updatePL);
+ });
+ }
+
+ private static boolean notEnabled() {
+ return !ComponentManager.isEnabled(MinecraftChatModule.class);
+ }
+
+ private static void updatePL(LastMsgData lmd) {
+ String topic = lmd.channel.getTopic();
+ if (topic == null || topic.length() == 0)
+ topic = ".\n----\nMinecraft chat\n----\n.";
+ String[] s = topic.split("\\n----\\n");
+ if (s.length < 3)
+ return;
+ s[0] = Bukkit.getOnlinePlayers().size() + " player" + (Bukkit.getOnlinePlayers().size() != 1 ? "s" : "")
+ + " online";
+ s[s.length - 1] = "Players: " + Bukkit.getOnlinePlayers().stream()
+ .map(p -> DPUtils.sanitizeString(p.getDisplayName())).collect(Collectors.joining(", "));
+ lmd.channel.changeTopic(String.join("\n----\n", s));
+ }
+
+ public static T addSender(HashMap> senders,
+ IUser user, T sender) {
+ return addSender(senders, user.getStringID(), sender);
+ }
+
+ public static T addSender(HashMap> senders,
+ String did, T sender) {
+ var map = senders.get(did);
+ if (map == null)
+ map = new HashMap<>();
+ map.put(sender.getChannel(), sender);
+ senders.put(did, map);
+ return sender;
+ }
+
+ public static T getSender(HashMap> senders,
+ IChannel channel, IUser user) {
+ var map = senders.get(user.getStringID());
+ if (map != null)
+ return map.get(channel);
+ return null;
+ }
+
+ public static T removeSender(HashMap> senders,
+ IChannel channel, IUser user) {
+ var map = senders.get(user.getStringID());
+ if (map != null)
+ return map.remove(channel);
+ return null;
+ }
+
+ public static void forAllMCChat(Consumer action) {
+ if (notEnabled()) return;
+ action.accept(DiscordPlugin.chatchannel);
+ for (LastMsgData data : MCChatPrivate.lastmsgPerUser)
+ action.accept(data.channel);
+ // lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat
+ }
+
+ /**
+ * For custom and all MC chat
+ *
+ * @param action The action to act
+ * @param toggle The toggle to check
+ * @param hookmsg Whether the message is also sent from the hook
+ */
+ public static void forCustomAndAllMCChat(Consumer action, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
+ if (notEnabled()) return;
+ if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg)
+ forAllMCChat(action);
+ final Consumer customLMDConsumer = cc -> action.accept(cc.channel);
+ if (toggle == null)
+ MCChatCustom.lastmsgCustom.forEach(customLMDConsumer);
+ else
+ MCChatCustom.lastmsgCustom.stream().filter(cc -> (cc.toggles & toggle.flag) != 0).forEach(customLMDConsumer);
+ }
+
+ /**
+ * Do the {@code action} for each custom chat the {@code sender} have access to and has that broadcast type enabled.
+ *
+ * @param action The action to do
+ * @param sender The sender to check perms of or null to send to all that has it toggled
+ * @param toggle The toggle to check or null to send to all allowed
+ */
+ public static void forAllowedCustomMCChat(Consumer action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle) {
+ if (notEnabled()) return;
+ MCChatCustom.lastmsgCustom.stream().filter(clmd -> {
+ //new TBMCChannelConnectFakeEvent(sender, clmd.mcchannel).shouldSendTo(clmd.dcp) - Thought it was this simple hehe - Wait, it *should* be this simple
+ if (toggle != null && (clmd.toggles & toggle.flag) == 0)
+ return false; //If null then allow
+ if (sender == null)
+ return true;
+ return clmd.groupID.equals(clmd.mcchannel.getGroupID(sender));
+ }).forEach(cc -> action.accept(cc.channel)); //TODO: Send error messages on channel connect
+ }
+
+ /**
+ * Do the {@code action} for each custom chat the {@code sender} have access to and has that broadcast type enabled.
+ *
+ * @param action The action to do
+ * @param sender The sender to check perms of or null to send to all that has it toggled
+ * @param toggle The toggle to check or null to send to all allowed
+ * @param hookmsg Whether the message is also sent from the hook
+ */
+ public static void forAllowedCustomAndAllMCChat(Consumer action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle, boolean hookmsg) {
+ if (notEnabled()) return;
+ if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg)
+ forAllMCChat(action);
+ forAllowedCustomMCChat(action, sender, toggle);
+ }
+
+ public static Consumer send(String message) {
+ return ch -> DiscordPlugin.sendMessageToChannel(ch, DPUtils.sanitizeString(message));
+ }
+
+ public static void forAllowedMCChat(Consumer action, TBMCSystemChatEvent event) {
+ if (notEnabled()) return;
+ if (event.getChannel().isGlobal())
+ action.accept(DiscordPlugin.chatchannel);
+ for (LastMsgData data : MCChatPrivate.lastmsgPerUser)
+ if (event.shouldSendTo(getSender(data.channel, data.user)))
+ action.accept(data.channel);
+ MCChatCustom.lastmsgCustom.stream().filter(clmd -> {
+ if ((clmd.toggles & ChannelconBroadcast.BROADCAST.flag) == 0)
+ return false;
+ return event.shouldSendTo(clmd.dcp);
+ }).map(clmd -> clmd.channel).forEach(action);
+ }
+
+ /**
+ * This method will find the best sender to use: if the player is online, use that, if not but connected then use that etc.
+ */
+ static DiscordSenderBase getSender(IChannel channel, final IUser author) {
+ //noinspection OptionalGetWithoutIsPresent
+ return Stream.>>of( // https://stackoverflow.com/a/28833677/2703239
+ () -> Optional.ofNullable(getSender(OnlineSenders, channel, author)), // Find first non-null
+ () -> Optional.ofNullable(getSender(ConnectedSenders, channel, author)), // This doesn't support the public chat, but it'll always return null for it
+ () -> Optional.ofNullable(getSender(UnconnectedSenders, channel, author)), //
+ () -> Optional.of(addSender(UnconnectedSenders, author,
+ new DiscordSender(author, channel)))).map(Supplier::get).filter(Optional::isPresent).map(Optional::get).findFirst().get();
+ }
+
+ /**
+ * Resets the last message, so it will start a new one instead of appending to it.
+ * This is used when someone (even the bot) sends a message to the channel.
+ *
+ * @param channel The channel to reset in - the process is slightly different for the public, private and custom chats
+ */
+ public static void resetLastMessage(IChannel channel) {
+ if (notEnabled()) return;
+ if (channel.getLongID() == DiscordPlugin.chatchannel.getLongID()) {
+ (lastmsgdata == null ? lastmsgdata = new LastMsgData(DiscordPlugin.chatchannel, null)
+ : lastmsgdata).message = null;
+ return;
+ } // Don't set the whole object to null, the player and channel information should be preserved
+ for (LastMsgData data : channel.isPrivate() ? MCChatPrivate.lastmsgPerUser : MCChatCustom.lastmsgCustom) {
+ if (data.channel.getLongID() == channel.getLongID()) {
+ data.message = null;
+ return;
+ }
+ }
+ //If it gets here, it's sending a message to a non-chat channel
+ }
+
+ @RequiredArgsConstructor
+ public static class LastMsgData {
+ public IMessage message;
+ public long time;
+ public String content;
+ public final IChannel channel;
+ public Channel mcchannel;
+ public final IUser user;
+ }
+}
diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java
new file mode 100644
index 0000000..2c5fdb0
--- /dev/null
+++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java
@@ -0,0 +1,153 @@
+package buttondevteam.discordplugin.mcchat;
+
+import buttondevteam.discordplugin.*;
+import buttondevteam.discordplugin.commands.ConnectCommand;
+import buttondevteam.lib.TBMCCoreAPI;
+import buttondevteam.lib.TBMCSystemChatEvent;
+import buttondevteam.lib.player.*;
+import com.earth2me.essentials.CommandSource;
+import lombok.val;
+import net.ess3.api.events.AfkStatusChangeEvent;
+import net.ess3.api.events.MuteStatusChangeEvent;
+import net.ess3.api.events.NickChangeEvent;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.PlayerDeathEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerKickEvent;
+import org.bukkit.event.player.PlayerLoginEvent;
+import org.bukkit.event.player.PlayerLoginEvent.Result;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.event.server.BroadcastMessageEvent;
+import sx.blah.discord.handle.obj.IRole;
+import sx.blah.discord.handle.obj.IUser;
+import sx.blah.discord.util.DiscordException;
+import sx.blah.discord.util.MissingPermissionsException;
+
+class MCListener implements Listener {
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onPlayerLogin(PlayerLoginEvent e) {
+ if (e.getResult() != Result.ALLOWED)
+ return;
+ MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream()) //Only private mcchat should be in ConnectedSenders
+ .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
+ .ifPresent(dcp -> buttondevteam.discordplugin.listeners.MCListener.callEventExcludingSome(new PlayerQuitEvent(dcp, "")));
+ }
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onPlayerJoin(TBMCPlayerJoinEvent e) {
+ if (e.getPlayer() instanceof DiscordConnectedPlayer)
+ return; // Don't show the joined message for the fake player
+ Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, () -> {
+ final Player p = e.getPlayer();
+ DiscordPlayer dp = e.GetPlayer().getAs(DiscordPlayer.class);
+ if (dp != null) {
+ val user = DiscordPlugin.dc.getUserByID(Long.parseLong(dp.getDiscordID()));
+ MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(),
+ new DiscordPlayerSender(user, user.getOrCreatePMChannel(), p));
+ MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(),
+ new DiscordPlayerSender(user, DiscordPlugin.chatchannel, p)); //Stored per-channel
+ }
+ if (ConnectCommand.WaitingToConnect.containsKey(e.GetPlayer().PlayerName().get())) {
+ IUser user = DiscordPlugin.dc
+ .getUserByID(Long.parseLong(ConnectCommand.WaitingToConnect.get(e.GetPlayer().PlayerName().get())));
+ p.sendMessage("§bTo connect with the Discord account @" + user.getName() + "#" + user.getDiscriminator()
+ + " do /discord accept");
+ p.sendMessage("§bIf it wasn't you, do /discord decline");
+ }
+ final String message = e.GetPlayer().PlayerName().get() + " joined the game";
+ MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true);
+ MCChatListener.ListC = 0;
+ ChromaBot.getInstance().updatePlayerList();
+ });
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onPlayerLeave(TBMCPlayerQuitEvent e) {
+ if (e.getPlayer() instanceof DiscordConnectedPlayer)
+ return; // Only care about real users
+ MCChatUtils.OnlineSenders.entrySet()
+ .removeIf(entry -> entry.getValue().entrySet().stream().anyMatch(p -> p.getValue().getUniqueId().equals(e.getPlayer().getUniqueId())));
+ Bukkit.getScheduler().runTask(DiscordPlugin.plugin,
+ () -> MCChatUtils.ConnectedSenders.values().stream().flatMap(v -> v.values().stream())
+ .filter(s -> s.getUniqueId().equals(e.getPlayer().getUniqueId())).findAny()
+ .ifPresent(dcp -> buttondevteam.discordplugin.listeners.MCListener.callEventExcludingSome(new PlayerJoinEvent(dcp, ""))));
+ Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin,
+ ChromaBot.getInstance()::updatePlayerList, 5);
+ final String message = e.GetPlayer().PlayerName().get() + " left the game";
+ MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true);
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onPlayerKick(PlayerKickEvent e) {
+ /*if (!DiscordPlugin.hooked && !e.getReason().equals("The server is restarting")
+ && !e.getReason().equals("Server closed")) // The leave messages errored with the previous setup, I could make it wait since I moved it here, but instead I have a special
+ MCChatListener.forAllowedCustomAndAllMCChat(e.getPlayer().getName() + " left the game"); // message for this - Oh wait this doesn't even send normally because of the hook*/
+ }
+
+ @EventHandler(priority = EventPriority.LOW)
+ public void onPlayerDeath(PlayerDeathEvent e) {
+ MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(e.getDeathMessage()), e.getEntity(), ChannelconBroadcast.DEATH, true);
+ }
+
+ @EventHandler
+ public void onPlayerAFK(AfkStatusChangeEvent e) {
+ final Player base = e.getAffected().getBase();
+ if (e.isCancelled() || !base.isOnline())
+ return;
+ final String msg = base.getDisplayName()
+ + " is " + (e.getValue() ? "now" : "no longer") + " AFK.";
+ MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, false);
+ }
+
+ @EventHandler
+ public void onPlayerMute(MuteStatusChangeEvent e) {
+ try {
+ DPUtils.performNoWait(() -> {
+ final IRole role = DiscordPlugin.dc.getRoleByID(164090010461667328L);
+ final CommandSource source = e.getAffected().getSource();
+ if (!source.isPlayer())
+ return;
+ final DiscordPlayer p = TBMCPlayerBase.getPlayer(source.getPlayer().getUniqueId(), TBMCPlayer.class)
+ .getAs(DiscordPlayer.class);
+ if (p == null) return;
+ final IUser user = DiscordPlugin.dc.getUserByID(
+ Long.parseLong(p.getDiscordID()));
+ if (e.getValue())
+ user.addRole(role);
+ else
+ user.removeRole(role);
+ DiscordPlugin.sendMessageToChannel(DiscordPlugin.modlogchannel, (e.getValue() ? "M" : "Unm") + "uted user: " + user.getName());
+ });
+ } catch (DiscordException | MissingPermissionsException ex) {
+ TBMCCoreAPI.SendException("Failed to give/take Muted role to player " + e.getAffected().getName() + "!",
+ ex);
+ }
+ }
+
+ @EventHandler
+ public void onChatSystemMessage(TBMCSystemChatEvent event) {
+ MCChatUtils.forAllowedMCChat(MCChatUtils.send(event.getMessage()), event);
+ }
+
+ @EventHandler
+ public void onBroadcastMessage(BroadcastMessageEvent event) {
+ MCChatUtils.forCustomAndAllMCChat(MCChatUtils.send(event.getMessage()), ChannelconBroadcast.BROADCAST, false);
+ }
+
+ @EventHandler
+ public void onYEEHAW(TBMCYEEHAWEvent event) { //TODO: Inherit from the chat event base to have channel support
+ String name = event.getSender() instanceof Player ? ((Player) event.getSender()).getDisplayName()
+ : event.getSender().getName();
+ //Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO
+ MCChatUtils.forAllMCChat(MCChatUtils.send(name + " <:YEEHAW:" + DiscordPlugin.mainServer.getEmojiByName("YEEHAW").getStringID() + ">s"));
+ }
+
+ @EventHandler
+ public void onNickChange(NickChangeEvent event) {
+ MCChatUtils.updatePlayerList();
+ }
+}
diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java
new file mode 100644
index 0000000..9af7a5e
--- /dev/null
+++ b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java
@@ -0,0 +1,26 @@
+package buttondevteam.discordplugin.mcchat;
+
+import buttondevteam.discordplugin.DiscordPlugin;
+import buttondevteam.lib.TBMCCoreAPI;
+import buttondevteam.lib.architecture.Component;
+import lombok.Getter;
+
+public class MinecraftChatModule extends Component {
+ private @Getter MCChatListener listener;
+
+ public MCChatListener getListener() { //It doesn't want to generate
+ return listener;
+ }
+ @Override
+ protected void enable() {
+ listener = new MCChatListener();
+ DiscordPlugin.dc.getDispatcher().registerListener(listener);
+ TBMCCoreAPI.RegisterEventsForExceptions(listener, getPlugin());
+ TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(), getPlugin());
+ }
+
+ @Override
+ protected void disable() {
+ //These get undone if restarting/resetting - it will ignore events if disabled
+ } //TODO: Use ComponentManager.isEnabled() at other places too, instead of SafeMode
+}
diff --git a/src/main/java/buttondevteam/discordplugin/mccommands/AcceptMCCommand.java b/src/main/java/buttondevteam/discordplugin/mccommands/AcceptMCCommand.java
index f695dba..2f2ad21 100755
--- a/src/main/java/buttondevteam/discordplugin/mccommands/AcceptMCCommand.java
+++ b/src/main/java/buttondevteam/discordplugin/mccommands/AcceptMCCommand.java
@@ -2,7 +2,7 @@ package buttondevteam.discordplugin.mccommands;
import buttondevteam.discordplugin.DiscordPlayer;
import buttondevteam.discordplugin.commands.ConnectCommand;
-import buttondevteam.discordplugin.listeners.MCChatListener;
+import buttondevteam.discordplugin.mcchat.MCChatUtils;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.player.TBMCPlayer;
@@ -35,7 +35,7 @@ public class AcceptMCCommand extends DiscordMCCommandBase {
dp.save();
mcp.save();
ConnectCommand.WaitingToConnect.remove(player.getName());
- MCChatListener.UnconnectedSenders.remove(did); //Remove all unconnected, will be recreated where needed
+ MCChatUtils.UnconnectedSenders.remove(did); //Remove all unconnected, will be recreated where needed
player.sendMessage("§bAccounts connected.");
return true;
}