From 428361c46c497d30df09bbe248505fa75e795d3c Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Thu, 25 Feb 2021 01:44:43 +0100 Subject: [PATCH 01/23] Convert some more classes to Scala Actually, a lot of them --- .../announcer/AnnouncerModule.java | 135 -------------- .../announcer/AnnouncerModule.scala | 103 ++++++++++ .../GeneralEventBroadcasterModule.java | 45 ----- .../GeneralEventBroadcasterModule.scala | 39 ++++ .../broadcaster/PlayerListWatcher.java | 176 ------------------ .../broadcaster/PlayerListWatcher.scala | 168 +++++++++++++++++ .../discordplugin/commands/Command2DC.java | 19 -- .../discordplugin/commands/Command2DC.scala | 15 ++ .../commands/Command2DCSender.java | 39 ---- .../commands/Command2DCSender.scala | 22 +++ .../commands/ConnectCommand.java | 59 ------ .../commands/ConnectCommand.scala | 46 +++++ .../discordplugin/commands/DebugCommand.java | 30 --- .../discordplugin/commands/DebugCommand.scala | 27 +++ .../discordplugin/commands/HelpCommand.java | 24 --- .../discordplugin/commands/HelpCommand.scala | 18 ++ .../discordplugin/commands/ICommand2DC.java | 20 -- .../discordplugin/commands/ICommand2DC.scala | 16 ++ .../commands/UserinfoCommand.java | 88 --------- .../commands/UserinfoCommand.scala | 76 ++++++++ .../commands/VersionCommand.java | 26 --- .../commands/VersionCommand.scala | 21 +++ .../exceptions/DebugMessageListener.java | 36 ---- .../exceptions/DebugMessageListener.scala | 32 ++++ .../exceptions/ExceptionListenerModule.java | 114 ------------ .../exceptions/ExceptionListenerModule.scala | 96 ++++++++++ .../discordplugin/fun/FunModule.java | 162 ---------------- .../discordplugin/fun/FunModule.scala | 125 +++++++++++++ .../listeners/CommandListener.scala | 10 +- .../listeners/CommonListeners.java | 88 --------- .../listeners/CommonListeners.scala | 92 +++++++++ .../discordplugin/listeners/MCListener.java | 68 ------- .../discordplugin/listeners/MCListener.scala | 54 ++++++ .../mcchat/ChannelconCommand.java | 2 - .../discordplugin/mcchat/MCChatCommand.java | 2 - .../discordplugin/mcchat/MCChatListener.java | 1 - .../discordplugin/mcchat/MCChatUtils.java | 1 - .../mccommands/DiscordMCCommand.java | 2 - .../discordplugin/role/RoleCommand.java | 2 - .../discordplugin/util/Timings.java | 2 - 40 files changed, 955 insertions(+), 1146 deletions(-) delete mode 100644 src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java create mode 100644 src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java create mode 100644 src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java create mode 100644 src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/commands/Command2DC.java create mode 100644 src/main/java/buttondevteam/discordplugin/commands/Command2DC.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.java create mode 100644 src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.java create mode 100644 src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/commands/DebugCommand.java create mode 100644 src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/commands/HelpCommand.java create mode 100644 src/main/java/buttondevteam/discordplugin/commands/HelpCommand.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.java create mode 100644 src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.java create mode 100644 src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/commands/VersionCommand.java create mode 100644 src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.java create mode 100644 src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java create mode 100644 src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/fun/FunModule.java create mode 100644 src/main/java/buttondevteam/discordplugin/fun/FunModule.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.java create mode 100644 src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/listeners/MCListener.java create mode 100644 src/main/java/buttondevteam/discordplugin/listeners/MCListener.scala diff --git a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java deleted file mode 100644 index e27a30f..0000000 --- a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.java +++ /dev/null @@ -1,135 +0,0 @@ -package buttondevteam.discordplugin.announcer; - -import buttondevteam.discordplugin.DPUtils; -import buttondevteam.discordplugin.DiscordPlayer; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.architecture.Component; -import buttondevteam.lib.architecture.ComponentMetadata; -import buttondevteam.lib.architecture.ConfigData; -import buttondevteam.lib.architecture.ReadOnlyConfigData; -import buttondevteam.lib.player.ChromaGamerBase; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import discord4j.core.object.entity.Message; -import discord4j.core.object.entity.channel.MessageChannel; -import lombok.val; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * Posts new posts from Reddit to the specified channel(s). It will pin the regular posts (not the mod posts). - */ -@ComponentMetadata(enabledByDefault = false) -public class AnnouncerModule extends Component { - /** - * Channel to post new posts. - */ - public final ReadOnlyConfigData> channel = DPUtils.channelData(getConfig(), "channel"); - - /** - * Channel where distinguished (moderator) posts go. - */ - private final ReadOnlyConfigData> modChannel = DPUtils.channelData(getConfig(), "modChannel"); - - /** - * Automatically unpins all messages except the last few. Set to 0 or >50 to disable - */ - private final ConfigData keepPinned = getConfig().getData("keepPinned", (short) 40); - - private final ConfigData lastAnnouncementTime = getConfig().getData("lastAnnouncementTime", 0L); - - private final ConfigData lastSeenTime = getConfig().getData("lastSeenTime", 0L); - - /** - * The subreddit to pull the posts from - */ - private final ConfigData subredditURL = getConfig().getData("subredditURL", "https://www.reddit.com/r/ChromaGamers"); - - private static boolean stop = false; - - @Override - protected void enable() { - if (DPUtils.disableIfConfigError(this, channel, modChannel)) return; - stop = false; //If not the first time - val kp = keepPinned.get(); - if (kp == 0) return; - Flux msgs = channel.get().flatMapMany(MessageChannel::getPinnedMessages).takeLast(kp); - msgs.subscribe(Message::unpin); - new Thread(this::AnnouncementGetterThreadMethod).start(); - } - - @Override - protected void disable() { - stop = true; - } - - private void AnnouncementGetterThreadMethod() { - while (!stop) { - try { - if (!isEnabled()) { - //noinspection BusyWait - Thread.sleep(10000); - continue; - } - String body = TBMCCoreAPI.DownloadString(subredditURL.get() + "/new/.json?limit=10"); - JsonArray json = new JsonParser().parse(body).getAsJsonObject().get("data").getAsJsonObject() - .get("children").getAsJsonArray(); - StringBuilder msgsb = new StringBuilder(); - StringBuilder modmsgsb = new StringBuilder(); - long lastanntime = lastAnnouncementTime.get(); - for (int i = json.size() - 1; i >= 0; i--) { - JsonObject item = json.get(i).getAsJsonObject(); - final JsonObject data = item.get("data").getAsJsonObject(); - String author = data.get("author").getAsString(); - JsonElement distinguishedjson = data.get("distinguished"); - String distinguished; - if (distinguishedjson.isJsonNull()) - distinguished = null; - else - distinguished = distinguishedjson.getAsString(); - String permalink = "https://www.reddit.com" + data.get("permalink").getAsString(); - long date = data.get("created_utc").getAsLong(); - if (date > lastSeenTime.get()) - lastSeenTime.set(date); - else if (date > lastAnnouncementTime.get()) { - //noinspection ConstantConditions - do { - val reddituserclass = ChromaGamerBase.getTypeForFolder("reddit"); - if (reddituserclass == null) - break; - val user = ChromaGamerBase.getUser(author, reddituserclass); - String id = user.getConnectedID(DiscordPlayer.class); - if (id != null) - author = "<@" + id + ">"; - } while (false); - if (!author.startsWith("<")) - author = "/u/" + author; - (distinguished != null && distinguished.equals("moderator") ? modmsgsb : msgsb) - .append("A new post was submitted to the subreddit by ").append(author).append("\n") - .append(permalink).append("\n"); - lastanntime = date; - } - } - if (msgsb.length() > 0) - channel.get().flatMap(ch -> ch.createMessage(msgsb.toString())) - .flatMap(Message::pin).subscribe(); - if (modmsgsb.length() > 0) - modChannel.get().flatMap(ch -> ch.createMessage(modmsgsb.toString())) - .flatMap(Message::pin).subscribe(); - if (lastAnnouncementTime.get() != lastanntime) - lastAnnouncementTime.set(lastanntime); // If sending succeeded - } catch (Exception e) { - e.printStackTrace(); - } - try { - //noinspection BusyWait - Thread.sleep(10000); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - } -} diff --git a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala new file mode 100644 index 0000000..118ec9f --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala @@ -0,0 +1,103 @@ +package buttondevteam.discordplugin.announcer + +import buttondevteam.discordplugin.{DPUtils, DiscordPlayer, DiscordPlugin} +import buttondevteam.lib.TBMCCoreAPI +import buttondevteam.lib.architecture.{Component, ComponentMetadata} +import buttondevteam.lib.player.ChromaGamerBase +import com.google.gson.JsonParser +import discord4j.core.`object`.entity.Message +import discord4j.core.`object`.entity.channel.MessageChannel +import reactor.core.publisher.Flux + +/** + * Posts new posts from Reddit to the specified channel(s). It will pin the regular posts (not the mod posts). + */ +@ComponentMetadata(enabledByDefault = false) object AnnouncerModule { + private var stop = false +} + +@ComponentMetadata(enabledByDefault = false) class AnnouncerModule extends Component[DiscordPlugin] { + /** + * Channel to post new posts. + */ + final val channel = DPUtils.channelData(getConfig, "channel") + /** + * Channel where distinguished (moderator) posts go. + */ + final private val modChannel = DPUtils.channelData(getConfig, "modChannel") + /** + * Automatically unpins all messages except the last few. Set to 0 or >50 to disable + */ + final private val keepPinned = getConfig.getData("keepPinned", 40.toShort) + final private val lastAnnouncementTime = getConfig.getData("lastAnnouncementTime", 0L) + final private val lastSeenTime = getConfig.getData("lastSeenTime", 0L) + /** + * The subreddit to pull the posts from + */ + final private val subredditURL = getConfig.getData("subredditURL", "https://www.reddit.com/r/ChromaGamers") + + override protected def enable(): Unit = { + if (DPUtils.disableIfConfigError(this, channel, modChannel)) return + AnnouncerModule.stop = false //If not the first time + val kp = keepPinned.get + if (kp eq 0) return + val msgs: Flux[Message] = channel.get.flatMapMany(_.getPinnedMessages).takeLast(kp) + msgs.subscribe(_.unpin) + new Thread(() => this.AnnouncementGetterThreadMethod()).start() + } + + override protected def disable(): Unit = AnnouncerModule.stop = true + + private def AnnouncementGetterThreadMethod(): Unit = while ( { + !AnnouncerModule.stop + }) { + try { + if (!isEnabled) { //noinspection BusyWait + Thread.sleep(10000) + continue //todo: continue is not supported + } + val body = TBMCCoreAPI.DownloadString(subredditURL.get + "/new/.json?limit=10") + val json = new JsonParser().parse(body).getAsJsonObject.get("data").getAsJsonObject.get("children").getAsJsonArray + val msgsb = new StringBuilder + val modmsgsb = new StringBuilder + var lastanntime = lastAnnouncementTime.get + for (i <- json.size - 1 to 0 by -1) { + val item = json.get(i).getAsJsonObject + val data = item.get("data").getAsJsonObject + var author = data.get("author").getAsString + val distinguishedjson = data.get("distinguished") + var distinguished = null + if (distinguishedjson.isJsonNull) distinguished = null + else distinguished = distinguishedjson.getAsString + val permalink = "https://www.reddit.com" + data.get("permalink").getAsString + val date = data.get("created_utc").getAsLong + if (date > lastSeenTime.get) lastSeenTime.set(date) + else if (date > lastAnnouncementTime.get) { //noinspection ConstantConditions + do { + val reddituserclass = ChromaGamerBase.getTypeForFolder("reddit") + if (reddituserclass == null) break //todo: break is not supported + val user = ChromaGamerBase.getUser(author, reddituserclass) + val id = user.getConnectedID(classOf[DiscordPlayer]) + if (id != null) author = "<@" + id + ">" + } while ( { + false + }) + if (!author.startsWith("<")) author = "/u/" + author + (if (distinguished != null && distinguished == "moderator") modmsgsb + else msgsb).append("A new post was submitted to the subreddit by ").append(author).append("\n").append(permalink).append("\n") + lastanntime = date + } + } + if (msgsb.length > 0) channel.get.flatMap((ch: MessageChannel) => ch.createMessage(msgsb.toString)).flatMap(Message.pin).subscribe + if (modmsgsb.length > 0) modChannel.get.flatMap((ch: MessageChannel) => ch.createMessage(modmsgsb.toString)).flatMap(Message.pin).subscribe + if (lastAnnouncementTime.get ne lastanntime) lastAnnouncementTime.set(lastanntime) // If sending succeeded} catch { + case e: Exception => + e.printStackTrace() + } + try Thread.sleep(10000) + catch { + case ex: InterruptedException => + Thread.currentThread.interrupt() + } + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java b/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java deleted file mode 100644 index a6ef5e2..0000000 --- a/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.java +++ /dev/null @@ -1,45 +0,0 @@ -package buttondevteam.discordplugin.broadcaster; - -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.architecture.Component; -import buttondevteam.lib.architecture.ComponentMetadata; -import lombok.Getter; - -/** - * Uses a bit of a hacky method of getting all broadcasted messages, including advancements and any other message that's for everyone. - * If this component is enabled then these messages will show up on Discord. - */ -@ComponentMetadata(enabledByDefault = false) -public class GeneralEventBroadcasterModule extends Component { - private static @Getter boolean hooked = false; - - @Override - protected void enable() { - try { - PlayerListWatcher.hookUpDown(true, this); - log("Finished hooking into the player list"); - hooked = true; - } catch (Exception e) { - TBMCCoreAPI.SendException("Error while hacking the player list! Disable this module if you're on an incompatible version.", e, this); - } catch (NoClassDefFoundError e) { - logWarn("Error while hacking the player list! Disable this module if you're on an incompatible version."); - } - - } - - @Override - protected void disable() { - try { - if (!hooked) return; - if (PlayerListWatcher.hookUpDown(false, this)) - log("Finished unhooking the player list!"); - else - log("Didn't have the player list hooked."); - hooked = false; - } catch (Exception e) { - TBMCCoreAPI.SendException("Error while hacking the player list!", e, this); - } catch (NoClassDefFoundError ignored) { - } - } -} diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala b/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala new file mode 100644 index 0000000..99e0ac4 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala @@ -0,0 +1,39 @@ +package buttondevteam.discordplugin.broadcaster + +import buttondevteam.discordplugin.DiscordPlugin +import buttondevteam.lib.TBMCCoreAPI +import buttondevteam.lib.architecture.{Component, ComponentMetadata} + +/** + * Uses a bit of a hacky method of getting all broadcasted messages, including advancements and any other message that's for everyone. + * If this component is enabled then these messages will show up on Discord. + */ +@ComponentMetadata(enabledByDefault = false) object GeneralEventBroadcasterModule { + def isHooked: Boolean = GeneralEventBroadcasterModule.hooked + + private var hooked = false +} + +@ComponentMetadata(enabledByDefault = false) class GeneralEventBroadcasterModule extends Component[DiscordPlugin] { + override protected def enable(): Unit = try { + PlayerListWatcher.hookUpDown(true, this) + log("Finished hooking into the player list") + GeneralEventBroadcasterModule.hooked = true + } catch { + case e: Exception => + TBMCCoreAPI.SendException("Error while hacking the player list! Disable this module if you're on an incompatible version.", e, this) + case _: NoClassDefFoundError => + logWarn("Error while hacking the player list! Disable this module if you're on an incompatible version.") + } + + override protected def disable(): Unit = try { + if (!GeneralEventBroadcasterModule.hooked) return + if (PlayerListWatcher.hookUpDown(false, this)) log("Finished unhooking the player list!") + else log("Didn't have the player list hooked.") + GeneralEventBroadcasterModule.hooked = false + } catch { + case e: Exception => + TBMCCoreAPI.SendException("Error while hacking the player list!", e, this) + case _: NoClassDefFoundError => + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java b/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java deleted file mode 100755 index 5fd86d7..0000000 --- a/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.java +++ /dev/null @@ -1,176 +0,0 @@ -package buttondevteam.discordplugin.broadcaster; - -import buttondevteam.discordplugin.mcchat.MCChatUtils; -import buttondevteam.discordplugin.playerfaker.DelegatingMockMaker; -import buttondevteam.lib.TBMCCoreAPI; -import lombok.val; -import org.bukkit.Bukkit; -import org.mockito.Mockito; -import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.UUID; - -public class PlayerListWatcher { - private static Object plist; - private static Object mock; - private static MethodHandle fHandle; //Handle for PlayerList.f(EntityPlayer) - Only needed for 1.16 - - static boolean hookUpDown(boolean up, GeneralEventBroadcasterModule module) throws Exception { - val csc = Bukkit.getServer().getClass(); - Field conf = csc.getDeclaredField("console"); - conf.setAccessible(true); - val server = conf.get(Bukkit.getServer()); - val nms = server.getClass().getPackage().getName(); - val dplc = Class.forName(nms + ".DedicatedPlayerList"); - val currentPL = server.getClass().getMethod("getPlayerList").invoke(server); - if (up) { - if (currentPL == mock) { - module.logWarn("Player list already mocked!"); - return false; - } - DelegatingMockMaker.getInstance().setMockMaker(new SubclassByteBuddyMockMaker()); - val icbcl = Class.forName(nms + ".IChatBaseComponent"); - Method sendMessageTemp; - try { - sendMessageTemp = server.getClass().getMethod("sendMessage", icbcl, UUID.class); - } catch (NoSuchMethodException e) { - sendMessageTemp = server.getClass().getMethod("sendMessage", icbcl); - } - val sendMessage = sendMessageTemp; - val cmtcl = Class.forName(nms + ".ChatMessageType"); - val systemType = cmtcl.getDeclaredField("SYSTEM").get(null); - val chatType = cmtcl.getDeclaredField("CHAT").get(null); - - val obc = csc.getPackage().getName(); - val ccmcl = Class.forName(obc + ".util.CraftChatMessage"); - val fixComponent = ccmcl.getMethod("fixComponent", icbcl); - val ppoc = Class.forName(nms + ".PacketPlayOutChat"); - Constructor ppocCTemp; - try { - ppocCTemp = ppoc.getConstructor(icbcl, cmtcl, UUID.class); - } catch (Exception e) { - ppocCTemp = ppoc.getConstructor(icbcl, cmtcl); - } - val ppocC = ppocCTemp; - val sendAll = dplc.getMethod("sendAll", Class.forName(nms + ".Packet")); - Method tpt; - try { - tpt = icbcl.getMethod("toPlainText"); - } catch (NoSuchMethodException e) { - tpt = icbcl.getMethod("getString"); - } - val toPlainText = tpt; - val sysb = Class.forName(nms + ".SystemUtils").getField("b"); - - //Find the original method without overrides - Constructor lookupConstructor; - if (nms.contains("1_16")) { - lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); - lookupConstructor.setAccessible(true); //Create lookup with a given class instead of caller - } else lookupConstructor = null; - mock = Mockito.mock(dplc, Mockito.withSettings().defaultAnswer(new Answer<>() { // Cannot call super constructor - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - final Method method = invocation.getMethod(); - if (!method.getName().equals("sendMessage")) { - if (method.getName().equals("sendAll")) { - sendAll(invocation.getArgument(0)); - return null; - } - //In 1.16 it passes a reference to the player list to advancement data for each player - if (nms.contains("1_16") && method.getName().equals("f") && method.getParameterCount() > 0 && method.getParameterTypes()[0].getSimpleName().equals("EntityPlayer")) { - method.setAccessible(true); - if (fHandle == null) { - assert lookupConstructor != null; - var lookup = lookupConstructor.newInstance(mock.getClass()); - fHandle = lookup.unreflectSpecial(method, mock.getClass()); //Special: super.method() - } - return fHandle.invoke(mock, invocation.getArgument(0)); //Invoke with our instance, so it passes that to advancement data, we have the fields as well - } - return method.invoke(plist, invocation.getArguments()); - } - val args = invocation.getArguments(); - val params = method.getParameterTypes(); - if (params.length == 0) { - TBMCCoreAPI.SendException("Found a strange method", - new Exception("Found a sendMessage() method without arguments."), module); - return null; - } - if (params[0].getSimpleName().equals("IChatBaseComponent[]")) - for (val arg : (Object[]) args[0]) - sendMessage(arg, true); - else if (params[0].getSimpleName().equals("IChatBaseComponent")) - if (params.length > 1 && params[1].getSimpleName().equalsIgnoreCase("boolean")) - sendMessage(args[0], (Boolean) args[1]); - else - sendMessage(args[0], true); - else - TBMCCoreAPI.SendException("Found a method with interesting params", - new Exception("Found a sendMessage(" + params[0].getSimpleName() + ") method"), module); - return null; - } - - private void sendMessage(Object chatComponent, boolean system) { - try { //Converted to use reflection - if (sendMessage.getParameterCount() == 2) - sendMessage.invoke(server, chatComponent, sysb.get(null)); - else - sendMessage.invoke(server, chatComponent); - Object chatmessagetype = system ? systemType : chatType; - - // CraftBukkit start - we run this through our processor first so we can get web links etc - var comp = fixComponent.invoke(null, chatComponent); - var packet = ppocC.getParameterCount() == 3 - ? ppocC.newInstance(comp, chatmessagetype, sysb.get(null)) - : ppocC.newInstance(comp, chatmessagetype); - this.sendAll(packet); - // CraftBukkit end - } catch (Exception e) { - TBMCCoreAPI.SendException("An error occurred while passing a vanilla message through the player list", e, module); - } - } - - private void sendAll(Object packet) { - try { // Some messages get sent by directly constructing a packet - sendAll.invoke(plist, packet); - if (packet.getClass() == ppoc) { - Field msgf = ppoc.getDeclaredField("a"); - msgf.setAccessible(true); - MCChatUtils.forPublicPrivateChat(MCChatUtils.send((String) toPlainText.invoke(msgf.get(packet)))).subscribe(); - } - } catch (Exception e) { - TBMCCoreAPI.SendException("Failed to broadcast message sent to all players - hacking failed.", e, module); - } - } - }).stubOnly()); - plist = currentPL; - for (var plc = dplc; plc != null; plc = plc.getSuperclass()) { //Set all fields - for (var f : plc.getDeclaredFields()) { - f.setAccessible(true); - Field modf = f.getClass().getDeclaredField("modifiers"); - modf.setAccessible(true); - modf.set(f, f.getModifiers() & ~Modifier.FINAL); - f.set(mock, f.get(plist)); - } - } - } - try { - server.getClass().getMethod("a", dplc).invoke(server, up ? mock : plist); - } catch (NoSuchMethodException e) { - server.getClass().getMethod("a", Class.forName(server.getClass().getPackage().getName() + ".PlayerList")) - .invoke(server, up ? mock : plist); - } - Field pllf = csc.getDeclaredField("playerList"); - pllf.setAccessible(true); - pllf.set(Bukkit.getServer(), up ? mock : plist); - return true; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala b/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala new file mode 100644 index 0000000..7e563d1 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala @@ -0,0 +1,168 @@ +package buttondevteam.discordplugin.broadcaster + +import buttondevteam.discordplugin.mcchat.MCChatUtils +import buttondevteam.discordplugin.playerfaker.DelegatingMockMaker +import buttondevteam.lib.TBMCCoreAPI +import org.bukkit.Bukkit +import org.mockito.Mockito +import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker +import org.mockito.invocation.InvocationOnMock +import org.mockito.stubbing.Answer + +import java.lang.invoke.{MethodHandle, MethodHandles} +import java.lang.reflect.{Constructor, Method, Modifier} +import java.util.UUID + +object PlayerListWatcher { + private var plist: AnyRef = null + private var mock: AnyRef = null + private var fHandle: MethodHandle = null //Handle for PlayerList.f(EntityPlayer) - Only needed for 1.16 + @throws[Exception] + private[broadcaster] def hookUpDown(up: Boolean, module: GeneralEventBroadcasterModule): Boolean = { + val csc = Bukkit.getServer.getClass + val conf = csc.getDeclaredField("console") + conf.setAccessible(true) + val server = conf.get(Bukkit.getServer) + val nms = server.getClass.getPackage.getName + val dplc = Class.forName(nms + ".DedicatedPlayerList") + val currentPL = server.getClass.getMethod("getPlayerList").invoke(server) + if (up) { + if (currentPL eq mock) { + module.logWarn("Player list already mocked!") + return false + } + DelegatingMockMaker.getInstance.setMockMaker(new SubclassByteBuddyMockMaker) + val icbcl = Class.forName(nms + ".IChatBaseComponent") + var sendMessageTemp: Method = null + try sendMessageTemp = server.getClass.getMethod("sendMessage", icbcl, classOf[UUID]) + catch { + case e: NoSuchMethodException => + sendMessageTemp = server.getClass.getMethod("sendMessage", icbcl) + } + val sendMessageMethod = sendMessageTemp + val cmtcl = Class.forName(nms + ".ChatMessageType") + val systemType = cmtcl.getDeclaredField("SYSTEM").get(null) + val chatType = cmtcl.getDeclaredField("CHAT").get(null) + val obc = csc.getPackage.getName + val ccmcl = Class.forName(obc + ".util.CraftChatMessage") + val fixComponent = ccmcl.getMethod("fixComponent", icbcl) + val ppoc = Class.forName(nms + ".PacketPlayOutChat") + var ppocCTemp: Constructor[_] = null + try ppocCTemp = ppoc.getConstructor(icbcl, cmtcl, classOf[UUID]) + catch { + case _: Exception => + ppocCTemp = ppoc.getConstructor(icbcl, cmtcl) + } + val ppocC = ppocCTemp + val sendAllMethod = dplc.getMethod("sendAll", Class.forName(nms + ".Packet")) + var tpt: Method = null + try tpt = icbcl.getMethod("toPlainText") + catch { + case _: NoSuchMethodException => + tpt = icbcl.getMethod("getString") + } + val toPlainText = tpt + val sysb = Class.forName(nms + ".SystemUtils").getField("b") + //Find the original method without overrides + var lookupConstructor: Constructor[MethodHandles.Lookup] = null + if (nms.contains("1_16")) { + lookupConstructor = classOf[MethodHandles.Lookup].getDeclaredConstructor(classOf[Class[_]]) + lookupConstructor.setAccessible(true) //Create lookup with a given class instead of caller + } + else lookupConstructor = null + mock = Mockito.mock(dplc, Mockito.withSettings.defaultAnswer(new Answer[AnyRef]() { // Cannot call super constructor + @throws[Throwable] + override def answer(invocation: InvocationOnMock): Any = { + val method = invocation.getMethod + if (!(method.getName == "sendMessage")) { + if (method.getName == "sendAll") { + sendAll(invocation.getArgument(0)) + return null + } + //In 1.16 it passes a reference to the player list to advancement data for each player + if (nms.contains("1_16") && method.getName == "f" && method.getParameterCount > 0 && method.getParameterTypes()(0).getSimpleName == "EntityPlayer") { + method.setAccessible(true) + if (fHandle == null) { + assert(lookupConstructor != null) + val lookup = lookupConstructor.newInstance(mock.getClass) + fHandle = lookup.unreflectSpecial(method, mock.getClass) //Special: super.method() + } + return fHandle.invoke(mock, invocation.getArgument(0)) //Invoke with our instance, so it passes that to advancement data, we have the fields as well + } + return method.invoke(plist, invocation.getArguments) + } + val args = invocation.getArguments + val params = method.getParameterTypes + if (params.isEmpty) { + TBMCCoreAPI.SendException("Found a strange method", new Exception("Found a sendMessage() method without arguments."), module) + return null + } + if (params(0).getSimpleName == "IChatBaseComponent[]") for (arg <- args(0).asInstanceOf[Array[AnyRef]]) { + sendMessage(arg, system = true) + } + else if (params(0).getSimpleName == "IChatBaseComponent") if (params.length > 1 && params(1).getSimpleName.equalsIgnoreCase("boolean")) sendMessage(args(0), args(1).asInstanceOf[Boolean]) + else sendMessage(args(0), system = true) + else TBMCCoreAPI.SendException("Found a method with interesting params", new Exception("Found a sendMessage(" + params(0).getSimpleName + ") method"), module) + null + } + + private + + def sendMessage(chatComponent: Any, system: Boolean) = try { //Converted to use reflection + if (sendMessageMethod.getParameterCount == 2) sendMessageMethod.invoke(server, chatComponent, sysb.get(null)) + else sendMessageMethod.invoke(server, chatComponent) + val chatmessagetype = if (system) systemType + else chatType + // CraftBukkit start - we run this through our processor first so we can get web links etc + val comp = fixComponent.invoke(null, chatComponent) + val packet = if (ppocC.getParameterCount == 3) ppocC.newInstance(comp, chatmessagetype, sysb.get(null)) + else ppocC.newInstance(comp, chatmessagetype) + this.sendAll(packet) + } catch { + case e: Exception => + TBMCCoreAPI.SendException("An error occurred while passing a vanilla message through the player list", e, module) + } + + private + + def sendAll(packet: Any) = try { // Some messages get sent by directly constructing a packet + sendAllMethod.invoke(plist, packet) + if (packet.getClass eq ppoc) { + val msgf = ppoc.getDeclaredField("a") + msgf.setAccessible(true) + MCChatUtils.forPublicPrivateChat(MCChatUtils.send(toPlainText.invoke(msgf.get(packet)).asInstanceOf[String])).subscribe + } + } catch { + case e: Exception => + TBMCCoreAPI.SendException("Failed to broadcast message sent to all players - hacking failed.", e, module) + } + }).stubOnly).asInstanceOf + plist = currentPL + var plc = dplc + while ( { + plc != null + }) { //Set all fields + for (f <- plc.getDeclaredFields) { + f.setAccessible(true) + val modf = f.getClass.getDeclaredField("modifiers") + modf.setAccessible(true) + modf.set(f, f.getModifiers & ~Modifier.FINAL) + f.set(mock, f.get(plist)) + } + plc = plc.getSuperclass + } + } + try server.getClass.getMethod("a", dplc).invoke(server, if (up) mock + else plist) + catch { + case e: NoSuchMethodException => + server.getClass.getMethod("a", Class.forName(server.getClass.getPackage.getName + ".PlayerList")).invoke(server, if (up) mock + else plist) + } + val pllf = csc.getDeclaredField("playerList") + pllf.setAccessible(true) + pllf.set(Bukkit.getServer, if (up) mock + else plist) + true + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/commands/Command2DC.java b/src/main/java/buttondevteam/discordplugin/commands/Command2DC.java deleted file mode 100644 index ab56eb8..0000000 --- a/src/main/java/buttondevteam/discordplugin/commands/Command2DC.java +++ /dev/null @@ -1,19 +0,0 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.chat.Command2; - -import java.lang.reflect.Method; - -public class Command2DC extends Command2 { - @Override - public void registerCommand(ICommand2DC command) { - super.registerCommand(command, DiscordPlugin.getPrefix()); //Needs to be configurable for the helps - } - - @Override - public boolean hasPermission(Command2DCSender sender, ICommand2DC command, Method method) { - //return !command.isModOnly() || sender.getMessage().getAuthor().hasRole(DiscordPlugin.plugin.modRole().get()); //TODO: modRole may be null; more customisable way? - return true; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/commands/Command2DC.scala b/src/main/java/buttondevteam/discordplugin/commands/Command2DC.scala new file mode 100644 index 0000000..690cb2d --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/commands/Command2DC.scala @@ -0,0 +1,15 @@ +package buttondevteam.discordplugin.commands + +import buttondevteam.discordplugin.DiscordPlugin +import buttondevteam.lib.chat.Command2 + +import java.lang.reflect.Method + +class Command2DC extends Command2[ICommand2DC, Command2DCSender] { + override def registerCommand(command: ICommand2DC): Unit = + super.registerCommand(command, DiscordPlugin.getPrefix) //Needs to be configurable for the helps + override def hasPermission(sender: Command2DCSender, command: ICommand2DC, method: Method): Boolean = { + //return !command.isModOnly() || sender.getMessage().getAuthor().hasRole(DiscordPlugin.plugin.modRole().get()); //TODO: modRole may be null; more customisable way? + true + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.java b/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.java deleted file mode 100644 index ab22e37..0000000 --- a/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.java +++ /dev/null @@ -1,39 +0,0 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.discordplugin.DPUtils; -import buttondevteam.lib.chat.Command2Sender; -import discord4j.core.object.entity.Message; -import discord4j.core.object.entity.User; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.val; - -@RequiredArgsConstructor -public class Command2DCSender implements Command2Sender { - private final @Getter - Message message; - - @Override - public void sendMessage(String message) { - if (message.length() == 0) return; - message = DPUtils.sanitizeString(message); - message = Character.toLowerCase(message.charAt(0)) + message.substring(1); - val msg = message; - /*this.message.getAuthorAsMember().flatMap(author -> - this.message.getChannel().flatMap(ch -> - ch.createMessage(author.getNicknameMention() + ", " + msg))).subscribe();*/ - this.message.getChannel().flatMap(ch -> - ch.createMessage(this.message.getAuthor().map(u -> DPUtils.nickMention(u.getId()) + ", ").orElse("") - + msg)).subscribe(); - } - - @Override - public void sendMessage(String[] message) { - sendMessage(String.join("\n", message)); - } - - @Override - public String getName() { - return message.getAuthor().map(User::getUsername).orElse("Discord"); - } -} diff --git a/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala b/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala new file mode 100644 index 0000000..f898fe8 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala @@ -0,0 +1,22 @@ +package buttondevteam.discordplugin.commands + +import buttondevteam.discordplugin.DPUtils +import buttondevteam.lib.chat.Command2Sender +import discord4j.core.`object`.entity.channel.MessageChannel +import discord4j.core.`object`.entity.{Message, User} +import lombok.RequiredArgsConstructor + +@RequiredArgsConstructor class Command2DCSender(val message: Message) extends Command2Sender { + def getMessage: Message = this.message + + override def sendMessage(message: String): Unit = { + if (message.isEmpty) return + var msg = DPUtils.sanitizeString(message) + msg = Character.toLowerCase(message.charAt(0)) + message.substring(1) + this.message.getChannel.flatMap((ch: MessageChannel) => ch.createMessage(this.message.getAuthor.map((u: User) => DPUtils.nickMention(u.getId) + ", ").orElse("") + msg)).subscribe + } + + override def sendMessage(message: Array[String]): Unit = sendMessage(String.join("\n", message)) + + override def getName = message.getAuthor.map(_.getUsername).orElse("Discord") +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.java b/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.java deleted file mode 100755 index f0d7fe9..0000000 --- a/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.java +++ /dev/null @@ -1,59 +0,0 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.discordplugin.DiscordPlayer; -import buttondevteam.lib.chat.Command2; -import buttondevteam.lib.chat.CommandClass; -import buttondevteam.lib.player.TBMCPlayer; -import buttondevteam.lib.player.TBMCPlayerBase; -import com.google.common.collect.HashBiMap; -import lombok.val; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; - -@CommandClass(helpText = { - "Connect command", // - "This command lets you connect your account with a Minecraft account. This allows using the private Minecraft chat and other things.", // -}) -public class ConnectCommand extends ICommand2DC { - - /** - * Key: Minecraft name
- * Value: Discord ID - */ - public static HashBiMap WaitingToConnect = HashBiMap.create(); - - @Command2.Subcommand - public boolean def(Command2DCSender sender, String Minecraftname) { - val message = sender.getMessage(); - val channel = message.getChannel().block(); - val author = message.getAuthor().orElse(null); - if (author == null || channel == null) return true; - if (WaitingToConnect.inverse().containsKey(author.getId().asString())) { - channel.createMessage( - "Replacing " + WaitingToConnect.inverse().get(author.getId().asString()) + " with " + Minecraftname).subscribe(); - WaitingToConnect.inverse().remove(author.getId().asString()); - } - @SuppressWarnings("deprecation") - OfflinePlayer p = Bukkit.getOfflinePlayer(Minecraftname); - if (p == null) { - channel.createMessage("The specified Minecraft player cannot be found").subscribe(); - return true; - } - TBMCPlayer pl = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class); - DiscordPlayer dp = pl.getAs(DiscordPlayer.class); - if (dp != null && author.getId().asString().equals(dp.getDiscordID())) { - channel.createMessage("You already have this account connected.").subscribe(); - return true; - } - WaitingToConnect.put(p.getName(), author.getId().asString()); - channel.createMessage( - "Alright! Now accept the connection in Minecraft from the account " + Minecraftname - + " before the next server restart. You can also adjust the Minecraft name you want to connect to by running this command again.").subscribe(); - if (p.isOnline()) - ((Player) p).sendMessage("§bTo connect with the Discord account " + author.getUsername() + "#" - + author.getDiscriminator() + " do /discord accept"); - return true; - } - -} diff --git a/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala new file mode 100644 index 0000000..db1d9a9 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala @@ -0,0 +1,46 @@ +package buttondevteam.discordplugin.commands + +import buttondevteam.discordplugin.DiscordPlayer +import buttondevteam.lib.chat.{Command2, CommandClass} +import buttondevteam.lib.player.{TBMCPlayer, TBMCPlayerBase} +import com.google.common.collect.HashBiMap +import org.bukkit.Bukkit +import org.bukkit.entity.Player + +@CommandClass(helpText = Array(Array("Connect command", // + "This command lets you connect your account with a Minecraft account. This allows using the private Minecraft chat and other things."))) object ConnectCommand { + /** + * Key: Minecraft name
+ * Value: Discord ID + */ + var WaitingToConnect: HashBiMap[String, String] = HashBiMap.create +} + +@CommandClass(helpText = Array(Array("Connect command", "This command lets you connect your account with a Minecraft account. This allows using the private Minecraft chat and other things."))) class ConnectCommand extends ICommand2DC { + @Command2.Subcommand def `def`(sender: Command2DCSender, Minecraftname: String): Boolean = { + val message = sender.getMessage + val channel = message.getChannel.block + val author = message.getAuthor.orElse(null) + if (author == null || channel == null) return true + if (ConnectCommand.WaitingToConnect.inverse.containsKey(author.getId.asString)) { + channel.createMessage("Replacing " + ConnectCommand.WaitingToConnect.inverse.get(author.getId.asString) + " with " + Minecraftname).subscribe + ConnectCommand.WaitingToConnect.inverse.remove(author.getId.asString) + } + //noinspection ScalaDeprecation + val p = Bukkit.getOfflinePlayer(Minecraftname) + if (p == null) { + channel.createMessage("The specified Minecraft player cannot be found").subscribe + return true + } + val pl = TBMCPlayerBase.getPlayer(p.getUniqueId, classOf[TBMCPlayer]) + val dp = pl.getAs(classOf[DiscordPlayer]) + if (dp != null && author.getId.asString == dp.getDiscordID) { + channel.createMessage("You already have this account connected.").subscribe + return true + } + ConnectCommand.WaitingToConnect.put(p.getName, author.getId.asString) + channel.createMessage("Alright! Now accept the connection in Minecraft from the account " + Minecraftname + " before the next server restart. You can also adjust the Minecraft name you want to connect to by running this command again.").subscribe + if (p.isOnline) p.asInstanceOf[Player].sendMessage("§bTo connect with the Discord account " + author.getUsername + "#" + author.getDiscriminator + " do /discord accept") + true + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.java b/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.java deleted file mode 100644 index 951cc71..0000000 --- a/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.java +++ /dev/null @@ -1,30 +0,0 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.listeners.CommonListeners; -import buttondevteam.lib.chat.Command2; -import buttondevteam.lib.chat.CommandClass; -import reactor.core.publisher.Mono; - -@CommandClass(helpText = { - "Switches debug mode." -}) -public class DebugCommand extends ICommand2DC { - @Command2.Subcommand - public boolean def(Command2DCSender sender) { - sender.getMessage().getAuthorAsMember() - .switchIfEmpty(sender.getMessage().getAuthor() //Support DMs - .map(u -> u.asMember(DiscordPlugin.mainServer.getId())) - .orElse(Mono.empty())) - .flatMap(m -> DiscordPlugin.plugin.modRole.get() - .map(mr -> m.getRoleIds().stream().anyMatch(r -> r.equals(mr.getId()))) - .switchIfEmpty(Mono.fromSupplier(() -> DiscordPlugin.mainServer.getOwnerId().asLong() == m.getId().asLong()))) //Role not found - .onErrorReturn(false).subscribe(success -> { - if (success) - sender.sendMessage("debug " + (CommonListeners.debug() ? "enabled" : "disabled")); - else - sender.sendMessage("you need to be a moderator to use this command."); - }); - return true; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala new file mode 100644 index 0000000..67fee6e --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala @@ -0,0 +1,27 @@ +package buttondevteam.discordplugin.commands + +import buttondevteam.discordplugin.DiscordPlugin +import buttondevteam.discordplugin.listeners.CommonListeners +import buttondevteam.lib.chat.{Command2, CommandClass} +import discord4j.core.`object`.entity.{Member, User} +import reactor.core.publisher.Mono + +@CommandClass(helpText = Array(Array("Switches debug mode."))) +class DebugCommand extends ICommand2DC { + @Command2.Subcommand + override def `def`(sender: Command2DCSender): Boolean = { + sender.getMessage.getAuthorAsMember.switchIfEmpty(sender.getMessage.getAuthor.map //Support DMs + ((u: User) => u.asMember(DiscordPlugin.mainServer.getId)).orElse(Mono.empty)).flatMap((m: Member) => DiscordPlugin.plugin.modRole.get.map((mr) => m.getRoleIds.stream.anyMatch((r: Snowflake) => r == mr.getId)).switchIfEmpty(Mono.fromSupplier(() => DiscordPlugin.mainServer.getOwnerId.asLong eq m.getId.asLong))) + .onErrorReturn(false) //Role not found + .subscribe((success: Any) => { + def foo(success: Any) = { + if (success) sender.sendMessage("debug " + (if (CommonListeners.debug) "enabled" + else "disabled")) + else sender.sendMessage("you need to be a moderator to use this command.") + } + + foo(success) + }) + true + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.java b/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.java deleted file mode 100755 index 546d4ee..0000000 --- a/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.lib.chat.Command2; -import buttondevteam.lib.chat.CommandClass; - -@CommandClass(helpText = { - "Help command", // - "Shows some info about a command or lists the available commands.", // -}) -public class HelpCommand extends ICommand2DC { - @Command2.Subcommand - public boolean def(Command2DCSender sender, @Command2.TextArg @Command2.OptionalArg String args) { - if (args == null || args.length() == 0) - sender.sendMessage(getManager().getCommandsText()); - else { - String[] ht = getManager().getHelpText(args); - if (ht == null) - sender.sendMessage("Command not found: " + args); - else - sender.sendMessage(ht); - } - return true; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.scala new file mode 100644 index 0000000..c3d1029 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.scala @@ -0,0 +1,18 @@ +package buttondevteam.discordplugin.commands + +import buttondevteam.lib.chat.{Command2, CommandClass} + +@CommandClass(helpText = Array(Array("Help command", // + "Shows some info about a command or lists the available commands."))) +class HelpCommand extends ICommand2DC { + @Command2.Subcommand + def `def`(sender: Command2DCSender, @Command2.TextArg @Command2.OptionalArg args: String): Boolean = { + if (args == null || args.isEmpty) sender.sendMessage(getManager.getCommandsText) + else { + val ht = getManager.getHelpText(args) + if (ht == null) sender.sendMessage("Command not found: " + args) + else sender.sendMessage(ht) + } + true + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.java b/src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.java deleted file mode 100644 index 6aae802..0000000 --- a/src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.java +++ /dev/null @@ -1,20 +0,0 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.chat.CommandClass; -import buttondevteam.lib.chat.ICommand2; -import lombok.Getter; -import lombok.val; - -public abstract class ICommand2DC extends ICommand2 { - public ICommand2DC() { - super(DiscordPlugin.plugin.getManager()); - val ann = getClass().getAnnotation(CommandClass.class); - if (ann == null) - modOnly = false; - else - modOnly = ann.modOnly(); - } - - private final @Getter boolean modOnly; -} diff --git a/src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.scala b/src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.scala new file mode 100644 index 0000000..31ae228 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.scala @@ -0,0 +1,16 @@ +package buttondevteam.discordplugin.commands + +import buttondevteam.discordplugin.DiscordPlugin +import buttondevteam.lib.chat.{CommandClass, ICommand2} + +abstract class ICommand2DC() extends ICommand2[Command2DCSender](DiscordPlugin.plugin.manager) { + final private var modOnly = false + + { + val ann: CommandClass = getClass.getAnnotation(classOf[CommandClass]) + if (ann == null) modOnly = false + else modOnly = ann.modOnly + } + + def isModOnly: Boolean = this.modOnly +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.java b/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.java deleted file mode 100755 index 46a06ce..0000000 --- a/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.java +++ /dev/null @@ -1,88 +0,0 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.discordplugin.DiscordPlayer; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.chat.Command2; -import buttondevteam.lib.chat.CommandClass; -import buttondevteam.lib.player.ChromaGamerBase; -import buttondevteam.lib.player.ChromaGamerBase.InfoTarget; -import discord4j.core.object.entity.Message; -import discord4j.core.object.entity.User; -import lombok.val; - -import java.util.List; - -@CommandClass(helpText = { - "User information", // - "Shows some information about users, from Discord, from Minecraft or from Reddit if they have these accounts connected.", // - "If used without args, shows your info.", // -}) -public class UserinfoCommand extends ICommand2DC { - @Command2.Subcommand - public boolean def(Command2DCSender sender, @Command2.OptionalArg @Command2.TextArg String user) { - val message = sender.getMessage(); - User target = null; - val channel = message.getChannel().block(); - assert channel != null; - if (user == null || user.length() == 0) - target = message.getAuthor().orElse(null); - else { - final User firstmention = message.getUserMentions() - .filter(m -> !m.getId().asString().equals(DiscordPlugin.dc.getSelfId().asString())).blockFirst(); - if (firstmention != null) - target = firstmention; - else if (user.contains("#")) { - String[] targettag = user.split("#"); - final List targets = getUsers(message, targettag[0]); - if (targets.size() == 0) { - channel.createMessage("The user cannot be found (by name): " + user).subscribe(); - return true; - } - for (User ptarget : targets) { - if (ptarget.getDiscriminator().equalsIgnoreCase(targettag[1])) { - target = ptarget; - break; - } - } - if (target == null) { - channel.createMessage("The user cannot be found (by discriminator): " + user + "(Found " + targets.size() - + " users with the name.)").subscribe(); - return true; - } - } else { - final List targets = getUsers(message, user); - if (targets.size() == 0) { - channel.createMessage("The user cannot be found on Discord: " + user).subscribe(); - return true; - } - if (targets.size() > 1) { - channel.createMessage("Multiple users found with that (nick)name. Please specify the whole tag, like ChromaBot#6338 or use a ping.").subscribe(); - return true; - } - target = targets.get(0); - } - } - if (target == null) { - sender.sendMessage("An error occurred."); - return true; - } - DiscordPlayer dp = ChromaGamerBase.getUser(target.getId().asString(), DiscordPlayer.class); - StringBuilder uinfo = new StringBuilder("User info for ").append(target.getUsername()).append(":\n"); - uinfo.append(dp.getInfo(InfoTarget.Discord)); - channel.createMessage(uinfo.toString()).subscribe(); - return true; - } - - private List getUsers(Message message, String args) { - final List targets; - val guild = message.getGuild().block(); - if (guild == null) //Private channel - targets = DiscordPlugin.dc.getUsers().filter(u -> u.getUsername().equalsIgnoreCase(args)) - .collectList().block(); - else - targets = guild.getMembers().filter(m -> m.getUsername().equalsIgnoreCase(args)) - .map(m -> (User) m).collectList().block(); - return targets; - } - -} diff --git a/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.scala new file mode 100644 index 0000000..c9e7203 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.scala @@ -0,0 +1,76 @@ +package buttondevteam.discordplugin.commands + +import buttondevteam.discordplugin.{DiscordPlayer, DiscordPlugin} +import buttondevteam.lib.chat.{Command2, CommandClass} +import buttondevteam.lib.player.ChromaGamerBase +import buttondevteam.lib.player.ChromaGamerBase.InfoTarget +import discord4j.core.`object`.entity.{Member, Message, User} + +import java.util + +@CommandClass(helpText = Array(Array("User information", // + "Shows some information about users, from Discord, from Minecraft or from Reddit if they have these accounts connected.", + "If used without args, shows your info."))) +class UserinfoCommand extends ICommand2DC { + @Command2.Subcommand + def `def`(sender: Command2DCSender, @Command2.OptionalArg @Command2.TextArg user: String): Boolean = { + val message = sender.getMessage + var target: User = null + val channel = message.getChannel.block + assert(channel != null) + if (user == null || user.isEmpty) target = message.getAuthor.orElse(null) + else { + val firstmention = message.getUserMentions.filter((m: User) => !(m.getId.asString == DiscordPlugin.dc.getSelfId.asString)).blockFirst + if (firstmention != null) target = firstmention + else if (user.contains("#")) { + val targettag = user.split("#") + val targets = getUsers(message, targettag(0)) + if (targets.size == 0) { + channel.createMessage("The user cannot be found (by name): " + user).subscribe + return true + } + for (ptarget <- targets) { + if (ptarget.getDiscriminator.equalsIgnoreCase(targettag(1))) { + target = ptarget + break //todo: break is not supported + } + } + if (target == null) { + channel.createMessage("The user cannot be found (by discriminator): " + user + "(Found " + targets.size + " users with the name.)").subscribe + return true + } + } + else { + val targets = getUsers(message, user) + if (targets.size == 0) { + channel.createMessage("The user cannot be found on Discord: " + user).subscribe + return true + } + if (targets.size > 1) { + channel.createMessage("Multiple users found with that (nick)name. Please specify the whole tag, like ChromaBot#6338 or use a ping.").subscribe + return true + } + target = targets.get(0) + } + } + if (target == null) { + sender.sendMessage("An error occurred.") + return true + } + val dp = ChromaGamerBase.getUser(target.getId.asString, classOf[DiscordPlayer]) + val uinfo = new StringBuilder("User info for ").append(target.getUsername).append(":\n") + uinfo.append(dp.getInfo(InfoTarget.Discord)) + channel.createMessage(uinfo.toString).subscribe + true + } + + private def getUsers(message: Message, args: String) = { + var targets: util.List[User] + val guild = message.getGuild.block + if (guild == null) { //Private channel + targets = DiscordPlugin.dc.getUsers.filter((u) => u.getUsername.equalsIgnoreCase(args)).collectList.block + } + else targets = guild.getMembers.filter((m: Member) => m.getUsername.equalsIgnoreCase(args)).map((m: Member) => m.asInstanceOf[User]).collectList.block + targets + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.java b/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.java deleted file mode 100644 index ac83243..0000000 --- a/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.java +++ /dev/null @@ -1,26 +0,0 @@ -package buttondevteam.discordplugin.commands; - -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.chat.Command2; -import buttondevteam.lib.chat.CommandClass; -import lombok.val; - -@CommandClass(helpText = { - "Version", - "Returns the plugin's version" -}) -public class VersionCommand extends ICommand2DC { - @Command2.Subcommand - public boolean def(Command2DCSender sender) { - sender.sendMessage(getVersion()); - return true; - } - - public static String[] getVersion() { - val desc = DiscordPlugin.plugin.getDescription(); - return new String[]{ // - desc.getFullName(), // - desc.getWebsite() // - }; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala new file mode 100644 index 0000000..59be8e3 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala @@ -0,0 +1,21 @@ +package buttondevteam.discordplugin.commands + +import buttondevteam.discordplugin.DiscordPlugin +import buttondevteam.lib.chat.{Command2, CommandClass} + +@CommandClass(helpText = Array(Array("Version", "Returns the plugin's version"))) +object VersionCommand { + def getVersion: Array[String] = { + val desc = DiscordPlugin.plugin.getDescription + Array[String]( // + desc.getFullName, desc.getWebsite //) + } +} + +@CommandClass(helpText = Array(Array("Version", "Returns the plugin's version"))) +class VersionCommand extends ICommand2DC { + @Command2.Subcommand override def `def`(sender: Command2DCSender): Boolean = { + sender.sendMessage(VersionCommand.getVersion) + true + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.java b/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.java deleted file mode 100755 index f90f0f3..0000000 --- a/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.java +++ /dev/null @@ -1,36 +0,0 @@ -package buttondevteam.discordplugin.exceptions; - -import buttondevteam.core.ComponentManager; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.TBMCDebugMessageEvent; -import discord4j.core.object.entity.channel.MessageChannel; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import reactor.core.publisher.Mono; - -public class DebugMessageListener implements Listener { - @EventHandler - public void onDebugMessage(TBMCDebugMessageEvent e) { - SendMessage(e.getDebugMessage()); - e.setSent(); - } - - private static void SendMessage(String message) { - if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(ExceptionListenerModule.class)) - return; - try { - Mono mc = ExceptionListenerModule.getChannel(); - if (mc == null) return; - StringBuilder sb = new StringBuilder(); - sb.append("```").append("\n"); - if (message.length() > 2000) - message = message.substring(0, 2000); - sb.append(message).append("\n"); - sb.append("```"); - mc.flatMap(ch -> ch.createMessage(sb.toString())).subscribe(); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - -} diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala b/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala new file mode 100644 index 0000000..265cdb6 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala @@ -0,0 +1,32 @@ +package buttondevteam.discordplugin.exceptions + +import buttondevteam.core.ComponentManager +import buttondevteam.discordplugin.DiscordPlugin +import buttondevteam.lib.TBMCDebugMessageEvent +import discord4j.core.`object`.entity.channel.MessageChannel +import org.bukkit.event.{EventHandler, Listener} + +object DebugMessageListener { + private def SendMessage(message: String): Unit = { + if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(classOf[ExceptionListenerModule])) return + try { + val mc = ExceptionListenerModule.getChannel + if (mc == null) return + val sb = new StringBuilder + sb.append("```").append("\n") + sb.append(if (message.length > 2000) message.substring(0, 2000) else message).append("\n") + sb.append("```") + mc.flatMap((ch: MessageChannel) => ch.createMessage(sb.toString)).subscribe + } catch { + case ex: Exception => + ex.printStackTrace() + } + } +} + +class DebugMessageListener extends Listener { + @EventHandler def onDebugMessage(e: TBMCDebugMessageEvent): Unit = { + DebugMessageListener.SendMessage(e.getDebugMessage) + e.setSent() + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java deleted file mode 100755 index 16d1e93..0000000 --- a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.java +++ /dev/null @@ -1,114 +0,0 @@ -package buttondevteam.discordplugin.exceptions; - -import buttondevteam.core.ComponentManager; -import buttondevteam.discordplugin.DPUtils; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.TBMCExceptionEvent; -import buttondevteam.lib.architecture.Component; -import buttondevteam.lib.architecture.ConfigData; -import buttondevteam.lib.architecture.ReadOnlyConfigData; -import discord4j.core.object.entity.Guild; -import discord4j.core.object.entity.Role; -import discord4j.core.object.entity.channel.GuildChannel; -import discord4j.core.object.entity.channel.MessageChannel; -import org.apache.commons.lang.exception.ExceptionUtils; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import reactor.core.publisher.Mono; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Listens for errors from the Chroma plugins and posts them to Discord, ignoring repeating errors so it's not that spammy. - */ -public class ExceptionListenerModule extends Component implements Listener { - private final List lastthrown = new ArrayList<>(); - private final List lastsourcemsg = new ArrayList<>(); - - @EventHandler - public void onException(TBMCExceptionEvent e) { - if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(getClass())) - return; - if (lastthrown.stream() - .anyMatch(ex -> Arrays.equals(e.getException().getStackTrace(), ex.getStackTrace()) - && (e.getException().getMessage() == null ? ex.getMessage() == null - : e.getException().getMessage().equals(ex.getMessage()))) // e.Exception.Message==ex.Message - && lastsourcemsg.contains(e.getSourceMessage())) - return; - SendException(e.getException(), e.getSourceMessage()); - if (lastthrown.size() >= 10) - lastthrown.remove(0); - if (lastsourcemsg.size() >= 10) - lastsourcemsg.remove(0); - lastthrown.add(e.getException()); - lastsourcemsg.add(e.getSourceMessage()); - e.setHandled(); - } - - private static void SendException(Throwable e, String sourcemessage) { - if (instance == null) return; - try { - getChannel().flatMap(channel -> { - Mono coderRole; - if (channel instanceof GuildChannel) - coderRole = instance.pingRole(((GuildChannel) channel).getGuild()).get(); - else - coderRole = Mono.empty(); - return coderRole.map(role -> TBMCCoreAPI.IsTestServer() ? new StringBuilder() - : new StringBuilder(role.getMention()).append("\n")) - .defaultIfEmpty(new StringBuilder()) - .flatMap(sb -> { - sb.append(sourcemessage).append("\n"); - sb.append("```").append("\n"); - String stackTrace = Arrays.stream(ExceptionUtils.getStackTrace(e).split("\\n")) - .filter(s -> !s.contains("\tat ") || s.contains("\tat buttondevteam.")) - .collect(Collectors.joining("\n")); - if (sb.length() + stackTrace.length() >= 1980) - stackTrace = stackTrace.substring(0, 1980 - sb.length()); - sb.append(stackTrace).append("\n"); - sb.append("```"); - return channel.createMessage(sb.toString()); - }); - }).subscribe(); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - - private static ExceptionListenerModule instance; - - public static Mono getChannel() { - if (instance != null) return instance.channel.get(); - return Mono.empty(); - } - - /** - * The channel to post the errors to. - */ - private final ReadOnlyConfigData> channel = DPUtils.channelData(getConfig(), "channel"); - - /** - * The role to ping if an error occurs. Set to empty ('') to disable. - */ - private ConfigData> pingRole(Mono guild) { - return DPUtils.roleData(getConfig(), "pingRole", "Coder", guild); - } - - @Override - protected void enable() { - if (DPUtils.disableIfConfigError(this, channel)) return; - instance = this; - Bukkit.getPluginManager().registerEvents(new ExceptionListenerModule(), getPlugin()); - TBMCCoreAPI.RegisterEventsForExceptions(new DebugMessageListener(), getPlugin()); - } - - @Override - protected void disable() { - instance = null; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala new file mode 100644 index 0000000..79f3e3e --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala @@ -0,0 +1,96 @@ +package buttondevteam.discordplugin.exceptions + +import buttondevteam.core.ComponentManager +import buttondevteam.discordplugin.exceptions.ExceptionListenerModule.SendException +import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} +import buttondevteam.lib.architecture.Component +import buttondevteam.lib.{TBMCCoreAPI, TBMCExceptionEvent} +import discord4j.core.`object`.entity.channel.{GuildChannel, MessageChannel} +import discord4j.core.`object`.entity.{Guild, Role} +import org.apache.commons.lang.exception.ExceptionUtils +import org.bukkit.Bukkit +import org.bukkit.event.{EventHandler, Listener} +import reactor.core.publisher.Mono + +import java.util.stream.Collectors + +/** + * Listens for errors from the Chroma plugins and posts them to Discord, ignoring repeating errors so it's not that spammy. + */ +object ExceptionListenerModule { + private def SendException(e: Throwable, sourcemessage: String): Unit = { + if (instance == null) return + try getChannel.flatMap((channel: MessageChannel) => { + def foo(channel: MessageChannel) = { + var coderRole: Mono[Role] = channel match { + case ch: GuildChannel => instance.pingRole(ch.getGuild).get + case _ => Mono.empty + } + coderRole.map((role: Role) => if (TBMCCoreAPI.IsTestServer) new StringBuilder + else new StringBuilder(role.getMention).append("\n")).defaultIfEmpty(new StringBuilder).flatMap((sb: StringBuilder) => { + def foo(sb: StringBuilder) = { + sb.append(sourcemessage).append("\n") + sb.append("```").append("\n") + var stackTrace = util.Arrays.stream(ExceptionUtils.getStackTrace(e).split("\\n")).filter((s: String) => !s.contains("\tat ") || s.contains("\tat buttondevteam.")).collect(Collectors.joining("\n")) + if (sb.length + stackTrace.length >= 1980) stackTrace = stackTrace.substring(0, 1980 - sb.length) + sb.append(stackTrace).append("\n") + sb.append("```") + channel.createMessage(sb.toString) + } + + foo(sb) + }) + } + + foo(channel) + }).subscribe + catch { + case ex: Exception => + ex.printStackTrace() + } + } + + private var instance: ExceptionListenerModule = null + + def getChannel: Mono[MessageChannel] = { + if (instance != null) return instance.channel.get + Mono.empty + } +} + +class ExceptionListenerModule extends Component[DiscordPlugin] with Listener { + final private val lastthrown = new util.ArrayList[Throwable] + final private val lastsourcemsg = new util.ArrayList[String] + + @EventHandler def onException(e: TBMCExceptionEvent): Unit = { + if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(getClass)) return + if (lastthrown.stream.anyMatch((ex: Throwable) => util.Arrays.equals(e.getException.getStackTrace, ex.getStackTrace) && (if (e.getException.getMessage == null) ex.getMessage == null + else e.getException.getMessage == ex.getMessage)) // e.Exception.Message==ex.Message && lastsourcemsg.contains(e.getSourceMessage)) { return } + ExceptionListenerModule + .SendException(e.getException, e.getSourceMessage) + if (lastthrown.size >= 10) lastthrown.remove(0) + if (lastsourcemsg.size >= 10) lastsourcemsg.remove(0) + lastthrown.add(e.getException) + lastsourcemsg.add(e.getSourceMessage) + e.setHandled() + } + + /** + * The channel to post the errors to. + */ + final private val channel = DPUtils.channelData(getConfig, "channel") + + /** + * The role to ping if an error occurs. Set to empty ('') to disable. + */ + private def pingRole(guild: Mono[Guild]) = DPUtils.roleData(getConfig, "pingRole", "Coder", guild) + + override protected def enable(): Unit = { + if (DPUtils.disableIfConfigError(this, channel)) return + ExceptionListenerModule.instance = this + Bukkit.getPluginManager.registerEvents(new ExceptionListenerModule, getPlugin) + TBMCCoreAPI.RegisterEventsForExceptions(new DebugMessageListener, getPlugin) + } + + override protected def disable(): Unit = ExceptionListenerModule.instance = null +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/fun/FunModule.java b/src/main/java/buttondevteam/discordplugin/fun/FunModule.java deleted file mode 100644 index a0997ac..0000000 --- a/src/main/java/buttondevteam/discordplugin/fun/FunModule.java +++ /dev/null @@ -1,162 +0,0 @@ -package buttondevteam.discordplugin.fun; - -import buttondevteam.core.ComponentManager; -import buttondevteam.discordplugin.DPUtils; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.architecture.Component; -import buttondevteam.lib.architecture.ConfigData; -import buttondevteam.lib.architecture.ReadOnlyConfigData; -import com.google.common.collect.Lists; -import discord4j.core.event.domain.PresenceUpdateEvent; -import discord4j.core.object.entity.Guild; -import discord4j.core.object.entity.Member; -import discord4j.core.object.entity.Message; -import discord4j.core.object.entity.Role; -import discord4j.core.object.entity.channel.GuildChannel; -import discord4j.core.object.entity.channel.MessageChannel; -import discord4j.core.object.presence.Status; -import lombok.val; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import reactor.core.publisher.Mono; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Random; -import java.util.concurrent.TimeUnit; -import java.util.stream.IntStream; - -/** - * All kinds of random things. - * The YEEHAW event uses an emoji named :YEEHAW: if available - */ -public class FunModule extends Component implements Listener { - private static final String[] serverReadyStrings = new String[]{"in one week from now", // Ali - "between now and the heat-death of the universe.", // Ghostise - "soon™", "ask again this time next month", // Ghostise - "in about 3 seconds", // Nicolai - "after we finish 8 plugins", // Ali - "tomorrow.", // Ali - "after one tiiiny feature", // Ali - "next commit", // Ali - "after we finish strangling Towny", // Ali - "when we kill every *fucking* bug", // Ali - "once the server stops screaming.", // Ali - "after HL3 comes out", // Ali - "next time you ask", // Ali - "when will *you* be open?" // Ali - }; - - /** - * Questions that the bot will choose a random answer to give to. - */ - private final ConfigData serverReady = getConfig().getData("serverReady", () -> new String[]{ - "when will the server be open", "when will the server be ready", "when will the server be done", - "when will the server be complete", "when will the server be finished", "when's the server ready", - "when's the server open", "vhen vill ze server be open?"}); - - /** - * Answers for a recognized question. Selected randomly. - */ - private final ConfigData> serverReadyAnswers = getConfig().getData("serverReadyAnswers", () -> Lists.newArrayList(serverReadyStrings)); - - private static final Random serverReadyRandom = new Random(); - private static final ArrayList usableServerReadyStrings = new ArrayList<>(0); - - private void createUsableServerReadyStrings() { - IntStream.range(0, serverReadyAnswers.get().size()) - .forEach(i -> FunModule.usableServerReadyStrings.add((short) i)); - } - - @Override - protected void enable() { - registerListener(this); - } - - @Override - protected void disable() { - lastlist = lastlistp = ListC = 0; - } - - private static short lastlist = 0; - private static short lastlistp = 0; - - private static short ListC = 0; - - public static boolean executeMemes(Message message) { - val fm = ComponentManager.getIfEnabled(FunModule.class); - if (fm == null) return false; - String msglowercased = message.getContent().toLowerCase(); - lastlist++; - if (lastlist > 5) { - ListC = 0; - lastlist = 0; - } - if (msglowercased.equals("/list") && Bukkit.getOnlinePlayers().size() == lastlistp && ListC++ > 2) // Lowered already - { - DPUtils.reply(message, Mono.empty(), "stop it. You know the answer.").subscribe(); - lastlist = 0; - lastlistp = (short) Bukkit.getOnlinePlayers().size(); - return true; //Handled - } - lastlistp = (short) Bukkit.getOnlinePlayers().size(); //Didn't handle - if (!TBMCCoreAPI.IsTestServer() - && Arrays.stream(fm.serverReady.get()).anyMatch(msglowercased::contains)) { - int next; - if (usableServerReadyStrings.size() == 0) - fm.createUsableServerReadyStrings(); - next = usableServerReadyStrings.remove(serverReadyRandom.nextInt(usableServerReadyStrings.size())); - DPUtils.reply(message, Mono.empty(), fm.serverReadyAnswers.get().get(next)).subscribe(); - return false; //Still process it as a command/mcchat if needed - } - return false; - } - - @EventHandler - public void onPlayerJoin(PlayerJoinEvent event) { - ListC = 0; - } - - /** - * If all of the people who have this role are online, the bot will post a full house. - */ - private ConfigData> fullHouseDevRole(Mono guild) { - return DPUtils.roleData(getConfig(), "fullHouseDevRole", "Developer", guild); - } - - - /** - * The channel to post the full house to. - */ - private final ReadOnlyConfigData> fullHouseChannel = DPUtils.channelData(getConfig(), "fullHouseChannel"); - - private static long lasttime = 0; - - public static void handleFullHouse(PresenceUpdateEvent event) { - val fm = ComponentManager.getIfEnabled(FunModule.class); - if (fm == null) return; - if (Calendar.getInstance().get(Calendar.DAY_OF_MONTH) % 5 != 0) return; - fm.fullHouseChannel.get() - .filter(ch -> ch instanceof GuildChannel) - .flatMap(channel -> fm.fullHouseDevRole(((GuildChannel) channel).getGuild()).get() - .filter(role -> event.getOld().map(p -> p.getStatus().equals(Status.OFFLINE)).orElse(false)) - .filter(role -> !event.getCurrent().getStatus().equals(Status.OFFLINE)) - .filterWhen(devrole -> event.getMember().flatMap(m -> m.getRoles() - .any(r -> r.getId().asLong() == devrole.getId().asLong()))) - .filterWhen(devrole -> - event.getGuild().flatMapMany(g -> g.getMembers().filter(m -> m.getRoleIds().stream().anyMatch(s -> s.equals(devrole.getId())))) - .flatMap(Member::getPresence).all(pr -> !pr.getStatus().equals(Status.OFFLINE))) - .filter(devrole -> lasttime + 10 < TimeUnit.NANOSECONDS.toHours(System.nanoTime())) //This should stay so it checks this last - .flatMap(devrole -> { - lasttime = TimeUnit.NANOSECONDS.toHours(System.nanoTime()); - return channel.createMessage(mcs -> mcs.setContent("Full house!").setEmbed(ecs -> - ecs.setImage( - "https://cdn.discordapp.com/attachments/249295547263877121/249687682618359808/poker-hand-full-house-aces-kings-playing-cards-15553791.png") - )); - })).subscribe(); - } -} diff --git a/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala b/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala new file mode 100644 index 0000000..c5f20d1 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala @@ -0,0 +1,125 @@ +package buttondevteam.discordplugin.fun + +import buttondevteam.core.ComponentManager +import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} +import buttondevteam.lib.TBMCCoreAPI +import buttondevteam.lib.architecture.{Component, ConfigData} +import com.google.common.collect.Lists +import discord4j.common.util.Snowflake +import discord4j.core.`object`.entity.channel.{GuildChannel, MessageChannel} +import discord4j.core.`object`.entity.{Guild, Member, Message, Role} +import discord4j.core.`object`.presence.{Presence, Status} +import discord4j.core.event.domain.PresenceUpdateEvent +import discord4j.core.spec.{EmbedCreateSpec, MessageCreateSpec} +import org.bukkit.Bukkit +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.{EventHandler, Listener} +import reactor.core.publisher.Mono + +import java.util +import java.util.Calendar +import java.util.concurrent.TimeUnit +import java.util.stream.IntStream +import scala.util.Random + +/** + * All kinds of random things. + * The YEEHAW event uses an emoji named :YEEHAW: if available + */ +object FunModule { + private val serverReadyStrings = Array[String]("in one week from now", // Ali + "between now and the heat-death of the universe.", // Ghostise + "soon™", "ask again this time next month", "in about 3 seconds", // Nicolai + "after we finish 8 plugins", "tomorrow.", "after one tiiiny feature", + "next commit", "after we finish strangling Towny", "when we kill every *fucking* bug", + "once the server stops screaming.", "after HL3 comes out", "next time you ask", + "when will *you* be open?") // Ali + private val serverReadyRandom = new Random + private val usableServerReadyStrings = new java.util.ArrayList[Short](0) + private var lastlist = 0 + private var lastlistp = 0 + private var ListC = 0 + + def executeMemes(message: Message): Boolean = { + val fm = ComponentManager.getIfEnabled(classOf[FunModule]) + if (fm == null) return false + val msglowercased = message.getContent.toLowerCase + lastlist += 1 + if (lastlist > 5) { + ListC = 0 + lastlist = 0 + } + if (msglowercased == "/list" && Bukkit.getOnlinePlayers.size == lastlistp && { + ListC += 1; + ListC - 1 + } > 2) { // Lowered already + DPUtils.reply(message, Mono.empty, "stop it. You know the answer.").subscribe + lastlist = 0 + lastlistp = Bukkit.getOnlinePlayers.size.toShort + return true //Handled + } + lastlistp = Bukkit.getOnlinePlayers.size.toShort //Didn't handle + if (!TBMCCoreAPI.IsTestServer && util.Arrays.stream(fm.serverReady.get.anyMatch(msglowercased.contains)) { + var next = 0 + if (usableServerReadyStrings.size == 0) fm.createUsableServerReadyStrings() + next = usableServerReadyStrings.remove(serverReadyRandom.nextInt(usableServerReadyStrings.size)) + DPUtils.reply(message, Mono.empty, fm.serverReadyAnswers.get.get(next)).subscribe + return false //Still process it as a command/mcchat if needed + } + false + } + + private var lasttime = 0 + + def handleFullHouse(event: PresenceUpdateEvent): Unit = { + val fm = ComponentManager.getIfEnabled(classOf[FunModule]) + if (fm == null) return + if (Calendar.getInstance.get(Calendar.DAY_OF_MONTH) % 5 != 0) return + fm.fullHouseChannel.get.filter((ch: MessageChannel) => ch.isInstanceOf[GuildChannel]) + .flatMap((channel: MessageChannel) => fm.fullHouseDevRole(channel.asInstanceOf[GuildChannel].getGuild).get.filter((role: Role) => event.getOld.map((p: Presence) => p.getStatus == Status.OFFLINE).orElse(false)).filter((role: Role) => !(event.getCurrent.getStatus == Status.OFFLINE)).filterWhen((devrole: Role) => event.getMember.flatMap((m: Member) => m.getRoles.any((r: Role) => r.getId.asLong == devrole.getId.asLong))).filterWhen((devrole: Role) => event.getGuild.flatMapMany((g: Guild) => g.getMembers.filter((m: Member) => m.getRoleIds.stream.anyMatch((s: Snowflake) => s == devrole.getId))).flatMap(Member.getPresence).all((pr: Presence) => !(pr.getStatus == Status.OFFLINE))).filter((devrole: Role) => lasttime + 10 < TimeUnit.NANOSECONDS.toHours(System.nanoTime)).flatMap //This should stay so it checks this last + ((devrole: Role) => { + def foo(devrole: Role) = { + lasttime = TimeUnit.NANOSECONDS.toHours(System.nanoTime) + channel.createMessage((mcs: MessageCreateSpec) => mcs.setContent("Full house!").setEmbed((ecs: EmbedCreateSpec) => ecs.setImage("https://cdn.discordapp.com/attachments/249295547263877121/249687682618359808/poker-hand-full-house-aces-kings-playing-cards-15553791.png"))) + } + + foo(devrole) + })).subscribe + } +} + +class FunModule extends Component[DiscordPlugin] with Listener { + /** + * Questions that the bot will choose a random answer to give to. + */ + final private val serverReady: ConfigData[Array[String]] = + getConfig.getData("serverReady", () => Array[String]( + "when will the server be open", "when will the server be ready", + "when will the server be done", "when will the server be complete", + "when will the server be finished", "when's the server ready", + "when's the server open", "vhen vill ze server be open?")) + /** + * Answers for a recognized question. Selected randomly. + */ + final private val serverReadyAnswers: ConfigData[util.ArrayList[String]] = + getConfig.getData("serverReadyAnswers", () => Lists.newArrayList(FunModule.serverReadyStrings)) + + private def createUsableServerReadyStrings(): Unit = + IntStream.range(0, serverReadyAnswers.get.size).forEach((i: Int) => FunModule.usableServerReadyStrings.add(i.toShort)) + + override protected def enable(): Unit = registerListener(this) + + override protected def disable(): Unit = FunModule.lastlist = FunModule.lastlistp = FunModule.ListC = 0 + + @EventHandler def onPlayerJoin(event: PlayerJoinEvent): Unit = FunModule.ListC = 0 + + /** + * If all of the people who have this role are online, the bot will post a full house. + */ + private def fullHouseDevRole(guild: Mono[Guild]) = DPUtils.roleData(getConfig, "fullHouseDevRole", "Developer", guild) + + /** + * The channel to post the full house to. + */ + final private val fullHouseChannel = DPUtils.channelData(getConfig, "fullHouseChannel") +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala index a810307..8f41efe 100644 --- a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala +++ b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala @@ -1,12 +1,12 @@ package buttondevteam.discordplugin.listeners -import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} import buttondevteam.discordplugin.commands.Command2DCSender +import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} import buttondevteam.lib.TBMCCoreAPI import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} import discord4j.core.`object`.entity.{Member, Message, Role, User} -import reactor.core.publisher.Mono +import reactor.core.publisher.{Flux, Mono} import java.util.concurrent.atomic.AtomicBoolean @@ -39,7 +39,7 @@ object CommandListener { val gotmention = new AtomicBoolean timings.printElapsed("Before self") tmp.flatMapMany((x: Boolean) => DiscordPlugin.dc.getSelf.flatMap((self: User) => self.asMember(DiscordPlugin.mainServer.getId)).flatMapMany((self: Member) => { - def foo(self: Member) = { + def foo(self: Member): Flux[String] = { timings.printElapsed("D") gotmention.set(checkanddeletemention(cmdwithargs, self.getMention, message)) gotmention.set(checkanddeletemention(cmdwithargs, self.getNicknameMention, message) || gotmention.get) @@ -49,14 +49,14 @@ object CommandListener { foo(self) }).map((mentionRole: String) => { - def foo(mentionRole: String) = { + def foo(mentionRole: String): Boolean = { timings.printElapsed("E") gotmention.set(checkanddeletemention(cmdwithargs, mentionRole, message) || gotmention.get) // Delete all mentions !mentionedonly || gotmention.get //Stops here if false } foo(mentionRole) - }).switchIfEmpty(Mono.fromSupplier(() => !mentionedonly || gotmention.get))).filter((b: Boolean) => b).last(false).filter((b: Boolean) => b).doOnNext((b: Boolean) => channel.`type`.subscribe).flatMap((b: Boolean) => { + }: Boolean)[Mono[Boolean]].switchIfEmpty(Mono.fromSupplier[Boolean](() => !mentionedonly || gotmention.get)))[Mono[Boolean]].filter((b: Boolean) => b).last(false).filter((b: Boolean) => b).doOnNext((b: Boolean) => channel.`type`.subscribe).flatMap((b: Boolean) => { def foo(): Mono[Boolean] = { val cmdwithargsString = cmdwithargs.toString try { diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.java b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.java deleted file mode 100755 index 57985aa..0000000 --- a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.java +++ /dev/null @@ -1,88 +0,0 @@ -package buttondevteam.discordplugin.listeners; - -import buttondevteam.discordplugin.DPUtils; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.fun.FunModule; -import buttondevteam.discordplugin.mcchat.MinecraftChatModule; -import buttondevteam.discordplugin.role.GameRoleModule; -import buttondevteam.discordplugin.util.Timings; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.architecture.Component; -import discord4j.core.event.EventDispatcher; -import discord4j.core.event.domain.PresenceUpdateEvent; -import discord4j.core.event.domain.message.MessageCreateEvent; -import discord4j.core.event.domain.role.RoleCreateEvent; -import discord4j.core.event.domain.role.RoleDeleteEvent; -import discord4j.core.event.domain.role.RoleUpdateEvent; -import discord4j.core.object.entity.channel.PrivateChannel; -import lombok.val; -import reactor.core.publisher.Mono; - -public class CommonListeners { - - public static final Timings timings = new Timings(); - - /* - MentionEvent: - - CommandListener (starts with mention, only 'channelcon' and not in #bot) - - MessageReceivedEvent: - - v CommandListener (starts with mention, in #bot or a connected chat) - - Minecraft chat (is enabled in the channel and message isn't [/]mcchat) - - CommandListener (with the correct prefix in #bot, or in private) - */ - public static void register(EventDispatcher dispatcher) { - dispatcher.on(MessageCreateEvent.class).flatMap(event -> { - timings.printElapsed("Message received"); - val def = Mono.empty(); - if (DiscordPlugin.SafeMode) - return def; - val author = event.getMessage().getAuthor(); - if (!author.isPresent() || author.get().isBot()) - return def; - if (FunModule.executeMemes(event.getMessage())) - return def; - val commandChannel = DiscordPlugin.plugin.commandChannel.get(); - return event.getMessage().getChannel().map(mch -> - (commandChannel != null && mch.getId().asLong() == commandChannel.asLong()) //If mentioned, that's higher than chat - || mch instanceof PrivateChannel - || event.getMessage().getContent().contains("channelcon")) //Only 'channelcon' is allowed in other channels - .flatMap(shouldRun -> { //Only continue if this doesn't handle the event - if (!shouldRun) - return Mono.just(true); //The condition is only for the first command execution, not mcchat - timings.printElapsed("Run command 1"); - return CommandListener.runCommand(event.getMessage(), commandChannel, true); //#bot is handled here - }).filterWhen(ch -> { - timings.printElapsed("mcchat"); - val mcchat = Component.getComponents().get(MinecraftChatModule.class); - if (mcchat != null && mcchat.isEnabled()) //ComponentManager.isEnabled() searches the component again - return ((MinecraftChatModule) mcchat).getListener().handleDiscord(event); //Also runs Discord commands in chat channels - return Mono.just(true); //Wasn't handled, continue - }).filterWhen(ch -> { - timings.printElapsed("Run command 2"); - return CommandListener.runCommand(event.getMessage(), commandChannel, false); - }); - }).onErrorContinue((err, obj) -> TBMCCoreAPI.SendException("An error occured while handling a message!", err, DiscordPlugin.plugin)) - .subscribe(); - dispatcher.on(PresenceUpdateEvent.class).subscribe(event -> { - if (DiscordPlugin.SafeMode) - return; - FunModule.handleFullHouse(event); - }); - dispatcher.on(RoleCreateEvent.class).subscribe(GameRoleModule::handleRoleEvent); - dispatcher.on(RoleDeleteEvent.class).subscribe(GameRoleModule::handleRoleEvent); - dispatcher.on(RoleUpdateEvent.class).subscribe(GameRoleModule::handleRoleEvent); - - } - - 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/CommonListeners.scala b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala new file mode 100644 index 0000000..afb28f2 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala @@ -0,0 +1,92 @@ +package buttondevteam.discordplugin.listeners + +import buttondevteam.discordplugin.fun.FunModule +import buttondevteam.discordplugin.mcchat.MinecraftChatModule +import buttondevteam.discordplugin.role.GameRoleModule +import buttondevteam.discordplugin.util.Timings +import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} +import buttondevteam.lib.TBMCCoreAPI +import buttondevteam.lib.architecture.Component +import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} +import discord4j.core.event.EventDispatcher +import discord4j.core.event.domain.PresenceUpdateEvent +import discord4j.core.event.domain.message.MessageCreateEvent +import discord4j.core.event.domain.role.{RoleCreateEvent, RoleDeleteEvent, RoleUpdateEvent} +import reactor.core.publisher.Mono + +object CommonListeners { + val timings = new Timings + + /* + MentionEvent: + - CommandListener (starts with mention, only 'channelcon' and not in #bot) + + MessageReceivedEvent: + - v CommandListener (starts with mention, in #bot or a connected chat) + - Minecraft chat (is enabled in the channel and message isn't [/]mcchat) + - CommandListener (with the correct prefix in #bot, or in private) + */ + def register(dispatcher: EventDispatcher) = { + dispatcher.on(classOf[MessageCreateEvent]).flatMap((event: MessageCreateEvent) => { + def foo(event: MessageCreateEvent) = { + timings.printElapsed("Message received") + val `def` = Mono.empty + if (DiscordPlugin.SafeMode) return `def` + val author = event.getMessage.getAuthor + if (!author.isPresent || author.get.isBot) return `def` + if (FunModule.executeMemes(event.getMessage)) return `def` + val commandChannel = DiscordPlugin.plugin.commandChannel.get + event.getMessage.getChannel.map((mch: MessageChannel) => (commandChannel != null && mch.getId.asLong == commandChannel.asLong) //If mentioned, that's higher than chat + || mch.isInstanceOf[PrivateChannel] || event.getMessage.getContent.contains("channelcon")).flatMap( + (shouldRun: Boolean) => { //Only 'channelcon' is allowed in other channels + def foo(shouldRun: Boolean): Mono[Boolean] = { //Only continue if this doesn't handle the event + if (!shouldRun) return Mono.just(true) //The condition is only for the first command execution, not mcchat + timings.printElapsed("Run command 1") + CommandListener.runCommand(event.getMessage, commandChannel, mentionedonly = true) //#bot is handled here + } + + foo(shouldRun) + }).filterWhen((ch: Any) => { + def foo(): Mono[Boolean] = { + timings.printElapsed("mcchat") + val mcchat = Component.getComponents.get(classOf[MinecraftChatModule]) + if (mcchat != null && mcchat.isEnabled) { //ComponentManager.isEnabled() searches the component again + return mcchat.asInstanceOf[MinecraftChatModule].getListener.handleDiscord(event) //Also runs Discord commands in chat channels + } + Mono.just(true) //Wasn't handled, continue + } + + foo() + }).filterWhen((ch: Any) => { + def foo(ch: Any) = { + timings.printElapsed("Run command 2") + CommandListener.runCommand(event.getMessage, commandChannel, mentionedonly = false) + } + + foo(ch) + }) + } + + foo(event) + }).onErrorContinue((err: Throwable, obj: Any) => TBMCCoreAPI.SendException("An error occured while handling a message!", err, DiscordPlugin.plugin)).subscribe + dispatcher.on(classOf[PresenceUpdateEvent]).subscribe((event: PresenceUpdateEvent) => { + def foo(event: PresenceUpdateEvent) = { + if (DiscordPlugin.SafeMode) return + FunModule.handleFullHouse(event) + } + + foo(event) + }) + dispatcher.on(classOf[RoleCreateEvent]).subscribe(GameRoleModule.handleRoleEvent _) + dispatcher.on(classOf[RoleDeleteEvent]).subscribe(GameRoleModule.handleRoleEvent _) + dispatcher.on(classOf[RoleUpdateEvent]).subscribe(GameRoleModule.handleRoleEvent _) + } + + private var debug = false + + def debug(debug: String): Unit = if (CommonListeners.debug) { //Debug + DPUtils.getLogger.info(debug) + } + + def debug(): Unit = debug = !debug +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java b/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java deleted file mode 100755 index febe7ea..0000000 --- a/src/main/java/buttondevteam/discordplugin/listeners/MCListener.java +++ /dev/null @@ -1,68 +0,0 @@ -package buttondevteam.discordplugin.listeners; - -import buttondevteam.discordplugin.DiscordPlayer; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.commands.ConnectCommand; -import buttondevteam.discordplugin.mcchat.MinecraftChatModule; -import buttondevteam.discordplugin.util.DPState; -import buttondevteam.lib.ScheduledServerRestartEvent; -import buttondevteam.lib.player.TBMCPlayerGetInfoEvent; -import discord4j.common.util.Snowflake; -import discord4j.core.object.entity.Member; -import discord4j.core.object.entity.User; -import lombok.val; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import reactor.core.publisher.Mono; - -public class MCListener implements Listener { - @EventHandler - public void onPlayerJoin(PlayerJoinEvent e) { - if (ConnectCommand.WaitingToConnect.containsKey(e.getPlayer().getName())) { - @SuppressWarnings("ConstantConditions") User user = DiscordPlugin.dc - .getUserById(Snowflake.of(ConnectCommand.WaitingToConnect.get(e.getPlayer().getName()))).block(); - if (user == null) return; - e.getPlayer().sendMessage("§bTo connect with the Discord account @" + user.getUsername() + "#" + user.getDiscriminator() - + " do /discord accept"); - e.getPlayer().sendMessage("§bIf it wasn't you, do /discord decline"); - } - } - - @EventHandler - public void onGetInfo(TBMCPlayerGetInfoEvent e) { - if (DiscordPlugin.SafeMode) - return; - DiscordPlayer dp = e.getPlayer().getAs(DiscordPlayer.class); - if (dp == null || dp.getDiscordID() == null || dp.getDiscordID().equals("")) - return; - val userOpt = DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID())).onErrorResume(t -> Mono.empty()).blockOptional(); - if (!userOpt.isPresent()) return; - User user = userOpt.get(); - e.addInfo("Discord tag: " + user.getUsername() + "#" + user.getDiscriminator()); - val memberOpt = user.asMember(DiscordPlugin.mainServer.getId()).onErrorResume(t -> Mono.empty()).blockOptional(); - if (!memberOpt.isPresent()) return; - Member member = memberOpt.get(); - val prOpt = member.getPresence().blockOptional(); - if (!prOpt.isPresent()) return; - val pr = prOpt.get(); - e.addInfo(pr.getStatus().toString()); - if (pr.getActivity().isPresent()) { - val activity = pr.getActivity().get(); - e.addInfo(activity.getType() + ": " + activity.getName()); - } - } - - /*@EventHandler - public void onCommandPreprocess(TBMCCommandPreprocessEvent e) { - if (e.getMessage().equalsIgnoreCase("/stop")) - MinecraftChatModule.state = DPState.STOPPING_SERVER; - else if (e.getMessage().equalsIgnoreCase("/restart")) - MinecraftChatModule.state = DPState.RESTARTING_SERVER; - }*/ - - @EventHandler //We don't really need this with the logger stuff but hey - public void onScheduledRestart(ScheduledServerRestartEvent e) { - MinecraftChatModule.state = DPState.RESTARTING_SERVER; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/listeners/MCListener.scala b/src/main/java/buttondevteam/discordplugin/listeners/MCListener.scala new file mode 100644 index 0000000..d464d84 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/listeners/MCListener.scala @@ -0,0 +1,54 @@ +package buttondevteam.discordplugin.listeners + +import buttondevteam.discordplugin.commands.ConnectCommand +import buttondevteam.discordplugin.mcchat.MinecraftChatModule +import buttondevteam.discordplugin.util.DPState +import buttondevteam.discordplugin.{DiscordPlayer, DiscordPlugin} +import buttondevteam.lib.ScheduledServerRestartEvent +import buttondevteam.lib.player.TBMCPlayerGetInfoEvent +import discord4j.common.util.Snowflake +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.{EventHandler, Listener} +import reactor.core.publisher.Mono + +class MCListener extends Listener { + @EventHandler def onPlayerJoin(e: PlayerJoinEvent): Unit = + if (ConnectCommand.WaitingToConnect.containsKey(e.getPlayer.getName)) { + @SuppressWarnings(Array("ConstantConditions")) val user = DiscordPlugin.dc.getUserById(Snowflake.of(ConnectCommand.WaitingToConnect.get(e.getPlayer.getName))).block + if (user == null) return + e.getPlayer.sendMessage("§bTo connect with the Discord account @" + user.getUsername + "#" + user.getDiscriminator + " do /discord accept") + e.getPlayer.sendMessage("§bIf it wasn't you, do /discord decline") + } + + @EventHandler def onGetInfo(e: TBMCPlayerGetInfoEvent): Unit = { + if (DiscordPlugin.SafeMode) return + val dp = e.getPlayer.getAs(classOf[DiscordPlayer]) + if (dp == null || dp.getDiscordID == null || dp.getDiscordID == "") return + val userOpt = DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID)).onErrorResume(_ => Mono.empty).blockOptional + if (!userOpt.isPresent) return + val user = userOpt.get + e.addInfo("Discord tag: " + user.getUsername + "#" + user.getDiscriminator) + val memberOpt = user.asMember(DiscordPlugin.mainServer.getId).onErrorResume((t: Throwable) => Mono.empty).blockOptional + if (!memberOpt.isPresent) return + val member = memberOpt.get + val prOpt = member.getPresence.blockOptional + if (!prOpt.isPresent) return + val pr = prOpt.get + e.addInfo(pr.getStatus.toString) + if (pr.getActivity.isPresent) { + val activity = pr.getActivity.get + e.addInfo(activity.getType + ": " + activity.getName) + } + } + + /*@EventHandler + public void onCommandPreprocess(TBMCCommandPreprocessEvent e) { + if (e.getMessage().equalsIgnoreCase("/stop")) + MinecraftChatModule.state = DPState.STOPPING_SERVER; + else if (e.getMessage().equalsIgnoreCase("/restart")) + MinecraftChatModule.state = DPState.RESTARTING_SERVER; + }*/ @EventHandler //We don't really need this with the logger stuff but hey + def onScheduledRestart(e: ScheduledServerRestartEvent): Unit = + MinecraftChatModule.state = DPState.RESTARTING_SERVER + +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java index 190c10e..b7cc116 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java @@ -3,8 +3,6 @@ package buttondevteam.discordplugin.mcchat; import buttondevteam.core.component.channel.Channel; import buttondevteam.core.component.channel.ChatRoom; import buttondevteam.discordplugin.*; -import buttondevteam.discordplugin.commands.Command2DCSender; -import buttondevteam.discordplugin.commands.ICommand2DC; import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.CommandClass; diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java index df7f6ad..0f60db5 100755 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java @@ -3,8 +3,6 @@ package buttondevteam.discordplugin.mcchat; import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DiscordPlayer; import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.commands.Command2DCSender; -import buttondevteam.discordplugin.commands.ICommand2DC; import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.CommandClass; import discord4j.core.object.entity.channel.PrivateChannel; diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java index 007b90c..3c257ad 100755 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java @@ -3,7 +3,6 @@ package buttondevteam.discordplugin.mcchat; import buttondevteam.core.ComponentManager; import buttondevteam.discordplugin.*; import buttondevteam.discordplugin.listeners.CommandListener; -import buttondevteam.discordplugin.listeners.CommonListeners; import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; import buttondevteam.discordplugin.playerfaker.VanillaCommandListener14; import buttondevteam.discordplugin.playerfaker.VanillaCommandListener15; diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java index b90a328..4319d3a 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java @@ -3,7 +3,6 @@ package buttondevteam.discordplugin.mcchat; import buttondevteam.core.ComponentManager; import buttondevteam.core.MainPlugin; import buttondevteam.discordplugin.*; -import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCSystemChatEvent; import com.google.common.collect.Sets; diff --git a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java index 35e78a7..91eb7ee 100644 --- a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java @@ -4,8 +4,6 @@ import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DiscordPlayer; import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordSenderBase; -import buttondevteam.discordplugin.commands.ConnectCommand; -import buttondevteam.discordplugin.commands.VersionCommand; import buttondevteam.discordplugin.mcchat.MCChatUtils; import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.discordplugin.util.DPState; diff --git a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java index 07fd0e2..aac0aae 100755 --- a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java +++ b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java @@ -1,8 +1,6 @@ package buttondevteam.discordplugin.role; import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.commands.Command2DCSender; -import buttondevteam.discordplugin.commands.ICommand2DC; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.CommandClass; diff --git a/src/main/java/buttondevteam/discordplugin/util/Timings.java b/src/main/java/buttondevteam/discordplugin/util/Timings.java index 12c12f2..af91b0f 100644 --- a/src/main/java/buttondevteam/discordplugin/util/Timings.java +++ b/src/main/java/buttondevteam/discordplugin/util/Timings.java @@ -1,7 +1,5 @@ package buttondevteam.discordplugin.util; -import buttondevteam.discordplugin.listeners.CommonListeners; - public class Timings { private long start; From 9f47509dcb6273b3a0178f334aa4ae10752e6577 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Fri, 26 Feb 2021 02:27:59 +0100 Subject: [PATCH 02/23] Converted mcchat classes to Scala --- .../discordplugin/BukkitLogWatcher.java | 1 - .../discordplugin/DiscordConnectedPlayer.java | 1 - .../discordplugin/DiscordPlayer.java | 1 - .../discordplugin/DiscordPlayerSender.java | 1 - .../mcchat/ChannelconCommand.java | 174 ------- .../mcchat/ChannelconCommand.scala | 183 +++++++ .../discordplugin/mcchat/MCChatCommand.java | 45 -- .../discordplugin/mcchat/MCChatCommand.scala | 37 ++ .../discordplugin/mcchat/MCChatCustom.java | 78 --- .../discordplugin/mcchat/MCChatCustom.scala | 66 +++ .../discordplugin/mcchat/MCChatListener.java | 416 --------------- .../discordplugin/mcchat/MCChatListener.scala | 477 ++++++++++++++++++ .../discordplugin/mcchat/MCChatPrivate.java | 76 --- .../discordplugin/mcchat/MCChatPrivate.scala | 76 +++ .../discordplugin/mcchat/MCChatUtils.java | 409 --------------- .../discordplugin/mcchat/MCChatUtils.scala | 369 ++++++++++++++ .../discordplugin/mcchat/MCListener.java | 187 ------- .../discordplugin/mcchat/MCListener.scala | 143 ++++++ .../mcchat/MinecraftChatModule.java | 261 ---------- .../mcchat/MinecraftChatModule.scala | 227 +++++++++ .../mccommands/DiscordMCCommand.java | 2 - .../playerfaker/ServerWatcher.java | 1 - .../playerfaker/VCMDWrapper.java | 1 - .../playerfaker/perm/LPInjector.java | 1 - 24 files changed, 1578 insertions(+), 1655 deletions(-) delete mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java create mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java create mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java create mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java create mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java create mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java create mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java create mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java create mode 100644 src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala diff --git a/src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.java b/src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.java index cc52f5e..c51471b 100644 --- a/src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.java +++ b/src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.java @@ -1,6 +1,5 @@ package buttondevteam.discordplugin; -import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.discordplugin.util.DPState; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.Filter; diff --git a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java index fab1c02..714d347 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java @@ -1,6 +1,5 @@ package buttondevteam.discordplugin; -import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.discordplugin.playerfaker.DiscordInventory; import buttondevteam.discordplugin.playerfaker.VCMDWrapper; import discord4j.core.object.entity.User; diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayer.java b/src/main/java/buttondevteam/discordplugin/DiscordPlayer.java index b6b8d99..40ce273 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlayer.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlayer.java @@ -1,6 +1,5 @@ package buttondevteam.discordplugin; -import buttondevteam.discordplugin.mcchat.MCChatPrivate; import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.UserClass; import discord4j.core.object.entity.User; diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java index ac3ae1c..b9e7f86 100755 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java @@ -1,6 +1,5 @@ package buttondevteam.discordplugin; -import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.discordplugin.playerfaker.VCMDWrapper; import discord4j.core.object.entity.User; import discord4j.core.object.entity.channel.MessageChannel; diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java deleted file mode 100644 index b7cc116..0000000 --- a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.java +++ /dev/null @@ -1,174 +0,0 @@ -package buttondevteam.discordplugin.mcchat; - -import buttondevteam.core.component.channel.Channel; -import buttondevteam.core.component.channel.ChatRoom; -import buttondevteam.discordplugin.*; -import buttondevteam.lib.TBMCSystemChatEvent; -import buttondevteam.lib.chat.Command2; -import buttondevteam.lib.chat.CommandClass; -import buttondevteam.lib.player.TBMCPlayer; -import discord4j.core.object.entity.Message; -import discord4j.core.object.entity.User; -import discord4j.core.object.entity.channel.GuildChannel; -import discord4j.core.object.entity.channel.MessageChannel; -import discord4j.rest.util.Permission; -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.bukkit.Bukkit; -import reactor.core.publisher.Mono; - -import javax.annotation.Nullable; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Objects; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -@SuppressWarnings("SimplifyOptionalCallChains") //Java 11 -@CommandClass(helpText = {"Channel connect", // - "This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).", // - "You need to have access to the MC channel and have manage permissions on the Discord channel.", // - "You also need to have your Minecraft account connected. In #bot use /connect .", // - "Call this command from the channel you want to use.", // - "Usage: @Bot channelcon ", // - "Use the ID (command) of the channel, for example `g` for the global chat.", // - "To remove a connection use @ChromaBot channelcon remove in the channel.", // - "Mentioning the bot is needed in this case because the / prefix only works in #bot.", // - "Invite link: " // -}) -@RequiredArgsConstructor -public class ChannelconCommand extends ICommand2DC { - private final MinecraftChatModule module; - - @Command2.Subcommand - public boolean remove(Command2DCSender sender) { - val message = sender.getMessage(); - if (checkPerms(message, null)) return true; - if (MCChatCustom.removeCustomChat(message.getChannelId())) - DPUtils.reply(message, Mono.empty(), "channel connection removed.").subscribe(); - else - DPUtils.reply(message, Mono.empty(), "this channel isn't connected.").subscribe(); - return true; - } - - @Command2.Subcommand - public boolean toggle(Command2DCSender sender, @Command2.OptionalArg String toggle) { - val message = sender.getMessage(); - if (checkPerms(message, null)) return true; - val cc = MCChatCustom.getCustomChat(message.getChannelId()); - if (cc == null) - return respond(sender, "this channel isn't connected."); - Supplier togglesString = () -> Arrays.stream(ChannelconBroadcast.values()).map(t -> t.toString().toLowerCase() + ": " + ((cc.toggles & t.flag) == 0 ? "disabled" : "enabled")).collect(Collectors.joining("\n")) - + "\n\n" + TBMCSystemChatEvent.BroadcastTarget.stream().map(target -> target.getName() + ": " + (cc.brtoggles.contains(target) ? "enabled" : "disabled")).collect(Collectors.joining("\n")); - if (toggle == null) { - DPUtils.reply(message, Mono.empty(), "toggles:\n" + togglesString.get()).subscribe(); - return true; - } - String arg = toggle.toUpperCase(); - val b = Arrays.stream(ChannelconBroadcast.values()).filter(t -> t.toString().equals(arg)).findAny(); - if (!b.isPresent()) { - val bt = TBMCSystemChatEvent.BroadcastTarget.get(arg); - if (bt == null) { - DPUtils.reply(message, Mono.empty(), "cannot find toggle. Toggles:\n" + togglesString.get()).subscribe(); - return true; - } - final boolean add; - if (add = !cc.brtoggles.contains(bt)) - cc.brtoggles.add(bt); - else - cc.brtoggles.remove(bt); - return respond(sender, "'" + bt.getName() + "' " + (add ? "en" : "dis") + "abled"); - } - //A B | F - //------- A: original - B: mask - F: new - //0 0 | 0 - //0 1 | 1 - //1 0 | 1 - //1 1 | 0 - // XOR - cc.toggles ^= b.get().flag; - DPUtils.reply(message, Mono.empty(), "'" + b.get().toString().toLowerCase() + "' " + ((cc.toggles & b.get().flag) == 0 ? "disabled" : "enabled")).subscribe(); - return true; - } - - @Command2.Subcommand - public boolean def(Command2DCSender sender, String channelID) { - val message = sender.getMessage(); - if (!module.allowCustomChat.get()) { - sender.sendMessage("channel connection is not allowed on this Minecraft server."); - return true; - } - val channel = message.getChannel().block(); - if (checkPerms(message, channel)) return true; - if (MCChatCustom.hasCustomChat(message.getChannelId())) - return respond(sender, "this channel is already connected to a Minecraft channel. Use `@ChromaBot channelcon remove` to remove it."); - val chan = Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase(channelID) || (Arrays.stream(ch.IDs.get()).anyMatch(cid -> cid.equalsIgnoreCase(channelID)))).findAny(); - if (!chan.isPresent()) { //TODO: Red embed that disappears over time (kinda like the highlight messages in OW) - DPUtils.reply(message, channel, "MC channel with ID '" + channelID + "' not found! The ID is the command for it without the /.").subscribe(); - return true; - } - if (!message.getAuthor().isPresent()) return true; - val author = message.getAuthor().get(); - val dp = DiscordPlayer.getUser(author.getId().asString(), DiscordPlayer.class); - val chp = dp.getAs(TBMCPlayer.class); - if (chp == null) { - DPUtils.reply(message, channel, "you need to connect your Minecraft account. On the main server in " + DPUtils.botmention() + " do " + DiscordPlugin.getPrefix() + "connect ").subscribe(); - return true; - } - DiscordConnectedPlayer dcp = DiscordConnectedPlayer.create(message.getAuthor().get(), channel, chp.getUUID(), Bukkit.getOfflinePlayer(chp.getUUID()).getName(), module); - //Using a fake player with no login/logout, should be fine for this event - String groupid = chan.get().getGroupID(dcp); - if (groupid == null && !(chan.get() instanceof ChatRoom)) { //ChatRooms don't allow it unless the user joins, which happens later - DPUtils.reply(message, channel, "sorry, you cannot use that Minecraft channel.").subscribe(); - return true; - } - if (chan.get() instanceof ChatRoom) { //ChatRooms don't work well - DPUtils.reply(message, channel, "chat rooms are not supported yet.").subscribe(); - return true; - } - /*if (MCChatListener.getCustomChats().stream().anyMatch(cc -> cc.groupID.equals(groupid) && cc.mcchannel.ID.equals(chan.get().ID))) { - DPUtils.reply(message, null, "sorry, this MC chat is already connected to a different channel, multiple channels are not supported atm."); - return true; - }*/ //TODO: "Channel admins" that can connect channels? - MCChatCustom.addCustomChat(channel, groupid, chan.get(), author, dcp, 0, new HashSet<>()); - if (chan.get() instanceof ChatRoom) - DPUtils.reply(message, channel, "alright, connection made to the room!").subscribe(); - else - DPUtils.reply(message, channel, "alright, connection made to group `" + groupid + "`!").subscribe(); - return true; - } - - @SuppressWarnings("ConstantConditions") - private boolean checkPerms(Message message, @Nullable MessageChannel channel) { - if (channel == null) - channel = message.getChannel().block(); - if (!(channel instanceof GuildChannel)) { - DPUtils.reply(message, channel, "you can only use this command in a server!").subscribe(); - return true; - } - //noinspection OptionalGetWithoutIsPresent - var perms = ((GuildChannel) channel).getEffectivePermissions(message.getAuthor().map(User::getId).get()).block(); - if (!perms.contains(Permission.ADMINISTRATOR) && !perms.contains(Permission.MANAGE_CHANNELS)) { - DPUtils.reply(message, channel, "you need to have manage permissions for this channel!").subscribe(); - return true; - } - return false; - } - - @Override - public String[] getHelpText(Method method, Command2.Subcommand ann) { - return new String[]{ // - "Channel connect", // - "This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).", // - "You need to have access to the MC channel and have manage permissions on the Discord channel.", // - "You also need to have your Minecraft account connected. In " + DPUtils.botmention() + " use " + DiscordPlugin.getPrefix() + "connect .", // - "Call this command from the channel you want to use.", // - "Usage: " + Objects.requireNonNull(DiscordPlugin.dc.getSelf().block()).getMention() + " channelcon ", // - "Use the ID (command) of the channel, for example `g` for the global chat.", // - "To remove a connection use @ChromaBot channelcon remove in the channel.", // - "Mentioning the bot is needed in this case because the " + DiscordPlugin.getPrefix() + " prefix only works in " + DPUtils.botmention() + ".", // - "Invite link: " - }; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala new file mode 100644 index 0000000..cb1df83 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala @@ -0,0 +1,183 @@ +package buttondevteam.discordplugin.mcchat + +import buttondevteam.core.component.channel.Channel +import buttondevteam.core.component.channel.ChatRoom +import buttondevteam.discordplugin._ +import buttondevteam.discordplugin.commands.{Command2DCSender, ICommand2DC} +import buttondevteam.lib.TBMCSystemChatEvent +import buttondevteam.lib.chat.Command2 +import buttondevteam.lib.chat.CommandClass +import buttondevteam.lib.player.TBMCPlayer +import discord4j.core.`object`.entity.Message +import discord4j.core.`object`.entity.channel.{GuildChannel, MessageChannel} +import discord4j.rest.util.{Permission, PermissionSet} +import lombok.RequiredArgsConstructor +import org.bukkit.Bukkit +import org.bukkit.command.CommandSender +import reactor.core.publisher.Mono + +import javax.annotation.Nullable +import java.lang.reflect.Method +import java.util +import java.util.{Objects, Optional} +import java.util.function.Supplier +import java.util.stream.Collectors + +@SuppressWarnings(Array("SimplifyOptionalCallChains")) //Java 11 +@CommandClass(helpText = Array(Array("Channel connect", // + "This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).", + "You need to have access to the MC channel and have manage permissions on the Discord channel.", + "You also need to have your Minecraft account connected. In #bot use /connect .", + "Call this command from the channel you want to use.", "Usage: @Bot channelcon ", + "Use the ID (command) of the channel, for example `g` for the global chat.", + "To remove a connection use @ChromaBot channelcon remove in the channel.", + "Mentioning the bot is needed in this case because the / prefix only works in #bot.", + "Invite link: " // +))) +class ChannelconCommand(private val module: MinecraftChatModule) extends ICommand2DC { + @Command2.Subcommand def remove(sender: Command2DCSender): Boolean = { + val message = sender.getMessage + if (checkPerms(message, null)) true + else if (MCChatCustom.removeCustomChat(message.getChannelId)) + DPUtils.reply(message, Mono.empty, "channel connection removed.").subscribe + else + DPUtils.reply(message, Mono.empty, "this channel isn't connected.").subscribe + true + } + + @Command2.Subcommand def toggle(sender: Command2DCSender, @Command2.OptionalArg toggle: String): Boolean = { + val message = sender.getMessage + if (checkPerms(message, null)) { + return true + } + val cc: MCChatCustom.CustomLMD = MCChatCustom.getCustomChat(message.getChannelId) + if (cc == null) { + return respond(sender, "this channel isn't connected.") + } + val togglesString: Supplier[String] = () => util.Arrays.stream(ChannelconBroadcast.values) + .map((t: ChannelconBroadcast) => + t.toString.toLowerCase + ": " + (if ((cc.toggles & t.flag) == 0) "disabled" else "enabled")) + .collect(Collectors.joining("\n")) + "\n\n" + + TBMCSystemChatEvent.BroadcastTarget.stream.map((target: TBMCSystemChatEvent.BroadcastTarget) => + target.getName + ": " + (if (cc.brtoggles.contains(target)) "enabled" else "disabled")) + .collect(Collectors.joining("\n")) + if (toggle == null) { + DPUtils.reply(message, Mono.empty, "toggles:\n" + togglesString.get).subscribe + return true + } + val arg: String = toggle.toUpperCase + val b: Optional[ChannelconBroadcast] = util.Arrays.stream(ChannelconBroadcast.values).filter((t: ChannelconBroadcast) => t.toString == arg).findAny + if (!b.isPresent) { + val bt: TBMCSystemChatEvent.BroadcastTarget = TBMCSystemChatEvent.BroadcastTarget.get(arg) + if (bt == null) { + DPUtils.reply(message, Mono.empty, "cannot find toggle. Toggles:\n" + togglesString.get).subscribe + return true + } + val add: Boolean = !(cc.brtoggles.contains(bt)) + if (add) { + cc.brtoggles.add(bt) + } + else { + cc.brtoggles.remove(bt) + } + return respond(sender, "'" + bt.getName + "' " + (if (add) "en" else "dis") + "abled") + } + //A B | F + //------- A: original - B: mask - F: new + //0 0 | 0 + //0 1 | 1 + //1 0 | 1 + //1 1 | 0 + // XOR + cc.toggles ^= b.get.flag + DPUtils.reply(message, Mono.empty, "'" + b.get.toString.toLowerCase + "' " + + (if ((cc.toggles & b.get.flag) == 0) "disabled" else "enabled")).subscribe + return true + } + + @Command2.Subcommand def `def`(sender: Command2DCSender, channelID: String): Boolean = { + val message = sender.getMessage + if (!(module.allowCustomChat.get)) { + sender.sendMessage("channel connection is not allowed on this Minecraft server.") + return true + } + val channel = message.getChannel.block + if (checkPerms(message, channel)) { + return true + } + if (MCChatCustom.hasCustomChat(message.getChannelId)) { + return respond(sender, "this channel is already connected to a Minecraft channel. Use `@ChromaBot channelcon remove` to remove it.") + } + val chan: Optional[Channel] = Channel.getChannels.filter((ch: Channel) => ch.ID.equalsIgnoreCase(channelID) || (util.Arrays.stream(ch.IDs.get).anyMatch((cid: String) => cid.equalsIgnoreCase(channelID)))).findAny + if (!(chan.isPresent)) { //TODO: Red embed that disappears over time (kinda like the highlight messages in OW) + DPUtils.reply(message, channel, "MC channel with ID '" + channelID + "' not found! The ID is the command for it without the /.").subscribe + return true + } + if (!(message.getAuthor.isPresent)) { + return true + } + val author = message.getAuthor.get + val dp: DiscordPlayer = ChromaGamerBase.getUser(author.getId.asString, classOf[DiscordPlayer]) + val chp: TBMCPlayer = dp.getAs(classOf[TBMCPlayer]) + if (chp == null) { + DPUtils.reply(message, channel, "you need to connect your Minecraft account. On the main server in " + DPUtils.botmention + " do " + DiscordPlugin.getPrefix + "connect ").subscribe + return true + } + val dcp: DiscordConnectedPlayer = DiscordConnectedPlayer.create(message.getAuthor.get, channel, chp.getUUID, Bukkit.getOfflinePlayer(chp.getUUID).getName, module) + //Using a fake player with no login/logout, should be fine for this event + val groupid: String = chan.get.getGroupID(dcp) + if (groupid == null && !((chan.get.isInstanceOf[ChatRoom]))) { //ChatRooms don't allow it unless the user joins, which happens later + DPUtils.reply(message, channel, "sorry, you cannot use that Minecraft channel.").subscribe + return true + } + if (chan.get.isInstanceOf[ChatRoom]) { //ChatRooms don't work well + DPUtils.reply(message, channel, "chat rooms are not supported yet.").subscribe + return true + } + /*if (MCChatListener.getCustomChats().stream().anyMatch(cc -> cc.groupID.equals(groupid) && cc.mcchannel.ID.equals(chan.get().ID))) { + DPUtils.reply(message, null, "sorry, this MC chat is already connected to a different channel, multiple channels are not supported atm."); + return true; + }*/ + //TODO: "Channel admins" that can connect channels? + MCChatCustom.addCustomChat(channel, groupid, chan.get, author, dcp, 0, new util.HashSet[TBMCSystemChatEvent.BroadcastTarget]) + if (chan.get.isInstanceOf[ChatRoom]) { + DPUtils.reply(message, channel, "alright, connection made to the room!").subscribe + } + else { + DPUtils.reply(message, channel, "alright, connection made to group `" + groupid + "`!").subscribe + } + return true + } + + @SuppressWarnings(Array("ConstantConditions")) + private def checkPerms(message: Message, @Nullable channel: MessageChannel): Boolean = { + if (channel == null) { + return checkPerms(message, message.getChannel.block) + } + if (!((channel.isInstanceOf[GuildChannel]))) { + DPUtils.reply(message, channel, "you can only use this command in a server!").subscribe + return true + } + //noinspection OptionalGetWithoutIsPresent + val perms: PermissionSet = (channel.asInstanceOf[GuildChannel]).getEffectivePermissions(message.getAuthor.map(_.getId).get).block + if (!(perms.contains(Permission.ADMINISTRATOR)) && !(perms.contains(Permission.MANAGE_CHANNELS))) { + DPUtils.reply(message, channel, "you need to have manage permissions for this channel!").subscribe + return true + } + false + } + + def getHelpText(method: Method, ann: Command2.Subcommand): Array[String] = + Array[String]( + "Channel connect", + "This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).", + "You need to have access to the MC channel and have manage permissions on the Discord channel.", + "You also need to have your Minecraft account connected. In " + DPUtils.botmention + " use " + DiscordPlugin.getPrefix + "connect .", + "Call this command from the channel you want to use.", "Usage: " + Objects.requireNonNull(DiscordPlugin.dc.getSelf.block).getMention + " channelcon ", + "Use the ID (command) of the channel, for example `g` for the global chat.", + "To remove a connection use @ChromaBot channelcon remove in the channel.", + "Mentioning the bot is needed in this case because the " + DiscordPlugin.getPrefix + " prefix only works in " + DPUtils.botmention + ".", + "Invite link: ") +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java deleted file mode 100755 index 0f60db5..0000000 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.java +++ /dev/null @@ -1,45 +0,0 @@ -package buttondevteam.discordplugin.mcchat; - -import buttondevteam.discordplugin.DPUtils; -import buttondevteam.discordplugin.DiscordPlayer; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.chat.Command2; -import buttondevteam.lib.chat.CommandClass; -import discord4j.core.object.entity.channel.PrivateChannel; -import lombok.RequiredArgsConstructor; -import lombok.val; - -@CommandClass(helpText = { - "MC Chat", - "This command 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." // -}) -@RequiredArgsConstructor -public class MCChatCommand extends ICommand2DC { - - private final MinecraftChatModule module; - - @Command2.Subcommand - public boolean def(Command2DCSender sender) { - if (!module.allowPrivateChat.get()) { - sender.sendMessage("using the private chat is not allowed on this Minecraft server."); - return true; - } - val message = sender.getMessage(); - val channel = message.getChannel().block(); - @SuppressWarnings("OptionalGetWithoutIsPresent") val author = message.getAuthor().get(); - if (!(channel instanceof PrivateChannel)) { - DPUtils.reply(message, channel, "this command can only be issued in a direct message with the bot.").subscribe(); - return true; - } - final DiscordPlayer user = DiscordPlayer.getUser(author.getId().asString(), DiscordPlayer.class); - boolean mcchat = !user.isMinecraftChatEnabled(); - MCChatPrivate.privateMCChat(channel, mcchat, author, user); - DPUtils.reply(message, channel, "Minecraft chat " + (mcchat // - ? "enabled. Use '" + DiscordPlugin.getPrefix() + "mcchat' again to turn it off." // - : "disabled.")).subscribe(); - return true; - } // TODO: Pin channel switching to indicate the current channel - -} diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.scala new file mode 100644 index 0000000..7b76572 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.scala @@ -0,0 +1,37 @@ +package buttondevteam.discordplugin.mcchat + +import buttondevteam.discordplugin.{DPUtils, DiscordPlayer, DiscordPlugin} +import buttondevteam.discordplugin.commands.{Command2DCSender, ICommand2DC} +import buttondevteam.lib.chat.{Command2, CommandClass} +import buttondevteam.lib.player.ChromaGamerBase +import discord4j.core.`object`.entity.channel.PrivateChannel + +@CommandClass(helpText = Array(Array( + "MC Chat", + "This command 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." // +))) +class MCChatCommand(private val module: MinecraftChatModule) extends ICommand2DC { + @Command2.Subcommand override def `def`(sender: Command2DCSender): Boolean = { + if (!(module.allowPrivateChat.get)) { + sender.sendMessage("using the private chat is not allowed on this Minecraft server.") + return true + } + val message = sender.getMessage + val channel = message.getChannel.block + @SuppressWarnings(Array("OptionalGetWithoutIsPresent")) val author = message.getAuthor.get + if (!((channel.isInstanceOf[PrivateChannel]))) { + DPUtils.reply(message, channel, "this command can only be issued in a direct message with the bot.").subscribe + return true + } + val user: DiscordPlayer = ChromaGamerBase.getUser(author.getId.asString, classOf[DiscordPlayer]) + val mcchat: Boolean = !(user.isMinecraftChatEnabled) + MCChatPrivate.privateMCChat(channel, mcchat, author, user) + DPUtils.reply(message, channel, "Minecraft chat " + + (if (mcchat) "enabled. Use '" + DiscordPlugin.getPrefix + "mcchat' again to turn it off." + else "disabled.")).subscribe + true + // TODO: Pin channel switching to indicate the current channel + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java deleted file mode 100644 index 611d0e8..0000000 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.java +++ /dev/null @@ -1,78 +0,0 @@ -package buttondevteam.discordplugin.mcchat; - -import buttondevteam.core.component.channel.Channel; -import buttondevteam.core.component.channel.ChatRoom; -import buttondevteam.discordplugin.DiscordConnectedPlayer; -import buttondevteam.lib.TBMCSystemChatEvent; -import discord4j.common.util.Snowflake; -import discord4j.core.object.entity.User; -import discord4j.core.object.entity.channel.MessageChannel; -import lombok.NonNull; -import lombok.val; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -public class MCChatCustom { - /** - * Used for town or nation chats or anything else - */ - static final ArrayList lastmsgCustom = new ArrayList<>(); - - public static void addCustomChat(MessageChannel channel, String groupid, Channel mcchannel, User user, DiscordConnectedPlayer dcp, int toggles, Set brtoggles) { - synchronized (lastmsgCustom) { - if (mcchannel instanceof ChatRoom) { - ((ChatRoom) mcchannel).joinRoom(dcp); - if (groupid == null) groupid = mcchannel.getGroupID(dcp); - } - val lmd = new CustomLMD(channel, user, groupid, mcchannel, dcp, toggles, brtoggles); - lastmsgCustom.add(lmd); - } - } - - public static boolean hasCustomChat(Snowflake channel) { - return lastmsgCustom.stream().anyMatch(lmd -> lmd.channel.getId().asLong() == channel.asLong()); - } - - @Nullable - public static CustomLMD getCustomChat(Snowflake channel) { - return lastmsgCustom.stream().filter(lmd -> lmd.channel.getId().asLong() == channel.asLong()).findAny().orElse(null); - } - - public static boolean removeCustomChat(Snowflake channel) { - synchronized (lastmsgCustom) { - MCChatUtils.lastmsgfromd.remove(channel.asLong()); - return lastmsgCustom.removeIf(lmd -> { - if (lmd.channel.getId().asLong() != channel.asLong()) - return false; - if (lmd.mcchannel instanceof ChatRoom) - ((ChatRoom) lmd.mcchannel).leaveRoom(lmd.dcp); - return true; - }); - } - } - - public static List getCustomChats() { - return Collections.unmodifiableList(lastmsgCustom); - } - - public static class CustomLMD extends MCChatUtils.LastMsgData { - public final String groupID; - public final DiscordConnectedPlayer dcp; - public int toggles; - public Set brtoggles; - - private CustomLMD(@NonNull MessageChannel channel, @NonNull User user, - @NonNull String groupid, @NonNull Channel mcchannel, @NonNull DiscordConnectedPlayer dcp, int toggles, Set brtoggles) { - super(channel, user); - groupID = groupid; - this.mcchannel = mcchannel; - this.dcp = dcp; - this.toggles = toggles; - this.brtoggles = brtoggles; - } - } -} diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala new file mode 100644 index 0000000..c6fbcec --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala @@ -0,0 +1,66 @@ +package buttondevteam.discordplugin.mcchat + +import buttondevteam.core.component.channel.{Channel, ChatRoom} +import buttondevteam.discordplugin.DiscordConnectedPlayer +import buttondevteam.lib.TBMCSystemChatEvent +import discord4j.common.util.Snowflake +import discord4j.core.`object`.entity.User +import discord4j.core.`object`.entity.channel.MessageChannel +import lombok.NonNull + +import java.util +import java.util.Collections +import javax.annotation.Nullable + +object MCChatCustom { + /** + * Used for town or nation chats or anything else + */ + private[mcchat] val lastmsgCustom = new util.ArrayList[MCChatCustom.CustomLMD] + + def addCustomChat(channel: MessageChannel, groupid: String, mcchannel: Channel, user: User, dcp: DiscordConnectedPlayer, toggles: Int, brtoggles: util.Set[TBMCSystemChatEvent.BroadcastTarget]): Boolean = { + lastmsgCustom synchronized { + var gid: String = null + mcchannel match { + case room: ChatRoom => + room.joinRoom(dcp) + gid = if (groupid == null) mcchannel.getGroupID(dcp) else groupid + case _ => + gid = groupid + } + val lmd = new MCChatCustom.CustomLMD(channel, user, gid, mcchannel, dcp, toggles, brtoggles) + lastmsgCustom.add(lmd) + } + true + } + + def hasCustomChat(channel: Snowflake): Boolean = + lastmsgCustom.stream.anyMatch((lmd: MCChatCustom.CustomLMD) => lmd.channel.getId.asLong == channel.asLong) + + @Nullable def getCustomChat(channel: Snowflake): CustomLMD = + lastmsgCustom.stream.filter((lmd: MCChatCustom.CustomLMD) => lmd.channel.getId.asLong == channel.asLong).findAny.orElse(null) + + def removeCustomChat(channel: Snowflake): Boolean = { + lastmsgCustom synchronized MCChatUtils.lastmsgfromd.remove(channel.asLong) + lastmsgCustom.removeIf((lmd: MCChatCustom.CustomLMD) => { + def foo(lmd: MCChatCustom.CustomLMD): Boolean = { + if (lmd.channel.getId.asLong != channel.asLong) return false + lmd.mcchannel match { + case room: ChatRoom => room.leaveRoom(lmd.dcp) + case _ => + } + true + } + + foo(lmd) + }) + } + + def getCustomChats: util.List[CustomLMD] = Collections.unmodifiableList(lastmsgCustom) + + class CustomLMD private(@NonNull channel: MessageChannel, @NonNull user: User, val groupID: String, + @NonNull val mcchannel: Channel, val dcp: DiscordConnectedPlayer, var toggles: Int, + var brtoggles: Set[TBMCSystemChatEvent.BroadcastTarget]) extends MCChatUtils.LastMsgData(channel, user) { + } + +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java deleted file mode 100755 index 3c257ad..0000000 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.java +++ /dev/null @@ -1,416 +0,0 @@ -package buttondevteam.discordplugin.mcchat; - -import buttondevteam.core.ComponentManager; -import buttondevteam.discordplugin.*; -import buttondevteam.discordplugin.listeners.CommandListener; -import buttondevteam.discordplugin.playerfaker.VanillaCommandListener; -import buttondevteam.discordplugin.playerfaker.VanillaCommandListener14; -import buttondevteam.discordplugin.playerfaker.VanillaCommandListener15; -import buttondevteam.discordplugin.util.Timings; -import buttondevteam.lib.*; -import buttondevteam.lib.chat.ChatMessage; -import buttondevteam.lib.chat.TBMCChatAPI; -import buttondevteam.lib.player.TBMCPlayer; -import com.vdurmont.emoji.EmojiParser; -import discord4j.common.util.Snowflake; -import discord4j.core.event.domain.message.MessageCreateEvent; -import discord4j.core.object.Embed; -import discord4j.core.object.entity.Attachment; -import discord4j.core.object.entity.Guild; -import discord4j.core.object.entity.Message; -import discord4j.core.object.entity.User; -import discord4j.core.object.entity.channel.GuildChannel; -import discord4j.core.object.entity.channel.PrivateChannel; -import discord4j.core.spec.EmbedCreateSpec; -import discord4j.rest.util.Color; -import lombok.val; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.scheduler.BukkitTask; -import reactor.core.publisher.Mono; - -import java.time.Instant; -import java.util.AbstractMap; -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.stream.Collectors; - -public class MCChatListener implements Listener { - private BukkitTask sendtask; - private final LinkedBlockingQueue> sendevents = new LinkedBlockingQueue<>(); - private Runnable sendrunnable; - private Thread sendthread; - private final MinecraftChatModule module; - private boolean stop = false; //A new instance will be created on enable - - public MCChatListener(MinecraftChatModule minecraftChatModule) { - module = minecraftChatModule; - } - - @EventHandler // Minecraft - public void onMCChat(TBMCChatEvent ev) { - 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) - return; - sendrunnable = () -> { - sendthread = Thread.currentThread(); - processMCToDiscord(); - if (DiscordPlugin.plugin.isEnabled() && !stop) //Don't run again if shutting down - sendtask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable); - }; - sendtask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable); - } - - private void processMCToDiscord() { - try { - TBMCChatEvent e; - Instant time; - val se = sendevents.take(); // Wait until an element is available - e = se.getKey(); - time = se.getValue(); - - final String authorPlayer = "[" + DPUtils.sanitizeStringNoEscape(e.getChannel().DisplayName.get()) + "] " // - + ("Minecraft".equals(e.getOrigin()) ? "" : "[" + e.getOrigin().charAt(0) + "]") // - + (DPUtils.sanitizeStringNoEscape(ChromaUtils.getDisplayName(e.getSender()))); - val color = e.getChannel().Color.get(); - final Consumer embed = ecs -> { - ecs.setDescription(e.getMessage()).setColor(Color.of(color.getRed(), - color.getGreen(), color.getBlue())); - String url = module.profileURL.get(); - if (e.getSender() instanceof Player) - DPUtils.embedWithHead(ecs, authorPlayer, e.getSender().getName(), - url.length() > 0 ? url + "?type=minecraft&id=" - + ((Player) e.getSender()).getUniqueId() : null); - else if (e.getSender() instanceof DiscordSenderBase) - ecs.setAuthor(authorPlayer, url.length() > 0 ? url + "?type=discord&id=" - + ((DiscordSenderBase) e.getSender()).getUser().getId().asString() : null, - ((DiscordSenderBase) e.getSender()).getUser().getAvatarUrl()); - else - DPUtils.embedWithHead(ecs, authorPlayer, e.getSender().getName(), null); - ecs.setTimestamp(time); - }; - final long nanoTime = System.nanoTime(); - InterruptibleConsumer doit = lastmsgdata -> { - if (lastmsgdata.message == null - || !authorPlayer.equals(lastmsgdata.message.getEmbeds().get(0).getAuthor().map(Embed.Author::getName).orElse(null)) - || lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120 - || !lastmsgdata.mcchannel.ID.equals(e.getChannel().ID) - || lastmsgdata.content.length() + e.getMessage().length() + 1 > 2048) { - lastmsgdata.message = lastmsgdata.channel.createEmbed(embed).block(); - lastmsgdata.time = nanoTime; - lastmsgdata.mcchannel = e.getChannel(); - lastmsgdata.content = e.getMessage(); - } else { - lastmsgdata.content = lastmsgdata.content + "\n" - + e.getMessage(); // The message object doesn't get updated - lastmsgdata.message.edit(mes -> mes.setEmbed(embed.andThen(ecs -> - ecs.setDescription(lastmsgdata.content)))).block(); - } - }; - // Checks if the given channel is different than where the message was sent from - // Or if it was from MC - Predicate isdifferentchannel = id -> !(e.getSender() instanceof DiscordSenderBase) - || ((DiscordSenderBase) e.getSender()).getChannel().getId().asLong() != id.asLong(); - - if (e.getChannel().isGlobal() - && (e.isFromCommand() || isdifferentchannel.test(module.chatChannel.get()))) - doit.accept(MCChatUtils.lastmsgdata == null - ? MCChatUtils.lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono().block(), null) - : MCChatUtils.lastmsgdata); - - for (MCChatUtils.LastMsgData data : MCChatPrivate.lastmsgPerUser) { - if ((e.isFromCommand() || isdifferentchannel.test(data.channel.getId())) - && e.shouldSendTo(MCChatUtils.getSender(data.channel.getId(), data.user))) - doit.accept(data); - } - - synchronized (MCChatCustom.lastmsgCustom) { - val iterator = MCChatCustom.lastmsgCustom.iterator(); - while (iterator.hasNext()) { - val lmd = iterator.next(); - if ((e.isFromCommand() || isdifferentchannel.test(lmd.channel.getId())) //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 - doit.accept(lmd); - else { - iterator.remove(); //If the user no longer has permission, remove the connection - lmd.channel.createMessage("The user no longer has permission to view the channel, connection removed.").subscribe(); - } - } - } - } - } catch (InterruptedException ex) { //Stop if interrupted anywhere - sendtask.cancel(); - sendtask = null; - } catch (Exception ex) { - TBMCCoreAPI.SendException("Error while sending message to Discord!", ex, module); - } - } - - @EventHandler - public void onChatPreprocess(TBMCChatPreprocessEvent event) { - int start = -1; - while ((start = event.getMessage().indexOf('@', start + 1)) != -1) { - int mid = event.getMessage().indexOf('#', start + 1); - if (mid == -1) - return; - int end_ = event.getMessage().indexOf(' ', mid + 1); - if (end_ == -1) - end_ = event.getMessage().length(); - final int end = end_; - final int startF = start; - val user = DiscordPlugin.dc.getUsers().filter(u -> u.getUsername().equals(event.getMessage().substring(startF + 1, mid))) - .filter(u -> u.getDiscriminator().equals(event.getMessage().substring(mid + 1, end))).blockFirst(); - if (user != null) //TODO: Nicknames - event.setMessage(event.getMessage().substring(0, startF) + "@" + user.getUsername() - + (event.getMessage().length() > end ? event.getMessage().substring(end) : "")); // TODO: Add formatting - start = end; // Skip any @s inside the mention - } - } - - // ......................DiscordSender....DiscordConnectedPlayer.DiscordPlayerSender - // Offline public chat......x............................................ - // Online public chat.......x...........................................x - // Offline private chat.....x.......................x.................... - // Online private chat......x.......................x...................x - // If online and enabling private chat, don't login - // If leaving the server and private chat is enabled (has ConnectedPlayer), call login in a task on lowest priority - // If private chat is enabled and joining the server, logout the fake player on highest priority - // If online and disabling private chat, don't logout - // The maps may not contain the senders for UnconnectedSenders - - /** - * Stop the listener permanently. Enabling the module will create a new instance. - * - * @param wait Wait 5 seconds for the threads to stop - */ - public void stop(boolean wait) { - stop = true; - MCChatPrivate.logoutAll(); - MCChatUtils.LoggedInPlayers.clear(); - if (sendthread != null) sendthread.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); - } - MCChatUtils.lastmsgdata = null; - MCChatPrivate.lastmsgPerUser.clear(); - MCChatCustom.lastmsgCustom.clear(); - MCChatUtils.lastmsgfromd.clear(); - MCChatUtils.UnconnectedSenders.clear(); - recthread = sendthread = null; - } catch (InterruptedException e) { - e.printStackTrace(); //This thread shouldn't be interrupted - } - } - - private BukkitTask rectask; - private final LinkedBlockingQueue recevents = new LinkedBlockingQueue<>(); - private Runnable recrun; - private Thread recthread; - - // Discord - public Mono handleDiscord(MessageCreateEvent ev) { - Timings timings = CommonListeners.timings; - timings.printElapsed("Chat event"); - val author = ev.getMessage().getAuthor(); - final boolean hasCustomChat = MCChatCustom.hasCustomChat(ev.getMessage().getChannelId()); - var prefix = DiscordPlugin.getPrefix(); - return ev.getMessage().getChannel().filter(channel -> { - timings.printElapsed("Filter 1"); - return !(ev.getMessage().getChannelId().asLong() != module.chatChannel.get().asLong() - && !(channel instanceof PrivateChannel - && author.map(u -> MCChatPrivate.isMinecraftChatEnabled(u.getId().asString())).orElse(false)) - && !hasCustomChat); //Chat isn't enabled on this channel - }).filter(channel -> { - timings.printElapsed("Filter 2"); - return !(channel instanceof PrivateChannel //Only in private chat - && ev.getMessage().getContent().length() < "/mcchat<>".length() - && ev.getMessage().getContent().replace(prefix + "", "") - .equalsIgnoreCase("mcchat")); //Either mcchat or /mcchat - //Allow disabling the chat if needed - }).filterWhen(channel -> CommandListener.runCommand(ev.getMessage(), DiscordPlugin.plugin.commandChannel.get(), true)) - //Allow running commands in chat channels - .filter(channel -> { - MCChatUtils.resetLastMessage(channel); - recevents.add(ev); - timings.printElapsed("Message event added"); - if (rectask != null) - return true; - recrun = () -> { //Don't return in a while loop next time - recthread = Thread.currentThread(); - processDiscordToMC(); - if (DiscordPlugin.plugin.isEnabled() && !stop) //Don't run again if shutting down - rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Continue message processing - }; - rectask = Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, recrun); //Start message processing - return true; - }).map(b -> false).defaultIfEmpty(true); - } - - private void processDiscordToMC() { - MessageCreateEvent event; - try { - event = recevents.take(); - } catch (InterruptedException e1) { - rectask.cancel(); - return; - } - val sender = event.getMessage().getAuthor().orElse(null); - String dmessage = event.getMessage().getContent(); - try { - final DiscordSenderBase dsender = MCChatUtils.getSender(event.getMessage().getChannelId(), sender); - val user = dsender.getChromaUser(); - - for (User u : event.getMessage().getUserMentions().toIterable()) { //TODO: Role mentions - dmessage = dmessage.replace(u.getMention(), "@" + u.getUsername()); // TODO: IG Formatting - val m = u.asMember(DiscordPlugin.mainServer.getId()).onErrorResume(t -> Mono.empty()).blockOptional(); - if (m.isPresent()) { - val mm = m.get(); - final String nick = mm.getDisplayName(); - dmessage = dmessage.replace(mm.getNicknameMention(), "@" + nick); - } - } - for (GuildChannel ch : event.getGuild().flux().flatMap(Guild::getChannels).toIterable()) { - dmessage = dmessage.replace(ch.getMention(), "#" + ch.getName()); // TODO: IG Formatting - } - - dmessage = EmojiParser.parseToAliases(dmessage, EmojiParser.FitzpatrickAction.PARSE); //Converts emoji to text- TODO: Add option to disable (resource pack?) - dmessage = dmessage.replaceAll(":(\\S+)\\|type_(?:(\\d)|(1)_2):", ":$1::skin-tone-$2:"); //Convert to Discord's format so it still shows up - - dmessage = dmessage.replaceAll("", ":$1:"); //We don't need info about the custom emojis, just display their text - - Function getChatMessage = msg -> // - msg + (event.getMessage().getAttachments().size() > 0 ? "\n" + event.getMessage() - .getAttachments().stream().map(Attachment::getUrl).collect(Collectors.joining("\n")) - : ""); - - MCChatCustom.CustomLMD clmd = MCChatCustom.getCustomChat(event.getMessage().getChannelId()); - - boolean react = false; - - val sendChannel = event.getMessage().getChannel().block(); - boolean isPrivate = sendChannel instanceof PrivateChannel; - if (dmessage.startsWith("/")) { // Ingame command - if (handleIngameCommand(event, dmessage, dsender, user, clmd, isPrivate)) return; - } else {// Not a command - react = handleIngameMessage(event, dmessage, dsender, user, getChatMessage, clmd, isPrivate); - } - if (react) { - try { - val lmfd = MCChatUtils.lastmsgfromd.get(event.getMessage().getChannelId().asLong()); - if (lmfd != null) { - lmfd.removeSelfReaction(DiscordPlugin.DELIVERED_REACTION).subscribe(); // Remove it no matter what, we know it's there 99.99% of the time - } - } catch (Exception e) { - TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e, module); - } - MCChatUtils.lastmsgfromd.put(event.getMessage().getChannelId().asLong(), event.getMessage()); - event.getMessage().addReaction(DiscordPlugin.DELIVERED_REACTION).subscribe(); - } - } catch (Exception e) { - TBMCCoreAPI.SendException("An error occured while handling message \"" + dmessage + "\"!", e, module); - } - } - - private boolean handleIngameMessage(MessageCreateEvent event, String dmessage, DiscordSenderBase dsender, DiscordPlayer user, Function getChatMessage, MCChatCustom.CustomLMD clmd, boolean isPrivate) { - boolean react = false; - if (dmessage.length() == 0 && event.getMessage().getAttachments().size() == 0 - && !isPrivate && event.getMessage().getType() == Message.Type.CHANNEL_PINNED_MESSAGE) { - 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.", TBMCSystemChatEvent.BroadcastTarget.ALL); - } else { - val cmb = ChatMessage.builder(dsender, user, getChatMessage.apply(dmessage)).fromCommand(false); - if (clmd != null) - TBMCChatAPI.SendChatMessage(cmb.permCheck(clmd.dcp).build(), clmd.mcchannel); - else - TBMCChatAPI.SendChatMessage(cmb.build()); - react = true; - } - return react; - } - - private boolean handleIngameCommand(MessageCreateEvent event, String dmessage, DiscordSenderBase dsender, DiscordPlayer user, MCChatCustom.CustomLMD clmd, boolean isPrivate) { - if (!isPrivate) - event.getMessage().delete().subscribe(); - final String cmd = dmessage.substring(1); - final String cmdlowercased = cmd.toLowerCase(); - if (dsender instanceof DiscordSender && module.whitelistedCommands().get().stream() - .noneMatch(s -> cmdlowercased.equals(s) || cmdlowercased.startsWith(s + " "))) { - // Command not whitelisted - dsender.sendMessage("Sorry, you can only access these commands from here:\n" - + module.whitelistedCommands().get().stream().map(uc -> "/" + uc) - .collect(Collectors.joining(", ")) - + (user.getConnectedID(TBMCPlayer.class) == null - ? "\nTo access your commands, first please connect your accounts, using /connect in " - + DPUtils.botmention() - + "\nThen y" - : "\nY") - + "ou can access all of your regular commands (even offline) in private chat: DM me `mcchat`!"); - return true; - } - module.log(dsender.getName() + " ran from DC: /" + cmd); - if (dsender instanceof DiscordSender && runCustomCommand(dsender, cmdlowercased)) return true; - val channel = clmd == null ? user.channel.get() : clmd.mcchannel; - val ev = new TBMCCommandPreprocessEvent(dsender, channel, dmessage, clmd == null ? dsender : clmd.dcp); - Bukkit.getScheduler().runTask(DiscordPlugin.plugin, //Commands need to be run sync - () -> { - Bukkit.getPluginManager().callEvent(ev); - if (ev.isCancelled()) - return; - try { - String mcpackage = Bukkit.getServer().getClass().getPackage().getName(); - if (!module.enableVanillaCommands.get()) - Bukkit.dispatchCommand(dsender, cmd); - else if (mcpackage.contains("1_12")) - VanillaCommandListener.runBukkitOrVanillaCommand(dsender, cmd); - else if (mcpackage.contains("1_14")) - VanillaCommandListener14.runBukkitOrVanillaCommand(dsender, cmd); - else if (mcpackage.contains("1_15") || mcpackage.contains("1_16")) - VanillaCommandListener15.runBukkitOrVanillaCommand(dsender, cmd); - else - Bukkit.dispatchCommand(dsender, cmd); - } catch (NoClassDefFoundError e) { - TBMCCoreAPI.SendException("A class is not found when trying to run command " + cmd + "!", e, module); - } catch (Exception e) { - TBMCCoreAPI.SendException("An error occurred when trying to run command " + cmd + "! Vanilla commands are only supported in some MC versions.", e, module); - } - }); - return true; - } - - private boolean runCustomCommand(DiscordSenderBase dsender, String cmdlowercased) { - if (cmdlowercased.startsWith("list")) { - var players = Bukkit.getOnlinePlayers(); - dsender.sendMessage("There are " + players.stream().filter(MCChatUtils::checkEssentials).count() + " out of " + Bukkit.getMaxPlayers() + " players online."); - dsender.sendMessage("Players: " + players.stream().filter(MCChatUtils::checkEssentials) - .map(Player::getDisplayName).collect(Collectors.joining(", "))); - return true; - } - return false; - } - - @FunctionalInterface - private interface InterruptibleConsumer { - void accept(T value) throws TimeoutException, InterruptedException; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala new file mode 100644 index 0000000..62eeef6 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala @@ -0,0 +1,477 @@ +package buttondevteam.discordplugin.mcchat + +import buttondevteam.core.ComponentManager +import buttondevteam.core.component.channel.Channel +import buttondevteam.discordplugin._ +import buttondevteam.discordplugin.listeners.{CommandListener, CommonListeners} +import buttondevteam.discordplugin.playerfaker.{VanillaCommandListener, VanillaCommandListener14, VanillaCommandListener15} +import buttondevteam.discordplugin.util.Timings +import buttondevteam.lib._ +import buttondevteam.lib.chat.{ChatMessage, TBMCChatAPI} +import buttondevteam.lib.player.TBMCPlayer +import com.vdurmont.emoji.EmojiParser +import discord4j.common.util.Snowflake +import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} +import discord4j.core.`object`.entity.{Member, Message, User} +import discord4j.core.event.domain.message.MessageCreateEvent +import discord4j.core.spec.{EmbedCreateSpec, MessageEditSpec} +import discord4j.rest.util.Color +import org.bukkit.Bukkit +import org.bukkit.entity.Player +import org.bukkit.event.{EventHandler, Listener} +import org.bukkit.scheduler.BukkitTask +import reactor.core.publisher.Mono + +import java.time.Instant +import java.util +import java.util.Optional +import java.util.concurrent.{LinkedBlockingQueue, TimeoutException} +import java.util.function.{Consumer, Function, Predicate} +import java.util.stream.Collectors + +object MCChatListener { + + // ......................DiscordSender....DiscordConnectedPlayer.DiscordPlayerSender + // Offline public chat......x............................................ + // Online public chat.......x...........................................x + // Offline private chat.....x.......................x.................... + // Online private chat......x.......................x...................x + // If online and enabling private chat, don't login + // If leaving the server and private chat is enabled (has ConnectedPlayer), call login in a task on lowest priority + // If private chat is enabled and joining the server, logout the fake player on highest priority + // If online and disabling private chat, don't logout + // The maps may not contain the senders for UnconnectedSenders + @FunctionalInterface private trait InterruptibleConsumer[T] { + @throws[TimeoutException] + @throws[InterruptedException] + def accept(value: T) + } + +} + +class MCChatListener(val module: MinecraftChatModule) extends Listener { + private var sendtask: BukkitTask = null + final private val sendevents = new LinkedBlockingQueue[util.AbstractMap.SimpleEntry[TBMCChatEvent, Instant]] + private var sendrunnable: Runnable = null + private var sendthread: Thread = null + private var stop = false //A new instance will be created on enable + @EventHandler // Minecraft + def onMCChat(ev: TBMCChatEvent): Unit = { + if (!(ComponentManager.isEnabled(classOf[MinecraftChatModule])) || ev.isCancelled) { //SafeMode: Needed so it doesn't restart after server shutdown + return + } + + sendevents.add(new util.AbstractMap.SimpleEntry[TBMCChatEvent, Instant](ev, Instant.now)) + if (sendtask != null) { + return + } + sendrunnable = () => { + def foo(): Unit = { + sendthread = Thread.currentThread + processMCToDiscord() + if (DiscordPlugin.plugin.isEnabled && !(stop)) { //Don't run again if shutting down + sendtask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable) + } + } + + foo() + } + sendtask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, sendrunnable) + } + + private def processMCToDiscord(): Unit = { + try { + var e: TBMCChatEvent = null + var time: Instant = null + val se: util.AbstractMap.SimpleEntry[TBMCChatEvent, Instant] = sendevents.take // Wait until an element is available + e = se.getKey + time = se.getValue + val authorPlayer: String = "[" + DPUtils.sanitizeStringNoEscape(e.getChannel.DisplayName.get) + "] " + // + (if ("Minecraft" == e.getOrigin) "" else "[" + e.getOrigin.charAt(0) + "]") + + DPUtils.sanitizeStringNoEscape(ChromaUtils.getDisplayName(e.getSender)) + val color: chat.Color = e.getChannel.Color.get + val embed: Consumer[EmbedCreateSpec] = (ecs: EmbedCreateSpec) => { + def foo(ecs: EmbedCreateSpec) = { + ecs.setDescription(e.getMessage).setColor(Color.of(color.getRed, color.getGreen, color.getBlue)) + val url: String = module.profileURL.get + e.getSender match { + case player: Player => + DPUtils.embedWithHead(ecs, authorPlayer, e.getSender.getName, + if (url.nonEmpty) url + "?type=minecraft&id=" + player.getUniqueId else null) + case dsender: DiscordSenderBase => + ecs.setAuthor(authorPlayer, + if (url.nonEmpty) url + "?type=discord&id=" + dsender.getUser.getId.asString else null, + dsender.getUser.getAvatarUrl) + case _ => + DPUtils.embedWithHead(ecs, authorPlayer, e.getSender.getName, null) + } + ecs.setTimestamp(time) + } + + foo(ecs) + } + val nanoTime: Long = System.nanoTime + val doit: MCChatListener.InterruptibleConsumer[MCChatUtils.LastMsgData] = (lastmsgdata: MCChatUtils.LastMsgData) => { + def foo(lastmsgdata: MCChatUtils.LastMsgData): Unit = { + if (lastmsgdata.message == null || !(authorPlayer == lastmsgdata.message.getEmbeds.get(0).getAuthor.map(_.getName).orElse(null)) || lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120 || !(lastmsgdata.mcchannel.ID == e.getChannel.ID) || lastmsgdata.content.length + e.getMessage.length + 1 > 2048) { + lastmsgdata.message = lastmsgdata.channel.createEmbed(embed).block + lastmsgdata.time = nanoTime + lastmsgdata.mcchannel = e.getChannel + lastmsgdata.content = e.getMessage + } + else { + lastmsgdata.content = lastmsgdata.content + "\n" + e.getMessage // The message object doesn't get updated + lastmsgdata.message.edit((mes: MessageEditSpec) => mes.setEmbed(embed.andThen((ecs: EmbedCreateSpec) => ecs.setDescription(lastmsgdata.content)))).block + } + } + + foo(lastmsgdata) + } + // Checks if the given channel is different than where the message was sent from + // Or if it was from MC + val isdifferentchannel: Predicate[Snowflake] = (id: Snowflake) => !((e.getSender.isInstanceOf[DiscordSenderBase])) || (e.getSender.asInstanceOf[DiscordSenderBase]).getChannel.getId.asLong != id.asLong + if (e.getChannel.isGlobal && (e.isFromCommand || isdifferentchannel.test(module.chatChannel.get))) { + if (MCChatUtils.lastmsgdata == null) + MCChatUtils.lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono.block, null) + doit.accept(MCChatUtils.lastmsgdata) + } + + for (data <- MCChatPrivate.lastmsgPerUser) { + if ((e.isFromCommand || isdifferentchannel.test(data.channel.getId)) && e.shouldSendTo(MCChatUtils.getSender(data.channel.getId, data.user))) { + doit.accept(data) + } + } + MCChatCustom.lastmsgCustom synchronized + val iterator = MCChatCustom.lastmsgCustom.iterator + while ( { + iterator.hasNext + }) { + val lmd = iterator.next + if ((e.isFromCommand || isdifferentchannel.test(lmd.channel.getId)) //Test if msg is from Discord + && e.getChannel.ID == lmd.mcchannel.ID //If it's from a command, the command msg has been deleted, so we need to send it + && e.getGroupID == 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) + } + else { + iterator.remove() //If the user no longer has permission, remove the connection + lmd.channel.createMessage("The user no longer has permission to view the channel, connection removed.").subscribe + } + } + } + } catch { + case ex: InterruptedException => + //Stop if interrupted anywhere + sendtask.cancel() + sendtask = null + case ex: Exception => + TBMCCoreAPI.SendException("Error while sending message to Discord!", ex, module) + } + } + + @EventHandler def onChatPreprocess(event: TBMCChatPreprocessEvent): Unit = { + var start: Int = -(1) + while ( { + (start = event.getMessage.indexOf('@', start + 1), start) != ((), -1) + }) { + val mid: Int = event.getMessage.indexOf('#', start + 1) + if (mid == -1) { + return + } + var end_ = event.getMessage.indexOf(' ', mid + 1) + if (end_ == -1) { + end_ = event.getMessage.length + } + val end: Int = end_ + val startF: Int = start + val user = DiscordPlugin.dc.getUsers.filter((u) => u.getUsername.equals(event.getMessage.substring(startF + 1, mid))).filter((u) => u.getDiscriminator.equals(event.getMessage.substring(mid + 1, end))).blockFirst + if (user != null) { //TODO: Nicknames + event.setMessage(event.getMessage.substring(0, startF) + "@" + user.getUsername + (if (event.getMessage.length > end) { + event.getMessage.substring(end) + } + else { + "" + })) // TODO: Add formatting + } + start = end // Skip any @s inside the mention + } + } + + /** + * Stop the listener permanently. Enabling the module will create a new instance. + * + * @param wait Wait 5 seconds for the threads to stop + */ + def stop(wait: Boolean): Unit = { + stop = true + MCChatPrivate.logoutAll() + MCChatUtils.LoggedInPlayers.clear() + if (sendthread != null) { + sendthread.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) + } + } + MCChatUtils.lastmsgdata = null + MCChatPrivate.lastmsgPerUser.clear() + MCChatCustom.lastmsgCustom.clear() + MCChatUtils.lastmsgfromd.clear() + MCChatUtils.UnconnectedSenders.clear() + recthread = null + sendthread = null + } catch { + case e: InterruptedException => + e.printStackTrace() //This thread shouldn't be interrupted + } + } + + private var rectask: BukkitTask = null + final private val recevents: LinkedBlockingQueue[MessageCreateEvent] = new LinkedBlockingQueue[MessageCreateEvent] + private var recrun: Runnable = null + private var recthread: Thread = null + + // Discord + def handleDiscord(ev: MessageCreateEvent): Mono[Boolean] = { + val timings: Timings = CommonListeners.timings + timings.printElapsed("Chat event") + val author: Optional[User] = ev.getMessage.getAuthor + val hasCustomChat: Boolean = MCChatCustom.hasCustomChat(ev.getMessage.getChannelId) + val prefix: Char = DiscordPlugin.getPrefix + return ev.getMessage.getChannel.filter((channel: MessageChannel) => { + def foo(channel: MessageChannel) = { + timings.printElapsed("Filter 1") + return !((ev.getMessage.getChannelId.asLong != module.chatChannel.get.asLong && !((channel.isInstanceOf[PrivateChannel] && author.map((u: User) => MCChatPrivate.isMinecraftChatEnabled(u.getId.asString)).orElse(false))) && !(hasCustomChat))) //Chat isn't enabled on this channel + } + + foo(channel) + }).filter((channel: MessageChannel) => { + def foo(channel: MessageChannel) = { + timings.printElapsed("Filter 2") + return !((channel.isInstanceOf[PrivateChannel] //Only in private chat && ev.getMessage.getContent.length < "/mcchat<>".length && ev.getMessage.getContent.replace(prefix + "", "").equalsIgnoreCase("mcchat")))//Either mcchat or /mcchat + //Allow disabling the chat if needed + } + + foo(channel) + }).filterWhen((channel: MessageChannel) => CommandListener.runCommand(ev.getMessage, DiscordPlugin.plugin.commandChannel.get, true)).filter //Allow running commands in chat channels + ((channel: MessageChannel) => { + def foo(channel: MessageChannel) = { + MCChatUtils.resetLastMessage(channel) + recevents.add(ev) + timings.printElapsed("Message event added") + if (rectask != null) { + return true + } + recrun = () => { + def foo() = { //Don't return in a while loop next time + recthread = Thread.currentThread + processDiscordToMC() + if (DiscordPlugin.plugin.isEnabled && !(stop)) { + rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Continue message processing + } + } + + foo() + } + rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Start message processing + return true + } + + foo(channel) + }).map((b: MessageChannel) => false).defaultIfEmpty(true) + } + + private def processDiscordToMC(): Unit = { + var event: MessageCreateEvent = null + try event = recevents.take + catch { + case e1: InterruptedException => + rectask.cancel() + return + } + val sender: User = event.getMessage.getAuthor.orElse(null) + var dmessage: String = event.getMessage.getContent + try { + val dsender: DiscordSenderBase = MCChatUtils.getSender(event.getMessage.getChannelId, sender) + val user: DiscordPlayer = dsender.getChromaUser + + for (u <- event.getMessage.getUserMentions.toIterable) { //TODO: Role mentions + dmessage = dmessage.replace(u.getMention, "@" + u.getUsername) // TODO: IG Formatting + val m: Optional[Member] = u.asMember(DiscordPlugin.mainServer.getId).onErrorResume((t: Throwable) => Mono.empty).blockOptional + if (m.isPresent) { + val mm: Member = m.get + val nick: String = mm.getDisplayName + dmessage = dmessage.replace(mm.getNicknameMention, "@" + nick) + } + } + + for (ch <- event.getGuild.flux.flatMap(_.getChannels).toIterable) { + dmessage = dmessage.replace(ch.getMention, "#" + ch.getName) + } + dmessage = EmojiParser.parseToAliases(dmessage, EmojiParser.FitzpatrickAction.PARSE) //Converts emoji to text- TODO: Add option to disable (resource pack?) + dmessage = dmessage.replaceAll(":(\\S+)\\|type_(?:(\\d)|(1)_2):", ":$1::skin-tone-$2:") //Convert to Discord's format so it still shows up + dmessage = dmessage.replaceAll("", ":$1:") //We don't need info about the custom emojis, just display their text + val getChatMessage: Function[String, String] = (msg: String) => // + msg + (if (event.getMessage.getAttachments.size > 0) { + "\n" + event.getMessage.getAttachments.stream.map(_.getUrl).collect(Collectors.joining("\n")) + } + else { + "" + }) + val clmd: MCChatCustom.CustomLMD = MCChatCustom.getCustomChat(event.getMessage.getChannelId) + var react: Boolean = false + val sendChannel: MessageChannel = event.getMessage.getChannel.block + val isPrivate: Boolean = sendChannel.isInstanceOf[PrivateChannel] + if (dmessage.startsWith("/")) { // Ingame command + if (handleIngameCommand(event, dmessage, dsender, user, clmd, isPrivate)) { + return + } + } + else { // Not a command + react = handleIngameMessage(event, dmessage, dsender, user, getChatMessage, clmd, isPrivate) + } + if (react) { + try { + val lmfd: Message = MCChatUtils.lastmsgfromd.get(event.getMessage.getChannelId.asLong) + if (lmfd != null) { + lmfd.removeSelfReaction(DiscordPlugin.DELIVERED_REACTION).subscribe // Remove it no matter what, we know it's there 99.99% of the time + } + } catch { + case e: Exception => + TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e, module) + } + MCChatUtils.lastmsgfromd.put(event.getMessage.getChannelId.asLong, event.getMessage) + event.getMessage.addReaction(DiscordPlugin.DELIVERED_REACTION).subscribe + } + } catch { + case e: Exception => + TBMCCoreAPI.SendException("An error occured while handling message \"" + dmessage + "\"!", e, module) + } + } + + private def handleIngameMessage(event: MessageCreateEvent, dmessage: String, dsender: DiscordSenderBase, user: DiscordPlayer, getChatMessage: Function[String, String], clmd: MCChatCustom.CustomLMD, isPrivate: Boolean): Boolean = { + var react: Boolean = false + if (dmessage.isEmpty && event.getMessage.getAttachments.size == 0 && !(isPrivate) && (event.getMessage.getType eq Message.Type.CHANNEL_PINNED_MESSAGE)) { + val rtr: Channel.RecipientTestResult = if (clmd != null) { + clmd.mcchannel.getRTR(clmd.dcp) + } + else { + dsender.getChromaUser.channel.get.getRTR(dsender) + } + TBMCChatAPI.SendSystemMessage(if (clmd != null) clmd.mcchannel else dsender.getChromaUser.channel.get, rtr, + (dsender match { + case player: Player => + player.getDisplayName + case _ => + dsender.getName + }) + " pinned a message on Discord.", TBMCSystemChatEvent.BroadcastTarget.ALL) + } + else { + val cmb: ChatMessage.ChatMessageBuilder = ChatMessage.builder(dsender, user, getChatMessage.apply(dmessage)).fromCommand(false) + if (clmd != null) { + TBMCChatAPI.SendChatMessage(cmb.permCheck(clmd.dcp).build, clmd.mcchannel) + } + else { + TBMCChatAPI.SendChatMessage(cmb.build) + } + react = true + } + return react + } + + private def handleIngameCommand(event: MessageCreateEvent, dmessage: String, dsender: DiscordSenderBase, user: DiscordPlayer, clmd: MCChatCustom.CustomLMD, isPrivate: Boolean): Boolean = { + if (!(isPrivate)) { + event.getMessage.delete.subscribe + } + val cmd: String = dmessage.substring(1) + val cmdlowercased: String = cmd.toLowerCase + if (dsender.isInstanceOf[DiscordSender] && module.whitelistedCommands.get.stream.noneMatch((s: String) => cmdlowercased == s || cmdlowercased.startsWith(s + " "))) { // Command not whitelisted + dsender.sendMessage("Sorry, you can only access these commands from here:\n" + module.whitelistedCommands.get.stream.map((uc: String) => "/" + uc).collect(Collectors.joining(", ")) + (if (user.getConnectedID(classOf[TBMCPlayer]) == null) { + "\nTo access your commands, first please connect your accounts, using /connect in " + DPUtils.botmention + "\nThen y" + } + else { + "\nY" + }) + "ou can access all of your regular commands (even offline) in private chat: DM me `mcchat`!") + return true + } + module.log(dsender.getName + " ran from DC: /" + cmd) + if (dsender.isInstanceOf[DiscordSender] && runCustomCommand(dsender, cmdlowercased)) { + return true + } + val channel: Channel = if (clmd == null) { + user.channel.get + } + else { + clmd.mcchannel + } + val ev: TBMCCommandPreprocessEvent = new TBMCCommandPreprocessEvent(dsender, channel, dmessage, if (clmd == null) { + dsender + } + else { + clmd.dcp + }) + Bukkit.getScheduler.runTask(DiscordPlugin.plugin, //Commands need to be run sync + () => { + def foo(): Unit = { + Bukkit.getPluginManager.callEvent(ev) + if (ev.isCancelled) { + return + } + try { + val mcpackage: String = Bukkit.getServer.getClass.getPackage.getName + if (!(module.enableVanillaCommands.get)) { + Bukkit.dispatchCommand(dsender, cmd) + } + else { + if (mcpackage.contains("1_12")) { + VanillaCommandListener.runBukkitOrVanillaCommand(dsender, cmd) + } + else { + if (mcpackage.contains("1_14")) { + VanillaCommandListener14.runBukkitOrVanillaCommand(dsender, cmd) + } + else { + if (mcpackage.contains("1_15") || mcpackage.contains("1_16")) { + VanillaCommandListener15.runBukkitOrVanillaCommand(dsender, cmd) + } + else { + Bukkit.dispatchCommand(dsender, cmd) + } + } + } + } + } catch { + case e: NoClassDefFoundError => + TBMCCoreAPI.SendException("A class is not found when trying to run command " + cmd + "!", e, module) + case e: Exception => + TBMCCoreAPI.SendException("An error occurred when trying to run command " + cmd + "! Vanilla commands are only supported in some MC versions.", e, module) + } + } + + foo() + }) + return true + } + + private def runCustomCommand(dsender: DiscordSenderBase, cmdlowercased: String): Boolean = { + if (cmdlowercased.startsWith("list")) { + val players: util.Collection[_ <: Player] = Bukkit.getOnlinePlayers + dsender.sendMessage("There are " + players.stream.filter(MCChatUtils.checkEssentials).count + " out of " + Bukkit.getMaxPlayers + " players online.") + dsender.sendMessage("Players: " + players.stream.filter(MCChatUtils.checkEssentials).map(Player.getDisplayName).collect(Collectors.joining(", "))) + return true + } + return false + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java deleted file mode 100644 index 647aa0d..0000000 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.java +++ /dev/null @@ -1,76 +0,0 @@ -package buttondevteam.discordplugin.mcchat; - -import buttondevteam.core.ComponentManager; -import buttondevteam.discordplugin.DiscordConnectedPlayer; -import buttondevteam.discordplugin.DiscordPlayer; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.DiscordSenderBase; -import buttondevteam.lib.player.TBMCPlayer; -import discord4j.core.object.entity.User; -import discord4j.core.object.entity.channel.MessageChannel; -import discord4j.core.object.entity.channel.PrivateChannel; -import lombok.val; -import org.bukkit.Bukkit; - -import java.util.ArrayList; - -public class MCChatPrivate { - - /** - * Used for messages in PMs (mcchat). - */ - static ArrayList lastmsgPerUser = new ArrayList<>(); - - public static boolean privateMCChat(MessageChannel channel, boolean start, User user, DiscordPlayer dp) { - synchronized (MCChatUtils.ConnectedSenders) { - 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()); - val mcm = ComponentManager.getIfEnabled(MinecraftChatModule.class); - if (start) { - val sender = DiscordConnectedPlayer.create(user, channel, mcp.getUUID(), op.getName(), mcm); - MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender); - MCChatUtils.LoggedInPlayers.put(mcp.getUUID(), sender); - if (p == null) // Player is offline - If the player is online, that takes precedence - MCChatUtils.callLoginEvents(sender); - } else { - val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId(), user); - assert sender != null; - Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> { - if ((p == null || p instanceof DiscordSenderBase) // Player is offline - If the player is online, that takes precedence - && sender.isLoggedIn()) //Don't call the quit event if login failed - MCChatUtils.callLogoutEvent(sender, false); //The next line has to run *after* this one, so can't use the needsSync parameter - MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId()); - sender.setLoggedIn(false); - }); - } - } // ---- PermissionsEx warning is normal on logout ---- - if (!start) - MCChatUtils.lastmsgfromd.remove(channel.getId().asLong()); - return start // - ? lastmsgPerUser.add(new MCChatUtils.LastMsgData(channel, user)) // Doesn't support group DMs - : lastmsgPerUser.removeIf(lmd -> lmd.channel.getId().asLong() == channel.getId().asLong()); - } - } - - 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 -> ((PrivateChannel) lmd.channel) - .getRecipientIds().stream().anyMatch(u -> u.asString().equals(did))); - } - - public static void logoutAll() { - synchronized (MCChatUtils.ConnectedSenders) { - for (val entry : MCChatUtils.ConnectedSenders.entrySet()) - for (val valueEntry : entry.getValue().entrySet()) - if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey(), valueEntry.getValue().getUser()) == null) //If the player is online then the fake player was already logged out - MCChatUtils.callLogoutEvent(valueEntry.getValue(), !Bukkit.isPrimaryThread()); - } - } - -} diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala new file mode 100644 index 0000000..e2e023e --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala @@ -0,0 +1,76 @@ +package buttondevteam.discordplugin.mcchat + +import buttondevteam.core.ComponentManager +import buttondevteam.discordplugin.{DiscordConnectedPlayer, DiscordPlayer, DiscordPlugin, DiscordSenderBase} +import buttondevteam.lib.player.TBMCPlayer +import discord4j.common.util.Snowflake +import discord4j.core.`object`.entity.User +import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} +import org.bukkit.Bukkit + +import java.util + +object MCChatPrivate { + /** + * Used for messages in PMs (mcchat). + */ + private[mcchat] val lastmsgPerUser = new util.ArrayList[MCChatUtils.LastMsgData] + + def privateMCChat(channel: MessageChannel, start: Boolean, user: User, dp: DiscordPlayer): Unit = { + MCChatUtils.ConnectedSenders synchronized + val mcp = dp.getAs(classOf[TBMCPlayer]) + 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) + val mcm = ComponentManager.getIfEnabled(classOf[MinecraftChatModule]) + if (start) { + val sender = DiscordConnectedPlayer.create(user, channel, mcp.getUUID, op.getName, mcm) + MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender) + MCChatUtils.LoggedInPlayers.put(mcp.getUUID, sender) + if (p == null) { // Player is offline - If the player is online, that takes precedence + MCChatUtils.callLoginEvents(sender) + } + } + else { + val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId, user) + assert(sender != null) + Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => { + def foo(): Unit = { + if ((p == null || p.isInstanceOf[DiscordSenderBase]) // Player is offline - If the player is online, that takes precedence + && sender.isLoggedIn) { //Don't call the quit event if login failed + MCChatUtils.callLogoutEvent(sender, false) //The next line has to run *after* this one, so can't use the needsSync parameter + } + + MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId) + sender.setLoggedIn(false) + } + + foo() + } + ) + } + // ---- PermissionsEx warning is normal on logout ---- + } + if (!start) MCChatUtils.lastmsgfromd.remove(channel.getId.asLong) + if (start) lastmsgPerUser.add(new MCChatUtils.LastMsgData(channel, user)) // Doesn't support group DMs + else lastmsgPerUser.removeIf((lmd: MCChatUtils.LastMsgData) => lmd.channel.getId.asLong == channel.getId.asLong) + } + + def isMinecraftChatEnabled(dp: DiscordPlayer): Boolean = isMinecraftChatEnabled(dp.getDiscordID) + + def isMinecraftChatEnabled(did: String): Boolean = { // Don't load the player data just for this + lastmsgPerUser.stream.anyMatch((lmd: MCChatUtils.LastMsgData) => + lmd.channel.asInstanceOf[PrivateChannel].getRecipientIds.stream.anyMatch((u: Snowflake) => u.asString == did)) + } + + def logoutAll(): Unit = { + MCChatUtils.ConnectedSenders synchronized + for (entry <- MCChatUtils.ConnectedSenders.entrySet) { + for (valueEntry <- entry.getValue.entrySet) { + if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey, valueEntry.getValue.getUser) == null) { //If the player is online then the fake player was already logged out + MCChatUtils.callLogoutEvent(valueEntry.getValue, !Bukkit.isPrimaryThread) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java deleted file mode 100644 index 4319d3a..0000000 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.java +++ /dev/null @@ -1,409 +0,0 @@ -package buttondevteam.discordplugin.mcchat; - -import buttondevteam.core.ComponentManager; -import buttondevteam.core.MainPlugin; -import buttondevteam.discordplugin.*; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.TBMCSystemChatEvent; -import com.google.common.collect.Sets; -import discord4j.common.util.Snowflake; -import discord4j.core.object.entity.Message; -import discord4j.core.object.entity.User; -import discord4j.core.object.entity.channel.Channel; -import discord4j.core.object.entity.channel.MessageChannel; -import discord4j.core.object.entity.channel.PrivateChannel; -import discord4j.core.object.entity.channel.TextChannel; -import io.netty.util.collection.LongObjectHashMap; -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.bukkit.event.player.AsyncPlayerPreLoginEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerLoginEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.plugin.AuthorNagException; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.RegisteredListener; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; - -import javax.annotation.Nullable; -import java.net.InetAddress; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.logging.Level; -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 ConcurrentHashMap> UnconnectedSenders = new ConcurrentHashMap<>(); - public static final ConcurrentHashMap> ConnectedSenders = new ConcurrentHashMap<>(); - /** - * May contain P<DiscordID> as key for public chat - */ - public static final ConcurrentHashMap> OnlineSenders = new ConcurrentHashMap<>(); - public static final ConcurrentHashMap LoggedInPlayers = new ConcurrentHashMap<>(); - static @Nullable LastMsgData lastmsgdata; - static LongObjectHashMap lastmsgfromd = new LongObjectHashMap<>(); // Last message sent by a Discord user, used for clearing checkmarks - private static MinecraftChatModule module; - private static final HashMap, HashSet> staticExcludedPlugins = new HashMap<>(); - - public static void updatePlayerList() { - val mod = getModule(); - if (mod == null || !mod.showPlayerListOnDC.get()) return; - if (lastmsgdata != null) - updatePL(lastmsgdata); - MCChatCustom.lastmsgCustom.forEach(MCChatUtils::updatePL); - } - - private static boolean notEnabled() { - return (module == null || !module.disabling) && getModule() == null; //Allow using things while disabling the module - } - - private static MinecraftChatModule getModule() { - if (module == null || !module.isEnabled()) module = ComponentManager.getIfEnabled(MinecraftChatModule.class); - //If disabled, it will try to get it again because another instance may be enabled - useful for /discord restart - return module; - } - - private static void updatePL(LastMsgData lmd) { - if (!(lmd.channel instanceof TextChannel)) { - TBMCCoreAPI.SendException("Failed to update player list for channel " + lmd.channel.getId(), - new Exception("The channel isn't a (guild) text channel."), getModule()); - return; - } - String topic = ((TextChannel) lmd.channel).getTopic().orElse(""); - if (topic.length() == 0) - topic = ".\n----\nMinecraft chat\n----\n."; - String[] s = topic.split("\\n----\\n"); - if (s.length < 3) - return; - String gid; - if (lmd instanceof MCChatCustom.CustomLMD) - gid = ((MCChatCustom.CustomLMD) lmd).groupID; - else //If we're not using a custom chat then it's either can ("everyone") or can't (null) see at most - gid = buttondevteam.core.component.channel.Channel.GROUP_EVERYONE; // (Though it's a public chat then rn) - AtomicInteger C = new AtomicInteger(); - s[s.length - 1] = "Players: " + Bukkit.getOnlinePlayers().stream() - .filter(p -> (lmd.mcchannel == null - ? gid.equals(buttondevteam.core.component.channel.Channel.GROUP_EVERYONE) //If null, allow if public (custom chats will have their channel stored anyway) - : gid.equals(lmd.mcchannel.getGroupID(p)))) //If they can see it - .filter(MCChatUtils::checkEssentials) - .filter(p -> C.incrementAndGet() > 0) //Always true - .map(p -> DPUtils.sanitizeString(p.getDisplayName())).collect(Collectors.joining(", ")); - s[0] = C + " player" + (C.get() != 1 ? "s" : "") + " online"; - ((TextChannel) lmd.channel).edit(tce -> tce.setTopic(String.join("\n----\n", s)).setReason("Player list update")).subscribe(); //Don't wait - } - - static boolean checkEssentials(Player p) { - var ess = MainPlugin.ess; - if (ess == null) return true; - return !ess.getUser(p).isHidden(); - } - - public static T addSender(ConcurrentHashMap> senders, - User user, T sender) { - return addSender(senders, user.getId().asString(), sender); - } - - public static T addSender(ConcurrentHashMap> senders, - String did, T sender) { - var map = senders.get(did); - if (map == null) - map = new ConcurrentHashMap<>(); - map.put(sender.getChannel().getId(), sender); - senders.put(did, map); - return sender; - } - - public static T getSender(ConcurrentHashMap> senders, - Snowflake channel, User user) { - var map = senders.get(user.getId().asString()); - if (map != null) - return map.get(channel); - return null; - } - - public static T removeSender(ConcurrentHashMap> senders, - Snowflake channel, User user) { - var map = senders.get(user.getId().asString()); - if (map != null) - return map.remove(channel); - return null; - } - - public static Mono forPublicPrivateChat(Function, Mono> action) { - if (notEnabled()) return Mono.empty(); - var list = new ArrayList>(); - list.add(action.apply(module.chatChannelMono())); - for (LastMsgData data : MCChatPrivate.lastmsgPerUser) - list.add(action.apply(Mono.just(data.channel))); - // lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat - return Mono.whenDelayError(list); - } - - /** - * For custom and all MC chat - * - * @param action The action to act (cannot complete empty) - * @param toggle The toggle to check - * @param hookmsg Whether the message is also sent from the hook - */ - public static Mono forCustomAndAllMCChat(Function, Mono> action, @Nullable ChannelconBroadcast toggle, boolean hookmsg) { - if (notEnabled()) return Mono.empty(); - var list = new ArrayList>(); - if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg) - list.add(forPublicPrivateChat(action)); - final Function> customLMDFunction = cc -> action.apply(Mono.just(cc.channel)); - if (toggle == null) - MCChatCustom.lastmsgCustom.stream().map(customLMDFunction).forEach(list::add); - else - MCChatCustom.lastmsgCustom.stream().filter(cc -> (cc.toggles & toggle.flag) != 0).map(customLMDFunction).forEach(list::add); - return Mono.whenDelayError(list); - } - - /** - * 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 Mono forAllowedCustomMCChat(Function, Mono> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle) { - if (notEnabled()) return Mono.empty(); - Stream> st = 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)); - }).map(cc -> action.apply(Mono.just(cc.channel))); //TODO: Send error messages on channel connect - return Mono.whenDelayError(st::iterator); //Can't convert as an iterator or inside the stream, but I can convert it as a stream - } - - /** - * 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 Mono forAllowedCustomAndAllMCChat(Function, Mono> action, @Nullable CommandSender sender, @Nullable ChannelconBroadcast toggle, boolean hookmsg) { - if (notEnabled()) return Mono.empty(); - var cc = forAllowedCustomMCChat(action, sender, toggle); - if (!GeneralEventBroadcasterModule.isHooked() || !hookmsg) - return Mono.whenDelayError(forPublicPrivateChat(action), cc); - return Mono.whenDelayError(cc); - } - - public static Function, Mono> send(String message) { - return ch -> ch.flatMap(mc -> { - resetLastMessage(mc); - return mc.createMessage(DPUtils.sanitizeString(message)); - }); - } - - public static Mono forAllowedMCChat(Function, Mono> action, TBMCSystemChatEvent event) { - if (notEnabled()) return Mono.empty(); - var list = new ArrayList>(); - if (event.getChannel().isGlobal()) - list.add(action.apply(module.chatChannelMono())); - for (LastMsgData data : MCChatPrivate.lastmsgPerUser) - if (event.shouldSendTo(getSender(data.channel.getId(), data.user))) - list.add(action.apply(Mono.just(data.channel))); //TODO: Only store ID? - MCChatCustom.lastmsgCustom.stream().filter(clmd -> { - if (!clmd.brtoggles.contains(event.getTarget())) - return false; - return event.shouldSendTo(clmd.dcp); - }).map(clmd -> action.apply(Mono.just(clmd.channel))).forEach(list::add); - return Mono.whenDelayError(list); - } - - /** - * 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(Snowflake channel, final User 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, (MessageChannel) DiscordPlugin.dc.getChannelById(channel).block())))).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(Channel channel) { - if (notEnabled()) return; - if (channel.getId().asLong() == module.chatChannel.get().asLong()) { - (lastmsgdata == null ? lastmsgdata = new LastMsgData(module.chatChannelMono().block(), 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 instanceof PrivateChannel ? MCChatPrivate.lastmsgPerUser : MCChatCustom.lastmsgCustom) { - if (data.channel.getId().asLong() == channel.getId().asLong()) { - data.message = null; - return; - } - } - //If it gets here, it's sending a message to a non-chat channel - } - - public static void addStaticExcludedPlugin(Class event, String plugin) { - staticExcludedPlugins.compute(event, (e, hs) -> hs == null - ? Sets.newHashSet(plugin) - : (hs.add(plugin) ? hs : hs)); - } - - public static void callEventExcludingSome(Event event) { - if (notEnabled()) return; - val second = staticExcludedPlugins.get(event.getClass()); - String[] first = module.excludedPlugins.get(); - String[] both = second == null ? first - : Arrays.copyOf(first, first.length + second.size()); - int i = first.length; - if (second != null) - for (String plugin : second) - both[i++] = plugin; - callEventExcluding(event, false, both); - } - - /** - * 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); - } - } - } - - 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(); - - for (RegisteredListener registration : listeners) { - if (!registration.getPlugin().isEnabled() - || Arrays.stream(plugins).anyMatch(p -> only ^ 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); - } - } - } - - /** - * Call it from an async thread. - */ - public static void callLoginEvents(DiscordConnectedPlayer dcp) { - Consumer> loginFail = kickMsg -> { - dcp.sendMessage("Minecraft chat disabled, as the login failed: " + kickMsg.get()); - MCChatPrivate.privateMCChat(dcp.getChannel(), false, dcp.getUser(), dcp.getChromaUser()); - }; //Probably also happens if the user is banned or so - val event = new AsyncPlayerPreLoginEvent(dcp.getName(), InetAddress.getLoopbackAddress(), dcp.getUniqueId()); - callEventExcludingSome(event); - if (event.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { - loginFail.accept(event::getKickMessage); - return; - } - Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> { - val ev = new PlayerLoginEvent(dcp, "localhost", InetAddress.getLoopbackAddress()); - callEventExcludingSome(ev); - if (ev.getResult() != PlayerLoginEvent.Result.ALLOWED) { - loginFail.accept(ev::getKickMessage); - return; - } - callEventExcludingSome(new PlayerJoinEvent(dcp, "")); - dcp.setLoggedIn(true); - if (module != null) { - if (module.serverWatcher != null) - module.serverWatcher.fakePlayers.add(dcp); - module.log(dcp.getName() + " (" + dcp.getUniqueId() + ") logged in from Discord"); - } - }); - } - - /** - * Only calls the events if the player is actually logged in - * - * @param dcp The player - * @param needsSync Whether we're in an async thread - */ - public static void callLogoutEvent(DiscordConnectedPlayer dcp, boolean needsSync) { - if (!dcp.isLoggedIn()) return; - val event = new PlayerQuitEvent(dcp, ""); - if (needsSync) callEventSync(event); - else callEventExcludingSome(event); - dcp.setLoggedIn(false); - if (module != null) { - module.log(dcp.getName() + " (" + dcp.getUniqueId() + ") logged out from Discord"); - if (module.serverWatcher != null) - module.serverWatcher.fakePlayers.remove(dcp); - } - } - - static void callEventSync(Event event) { - Bukkit.getScheduler().runTask(DiscordPlugin.plugin, () -> callEventExcludingSome(event)); - } - - @RequiredArgsConstructor - public static class LastMsgData { - public Message message; - public long time; - public String content; - public final MessageChannel channel; - public buttondevteam.core.component.channel.Channel mcchannel; - public final User user; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala new file mode 100644 index 0000000..e06a34d --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala @@ -0,0 +1,369 @@ +package buttondevteam.discordplugin.mcchat + +import buttondevteam.core.{ComponentManager, MainPlugin, component} +import buttondevteam.discordplugin._ +import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule +import buttondevteam.discordplugin.mcchat.MCChatCustom.CustomLMD +import buttondevteam.lib.{TBMCCoreAPI, TBMCSystemChatEvent} +import com.google.common.collect.Sets +import discord4j.common.util.Snowflake +import discord4j.core.`object`.entity.channel.{Channel, MessageChannel, PrivateChannel, TextChannel} +import discord4j.core.`object`.entity.{Message, User} +import discord4j.core.spec.TextChannelEditSpec +import io.netty.util.collection.LongObjectHashMap +import org.bukkit.Bukkit +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player +import org.bukkit.event.Event +import org.bukkit.event.player.{AsyncPlayerPreLoginEvent, PlayerJoinEvent, PlayerLoginEvent, PlayerQuitEvent} +import org.bukkit.plugin.AuthorNagException +import org.reactivestreams.Publisher +import reactor.core.publisher.Mono + +import java.net.InetAddress +import java.util +import java.util._ +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicInteger +import java.util.function.Supplier +import java.util.logging.Level +import java.util.stream.{Collectors, Stream} +import javax.annotation.Nullable + +object MCChatUtils { + /** + * May contain P<DiscordID> as key for public chat + */ + val UnconnectedSenders = new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordSender]] + val ConnectedSenders = new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordConnectedPlayer]] + val OnlineSenders = new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordPlayerSender]] + val LoggedInPlayers = new ConcurrentHashMap[UUID, DiscordConnectedPlayer] + @Nullable private[mcchat] var lastmsgdata: MCChatUtils.LastMsgData = null + private[mcchat] val lastmsgfromd = new LongObjectHashMap[Message] // Last message sent by a Discord user, used for clearing checkmarks + private var module: MinecraftChatModule = null + private val staticExcludedPlugins = new util.HashMap[Class[_ <: Event], util.HashSet[String]] + + def updatePlayerList(): Unit = { + val mod = getModule + if (mod == null || !mod.showPlayerListOnDC.get) return + if (lastmsgdata != null) updatePL(lastmsgdata) + MCChatCustom.lastmsgCustom.forEach(MCChatUtils.updatePL) + } + + private def notEnabled = (module == null || !module.disabling) && getModule == null //Allow using things while disabling the module + private def getModule = { + if (module == null || !module.isEnabled) module = ComponentManager.getIfEnabled(classOf[MinecraftChatModule]) + //If disabled, it will try to get it again because another instance may be enabled - useful for /discord restart + module + } + + private def updatePL(lmd: MCChatUtils.LastMsgData): Unit = { + if (!lmd.channel.isInstanceOf[TextChannel]) { + TBMCCoreAPI.SendException("Failed to update player list for channel " + lmd.channel.getId, new Exception("The channel isn't a (guild) text channel."), getModule) + return + } + var topic = lmd.channel.asInstanceOf[TextChannel].getTopic.orElse("") + if (topic.isEmpty) topic = ".\n----\nMinecraft chat\n----\n." + val s = topic.split("\\n----\\n") + if (s.length < 3) return + var gid: String = null + lmd match { + case clmd: CustomLMD => gid = clmd.groupID + case _ => //If we're not using a custom chat then it's either can ("everyone") or can't (null) see at most + gid = buttondevteam.core.component.channel.Channel.GROUP_EVERYONE // (Though it's a public chat then rn) + } + val C = new AtomicInteger + s(s.length - 1) = "Players: " + Bukkit.getOnlinePlayers.stream.filter(p => if (lmd.mcchannel == null) { + gid == buttondevteam.core.component.channel.Channel.GROUP_EVERYONE //If null, allow if public (custom chats will have their channel stored anyway) + } + else { + gid == lmd.mcchannel.getGroupID(p) + } + ).filter(MCChatUtils.checkEssentials) //If they can see it + .filter(_ => C.incrementAndGet > 0) //Always true + .map((p) => DPUtils.sanitizeString(p.getDisplayName)).collect(Collectors.joining(", ")) + s(0) = C + " player" + (if (C.get != 1) "s" else "") + " online" + lmd.channel.asInstanceOf[TextChannel].edit((tce: TextChannelEditSpec) => tce.setTopic(String.join("\n----\n", s)).setReason("Player list update")).subscribe //Don't wait + } + + private[mcchat] def checkEssentials(p: Player): Boolean = { + val ess = MainPlugin.ess + if (ess == null) return true + !ess.getUser(p).isHidden + } + + def addSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], user: User, sender: T): T = + addSender(senders, user.getId.asString, sender) + + def addSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], did: String, sender: T): T = { + var map = senders.get(did) + if (map == null) map = new ConcurrentHashMap[Snowflake, T] + map.put(sender.getChannel.getId, sender) + senders.put(did, map) + sender + } + + def getSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = { + val map = senders.get(user.getId.asString) + if (map != null) return map.get(channel) + null + } + + def removeSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = { + val map = senders.get(user.getId.asString) + if (map != null) return map.remove(channel) + null + } + + def forPublicPrivateChat(action: Mono[MessageChannel] => Mono[_]): Mono[_] = { + if (notEnabled) return Mono.empty + val list = new util.ArrayList[Mono[_]] + list.add(action.apply(module.chatChannelMono)) + for (data <- MCChatPrivate.lastmsgPerUser) { + list.add(action.apply(Mono.just(data.channel))) + } + // lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat + Mono.whenDelayError(list) + } + + /** + * For custom and all MC chat + * + * @param action The action to act (cannot complete empty) + * @param toggle The toggle to check + * @param hookmsg Whether the message is also sent from the hook + */ + def forCustomAndAllMCChat(action: Mono[MessageChannel] => Mono[_], @Nullable toggle: ChannelconBroadcast, hookmsg: Boolean): Mono[_] = { + if (notEnabled) return Mono.empty + val list = new util.ArrayList[Publisher[_]] + if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) list.add(forPublicPrivateChat(action)) + val customLMDFunction = (cc: MCChatCustom.CustomLMD) => action.apply(Mono.just(cc.channel)) + if (toggle == null) MCChatCustom.lastmsgCustom.stream.map(customLMDFunction).forEach(list.add(_)) + else MCChatCustom.lastmsgCustom.stream.filter((cc) => (cc.toggles & toggle.flag) ne 0).map(customLMDFunction).forEach(list.add(_)) + Mono.whenDelayError(list) + } + + /** + * 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 + */ + def forAllowedCustomMCChat(action: Mono[MessageChannel] => Mono[_], @Nullable sender: CommandSender, @Nullable toggle: ChannelconBroadcast): Mono[_] = { + if (notEnabled) return Mono.empty + val st = MCChatCustom.lastmsgCustom.stream.filter((clmd) => { + def foo(clmd: CustomLMD): Boolean = { //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) eq 0)) return false //If null then allow + if (sender == null) return true + clmd.groupID.equals(clmd.mcchannel.getGroupID(sender)) + } + + foo(clmd) + }).map((cc) => action.apply(Mono.just(cc.channel))) //TODO: Send error messages on channel connect + Mono.whenDelayError(st.iterator) //Can't convert as an iterator or inside the stream, but I can convert it as a stream + } + + /** + * 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 + */ + def forAllowedCustomAndAllMCChat(action: Mono[MessageChannel] => Mono[_], @Nullable sender: CommandSender, @Nullable toggle: ChannelconBroadcast, hookmsg: Boolean): Mono[_] = { + if (notEnabled) return Mono.empty + val cc = forAllowedCustomMCChat(action, sender, toggle) + if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) return Mono.whenDelayError(forPublicPrivateChat(action), cc) + Mono.whenDelayError(cc) + } + + def send(message: String): Mono[MessageChannel] => Mono[_] = (ch: Mono[MessageChannel]) => ch.flatMap((mc: MessageChannel) => { + def foo(mc: MessageChannel) = { + resetLastMessage(mc) + mc.createMessage(DPUtils.sanitizeString(message)) + } + + foo(mc) + }) + + def forAllowedMCChat(action: Mono[MessageChannel] => Mono[_], event: TBMCSystemChatEvent): Mono[_] = { + if (notEnabled) return Mono.empty + val list = new util.ArrayList[Mono[_]] + if (event.getChannel.isGlobal) list.add(action.apply(module.chatChannelMono)) + for (data <- MCChatPrivate.lastmsgPerUser) + if (event.shouldSendTo(getSender(data.channel.getId, data.user))) list.add(action.apply(Mono.just(data.channel))) //TODO: Only store ID?} + MCChatCustom.lastmsgCustom.stream.filter((clmd) => { + def foo(clmd: CustomLMD): Boolean = { + clmd.brtoggles.contains(event.getTarget) && event.shouldSendTo(clmd.dcp) + } + + foo(clmd) + }).map((clmd) => action.apply(Mono.just(clmd.channel))).forEach(list.add(_)) + Mono.whenDelayError(list) + } + + /** + * 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[mcchat] def getSender(channel: Snowflake, author: User) = { //noinspection OptionalGetWithoutIsPresent + Stream.of[Supplier[Optional[DiscordSenderBase]]]( // 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, DiscordPlugin.dc.getChannelById(channel).block.asInstanceOf[MessageChannel])))).map(_.get).filter(_.isPresent).map(_.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 + */ + def resetLastMessage(channel: Channel): Unit = { + if (notEnabled) return + if (channel.getId.asLong == module.chatChannel.get.asLong) { + if (lastmsgdata == null) lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono.block, null) + else lastmsgdata.message = null + return + } // Don't set the whole object to null, the player and channel information should be preserved + for (data <- if (channel.isInstanceOf[PrivateChannel]) MCChatPrivate.lastmsgPerUser + else MCChatCustom.lastmsgCustom) { + if (data.channel.getId.asLong == channel.getId.asLong) { + data.message = null + return + } + } + //If it gets here, it's sending a message to a non-chat channel + } + + def addStaticExcludedPlugin(event: Class[_ <: Event], plugin: String): util.HashSet[String] = + staticExcludedPlugins.compute(event, (e: Class[_ <: Event], hs: util.HashSet[String]) => + if (hs == null) Sets.newHashSet(plugin) else if (hs.add(plugin)) hs else hs) + + def callEventExcludingSome(event: Event): Unit = { + if (notEnabled) return + val second = staticExcludedPlugins.get(event.getClass) + val first = module.excludedPlugins.get + val both = if (second == null) first + else util.Arrays.copyOf(first, first.length + second.size) + var i = first.length + if (second != null) { + for (plugin <- second) { + both({ + i += 1; + i - 1 + }) = plugin + } + } + callEventExcluding(event, false, both) + } + + /** + * 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. + */ + def callEventExcluding(event: Event, only: Boolean, plugins: String*): Unit = { // 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 Bukkit.getPluginManager synchronized fireEventExcluding(event, only, plugins) + } + + private def fireEventExcluding(event: Event, only: Boolean, plugins: String*): Unit = { + val handlers = event.getHandlers // Code taken from SimplePluginManager in Spigot-API + val listeners = handlers.getRegisteredListeners + val server = Bukkit.getServer + for (registration <- listeners) { + if (!registration.getPlugin.isEnabled || util.Arrays.stream(plugins).anyMatch((p: String) => only ^ p.equalsIgnoreCase(registration.getPlugin.getName))) { + continue //todo: continue is not supported + // Modified to exclude plugins + } + try registration.callEvent(event) + catch { + case ex: AuthorNagException => + val 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)) + } + case ex: Throwable => + server.getLogger.log(Level.SEVERE, "Could not pass event " + event.getEventName + " to " + registration.getPlugin.getDescription.getFullName, ex) + } + } + } + + /** + * Call it from an async thread. + */ + def callLoginEvents(dcp: DiscordConnectedPlayer): Unit = { + val loginFail = (kickMsg: String) => { + def foo(kickMsg: String): Unit = { + dcp.sendMessage("Minecraft chat disabled, as the login failed: " + kickMsg) + MCChatPrivate.privateMCChat(dcp.getChannel, start = false, dcp.getUser, dcp.getChromaUser) + } + + foo(kickMsg) + } //Probably also happens if the user is banned or so + val event = new AsyncPlayerPreLoginEvent(dcp.getName, InetAddress.getLoopbackAddress, dcp.getUniqueId) + callEventExcludingSome(event) + if (event.getLoginResult ne AsyncPlayerPreLoginEvent.Result.ALLOWED) { + loginFail(event.getKickMessage) + return + } + Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => { + def foo(): Unit = { + val ev = new PlayerLoginEvent(dcp, "localhost", InetAddress.getLoopbackAddress) + callEventExcludingSome(ev) + if (ev.getResult ne PlayerLoginEvent.Result.ALLOWED) { + loginFail(ev.getKickMessage) + return + } + callEventExcludingSome(new PlayerJoinEvent(dcp, "")) + dcp.setLoggedIn(true) + if (module != null) { + if (module.serverWatcher != null) module.serverWatcher.fakePlayers.add(dcp) + module.log(dcp.getName + " (" + dcp.getUniqueId + ") logged in from Discord") + } + } + + foo() + }) + } + + /** + * Only calls the events if the player is actually logged in + * + * @param dcp The player + * @param needsSync Whether we're in an async thread + */ + def callLogoutEvent(dcp: DiscordConnectedPlayer, needsSync: Boolean): Unit = { + if (!dcp.isLoggedIn) return + val event = new PlayerQuitEvent(dcp, "") + if (needsSync) callEventSync(event) + else callEventExcludingSome(event) + dcp.setLoggedIn(false) + if (module != null) { + module.log(dcp.getName + " (" + dcp.getUniqueId + ") logged out from Discord") + if (module.serverWatcher != null) module.serverWatcher.fakePlayers.remove(dcp) + } + } + + private[mcchat] def callEventSync(event: Event) = Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => callEventExcludingSome(event)) + + class LastMsgData(val channel: MessageChannel, val user: User) { + var message: String = null + var time = 0L + var content: String = null + var mcchannel: component.channel.Channel = null + } + +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java deleted file mode 100644 index f76c5a9..0000000 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.java +++ /dev/null @@ -1,187 +0,0 @@ -package buttondevteam.discordplugin.mcchat; - -import buttondevteam.discordplugin.*; -import buttondevteam.lib.TBMCSystemChatEvent; -import buttondevteam.lib.architecture.ConfigData; -import buttondevteam.lib.player.TBMCPlayer; -import buttondevteam.lib.player.TBMCPlayerBase; -import buttondevteam.lib.player.TBMCYEEHAWEvent; -import com.earth2me.essentials.CommandSource; -import discord4j.common.util.Snowflake; -import discord4j.core.object.entity.Role; -import lombok.val; -import net.ess3.api.events.AfkStatusChangeEvent; -import net.ess3.api.events.MuteStatusChangeEvent; -import net.ess3.api.events.NickChangeEvent; -import net.ess3.api.events.VanishStatusChangeEvent; -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.*; -import org.bukkit.event.player.PlayerLoginEvent.Result; -import org.bukkit.event.server.BroadcastMessageEvent; -import org.bukkit.event.server.TabCompleteEvent; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.util.Optional; - -class MCListener implements Listener { - private final MinecraftChatModule module; - private final ConfigData> muteRole; - - public MCListener(MinecraftChatModule module) { - this.module = module; - muteRole = DPUtils.roleData(module.getConfig(), "muteRole", "Muted"); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onPlayerLogin(PlayerLoginEvent e) { - if (e.getResult() != Result.ALLOWED) - return; - if (e.getPlayer() instanceof DiscordConnectedPlayer) - return; - var dcp = MCChatUtils.LoggedInPlayers.get(e.getPlayer().getUniqueId()); - if (dcp != null) - MCChatUtils.callLogoutEvent(dcp, false); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerJoin(PlayerJoinEvent 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 = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class).getAs(DiscordPlayer.class); - if (dp != null) { - DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID())).flatMap(user -> user.getPrivateChannel().flatMap(chan -> module.chatChannelMono().flatMap(cc -> { - MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(), - DiscordPlayerSender.create(user, chan, p, module)); - MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID(), - DiscordPlayerSender.create(user, cc, p, module)); //Stored per-channel - return Mono.empty(); - }))).subscribe(); - } - final String message = e.getJoinMessage(); - if (message != null && message.trim().length() > 0) - MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe(); - ChromaBot.updatePlayerList(); - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerLeave(PlayerQuitEvent 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().runTaskAsynchronously(DiscordPlugin.plugin, - () -> Optional.ofNullable(MCChatUtils.LoggedInPlayers.get(e.getPlayer().getUniqueId())).ifPresent(MCChatUtils::callLoginEvents)); - Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, - ChromaBot::updatePlayerList, 5); - final String message = e.getQuitMessage(); - if (message != null && message.trim().length() > 0) - MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), e.getPlayer(), ChannelconBroadcast.JOINLEAVE, true).subscribe(); - } - - @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).subscribe(); - } - - @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).subscribe(); - } - - @EventHandler - public void onPlayerMute(MuteStatusChangeEvent e) { - final Mono role = muteRole.get(); - if (role == null) return; - 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; - DPUtils.ignoreError(DiscordPlugin.dc().getUserById(Snowflake.of(p.getDiscordID())) - .flatMap(user -> user.asMember(DiscordPlugin.mainServer.getId())) - .flatMap(user -> role.flatMap(r -> { - if (e.getValue()) - user.addRole(r.getId()); - else - user.removeRole(r.getId()); - val modlog = module.modlogChannel.get(); - String msg = (e.getValue() ? "M" : "Unm") + "uted user: " + user.getUsername() + "#" + user.getDiscriminator(); - module.log(msg); - if (modlog != null) - return modlog.flatMap(ch -> ch.createMessage(msg)); - return Mono.empty(); - }))).subscribe(); - } - - @EventHandler - public void onChatSystemMessage(TBMCSystemChatEvent event) { - MCChatUtils.forAllowedMCChat(MCChatUtils.send(event.getMessage()), event).subscribe(); - } - - @EventHandler - public void onBroadcastMessage(BroadcastMessageEvent event) { - MCChatUtils.forCustomAndAllMCChat(MCChatUtils.send(event.getMessage()), ChannelconBroadcast.BROADCAST, false).subscribe(); - } - - @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 - DiscordPlugin.mainServer.getEmojis().filter(e -> "YEEHAW".equals(e.getName())) - .take(1).singleOrEmpty().map(Optional::of).defaultIfEmpty(Optional.empty()).flatMap(yeehaw -> - MCChatUtils.forPublicPrivateChat(MCChatUtils.send(name + (yeehaw.map(guildEmoji -> " <:YEEHAW:" + guildEmoji.getId().asString() + ">s").orElse(" YEEHAWs"))))).subscribe(); - } - - @EventHandler - public void onNickChange(NickChangeEvent event) { - MCChatUtils.updatePlayerList(); - } - - @EventHandler - public void onTabComplete(TabCompleteEvent event) { - int i = event.getBuffer().lastIndexOf(' '); - String t = event.getBuffer().substring(i + 1); //0 if not found - if (!t.startsWith("@")) - return; - String token = t.substring(1); - val x = DiscordPlugin.mainServer.getMembers() - .flatMap(m -> Flux.just(m.getUsername(), m.getNickname().orElse(""))) - .filter(s -> s.startsWith(token)) - .map(s -> "@" + s) - .doOnNext(event.getCompletions()::add).blockLast(); - } - - @EventHandler - public void onCommandSend(PlayerCommandSendEvent event) { - event.getCommands().add("g"); - } - - @EventHandler - public void onVanish(VanishStatusChangeEvent event) { - if (event.isCancelled()) return; - Bukkit.getScheduler().runTask(DiscordPlugin.plugin, MCChatUtils::updatePlayerList); - } -} diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala new file mode 100644 index 0000000..57b1e14 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala @@ -0,0 +1,143 @@ +package buttondevteam.discordplugin.mcchat + +import buttondevteam.discordplugin._ +import buttondevteam.lib.TBMCSystemChatEvent +import buttondevteam.lib.player.{TBMCPlayer, TBMCPlayerBase, TBMCYEEHAWEvent} +import discord4j.common.util.Snowflake +import discord4j.core.`object`.entity.channel.MessageChannel +import discord4j.core.`object`.entity.{Member, Role, User} +import net.ess3.api.events.{AfkStatusChangeEvent, MuteStatusChangeEvent, NickChangeEvent, VanishStatusChangeEvent} +import org.bukkit.Bukkit +import org.bukkit.entity.Player +import org.bukkit.event.{EventHandler, EventPriority, Listener} +import org.bukkit.event.entity.PlayerDeathEvent +import org.bukkit.event.player.PlayerLoginEvent.Result +import org.bukkit.event.player._ +import org.bukkit.event.server.{BroadcastMessageEvent, TabCompleteEvent} +import reactor.core.publisher.{Flux, Mono} + +import java.util.Optional + +class MCListener(val module: MinecraftChatModule) extends Listener { + final private val muteRole = DPUtils.roleData(module.getConfig, "muteRole", "Muted") + + @EventHandler(priority = EventPriority.HIGHEST) def onPlayerLogin(e: PlayerLoginEvent): Unit = { + if (e.getResult ne Result.ALLOWED) return + if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return + val dcp = MCChatUtils.LoggedInPlayers.get(e.getPlayer.getUniqueId) + if (dcp != null) MCChatUtils.callLogoutEvent(dcp, needsSync = false) + } + + @EventHandler(priority = EventPriority.MONITOR) def onPlayerJoin(e: PlayerJoinEvent): Unit = { + if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return // Don't show the joined message for the fake player + Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, () => { + def foo(): Unit = { + val p = e.getPlayer + val dp = TBMCPlayerBase.getPlayer(p.getUniqueId, classOf[TBMCPlayer]).getAs(classOf[DiscordPlayer]) + if (dp != null) DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID)).flatMap((user) => user.getPrivateChannel.flatMap((chan) => module.chatChannelMono.flatMap((cc: MessageChannel) => { + def foo(cc: MessageChannel) = { + MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, chan, p, module)) + MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, cc, p, module)) //Stored per-channel + Mono.empty + } + + foo(cc) + }))).subscribe + val message = e.getJoinMessage + sendJoinLeaveMessage(message, e.getPlayer) + ChromaBot.updatePlayerList() + } + + foo() + }) + } + + private def sendJoinLeaveMessage(message: String, player: Player): Unit = + if (message != null && message.trim.nonEmpty) + MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), player, ChannelconBroadcast.JOINLEAVE, hookmsg = true).subscribe + + @EventHandler(priority = EventPriority.MONITOR) def onPlayerLeave(e: PlayerQuitEvent): Unit = { + if (e.getPlayer.isInstanceOf[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.runTaskAsynchronously(DiscordPlugin.plugin, () => Optional.ofNullable(MCChatUtils.LoggedInPlayers.get(e.getPlayer.getUniqueId)).ifPresent(MCChatUtils.callLoginEvents)) + Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => ChromaBot.updatePlayerList(), 5) + val message = e.getQuitMessage + sendJoinLeaveMessage(message, e.getPlayer) + } + + @EventHandler(priority = EventPriority.HIGHEST) def onPlayerKick(e: PlayerKickEvent): Unit = { + /*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) def onPlayerDeath(e: PlayerDeathEvent): Unit = + MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(e.getDeathMessage), e.getEntity, ChannelconBroadcast.DEATH, hookmsg = true).subscribe + + @EventHandler def onPlayerAFK(e: AfkStatusChangeEvent): Unit = { + val base = e.getAffected.getBase + if (e.isCancelled || !base.isOnline) return + val msg = base.getDisplayName + " is " + (if (e.getValue) "now" + else "no longer") + " AFK." + MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, hookmsg = false).subscribe + } + + @EventHandler def onPlayerMute(e: MuteStatusChangeEvent): Unit = { + val role = muteRole.get + if (role == null) return + val source = e.getAffected.getSource + if (!source.isPlayer) return + val p = TBMCPlayerBase.getPlayer(source.getPlayer.getUniqueId, classOf[TBMCPlayer]).getAs(classOf[DiscordPlayer]) + if (p == null) return + DPUtils.ignoreError(DiscordPlugin.dc.getUserById(Snowflake.of(p.getDiscordID)).flatMap((user: User) => user.asMember(DiscordPlugin.mainServer.getId)).flatMap((user: Member) => role.flatMap((r: Role) => { + def foo(r: Role): Mono[_] = { + if (e.getValue) user.addRole(r.getId) + else user.removeRole(r.getId) + val modlog = module.modlogChannel.get + val msg = (if (e.getValue) "M" + else "Unm") + "uted user: " + user.getUsername + "#" + user.getDiscriminator + module.log(msg) + if (modlog != null) return modlog.flatMap((ch: MessageChannel) => ch.createMessage(msg)) + Mono.empty + } + + foo(r) + }))).subscribe + } + + @EventHandler def onChatSystemMessage(event: TBMCSystemChatEvent): Unit = + MCChatUtils.forAllowedMCChat(MCChatUtils.send(event.getMessage), event).subscribe + + @EventHandler def onBroadcastMessage(event: BroadcastMessageEvent): Unit = + MCChatUtils.forCustomAndAllMCChat(MCChatUtils.send(event.getMessage), ChannelconBroadcast.BROADCAST, hookmsg = false).subscribe + + @EventHandler def onYEEHAW(event: TBMCYEEHAWEvent): Unit = { //TODO: Inherit from the chat event base to have channel support + val name = event.getSender match { + case player: Player => player.getDisplayName + case _ => event.getSender.getName + } + //Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO + DiscordPlugin.mainServer.getEmojis.filter(e => "YEEHAW" == e.getName).take(1).singleOrEmpty + .map(Optional.of(_)).defaultIfEmpty(Optional.empty) + .flatMap((yeehaw) => MCChatUtils.forPublicPrivateChat(MCChatUtils.send(name + + yeehaw.map((guildEmoji) => " <:YEEHAW:" + guildEmoji.getId.asString + ">s").orElse(" YEEHAWs")))).subscribe + } + + @EventHandler def onNickChange(event: NickChangeEvent): Unit = MCChatUtils.updatePlayerList() + + @EventHandler def onTabComplete(event: TabCompleteEvent): Unit = { + val i = event.getBuffer.lastIndexOf(' ') + val t = event.getBuffer.substring(i + 1) //0 if not found + if (!t.startsWith("@")) return + val token = t.substring(1) + val x = DiscordPlugin.mainServer.getMembers.flatMap((m) => Flux.just(m.getUsername, m.getNickname.orElse(""))) + .filter((s) => s.startsWith(token)).map((s) => "@" + s).doOnNext(event.getCompletions.add(_)).blockLast + } + + @EventHandler def onCommandSend(event: PlayerCommandSendEvent): Boolean = event.getCommands.add("g") + + @EventHandler def onVanish(event: VanishStatusChangeEvent): Unit = { + if (event.isCancelled) return + Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => MCChatUtils.updatePlayerList()) + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java deleted file mode 100644 index 243cf83..0000000 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.java +++ /dev/null @@ -1,261 +0,0 @@ -package buttondevteam.discordplugin.mcchat; - -import buttondevteam.core.component.channel.Channel; -import buttondevteam.discordplugin.ChannelconBroadcast; -import buttondevteam.discordplugin.DPUtils; -import buttondevteam.discordplugin.DiscordConnectedPlayer; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.playerfaker.ServerWatcher; -import buttondevteam.discordplugin.playerfaker.perm.LPInjector; -import buttondevteam.discordplugin.util.DPState; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.TBMCSystemChatEvent; -import buttondevteam.lib.architecture.Component; -import buttondevteam.lib.architecture.ConfigData; -import buttondevteam.lib.architecture.ReadOnlyConfigData; -import com.google.common.collect.Lists; -import discord4j.common.util.Snowflake; -import discord4j.core.object.entity.channel.MessageChannel; -import discord4j.rest.util.Color; -import lombok.Getter; -import lombok.val; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import reactor.core.publisher.Mono; - -import java.util.ArrayList; -import java.util.Objects; -import java.util.UUID; -import java.util.stream.Collectors; - -/** - * Provides Minecraft chat connection to Discord. Commands may be used either in a public chat (limited) or in a DM. - */ -public class MinecraftChatModule extends Component { - public static DPState state = DPState.RUNNING; - private @Getter MCChatListener listener; - ServerWatcher serverWatcher; - private LPInjector lpInjector; - boolean disabling = false; - - /** - * A list of commands that can be used in public chats - Warning: Some plugins will treat players as OPs, always test before allowing a command! - */ - public ConfigData> whitelistedCommands() { - return getConfig().getData("whitelistedCommands", () -> Lists.newArrayList("list", "u", "shrug", "tableflip", "unflip", "mwiki", - "yeehaw", "lenny", "rp", "plugins")); - } - - /** - * The channel to use as the public Minecraft chat - everything public gets broadcasted here - */ - public ReadOnlyConfigData chatChannel = DPUtils.snowflakeData(getConfig(), "chatChannel", 0L); - - public Mono chatChannelMono() { - return DPUtils.getMessageChannel(chatChannel.getPath(), chatChannel.get()); - } - - /** - * The channel where the plugin can log when it mutes a player on Discord because of a Minecraft mute - */ - public ReadOnlyConfigData> modlogChannel = DPUtils.channelData(getConfig(), "modlogChannel"); - - /** - * The plugins to exclude from fake player events used for the 'mcchat' command - some plugins may crash, add them here - */ - public ConfigData excludedPlugins = getConfig().getData("excludedPlugins", new String[]{"ProtocolLib", "LibsDisguises", "JourneyMapServer"}); - - /** - * If this setting is on then players logged in through the 'mcchat' command will be able to teleport using plugin commands. - * They can then use commands like /tpahere to teleport others to that place.
- * If this is off, then teleporting will have no effect. - */ - public ConfigData allowFakePlayerTeleports = getConfig().getData("allowFakePlayerTeleports", false); - - /** - * If this is on, each chat channel will have a player list in their description. - * It only gets added if there's no description yet or there are (at least) two lines of "----" following each other. - * Note that it will replace everything above the first and below the last "----" but it will only detect exactly four dashes. - * So if you want to use dashes for something else in the description, make sure it's either less or more dashes in one line. - */ - public ConfigData showPlayerListOnDC = getConfig().getData("showPlayerListOnDC", true); - - /** - * This setting controls whether custom chat connections can be created (existing connections will always work). - * Custom chat connections can be created using the channelcon command and they allow players to display town chat in a Discord channel for example. - * See the channelcon command for more details. - */ - public ConfigData allowCustomChat = getConfig().getData("allowCustomChat", true); - - /** - * This setting allows you to control if players can DM the bot to log on the server from Discord. - * This allows them to both chat and perform any command they can in-game. - */ - public ConfigData allowPrivateChat = getConfig().getData("allowPrivateChat", true); - - /** - * If set, message authors appearing on Discord will link to this URL. A 'type' and 'id' parameter will be added with the user's platform (Discord, Minecraft, ...) and ID. - */ - public ConfigData profileURL = getConfig().getData("profileURL", ""); - - /** - * Enables support for running vanilla commands through Discord, if you ever need it. - */ - public ConfigData enableVanillaCommands = getConfig().getData("enableVanillaCommands", true); - - /** - * Whether players logged on from Discord (mcchat command) should be recognised by other plugins. Some plugins might break if it's turned off. - * But it's really hacky. - */ - private final ConfigData addFakePlayersToBukkit = getConfig().getData("addFakePlayersToBukkit", false); - - /** - * Set by the component to report crashes. - */ - private final ConfigData serverUp = getConfig().getData("serverUp", false); - - private final MCChatCommand mcChatCommand = new MCChatCommand(this); - private final ChannelconCommand channelconCommand = new ChannelconCommand(this); - - @Override - protected void enable() { - if (DPUtils.disableIfConfigErrorRes(this, chatChannel, chatChannelMono())) - return; - listener = new MCChatListener(this); - TBMCCoreAPI.RegisterEventsForExceptions(listener, getPlugin()); - TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(this), getPlugin());//These get undone if restarting/resetting - it will ignore events if disabled - getPlugin().getManager().registerCommand(mcChatCommand); - getPlugin().getManager().registerCommand(channelconCommand); - - val chcons = getConfig().getConfig().getConfigurationSection("chcons"); - if (chcons == null) //Fallback to old place - getConfig().getConfig().getRoot().getConfigurationSection("chcons"); - if (chcons != null) { - val chconkeys = chcons.getKeys(false); - for (val chconkey : chconkeys) { - val chcon = chcons.getConfigurationSection(chconkey); - val mcch = Channel.getChannels().filter(ch -> ch.ID.equals(chcon.getString("mcchid"))).findAny(); - val ch = DiscordPlugin.dc.getChannelById(Snowflake.of(chcon.getLong("chid"))).block(); - val did = chcon.getLong("did"); - val user = DiscordPlugin.dc.getUserById(Snowflake.of(did)).block(); - val groupid = chcon.getString("groupid"); - val toggles = chcon.getInt("toggles"); - val brtoggles = chcon.getStringList("brtoggles"); - if (!mcch.isPresent() || ch == null || user == null || groupid == null) - continue; - Bukkit.getScheduler().runTask(getPlugin(), () -> { //<-- Needed because of occasional ConcurrentModificationExceptions when creating the player (PermissibleBase) - val dcp = DiscordConnectedPlayer.create(user, (MessageChannel) ch, UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname"), this); - MCChatCustom.addCustomChat((MessageChannel) ch, groupid, mcch.get(), user, dcp, toggles, brtoggles.stream().map(TBMCSystemChatEvent.BroadcastTarget::get).filter(Objects::nonNull).collect(Collectors.toSet())); - }); - } - } - - try { - if (lpInjector == null) - lpInjector = new LPInjector(DiscordPlugin.plugin); - } catch (Exception e) { - TBMCCoreAPI.SendException("Failed to init LuckPerms injector", e, this); - } catch (NoClassDefFoundError e) { - log("No LuckPerms, not injecting"); - //e.printStackTrace(); - } - - if (addFakePlayersToBukkit.get()) { - try { - serverWatcher = new ServerWatcher(); - serverWatcher.enableDisable(true); - log("Finished hooking into the server"); - } catch (Exception e) { - TBMCCoreAPI.SendException("Failed to hack the server (object)! Disable addFakePlayersToBukkit in the config.", e, this); - } - } - - if (state == DPState.RESTARTING_PLUGIN) { //These will only execute if the chat is enabled - sendStateMessage(Color.CYAN, "Discord plugin restarted - chat connected."); //Really important to note the chat, hmm - state = DPState.RUNNING; - } else if (state == DPState.DISABLED_MCCHAT) { - sendStateMessage(Color.CYAN, "Minecraft chat enabled - chat connected."); - state = DPState.RUNNING; - } else if (serverUp.get()) { - sendStateMessage(Color.YELLOW, "Server started after a crash - chat connected."); - val thr = new Throwable("The server shut down unexpectedly. See the log of the previous run for more details."); - thr.setStackTrace(new StackTraceElement[0]); - TBMCCoreAPI.SendException("The server crashed!", thr, this); - } else - sendStateMessage(Color.GREEN, "Server started - chat connected."); - serverUp.set(true); - } - - @Override - protected void disable() { - disabling = true; - if (state == DPState.RESTARTING_PLUGIN) //These will only execute if the chat is enabled - sendStateMessage(Color.ORANGE, "Discord plugin restarting"); - else if (state == DPState.RUNNING) { - sendStateMessage(Color.ORANGE, "Minecraft chat disabled"); - state = DPState.DISABLED_MCCHAT; - } else { - String kickmsg = Bukkit.getOnlinePlayers().size() > 0 - ? (DPUtils - .sanitizeString(Bukkit.getOnlinePlayers().stream() - .map(Player::getDisplayName).collect(Collectors.joining(", "))) - + (Bukkit.getOnlinePlayers().size() == 1 ? " was " : " were ") - + "thrown out") //TODO: Make configurable - : ""; - if (state == DPState.RESTARTING_SERVER) - sendStateMessage(Color.ORANGE, "Server restarting", kickmsg); - else if (state == DPState.STOPPING_SERVER) - sendStateMessage(Color.RED, "Server stopping", kickmsg); - else - sendStateMessage(Color.GRAY, "Unknown state, please report."); - } //If 'restart' is disabled then this isn't shown even if joinleave is enabled - - serverUp.set(false); //Disable even if just the component is disabled because that way it won't falsely report crashes - - try { //If it's not enabled it won't do anything - if (serverWatcher != null) { - serverWatcher.enableDisable(false); - log("Finished unhooking the server"); - } - } catch ( - Exception e) { - TBMCCoreAPI.SendException("Failed to restore the server object!", e, this); - } - - val chcons = MCChatCustom.getCustomChats(); - val chconsc = getConfig().getConfig().createSection("chcons"); - for ( - val chcon : chcons) { - val chconc = chconsc.createSection(chcon.channel.getId().asString()); - chconc.set("mcchid", chcon.mcchannel.ID); - chconc.set("chid", chcon.channel.getId().asLong()); - chconc.set("did", chcon.user.getId().asLong()); - chconc.set("mcuid", chcon.dcp.getUniqueId().toString()); - chconc.set("mcname", chcon.dcp.getName()); - chconc.set("groupid", chcon.groupID); - chconc.set("toggles", chcon.toggles); - chconc.set("brtoggles", chcon.brtoggles.stream().map(TBMCSystemChatEvent.BroadcastTarget::getName).collect(Collectors.toList())); - } - if (listener != null) //Can be null if disabled because of a config error - listener.stop(true); - getPlugin().getManager().unregisterCommand(mcChatCommand); - getPlugin().getManager().unregisterCommand(channelconCommand); - disabling = false; - } - - /** - * It will block to make sure all messages are sent - */ - private void sendStateMessage(Color color, String message) { - MCChatUtils.forCustomAndAllMCChat(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(color) - .setTitle(message))), ChannelconBroadcast.RESTART, false).block(); - } - - /** - * It will block to make sure all messages are sent - */ - private void sendStateMessage(Color color, String message, String extra) { - MCChatUtils.forCustomAndAllMCChat(chan -> chan.flatMap(ch -> ch.createEmbed(ecs -> ecs.setColor(color) - .setTitle(message).setDescription(extra)).onErrorResume(t -> Mono.empty())), ChannelconBroadcast.RESTART, false).block(); - } -} diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala new file mode 100644 index 0000000..5b8aa3f --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala @@ -0,0 +1,227 @@ +package buttondevteam.discordplugin.mcchat + +import buttondevteam.core.component.channel.Channel +import buttondevteam.discordplugin.{ChannelconBroadcast, DPUtils, DiscordConnectedPlayer, DiscordPlugin} +import buttondevteam.discordplugin.playerfaker.ServerWatcher +import buttondevteam.discordplugin.playerfaker.perm.LPInjector +import buttondevteam.discordplugin.util.DPState +import buttondevteam.lib.{TBMCCoreAPI, TBMCSystemChatEvent} +import buttondevteam.lib.architecture.{Component, ConfigData, ReadOnlyConfigData} +import com.google.common.collect.Lists +import discord4j.common.util.Snowflake +import discord4j.core.`object`.entity.channel.MessageChannel +import discord4j.rest.util.Color +import org.bukkit.Bukkit +import reactor.core.publisher.Mono + +import java.util +import java.util.{Objects, UUID} +import java.util.stream.Collectors + +/** + * Provides Minecraft chat connection to Discord. Commands may be used either in a public chat (limited) or in a DM. + */ +object MinecraftChatModule { + var state = DPState.RUNNING +} + +class MinecraftChatModule extends Component[DiscordPlugin] { + def getListener: MCChatListener = this.listener + + private var listener: MCChatListener = null + private[mcchat] var serverWatcher: ServerWatcher = null + private var lpInjector: LPInjector = null + private[mcchat] var disabling = false + + /** + * A list of commands that can be used in public chats - Warning: Some plugins will treat players as OPs, always test before allowing a command! + */ + val whitelistedCommands: ConfigData[util.ArrayList[String]] = + getConfig.getData("whitelistedCommands", + () => Lists.newArrayList("list", "u", "shrug", "tableflip", "unflip", "mwiki", "yeehaw", "lenny", "rp", "plugins")) + + /** + * The channel to use as the public Minecraft chat - everything public gets broadcasted here + */ + val chatChannel: ReadOnlyConfigData[Snowflake] = DPUtils.snowflakeData(getConfig, "chatChannel", 0L) + + def chatChannelMono: Mono[MessageChannel] = DPUtils.getMessageChannel(chatChannel.getPath, chatChannel.get) + + /** + * The channel where the plugin can log when it mutes a player on Discord because of a Minecraft mute + */ + val modlogChannel: ReadOnlyConfigData[Mono[MessageChannel]] = DPUtils.channelData(getConfig, "modlogChannel") + /** + * The plugins to exclude from fake player events used for the 'mcchat' command - some plugins may crash, add them here + */ + val excludedPlugins: ConfigData[Array[String]] = getConfig.getData("excludedPlugins", Array[String]("ProtocolLib", "LibsDisguises", "JourneyMapServer")) + /** + * If this setting is on then players logged in through the 'mcchat' command will be able to teleport using plugin commands. + * They can then use commands like /tpahere to teleport others to that place.
+ * If this is off, then teleporting will have no effect. + */ + val allowFakePlayerTeleports: ConfigData[Boolean] = getConfig.getData("allowFakePlayerTeleports", false) + /** + * If this is on, each chat channel will have a player list in their description. + * It only gets added if there's no description yet or there are (at least) two lines of "----" following each other. + * Note that it will replace everything above the first and below the last "----" but it will only detect exactly four dashes. + * So if you want to use dashes for something else in the description, make sure it's either less or more dashes in one line. + */ + val showPlayerListOnDC: ConfigData[Boolean] = getConfig.getData("showPlayerListOnDC", true) + /** + * This setting controls whether custom chat connections can be created (existing connections will always work). + * Custom chat connections can be created using the channelcon command and they allow players to display town chat in a Discord channel for example. + * See the channelcon command for more details. + */ + val allowCustomChat: ConfigData[Boolean] = getConfig.getData("allowCustomChat", true) + /** + * This setting allows you to control if players can DM the bot to log on the server from Discord. + * This allows them to both chat and perform any command they can in-game. + */ + val allowPrivateChat: ConfigData[Boolean] = getConfig.getData("allowPrivateChat", true) + /** + * If set, message authors appearing on Discord will link to this URL. A 'type' and 'id' parameter will be added with the user's platform (Discord, Minecraft, ...) and ID. + */ + val profileURL: ConfigData[String] = getConfig.getData("profileURL", "") + /** + * Enables support for running vanilla commands through Discord, if you ever need it. + */ + val enableVanillaCommands: ConfigData[Boolean] = getConfig.getData("enableVanillaCommands", true) + /** + * Whether players logged on from Discord (mcchat command) should be recognised by other plugins. Some plugins might break if it's turned off. + * But it's really hacky. + */ + final private val addFakePlayersToBukkit = getConfig.getData("addFakePlayersToBukkit", false) + /** + * Set by the component to report crashes. + */ + final private val serverUp = getConfig.getData("serverUp", false) + final private val mcChatCommand = new MCChatCommand(this) + final private val channelconCommand = new ChannelconCommand(this) + + override protected def enable(): Unit = { + if (DPUtils.disableIfConfigErrorRes(this, chatChannel, chatChannelMono)) return + listener = new MCChatListener(this) + TBMCCoreAPI.RegisterEventsForExceptions(listener, getPlugin) + TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(this), getPlugin) //These get undone if restarting/resetting - it will ignore events if disabled + getPlugin.manager.registerCommand(mcChatCommand) + getPlugin.manager.registerCommand(channelconCommand) + val chcons = getConfig.getConfig.getConfigurationSection("chcons") + if (chcons == null) { //Fallback to old place + getConfig.getConfig.getRoot.getConfigurationSection("chcons") + } + if (chcons != null) { + val chconkeys = chcons.getKeys(false) + for (chconkey <- chconkeys) { + val chcon = chcons.getConfigurationSection(chconkey) + val mcch = Channel.getChannels.filter((ch: Channel) => ch.ID == chcon.getString("mcchid")).findAny + val ch = DiscordPlugin.dc.getChannelById(Snowflake.of(chcon.getLong("chid"))).block + val did = chcon.getLong("did") + val user = DiscordPlugin.dc.getUserById(Snowflake.of(did)).block + val groupid = chcon.getString("groupid") + val toggles = chcon.getInt("toggles") + val brtoggles = chcon.getStringList("brtoggles") + if (!mcch.isPresent || ch == null || user == null || groupid == null) continue //todo: continue is not supported + Bukkit.getScheduler.runTask(getPlugin, () => { + def foo() = { //<-- Needed because of occasional ConcurrentModificationExceptions when creating the player (PermissibleBase) + val dcp = DiscordConnectedPlayer.create(user, ch.asInstanceOf[MessageChannel], UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname"), this) + MCChatCustom.addCustomChat(ch.asInstanceOf[MessageChannel], groupid, mcch.get, user, dcp, toggles, brtoggles.stream.map(TBMCSystemChatEvent.BroadcastTarget.get).filter(Objects.nonNull).collect(Collectors.toSet)) + } + + foo() + }) + } + } + try if (lpInjector == null) lpInjector = new LPInjector(DiscordPlugin.plugin) + catch { + case e: Exception => + TBMCCoreAPI.SendException("Failed to init LuckPerms injector", e, this) + case e: NoClassDefFoundError => + log("No LuckPerms, not injecting") + //e.printStackTrace(); + } + if (addFakePlayersToBukkit.get) try { + serverWatcher = new ServerWatcher + serverWatcher.enableDisable(true) + log("Finished hooking into the server") + } catch { + case e: Exception => + TBMCCoreAPI.SendException("Failed to hack the server (object)! Disable addFakePlayersToBukkit in the config.", e, this) + } + if (MinecraftChatModule.state eq DPState.RESTARTING_PLUGIN) { //These will only execute if the chat is enabled + sendStateMessage(Color.CYAN, "Discord plugin restarted - chat connected.") //Really important to note the chat, hmm + MinecraftChatModule.state = DPState.RUNNING + } + else if (MinecraftChatModule.state eq DPState.DISABLED_MCCHAT) { + sendStateMessage(Color.CYAN, "Minecraft chat enabled - chat connected.") + MinecraftChatModule.state = DPState.RUNNING + } + else if (serverUp.get) { + sendStateMessage(Color.YELLOW, "Server started after a crash - chat connected.") + val thr = new Throwable("The server shut down unexpectedly. See the log of the previous run for more details.") + thr.setStackTrace(new Array[StackTraceElement](0)) + TBMCCoreAPI.SendException("The server crashed!", thr, this) + } + else sendStateMessage(Color.GREEN, "Server started - chat connected.") + serverUp.set(true) + } + + override protected def disable(): Unit = { + disabling = true + if (MinecraftChatModule.state eq DPState.RESTARTING_PLUGIN) sendStateMessage(Color.ORANGE, "Discord plugin restarting") + else if (MinecraftChatModule.state eq DPState.RUNNING) { + sendStateMessage(Color.ORANGE, "Minecraft chat disabled") + MinecraftChatModule.state = DPState.DISABLED_MCCHAT + } + else { + val kickmsg = if (Bukkit.getOnlinePlayers.size > 0) + DPUtils.sanitizeString(Bukkit.getOnlinePlayers.stream.map(_.getDisplayName).collect(Collectors.joining(", "))) + + (if (Bukkit.getOnlinePlayers.size == 1) " was " else " were ") + "thrown out" //TODO: Make configurable + else "" + if (MinecraftChatModule.state eq DPState.RESTARTING_SERVER) sendStateMessage(Color.ORANGE, "Server restarting", kickmsg) + else if (MinecraftChatModule.state eq DPState.STOPPING_SERVER) sendStateMessage(Color.RED, "Server stopping", kickmsg) + else sendStateMessage(Color.GRAY, "Unknown state, please report.") + //If 'restart' is disabled then this isn't shown even if joinleave is enabled} + } + serverUp.set(false) //Disable even if just the component is disabled because that way it won't falsely report crashes + try //If it's not enabled it won't do anything + if (serverWatcher != null) { + serverWatcher.enableDisable(false) + log("Finished unhooking the server") + } + catch { + case e: Exception => + TBMCCoreAPI.SendException("Failed to restore the server object!", e, this) + } + val chcons = MCChatCustom.getCustomChats + val chconsc = getConfig.getConfig.createSection("chcons") + for (chcon <- chcons) { + val chconc = chconsc.createSection(chcon.channel.getId.asString) + chconc.set("mcchid", chcon.mcchannel.ID) + chconc.set("chid", chcon.channel.getId.asLong) + chconc.set("did", chcon.user.getId.asLong) + chconc.set("mcuid", chcon.dcp.getUniqueId.toString) + chconc.set("mcname", chcon.dcp.getName) + chconc.set("groupid", chcon.groupID) + chconc.set("toggles", chcon.toggles) + chconc.set("brtoggles", chcon.brtoggles.stream.map(_.getName).collect(Collectors.toList)) + } + if (listener != null) { //Can be null if disabled because of a config error + listener.stop(true) + } + getPlugin.manager.unregisterCommand(mcChatCommand) + getPlugin.manager.unregisterCommand(channelconCommand) + disabling = false + } + + /** + * It will block to make sure all messages are sent + */ + private def sendStateMessage(color: Color, message: String) = + MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(_.setColor(color).setTitle(message))), + ChannelconBroadcast.RESTART, hookmsg = false).block + + private def sendStateMessage(color: Color, message: String, extra: String) = + MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(_.setColor(color).setTitle(message).setDescription(extra)) + .onErrorResume((_: Throwable) => Mono.empty)), ChannelconBroadcast.RESTART, hookmsg = false).block +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java index 91eb7ee..e4cd17d 100644 --- a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java +++ b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java @@ -4,8 +4,6 @@ import buttondevteam.discordplugin.DPUtils; import buttondevteam.discordplugin.DiscordPlayer; import buttondevteam.discordplugin.DiscordPlugin; import buttondevteam.discordplugin.DiscordSenderBase; -import buttondevteam.discordplugin.mcchat.MCChatUtils; -import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.discordplugin.util.DPState; import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.CommandClass; diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java b/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java index 903cea3..bf3051e 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java @@ -1,6 +1,5 @@ package buttondevteam.discordplugin.playerfaker; -import buttondevteam.discordplugin.mcchat.MCChatUtils; import com.destroystokyo.paper.profile.CraftPlayerProfile; import lombok.RequiredArgsConstructor; import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding; diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java index 6ff856b..7727673 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java @@ -2,7 +2,6 @@ package buttondevteam.discordplugin.playerfaker; import buttondevteam.discordplugin.DiscordSenderBase; import buttondevteam.discordplugin.IMCPlayer; -import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.lib.TBMCCoreAPI; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java b/src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java index 055c5e8..c13b7dd 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java @@ -2,7 +2,6 @@ package buttondevteam.discordplugin.playerfaker.perm; import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.mcchat.MCChatUtils; import buttondevteam.lib.TBMCCoreAPI; import me.lucko.luckperms.bukkit.LPBukkitBootstrap; import me.lucko.luckperms.bukkit.LPBukkitPlugin; From c57ac26b2d2b1e6d70e473c38331348c855f9f23 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Mon, 1 Mar 2021 02:07:40 +0100 Subject: [PATCH 03/23] All classes converted that I wanted --- .../discordplugin/BukkitLogWatcher.java | 23 -- .../discordplugin/BukkitLogWatcher.scala | 17 + .../discordplugin/ChannelconBroadcast.java | 15 - .../discordplugin/ChannelconBroadcast.scala | 6 + .../discordplugin/ChromaBot.scala | 1 + .../buttondevteam/discordplugin/DPUtils.java | 222 ------------ .../buttondevteam/discordplugin/DPUtils.scala | 209 +++++++++++ .../discordplugin/DiscordConnectedPlayer.java | 324 ------------------ .../DiscordConnectedPlayer.scala | 251 ++++++++++++++ .../discordplugin/DiscordPlayer.java | 29 -- .../discordplugin/DiscordPlayer.scala | 20 ++ .../discordplugin/DiscordPlayerSender.java | 42 --- .../discordplugin/DiscordPlayerSender.scala | 41 +++ .../discordplugin/DiscordSender.java | 117 ------- .../discordplugin/DiscordSender.scala | 64 ++++ .../discordplugin/DiscordSenderBase.java | 75 ---- .../discordplugin/DiscordSenderBase.scala | 64 ++++ .../discordplugin/IMCPlayer.java | 8 - .../discordplugin/IMCPlayer.scala | 8 + .../discordplugin/mcchat/MCChatUtils.scala | 11 +- .../mccommands/DiscordMCCommand.java | 144 -------- .../mccommands/DiscordMCCommand.scala | 128 +++++++ .../playerfaker/DelegatingMockMaker.java | 20 -- .../playerfaker/DelegatingMockMaker.scala | 49 +++ .../playerfaker/ServerWatcher.java | 96 ------ .../playerfaker/ServerWatcher.scala | 91 +++++ .../playerfaker/VCMDWrapper.java | 1 + .../playerfaker/perm/LPInjector.java | 1 + .../discordplugin/role/GameRoleModule.java | 126 ------- .../discordplugin/role/GameRoleModule.scala | 107 ++++++ .../discordplugin/role/RoleCommand.java | 107 ------ .../discordplugin/role/RoleCommand.scala | 84 +++++ .../discordplugin/util/DPState.java | 24 -- .../discordplugin/util/DPState.scala | 31 ++ .../discordplugin/util/Timings.java | 14 - .../discordplugin/util/Timings.scala | 12 + src/main/scala/Test.scala | 5 - 37 files changed, 1191 insertions(+), 1396 deletions(-) delete mode 100644 src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.java create mode 100644 src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/ChannelconBroadcast.java create mode 100644 src/main/java/buttondevteam/discordplugin/ChannelconBroadcast.scala rename src/main/{scala => java}/buttondevteam/discordplugin/ChromaBot.scala (94%) delete mode 100755 src/main/java/buttondevteam/discordplugin/DPUtils.java create mode 100644 src/main/java/buttondevteam/discordplugin/DPUtils.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java create mode 100644 src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/DiscordPlayer.java create mode 100644 src/main/java/buttondevteam/discordplugin/DiscordPlayer.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java create mode 100644 src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/DiscordSender.java create mode 100644 src/main/java/buttondevteam/discordplugin/DiscordSender.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/DiscordSenderBase.java create mode 100644 src/main/java/buttondevteam/discordplugin/DiscordSenderBase.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/IMCPlayer.java create mode 100644 src/main/java/buttondevteam/discordplugin/IMCPlayer.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java create mode 100644 src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.java create mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java create mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java create mode 100644 src/main/java/buttondevteam/discordplugin/role/GameRoleModule.scala delete mode 100755 src/main/java/buttondevteam/discordplugin/role/RoleCommand.java create mode 100644 src/main/java/buttondevteam/discordplugin/role/RoleCommand.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/util/DPState.java create mode 100644 src/main/java/buttondevteam/discordplugin/util/DPState.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/util/Timings.java create mode 100644 src/main/java/buttondevteam/discordplugin/util/Timings.scala delete mode 100644 src/main/scala/Test.scala diff --git a/src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.java b/src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.java deleted file mode 100644 index c51471b..0000000 --- a/src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.java +++ /dev/null @@ -1,23 +0,0 @@ -package buttondevteam.discordplugin; - -import buttondevteam.discordplugin.util.DPState; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.core.Filter; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.appender.AbstractAppender; -import org.apache.logging.log4j.core.filter.LevelRangeFilter; -import org.apache.logging.log4j.core.layout.PatternLayout; - -public class BukkitLogWatcher extends AbstractAppender { - protected BukkitLogWatcher() { - super("ChromaDiscord", - LevelRangeFilter.createFilter(Level.INFO, Level.INFO, Filter.Result.ACCEPT, Filter.Result.DENY), - PatternLayout.createDefaultLayout()); - } - - @Override - public void append(LogEvent logEvent) { - if (logEvent.getMessage().getFormattedMessage().contains("Attempting to restart with ")) - MinecraftChatModule.state = DPState.RESTARTING_SERVER; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.scala b/src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.scala new file mode 100644 index 0000000..7fee830 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.scala @@ -0,0 +1,17 @@ +package buttondevteam.discordplugin + +import buttondevteam.discordplugin.mcchat.MinecraftChatModule +import buttondevteam.discordplugin.util.DPState +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.core.{Filter, LogEvent} +import org.apache.logging.log4j.core.appender.AbstractAppender +import org.apache.logging.log4j.core.filter.LevelRangeFilter +import org.apache.logging.log4j.core.layout.PatternLayout + +class BukkitLogWatcher private[discordplugin]() extends AbstractAppender("ChromaDiscord", + LevelRangeFilter.createFilter(Level.INFO, Level.INFO, Filter.Result.ACCEPT, Filter.Result.DENY), + PatternLayout.createDefaultLayout) { + override def append(logEvent: LogEvent): Unit = + if (logEvent.getMessage.getFormattedMessage.contains("Attempting to restart with ")) + MinecraftChatModule.state = DPState.RESTARTING_SERVER +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/ChannelconBroadcast.java b/src/main/java/buttondevteam/discordplugin/ChannelconBroadcast.java deleted file mode 100644 index 994c8ed..0000000 --- a/src/main/java/buttondevteam/discordplugin/ChannelconBroadcast.java +++ /dev/null @@ -1,15 +0,0 @@ -package buttondevteam.discordplugin; - -public enum ChannelconBroadcast { - JOINLEAVE, - AFK, - RESTART, - DEATH, - BROADCAST; - - public final int flag; - - ChannelconBroadcast() { - this.flag = 1 << this.ordinal(); - } -} diff --git a/src/main/java/buttondevteam/discordplugin/ChannelconBroadcast.scala b/src/main/java/buttondevteam/discordplugin/ChannelconBroadcast.scala new file mode 100644 index 0000000..317a759 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/ChannelconBroadcast.scala @@ -0,0 +1,6 @@ +package buttondevteam.discordplugin + +object ChannelconBroadcast extends Enumeration { + type ChannelconBroadcast = Value + val JOINLEAVE, AFK, RESTART, DEATH, BROADCAST = Value +} \ No newline at end of file diff --git a/src/main/scala/buttondevteam/discordplugin/ChromaBot.scala b/src/main/java/buttondevteam/discordplugin/ChromaBot.scala similarity index 94% rename from src/main/scala/buttondevteam/discordplugin/ChromaBot.scala rename to src/main/java/buttondevteam/discordplugin/ChromaBot.scala index 6a8b7e0..187c7f8 100644 --- a/src/main/scala/buttondevteam/discordplugin/ChromaBot.scala +++ b/src/main/java/buttondevteam/discordplugin/ChromaBot.scala @@ -1,5 +1,6 @@ package buttondevteam.discordplugin +import buttondevteam.discordplugin.ChannelconBroadcast.ChannelconBroadcast import buttondevteam.discordplugin.mcchat.MCChatUtils import discord4j.core.`object`.entity.Message import discord4j.core.`object`.entity.channel.MessageChannel diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.java b/src/main/java/buttondevteam/discordplugin/DPUtils.java deleted file mode 100755 index 747c21b..0000000 --- a/src/main/java/buttondevteam/discordplugin/DPUtils.java +++ /dev/null @@ -1,222 +0,0 @@ -package buttondevteam.discordplugin; - -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.architecture.Component; -import buttondevteam.lib.architecture.ConfigData; -import buttondevteam.lib.architecture.IHaveConfig; -import buttondevteam.lib.architecture.ReadOnlyConfigData; -import discord4j.common.util.Snowflake; -import discord4j.core.object.entity.Guild; -import discord4j.core.object.entity.Message; -import discord4j.core.object.entity.Role; -import discord4j.core.object.entity.channel.MessageChannel; -import discord4j.core.spec.EmbedCreateSpec; -import lombok.val; -import reactor.core.publisher.Mono; - -import javax.annotation.Nullable; -import java.util.Comparator; -import java.util.Optional; -import java.util.TreeSet; -import java.util.logging.Logger; -import java.util.regex.Pattern; - -public final class DPUtils { - - private static final Pattern URL_PATTERN = Pattern.compile("https?://\\S*"); - private static final Pattern FORMAT_PATTERN = Pattern.compile("[*_~]"); - - public static EmbedCreateSpec embedWithHead(EmbedCreateSpec ecs, String displayname, String playername, String profileUrl) { - return ecs.setAuthor(displayname, profileUrl, "https://minotar.net/avatar/" + playername + "/32.png"); - } - - /** - * Removes §[char] colour codes from strings & escapes them for Discord
- * Ensure that this method only gets called once (escaping) - */ - public static String sanitizeString(String string) { - return escape(sanitizeStringNoEscape(string)); - } - - /** - * Removes §[char] colour codes from strings - */ - public static String sanitizeStringNoEscape(String string) { - StringBuilder sanitizedString = new StringBuilder(); - boolean random = false; - for (int i = 0; i < string.length(); i++) { - if (string.charAt(i) == '§') { - i++;// Skips the data value, the 4 in "§4Alisolarflare" - random = string.charAt(i) == 'k'; - } else { - if (!random) // Skip random/obfuscated characters - sanitizedString.append(string.charAt(i)); - } - } - return sanitizedString.toString(); - } - - private static String escape(String message) { - //var ts = new TreeSet<>(); - var ts = new TreeSet(Comparator.comparingInt(a -> a[0])); //Compare the start, then check the end - var matcher = URL_PATTERN.matcher(message); - while (matcher.find()) - ts.add(new int[]{matcher.start(), matcher.end()}); - matcher = FORMAT_PATTERN.matcher(message); - /*Function aFunctionalInterface = result -> - Optional.ofNullable(ts.floor(new int[]{result.start(), 0})).map(a -> a[1]).orElse(0) < result.start() - ? "\\\\" + result.group() : result.group(); - return matcher.replaceAll(aFunctionalInterface); //Find nearest URL match and if it's not reaching to the char then escape*/ - StringBuffer sb = new StringBuffer(); - while (matcher.find()) { - matcher.appendReplacement(sb, Optional.ofNullable(ts.floor(new int[]{matcher.start(), 0})) //Find a URL start <= our start - .map(a -> a[1]).orElse(-1) < matcher.start() //Check if URL end < our start - ? "\\\\" + matcher.group() : matcher.group()); - } - matcher.appendTail(sb); - return sb.toString(); - } - - public static Logger getLogger() { - if (DiscordPlugin.plugin == null || DiscordPlugin.plugin.getLogger() == null) - return Logger.getLogger("DiscordPlugin"); - return DiscordPlugin.plugin.getLogger(); - } - - public static ReadOnlyConfigData> channelData(IHaveConfig config, String key) { - return config.getReadOnlyDataPrimDef(key, 0L, id -> getMessageChannel(key, Snowflake.of((Long) id)), ch -> 0L); //We can afford to search for the channel in the cache once (instead of using mainServer) - } - - public static ReadOnlyConfigData> roleData(IHaveConfig config, String key, String defName) { - return roleData(config, key, defName, Mono.just(DiscordPlugin.mainServer)); - } - - /** - * Needs to be a {@link ConfigData} for checking if it's set - */ - public static ReadOnlyConfigData> roleData(IHaveConfig config, String key, String defName, Mono guild) { - return config.getReadOnlyDataPrimDef(key, defName, name -> { - if (!(name instanceof String) || ((String) name).length() == 0) return Mono.empty(); - return guild.flatMapMany(Guild::getRoles).filter(r -> r.getName().equals(name)).onErrorResume(e -> { - getLogger().warning("Failed to get role data for " + key + "=" + name + " - " + e.getMessage()); - return Mono.empty(); - }).next(); - }, r -> defName); - } - - public static ReadOnlyConfigData snowflakeData(IHaveConfig config, String key, long defID) { - return config.getReadOnlyDataPrimDef(key, defID, id -> Snowflake.of((long) id), Snowflake::asLong); - } - - /** - * Mentions the bot channel. Useful for help texts. - * - * @return The string for mentioning the channel - */ - public static String botmention() { - if (DiscordPlugin.plugin == null) return "#bot"; - return channelMention(DiscordPlugin.plugin.commandChannel.get()); - } - - /** - * Disables the component if one of the given configs return null. Useful for channel/role configs. - * - * @param component The component to disable if needed - * @param configs The configs to check for null - * @return Whether the component got disabled and a warning logged - */ - public static boolean disableIfConfigError(@Nullable Component component, ConfigData... configs) { - for (val config : configs) { - Object v = config.get(); - if (disableIfConfigErrorRes(component, config, v)) - return true; - } - return false; - } - - /** - * Disables the component if one of the given configs return null. Useful for channel/role configs. - * - * @param component The component to disable if needed - * @param config The (snowflake) config to check for null - * @param result The result of getting the value - * @return Whether the component got disabled and a warning logged - */ - public static boolean disableIfConfigErrorRes(@Nullable Component component, ConfigData config, Object result) { - //noinspection ConstantConditions - if (result == null || (result instanceof Mono && !((Mono) result).hasElement().block())) { - String path = null; - try { - if (component != null) - Component.setComponentEnabled(component, false); - path = config.getPath(); - } catch (Exception e) { - if (component != null) - TBMCCoreAPI.SendException("Failed to disable component after config error!", e, component); - else - TBMCCoreAPI.SendException("Failed to disable component after config error!", e, DiscordPlugin.plugin); - } - getLogger().warning("The config value " + path + " isn't set correctly " + (component == null ? "in global settings!" : "for component " + component.getClass().getSimpleName() + "!")); - getLogger().warning("Set the correct ID in the config" + (component == null ? "" : " or disable this component") + " to remove this message."); - return true; - } - return false; - } - - /** - * Send a response in the form of "@User, message". Use Mono.empty() if you don't have a channel object. - * - * @param original The original message to reply to - * @param channel The channel to send the message in, defaults to the original - * @param message The message to send - * @return A mono to send the message - */ - public static Mono reply(Message original, @Nullable MessageChannel channel, String message) { - Mono ch; - if (channel == null) - ch = original.getChannel(); - else - ch = Mono.just(channel); - return reply(original, ch, message); - } - - /** - * @see #reply(Message, MessageChannel, String) - */ - public static Mono reply(Message original, Mono ch, String message) { - return ch.flatMap(chan -> chan.createMessage((original.getAuthor().isPresent() - ? original.getAuthor().get().getMention() + ", " : "") + message)); - } - - public static String nickMention(Snowflake userId) { - return "<@!" + userId.asString() + ">"; - } - - public static String channelMention(Snowflake channelId) { - return "<#" + channelId.asString() + ">"; - } - - /** - * Gets a message channel for a config. Returns empty for ID 0. - * - * @param key The config key - * @param id The channel ID - * @return A message channel - */ - public static Mono getMessageChannel(String key, Snowflake id) { - if (id.asLong() == 0L) return Mono.empty(); - return DiscordPlugin.dc.getChannelById(id).onErrorResume(e -> { - getLogger().warning("Failed to get channel data for " + key + "=" + id + " - " + e.getMessage()); - return Mono.empty(); - }).filter(ch -> ch instanceof MessageChannel).cast(MessageChannel.class); - } - - public static Mono getMessageChannel(ConfigData config) { - return getMessageChannel(config.getPath(), config.get()); - } - - public static Mono ignoreError(Mono mono) { - return mono.onErrorResume(t -> Mono.empty()); - } - -} diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.scala b/src/main/java/buttondevteam/discordplugin/DPUtils.scala new file mode 100644 index 0000000..f69db27 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/DPUtils.scala @@ -0,0 +1,209 @@ +package buttondevteam.discordplugin + +import buttondevteam.lib.TBMCCoreAPI +import buttondevteam.lib.architecture.{Component, ConfigData, IHaveConfig, ReadOnlyConfigData} +import discord4j.common.util.Snowflake +import discord4j.core.`object`.entity.channel.MessageChannel +import discord4j.core.`object`.entity.{Guild, Message, Role} +import discord4j.core.spec.EmbedCreateSpec +import reactor.core.publisher.Mono + +import java.util +import java.util.{Comparator, Optional} +import java.util.logging.Logger +import java.util.regex.Pattern +import javax.annotation.Nullable + +object DPUtils { + private val URL_PATTERN = Pattern.compile("https?://\\S*") + private val FORMAT_PATTERN = Pattern.compile("[*_~]") + + def embedWithHead(ecs: EmbedCreateSpec, displayname: String, playername: String, profileUrl: String): EmbedCreateSpec = + ecs.setAuthor(displayname, profileUrl, "https://minotar.net/avatar/" + playername + "/32.png") + + /** + * Removes §[char] colour codes from strings & escapes them for Discord
+ * Ensure that this method only gets called once (escaping) + */ + def sanitizeString(string: String): String = escape(sanitizeStringNoEscape(string)) + + /** + * Removes §[char] colour codes from strings + */ + def sanitizeStringNoEscape(string: String): String = { + val sanitizedString = new StringBuilder + var random = false + var i = 0 + while ( { + i < string.length + }) { + if (string.charAt(i) == '§') { + i += 1 // Skips the data value, the 4 in "§4Alisolarflare" + random = string.charAt(i) == 'k' + } + else if (!random) { // Skip random/obfuscated characters + sanitizedString.append(string.charAt(i)) + } + i += 1 + } + sanitizedString.toString + } + + private def escape(message: String) = { //var ts = new TreeSet<>(); + val ts = new util.TreeSet[Array[Int]](Comparator.comparingInt((a: Array[Int]) => a(0)): Comparator[Array[Int]]) //Compare the start, then check the end + var matcher = URL_PATTERN.matcher(message) + while ( { + matcher.find + }) ts.add(Array[Int](matcher.start, matcher.end)) + matcher = FORMAT_PATTERN.matcher(message) + /*Function aFunctionalInterface = result -> + Optional.ofNullable(ts.floor(new int[]{result.start(), 0})).map(a -> a[1]).orElse(0) < result.start() + ? "\\\\" + result.group() : result.group(); + return matcher.replaceAll(aFunctionalInterface); //Find nearest URL match and if it's not reaching to the char then escape*/ val sb = new StringBuffer + while ( { + matcher.find + }) matcher.appendReplacement(sb, if (Optional.ofNullable(ts.floor(Array[Int](matcher.start, 0))).map( //Find a URL start <= our start + (a: Array[Int]) => a(1)).orElse(-1) < matcher.start //Check if URL end < our start + ) "\\\\" + matcher.group else matcher.group) + matcher.appendTail(sb) + sb.toString + } + + def getLogger: Logger = { + if (DiscordPlugin.plugin == null || DiscordPlugin.plugin.getLogger == null) return Logger.getLogger("DiscordPlugin") + DiscordPlugin.plugin.getLogger + } + + def channelData(config: IHaveConfig, key: String): ReadOnlyConfigData[Mono[MessageChannel]] = + config.getReadOnlyDataPrimDef(key, 0L, (id: Any) => + getMessageChannel(key, Snowflake.of(id.asInstanceOf[Long])), (_: Mono[MessageChannel]) => 0L) //We can afford to search for the channel in the cache once (instead of using mainServer) + def roleData(config: IHaveConfig, key: String, defName: String): ReadOnlyConfigData[Mono[Role]] = + roleData(config, key, defName, Mono.just(DiscordPlugin.mainServer)) + + /** + * Needs to be a {@link ConfigData} for checking if it's set + */ + def roleData(config: IHaveConfig, key: String, defName: String, guild: Mono[Guild]): ReadOnlyConfigData[Mono[Role]] = config.getReadOnlyDataPrimDef(key, defName, (name: Any) => { + def foo(name: Any): Mono[Role] = { + if (!name.isInstanceOf[String] || name.asInstanceOf[String].isEmpty) return Mono.empty[Role] + guild.flatMapMany(_.getRoles).filter((r: Role) => r.getName == name).onErrorResume((e: Throwable) => { + def foo(e: Throwable): Mono[Role] = { + getLogger.warning("Failed to get role data for " + key + "=" + name + " - " + e.getMessage) + Mono.empty[Role] + } + + foo(e) + }).next + } + + foo(name) + }, (_: Mono[Role]) => defName) + + def snowflakeData(config: IHaveConfig, key: String, defID: Long): ReadOnlyConfigData[Snowflake] = + config.getReadOnlyDataPrimDef(key, defID, (id: Any) => Snowflake.of(id.asInstanceOf[Long]), _.asLong) + + /** + * Mentions the bot channel. Useful for help texts. + * + * @return The string for mentioning the channel + */ + def botmention: String = { + if (DiscordPlugin.plugin == null) return "#bot" + channelMention(DiscordPlugin.plugin.commandChannel.get) + } + + /** + * Disables the component if one of the given configs return null. Useful for channel/role configs. + * + * @param component The component to disable if needed + * @param configs The configs to check for null + * @return Whether the component got disabled and a warning logged + */ + def disableIfConfigError(@Nullable component: Component[DiscordPlugin], configs: ConfigData[_]*): Boolean = { + for (config <- configs) { + val v = config.get + if (disableIfConfigErrorRes(component, config, v)) return true + } + false + } + + /** + * Disables the component if one of the given configs return null. Useful for channel/role configs. + * + * @param component The component to disable if needed + * @param config The (snowflake) config to check for null + * @param result The result of getting the value + * @return Whether the component got disabled and a warning logged + */ + def disableIfConfigErrorRes(@Nullable component: Component[DiscordPlugin], config: ConfigData[_], result: Any): Boolean = { + //noinspection ConstantConditions + if (result == null || (result.isInstanceOf[Mono[_]] && !result.asInstanceOf[Mono[_]].hasElement.block)) { + var path: String = null + try { + if (component != null) Component.setComponentEnabled(component, false) + path = config.getPath + } catch { + case e: Exception => + if (component != null) TBMCCoreAPI.SendException("Failed to disable component after config error!", e, component) + else TBMCCoreAPI.SendException("Failed to disable component after config error!", e, DiscordPlugin.plugin) + } + getLogger.warning("The config value " + path + " isn't set correctly " + (if (component == null) "in global settings!" + else "for component " + component.getClass.getSimpleName + "!")) + getLogger.warning("Set the correct ID in the config" + (if (component == null) "" + else " or disable this component") + " to remove this message.") + return true + } + false + } + + /** + * Send a response in the form of "@User, message". Use Mono.empty() if you don't have a channel object. + * + * @param original The original message to reply to + * @param channel The channel to send the message in, defaults to the original + * @param message The message to send + * @return A mono to send the message + */ + def reply(original: Message, @Nullable channel: MessageChannel, message: String): Mono[Message] = { + val ch = if (channel == null) original.getChannel + else Mono.just(channel) + reply(original, ch, message) + } + + /** + * @see #reply(Message, MessageChannel, String) + */ + def reply(original: Message, ch: Mono[MessageChannel], message: String): Mono[Message] = + ch.flatMap(_.createMessage((if (original.getAuthor.isPresent) + original.getAuthor.get.getMention + ", " + else "") + message)) + + def nickMention(userId: Snowflake): String = "<@!" + userId.asString + ">" + + def channelMention(channelId: Snowflake): String = "<#" + channelId.asString + ">" + + /** + * Gets a message channel for a config. Returns empty for ID 0. + * + * @param key The config key + * @param id The channel ID + * @return A message channel + */ + def getMessageChannel(key: String, id: Snowflake): Mono[MessageChannel] = { + if (id.asLong == 0L) return Mono.empty[MessageChannel] + + DiscordPlugin.dc.getChannelById(id).onErrorResume(e => { + def foo(e: Throwable) = { + getLogger.warning("Failed to get channel data for " + key + "=" + id + " - " + e.getMessage) + Mono.empty + } + + foo(e) + }).filter(ch => ch.isInstanceOf[MessageChannel]).cast(classOf[MessageChannel]) + } + + def getMessageChannel(config: ConfigData[Snowflake]): Mono[MessageChannel] = + getMessageChannel(config.getPath, config.get) + + def ignoreError[T](mono: Mono[T]): Mono[T] = mono.onErrorResume((_: Throwable) => Mono.empty) +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java deleted file mode 100644 index 714d347..0000000 --- a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.java +++ /dev/null @@ -1,324 +0,0 @@ -package buttondevteam.discordplugin; - -import buttondevteam.discordplugin.playerfaker.DiscordInventory; -import buttondevteam.discordplugin.playerfaker.VCMDWrapper; -import discord4j.core.object.entity.User; -import discord4j.core.object.entity.channel.MessageChannel; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Delegate; -import net.md_5.bungee.api.ChatMessageType; -import net.md_5.bungee.api.chat.BaseComponent; -import org.bukkit.*; -import org.bukkit.attribute.Attribute; -import org.bukkit.attribute.AttributeInstance; -import org.bukkit.attribute.AttributeModifier; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.permissions.PermissibleBase; -import org.bukkit.permissions.ServerOperator; -import org.mockito.MockSettings; -import org.mockito.Mockito; - -import java.lang.reflect.Modifier; -import java.net.InetSocketAddress; -import java.util.*; - -import static org.mockito.Answers.RETURNS_DEFAULTS; - -public abstract class DiscordConnectedPlayer extends DiscordSenderBase implements IMCPlayer { - private @Getter VCMDWrapper vanillaCmdListener; - @Getter - @Setter - private boolean loggedIn = false; - - @Delegate(excludes = ServerOperator.class) - private PermissibleBase origPerm; - - private @Getter String name; - - private @Getter OfflinePlayer basePlayer; - - @Getter - @Setter - private PermissibleBase perm; - - private Location location; - - private final MinecraftChatModule module; - - @Getter - private final UUID uniqueId; - - /** - * The parameters must match with {@link #create(User, MessageChannel, UUID, String, MinecraftChatModule)} - */ - protected DiscordConnectedPlayer(User user, MessageChannel channel, UUID uuid, String mcname, - MinecraftChatModule module) { - super(user, channel); - location = Bukkit.getWorlds().get(0).getSpawnLocation(); - origPerm = perm = new PermissibleBase(basePlayer = Bukkit.getOfflinePlayer(uuid)); - name = mcname; - this.module = module; - uniqueId = uuid; - displayName = mcname; - vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, module)); - } - - /** - * For testing - */ - protected DiscordConnectedPlayer(User user, MessageChannel channel) { - super(user, channel); - module = null; - uniqueId = UUID.randomUUID(); - } - - public void setOp(boolean value) { //CraftPlayer-compatible implementation - this.origPerm.setOp(value); - this.perm.recalculatePermissions(); - } - - public boolean isOp() { return this.origPerm.isOp(); } - - @Override - public boolean teleport(Location location) { - if (module.allowFakePlayerTeleports.get()) - this.location = location; - return true; - } - - @Override - public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { - if (module.allowFakePlayerTeleports.get()) - this.location = location; - return true; - } - - @Override - public boolean teleport(Entity destination) { - if (module.allowFakePlayerTeleports.get()) - this.location = destination.getLocation(); - return true; - } - - @Override - public boolean teleport(Entity destination, PlayerTeleportEvent.TeleportCause cause) { - if (module.allowFakePlayerTeleports.get()) - this.location = destination.getLocation(); - return true; - } - - @Override - public Location getLocation(Location loc) { - if (loc != null) { - loc.setWorld(getWorld()); - loc.setX(location.getX()); - loc.setY(location.getY()); - loc.setZ(location.getZ()); - loc.setYaw(location.getYaw()); - loc.setPitch(location.getPitch()); - } - - return loc; - } - - @Override - public Server getServer() { - return Bukkit.getServer(); - } - - @Override - public void sendRawMessage(String message) { - sendMessage(message); - } - - @Override - public void chat(String msg) { - Bukkit.getPluginManager() - .callEvent(new AsyncPlayerChatEvent(true, this, msg, new HashSet<>(Bukkit.getOnlinePlayers()))); - } - - @Override - public World getWorld() { - return Bukkit.getWorlds().get(0); - } - - @Override - public boolean isOnline() { - return true; - } - - @Override - public Location getLocation() { - return new Location(getWorld(), location.getX(), location.getY(), location.getZ(), - location.getYaw(), location.getPitch()); - } - - @Override - public Location getEyeLocation() { - return getLocation(); - } - - @Override - @Deprecated - public double getMaxHealth() { - return 20; - } - - @Override - public Player getPlayer() { - return this; - } - - @Getter - @Setter - private String displayName; - - @Override - public AttributeInstance getAttribute(Attribute attribute) { - return new AttributeInstance() { - @Override - public Attribute getAttribute() { - return attribute; - } - - @Override - public double getBaseValue() { - return getDefaultValue(); - } - - @Override - public void setBaseValue(double value) { - } - - @Override - public Collection getModifiers() { - return Collections.emptyList(); - } - - @Override - public void addModifier(AttributeModifier modifier) { - } - - @Override - public void removeModifier(AttributeModifier modifier) { - } - - @Override - public double getValue() { - return getDefaultValue(); - } - - @Override - public double getDefaultValue() { - return 20; //Works for max health, should be okay for the rest - } - }; - } - - @Override - public GameMode getGameMode() { - return GameMode.SPECTATOR; - } - - @SuppressWarnings("deprecation") - private final Player.Spigot spigot = new Player.Spigot() { - @Override - public InetSocketAddress getRawAddress() { - return null; - } - - @Override - public void playEffect(Location location, Effect effect, int id, int data, float offsetX, float offsetY, float offsetZ, float speed, int particleCount, int radius) { - } - - @Override - public boolean getCollidesWithEntities() { - return false; - } - - @Override - public void setCollidesWithEntities(boolean collides) { - } - - @Override - public void respawn() { - } - - @Override - public String getLocale() { - return "en_us"; - } - - @Override - public Set getHiddenPlayers() { - return Collections.emptySet(); - } - - @Override - public void sendMessage(BaseComponent component) { - DiscordConnectedPlayer.super.sendMessage(component.toPlainText()); - } - - @Override - public void sendMessage(BaseComponent... components) { - for (var component : components) - sendMessage(component); - } - - @Override - public void sendMessage(ChatMessageType position, BaseComponent component) { - sendMessage(component); //Ignore position - } - - @Override - public void sendMessage(ChatMessageType position, BaseComponent... components) { - sendMessage(components); //Ignore position - } - - @Override - public boolean isInvulnerable() { - return true; - } - }; - - @Override - public Player.Spigot spigot() { - return spigot; - } - - public static DiscordConnectedPlayer create(User user, MessageChannel channel, UUID uuid, String mcname, - MinecraftChatModule module) { - return Mockito.mock(DiscordConnectedPlayer.class, - getSettings().useConstructor(user, channel, uuid, mcname, module)); - } - - public static DiscordConnectedPlayer createTest() { - return Mockito.mock(DiscordConnectedPlayer.class, getSettings().useConstructor(null, null)); - } - - private static MockSettings getSettings() { - return Mockito.withSettings() - .defaultAnswer(invocation -> { - try { - if (!Modifier.isAbstract(invocation.getMethod().getModifiers())) - return invocation.callRealMethod(); - if (PlayerInventory.class.isAssignableFrom(invocation.getMethod().getReturnType())) - return Mockito.mock(DiscordInventory.class, Mockito.withSettings().extraInterfaces(PlayerInventory.class)); - if (Inventory.class.isAssignableFrom(invocation.getMethod().getReturnType())) - return new DiscordInventory(); - return RETURNS_DEFAULTS.answer(invocation); - } catch (Exception e) { - System.err.println("Error in mocked player!"); - e.printStackTrace(); - return RETURNS_DEFAULTS.answer(invocation); - } - }) - .stubOnly(); - } -} diff --git a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala new file mode 100644 index 0000000..e627049 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala @@ -0,0 +1,251 @@ +package buttondevteam.discordplugin + +import buttondevteam.discordplugin.mcchat.MinecraftChatModule +import buttondevteam.discordplugin.playerfaker.{DiscordInventory, VCMDWrapper} +import discord4j.core.`object`.entity.User +import discord4j.core.`object`.entity.channel.MessageChannel +import net.md_5.bungee.api.ChatMessageType +import net.md_5.bungee.api.chat.BaseComponent +import org.bukkit._ +import org.bukkit.attribute.{Attribute, AttributeInstance, AttributeModifier} +import org.bukkit.entity.Player.Spigot +import org.bukkit.entity.{Entity, Player} +import org.bukkit.event.player.{AsyncPlayerChatEvent, PlayerTeleportEvent} +import org.bukkit.inventory.{Inventory, PlayerInventory} +import org.bukkit.permissions.{PermissibleBase, Permission, PermissionAttachment, PermissionAttachmentInfo} +import org.bukkit.plugin.Plugin +import org.mockito.Answers.RETURNS_DEFAULTS +import org.mockito.{MockSettings, Mockito} +import org.mockito.invocation.InvocationOnMock + +import java.lang.reflect.Modifier +import java.net.InetSocketAddress +import java.util +import java.util._ + +object DiscordConnectedPlayer { + def create(user: User, channel: MessageChannel, uuid: UUID, mcname: String, module: MinecraftChatModule): DiscordConnectedPlayer = + Mockito.mock(classOf[DiscordConnectedPlayer], getSettings.useConstructor(user, channel, uuid, mcname, module)) + + def createTest: DiscordConnectedPlayer = + Mockito.mock(classOf[DiscordConnectedPlayer], getSettings.useConstructor(null, null)) + + private def getSettings: MockSettings = Mockito.withSettings.defaultAnswer((invocation: InvocationOnMock) => { + def foo(invocation: InvocationOnMock): AnyRef = + try { + if (!Modifier.isAbstract(invocation.getMethod.getModifiers)) + invocation.callRealMethod + else if (classOf[PlayerInventory].isAssignableFrom(invocation.getMethod.getReturnType)) + Mockito.mock(classOf[DiscordInventory], Mockito.withSettings.extraInterfaces(classOf[PlayerInventory])) + else if (classOf[Inventory].isAssignableFrom(invocation.getMethod.getReturnType)) + new DiscordInventory + else + RETURNS_DEFAULTS.answer(invocation) + } catch { + case e: Exception => + System.err.println("Error in mocked player!") + e.printStackTrace() + RETURNS_DEFAULTS.answer(invocation) + } + + foo(invocation) + }).stubOnly +} + +abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel) extends DiscordSenderBase(user, channel) with IMCPlayer[DiscordConnectedPlayer] { + override def isPermissionSet(name: String): Boolean = this.origPerm.isPermissionSet(name) + + override def isPermissionSet(perm: Permission): Boolean = this.origPerm.isPermissionSet(perm) + + override def hasPermission(inName: String): Boolean = this.origPerm.hasPermission(inName) + + override def hasPermission(perm: Permission): Boolean = this.origPerm.hasPermission(perm) + + override def addAttachment(plugin: Plugin, name: String, value: Boolean): PermissionAttachment = this.origPerm.addAttachment(plugin, name, value) + + override def addAttachment(plugin: Plugin): PermissionAttachment = this.origPerm.addAttachment(plugin) + + override def removeAttachment(attachment: PermissionAttachment): Unit = this.origPerm.removeAttachment(attachment) + + override def recalculatePermissions(): Unit = this.origPerm.recalculatePermissions() + + def clearPermissions(): Unit = this.origPerm.clearPermissions() + + override def addAttachment(plugin: Plugin, name: String, value: Boolean, ticks: Int): PermissionAttachment = + this.origPerm.addAttachment(plugin, name, value, ticks) + + override def addAttachment(plugin: Plugin, ticks: Int): PermissionAttachment = this.origPerm.addAttachment(plugin, ticks) + + override def getEffectivePermissions: util.Set[PermissionAttachmentInfo] = this.origPerm.getEffectivePermissions + + def setLoggedIn(loggedIn: Boolean): Unit = this.loggedIn = loggedIn + + def setPerm(perm: PermissibleBase): Unit = this.perm = perm + + override def setDisplayName(displayName: String): Unit = this.displayName = displayName + + override def getVanillaCmdListener: VCMDWrapper = this.vanillaCmdListener + + def isLoggedIn: Boolean = this.loggedIn + + override def getName: String = this.name + + def getBasePlayer: OfflinePlayer = this.basePlayer + + def getPerm: PermissibleBase = this.perm + + override def getUniqueId: UUID = this.uniqueId + + override def getDisplayName: String = this.displayName + + private var vanillaCmdListener: VCMDWrapper = null + private var loggedIn = false + private var origPerm: PermissibleBase = null + private var name: String = null + private var basePlayer: OfflinePlayer = null + private var perm: PermissibleBase = null + private var location: Location = null + final private var module: MinecraftChatModule = null + final private var uniqueId: UUID = null + final private var displayName: String = null + + /** + * The parameters must match with {@link #create ( User, MessageChannel, UUID, String, MinecraftChatModule)} + */ + def this(user: User, channel: MessageChannel, uuid: UUID, mcname: String, module: MinecraftChatModule) { + this(user, channel) + location = Bukkit.getWorlds.get(0).getSpawnLocation + perm = new PermissibleBase(basePlayer = Bukkit.getOfflinePlayer(uuid)) + origPerm = perm + name = mcname + this.module = module + uniqueId = uuid + displayName = mcname + vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, module)) + } + + /** + * For testing + */ + def this(user: User, channel: MessageChannel) { + this(user, channel) + module = null + uniqueId = UUID.randomUUID + } + + override def setOp(value: Boolean): Unit = { //CraftPlayer-compatible implementation + this.origPerm.setOp(value) + this.perm.recalculatePermissions() + } + + override def isOp: Boolean = this.origPerm.isOp + + override def teleport(location: Location): Boolean = { + if (module.allowFakePlayerTeleports.get) this.location = location + true + } + + def teleport(location: Location, cause: PlayerTeleportEvent.TeleportCause): Boolean = { + if (module.allowFakePlayerTeleports.get) this.location = location + true + } + + override def teleport(destination: Entity): Boolean = { + if (module.allowFakePlayerTeleports.get) this.location = destination.getLocation + true + } + + def teleport(destination: Entity, cause: PlayerTeleportEvent.TeleportCause): Boolean = { + if (module.allowFakePlayerTeleports.get) this.location = destination.getLocation + true + } + + override def getLocation(loc: Location): Location = { + if (loc != null) { + loc.setWorld(getWorld) + loc.setX(location.getX) + loc.setY(location.getY) + loc.setZ(location.getZ) + loc.setYaw(location.getYaw) + loc.setPitch(location.getPitch) + } + loc + } + + override def getServer: Server = Bukkit.getServer + + override def sendRawMessage(message: String): Unit = sendMessage(message) + + override def chat(msg: String): Unit = Bukkit.getPluginManager.callEvent(new AsyncPlayerChatEvent(true, this, msg, new util.HashSet[Player](Bukkit.getOnlinePlayers))) + + override def getWorld: World = Bukkit.getWorlds.get(0) + + override def isOnline = true + + override def getLocation = new Location(getWorld, location.getX, location.getY, location.getZ, location.getYaw, location.getPitch) + + override def getEyeLocation: Location = getLocation + + @deprecated override def getMaxHealth = 20 + + override def getPlayer: DiscordConnectedPlayer = this + + override def getAttribute(attribute: Attribute): AttributeInstance = new AttributeInstance() { + override def getAttribute: Attribute = attribute + + override def getBaseValue: Double = getDefaultValue + + override def setBaseValue(value: Double): Unit = { + } + + override def getModifiers: util.Collection[AttributeModifier] = Collections.emptyList + + override def addModifier(modifier: AttributeModifier): Unit = { + } + + override def removeModifier(modifier: AttributeModifier): Unit = { + } + + override def getValue: Double = getDefaultValue + + override def getDefaultValue: Double = 20 //Works for max health, should be okay for the rest + } + + override def getGameMode = GameMode.SPECTATOR + + //noinspection ScalaDeprecation + @SuppressWarnings(Array("deprecation")) final private val spigot: Spigot = new Spigot() { + override def getRawAddress: InetSocketAddress = null + + override def playEffect(location: Location, effect: Effect, id: Int, data: Int, offsetX: Float, offsetY: Float, offsetZ: Float, speed: Float, particleCount: Int, radius: Int): Unit = { + } + + override def getCollidesWithEntities = false + + override def setCollidesWithEntities(collides: Boolean): Unit = { + } + + override def respawn(): Unit = { + } + + override def getLocale = "en_us" + + override def getHiddenPlayers: util.Set[Player] = Collections.emptySet + + override def sendMessage(component: BaseComponent): Unit = + DiscordConnectedPlayer.super.sendMessage(component.toPlainText) + + override def sendMessage(components: BaseComponent*): Unit = + for (component <- components) + sendMessage(component) + + override def sendMessage(position: ChatMessageType, component: BaseComponent): Unit = + sendMessage(component) //Ignore position + override def sendMessage(position: ChatMessageType, components: BaseComponent*) = + sendMessage(components) + + override def isInvulnerable = true + } + + override def spigot: Spigot = spigot +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayer.java b/src/main/java/buttondevteam/discordplugin/DiscordPlayer.java deleted file mode 100755 index 40ce273..0000000 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlayer.java +++ /dev/null @@ -1,29 +0,0 @@ -package buttondevteam.discordplugin; - -import buttondevteam.lib.player.ChromaGamerBase; -import buttondevteam.lib.player.UserClass; -import discord4j.core.object.entity.User; -import discord4j.core.object.entity.channel.MessageChannel; - -@UserClass(foldername = "discord") -public class DiscordPlayer extends ChromaGamerBase { - private String did; - // private @Getter @Setter boolean minecraftChatEnabled; - - public DiscordPlayer() { - } - - public String getDiscordID() { - if (did == null) - did = getFileName(); - return did; - } - - /** - * Returns true if player has the private Minecraft chat enabled. For setting the value, see - * {@link MCChatPrivate#privateMCChat(MessageChannel, boolean, User, DiscordPlayer)} - */ - public boolean isMinecraftChatEnabled() { - return MCChatPrivate.isMinecraftChatEnabled(this); - } -} diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayer.scala b/src/main/java/buttondevteam/discordplugin/DiscordPlayer.scala new file mode 100644 index 0000000..017e12a --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlayer.scala @@ -0,0 +1,20 @@ +package buttondevteam.discordplugin + +import buttondevteam.discordplugin.mcchat.MCChatPrivate +import buttondevteam.lib.player.{ChromaGamerBase, UserClass} + +@UserClass(foldername = "discord") class DiscordPlayer() extends ChromaGamerBase { + private var did: String = null + + // private @Getter @Setter boolean minecraftChatEnabled; + def getDiscordID: String = { + if (did == null) did = getFileName + did + } + + /** + * Returns true if player has the private Minecraft chat enabled. For setting the value, see + * {@link MCChatPrivate# privateMCChat ( MessageChannel, boolean, User, DiscordPlayer)} + */ + def isMinecraftChatEnabled: Boolean = MCChatPrivate.isMinecraftChatEnabled(this) +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java deleted file mode 100755 index b9e7f86..0000000 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.java +++ /dev/null @@ -1,42 +0,0 @@ -package buttondevteam.discordplugin; - -import buttondevteam.discordplugin.playerfaker.VCMDWrapper; -import discord4j.core.object.entity.User; -import discord4j.core.object.entity.channel.MessageChannel; -import lombok.Getter; -import org.bukkit.entity.Player; -import org.mockito.Mockito; - -import java.lang.reflect.Modifier; - -public abstract class DiscordPlayerSender extends DiscordSenderBase implements IMCPlayer { - - protected Player player; - private @Getter final VCMDWrapper vanillaCmdListener; - - public DiscordPlayerSender(User user, MessageChannel channel, Player player, MinecraftChatModule module) { - super(user, channel); - this.player = player; - vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, player, module)); - } - - @Override - public void sendMessage(String message) { - player.sendMessage(message); - super.sendMessage(message); - } - - @Override - public void sendMessage(String[] messages) { - player.sendMessage(messages); - super.sendMessage(messages); - } - - public static DiscordPlayerSender create(User user, MessageChannel channel, Player player, MinecraftChatModule module) { - return Mockito.mock(DiscordPlayerSender.class, Mockito.withSettings().stubOnly().defaultAnswer(invocation -> { - if (!Modifier.isAbstract(invocation.getMethod().getModifiers())) - return invocation.callRealMethod(); - return invocation.getMethod().invoke(((DiscordPlayerSender) invocation.getMock()).player, invocation.getArguments()); - }).useConstructor(user, channel, player, module)); - } -} diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.scala b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.scala new file mode 100644 index 0000000..5994da7 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.scala @@ -0,0 +1,41 @@ +package buttondevteam.discordplugin + +import buttondevteam.discordplugin.mcchat.MinecraftChatModule +import buttondevteam.discordplugin.playerfaker.VCMDWrapper +import discord4j.core.`object`.entity.User +import discord4j.core.`object`.entity.channel.MessageChannel +import org.bukkit.entity.Player +import org.mockito.Mockito +import org.mockito.invocation.InvocationOnMock + +import java.lang.reflect.Modifier + +object DiscordPlayerSender { + def create(user: User, channel: MessageChannel, player: Player, module: MinecraftChatModule): DiscordPlayerSender = + Mockito.mock(classOf[DiscordPlayerSender], Mockito.withSettings.stubOnly.defaultAnswer((invocation: InvocationOnMock) => { + def foo(invocation: InvocationOnMock): AnyRef = { + if (!Modifier.isAbstract(invocation.getMethod.getModifiers)) + invocation.callRealMethod + else + invocation.getMethod.invoke(invocation.getMock.asInstanceOf[DiscordPlayerSender].player, invocation.getArguments) + } + + foo(invocation) + }).useConstructor(user, channel, player, module)) +} + +abstract class DiscordPlayerSender(val user: User, val channel: MessageChannel, var player: Player, val module: Nothing) extends DiscordSenderBase(user, channel) with IMCPlayer[DiscordPlayerSender] { + val vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, player, module)) + + override def getVanillaCmdListener: VCMDWrapper = this.vanillaCmdListener + + override def sendMessage(message: String): Unit = { + player.sendMessage(message) + super.sendMessage(message) + } + + override def sendMessage(messages: Array[String]): Unit = { + player.sendMessage(messages) + super.sendMessage(messages) + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/DiscordSender.java b/src/main/java/buttondevteam/discordplugin/DiscordSender.java deleted file mode 100755 index 0ef62a9..0000000 --- a/src/main/java/buttondevteam/discordplugin/DiscordSender.java +++ /dev/null @@ -1,117 +0,0 @@ -package buttondevteam.discordplugin; - -import discord4j.core.object.entity.Member; -import discord4j.core.object.entity.User; -import discord4j.core.object.entity.channel.MessageChannel; -import lombok.val; -import org.bukkit.Bukkit; -import org.bukkit.Server; -import org.bukkit.command.CommandSender; -import org.bukkit.permissions.PermissibleBase; -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionAttachment; -import org.bukkit.permissions.PermissionAttachmentInfo; -import org.bukkit.plugin.Plugin; -import reactor.core.publisher.Mono; - -import java.util.Set; - -public class DiscordSender extends DiscordSenderBase implements CommandSender { - private PermissibleBase perm = new PermissibleBase(this); - - private String name; - - public DiscordSender(User user, MessageChannel channel) { - super(user, channel); - val def = "Discord user"; - name = user == null ? def : user.asMember(DiscordPlugin.mainServer.getId()) - .onErrorResume(t -> Mono.empty()).blockOptional().map(Member::getDisplayName).orElse(def); - } - - public DiscordSender(User user, MessageChannel channel, String name) { - super(user, channel); - this.name = name; - } - - @Override - public boolean isPermissionSet(String name) { - return perm.isPermissionSet(name); - } - - @Override - public boolean isPermissionSet(Permission perm) { - return this.perm.isPermissionSet(perm); - } - - @Override - public boolean hasPermission(String name) { - if (name.contains("essentials") && !name.equals("essentials.list")) - return false; - return perm.hasPermission(name); - } - - @Override - public boolean hasPermission(Permission perm) { - return this.perm.hasPermission(perm); - } - - @Override - public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { - return perm.addAttachment(plugin, name, value); - } - - @Override - public PermissionAttachment addAttachment(Plugin plugin) { - return perm.addAttachment(plugin); - } - - @Override - public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { - return perm.addAttachment(plugin, name, value, ticks); - } - - @Override - public PermissionAttachment addAttachment(Plugin plugin, int ticks) { - return perm.addAttachment(plugin, ticks); - } - - @Override - public void removeAttachment(PermissionAttachment attachment) { - perm.removeAttachment(attachment); - } - - @Override - public void recalculatePermissions() { - perm.recalculatePermissions(); - } - - @Override - public Set getEffectivePermissions() { - return perm.getEffectivePermissions(); - } - - @Override - public boolean isOp() { - return false; - } - - @Override - public void setOp(boolean value) { - } - - @Override - public Server getServer() { - return Bukkit.getServer(); - } - - @Override - public String getName() { - return name; - } - - @Override - public Spigot spigot() { - return new CommandSender.Spigot(); - } - -} diff --git a/src/main/java/buttondevteam/discordplugin/DiscordSender.scala b/src/main/java/buttondevteam/discordplugin/DiscordSender.scala new file mode 100644 index 0000000..5e9c038 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/DiscordSender.scala @@ -0,0 +1,64 @@ +package buttondevteam.discordplugin + +import discord4j.core.`object`.entity.User +import discord4j.core.`object`.entity.channel.MessageChannel +import org.bukkit.{Bukkit, Server} +import org.bukkit.command.CommandSender +import org.bukkit.permissions.{PermissibleBase, Permission, PermissionAttachment, PermissionAttachmentInfo} +import org.bukkit.plugin.Plugin +import reactor.core.publisher.Mono + +import java.util + +class DiscordSender(user: User, channel: MessageChannel) extends DiscordSenderBase(user, channel) with CommandSender { + private val perm = new PermissibleBase(this) + private var name: String = null + + def this(user: User, channel: MessageChannel) { + this(user, channel) + val `def` = "Discord user" + name = if (user == null) `def` + else user.asMember(DiscordPlugin.mainServer.getId).onErrorResume((_: Throwable) => Mono.empty).blockOptional.map(_.getDisplayName).orElse(`def`) + } + + def this(user: User, channel: MessageChannel, name: String) { + this(user, channel) + this.name = name + } + + override def isPermissionSet(name: String): Boolean = perm.isPermissionSet(name) + + override def isPermissionSet(perm: Permission): Boolean = this.perm.isPermissionSet(perm) + + override def hasPermission(name: String): Boolean = { + if (name.contains("essentials") && !(name == "essentials.list")) return false + perm.hasPermission(name) + } + + override def hasPermission(perm: Permission): Boolean = this.perm.hasPermission(perm) + + override def addAttachment(plugin: Plugin, name: String, value: Boolean): PermissionAttachment = perm.addAttachment(plugin, name, value) + + override def addAttachment(plugin: Plugin): PermissionAttachment = perm.addAttachment(plugin) + + override def addAttachment(plugin: Plugin, name: String, value: Boolean, ticks: Int): PermissionAttachment = perm.addAttachment(plugin, name, value, ticks) + + override def addAttachment(plugin: Plugin, ticks: Int): PermissionAttachment = perm.addAttachment(plugin, ticks) + + override def removeAttachment(attachment: PermissionAttachment): Unit = perm.removeAttachment(attachment) + + override def recalculatePermissions(): Unit = perm.recalculatePermissions() + + override def getEffectivePermissions: util.Set[PermissionAttachmentInfo] = perm.getEffectivePermissions + + override def isOp = false + + override def setOp(value: Boolean): Unit = { + } + + override def getServer: Server = Bukkit.getServer + + override def getName: String = name + + override def spigot = new CommandSender.Spigot +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.java b/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.java deleted file mode 100755 index 7ada97f..0000000 --- a/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.java +++ /dev/null @@ -1,75 +0,0 @@ -package buttondevteam.discordplugin; - -import buttondevteam.lib.TBMCCoreAPI; -import discord4j.core.object.entity.User; -import discord4j.core.object.entity.channel.MessageChannel; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.scheduler.BukkitTask; - -public abstract class DiscordSenderBase implements CommandSender { - /** - * May be null. - */ - protected User user; - protected MessageChannel channel; - - protected DiscordSenderBase(User user, MessageChannel channel) { - this.user = user; - this.channel = channel; - } - - private volatile String msgtosend = ""; - private volatile BukkitTask sendtask; - - /** - * Returns the user. May be null. - * - * @return The user or null. - */ - public User getUser() { - return user; - } - - public MessageChannel getChannel() { - return channel; - } - - private DiscordPlayer chromaUser; - - /** - * Loads the user data on first query. - * - * @return A Chroma user of Discord or a Discord user of Chroma - */ - public DiscordPlayer getChromaUser() { - if (chromaUser == null) chromaUser = DiscordPlayer.getUser(user.getId().asString(), DiscordPlayer.class); - return chromaUser; - } - - @Override - public void sendMessage(String message) { - try { - final boolean broadcast = new Exception().getStackTrace()[2].getMethodName().contains("broadcast"); - if (broadcast) //We're catching broadcasts using the Bukkit event - return; - final String sendmsg = DPUtils.sanitizeString(message); - synchronized (this) { - msgtosend += "\n" + sendmsg; - if (sendtask == null) - sendtask = Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, () -> { - channel.createMessage((user != null ? user.getMention() + "\n" : "") + msgtosend.trim()).subscribe(); - sendtask = null; - msgtosend = ""; - }, 4); // Waits a 0.2 second to gather all/most of the different messages - } - } catch (Exception e) { - TBMCCoreAPI.SendException("An error occured while sending message to DiscordSender", e, DiscordPlugin.plugin); - } - } - - @Override - public void sendMessage(String[] messages) { - sendMessage(String.join("\n", messages)); - } -} diff --git a/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.scala b/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.scala new file mode 100644 index 0000000..83121aa --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.scala @@ -0,0 +1,64 @@ +package buttondevteam.discordplugin + +import buttondevteam.lib.TBMCCoreAPI +import buttondevteam.lib.player.ChromaGamerBase +import discord4j.core.`object`.entity.User +import discord4j.core.`object`.entity.channel.MessageChannel +import org.bukkit.Bukkit +import org.bukkit.command.CommandSender +import org.bukkit.scheduler.BukkitTask + +/** + * + * @param user May be null. + * @param channel May not be null. + */ +abstract class DiscordSenderBase protected(var user: User, var channel: MessageChannel) extends CommandSender { + private var msgtosend = "" + private var sendtask: BukkitTask = null + + /** + * Returns the user. May be null. + * + * @return The user or null. + */ + def getUser: User = user + + def getChannel: MessageChannel = channel + + private var chromaUser: DiscordPlayer = null + + /** + * Loads the user data on first query. + * + * @return A Chroma user of Discord or a Discord user of Chroma + */ + def getChromaUser: DiscordPlayer = { + if (chromaUser == null) chromaUser = ChromaGamerBase.getUser(user.getId.asString, classOf[DiscordPlayer]) + chromaUser + } + + override def sendMessage(message: String): Unit = try { + val broadcast = new Exception().getStackTrace()(2).getMethodName.contains("broadcast") + if (broadcast) { //We're catching broadcasts using the Bukkit event + return + } + val sendmsg = DPUtils.sanitizeString(message) + this synchronized msgtosend += "\n" + sendmsg + if (sendtask == null) sendtask = Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => { + def foo(): Unit = { + channel.createMessage((if (user != null) user.getMention + "\n" + else "") + msgtosend.trim).subscribe + sendtask = null + msgtosend = "" + } + + foo() + }, 4) // Waits a 0.2 second to gather all/most of the different messages + } catch { + case e: Exception => + TBMCCoreAPI.SendException("An error occured while sending message to DiscordSender", e, DiscordPlugin.plugin) + } + + override def sendMessage(messages: Array[String]): Unit = sendMessage(String.join("\n", messages)) +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/IMCPlayer.java b/src/main/java/buttondevteam/discordplugin/IMCPlayer.java deleted file mode 100755 index c2ee28e..0000000 --- a/src/main/java/buttondevteam/discordplugin/IMCPlayer.java +++ /dev/null @@ -1,8 +0,0 @@ -package buttondevteam.discordplugin; - -import buttondevteam.discordplugin.playerfaker.VCMDWrapper; -import org.bukkit.entity.Player; - -public interface IMCPlayer extends Player { - VCMDWrapper getVanillaCmdListener(); -} diff --git a/src/main/java/buttondevteam/discordplugin/IMCPlayer.scala b/src/main/java/buttondevteam/discordplugin/IMCPlayer.scala new file mode 100644 index 0000000..70f1a5d --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/IMCPlayer.scala @@ -0,0 +1,8 @@ +package buttondevteam.discordplugin + +import buttondevteam.discordplugin.playerfaker.VCMDWrapper +import org.bukkit.entity.Player + +trait IMCPlayer[T] extends Player { + def getVanillaCmdListener: VCMDWrapper +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala index e06a34d..7eaae1e 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala @@ -1,6 +1,7 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.{ComponentManager, MainPlugin, component} +import buttondevteam.discordplugin.ChannelconBroadcast.ChannelconBroadcast import buttondevteam.discordplugin._ import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule import buttondevteam.discordplugin.mcchat.MCChatCustom.CustomLMD @@ -105,14 +106,14 @@ object MCChatUtils { def getSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = { val map = senders.get(user.getId.asString) - if (map != null) return map.get(channel) - null + if (map != null) map.get(channel) + else null.asInstanceOf } def removeSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = { val map = senders.get(user.getId.asString) - if (map != null) return map.remove(channel) - null + if (map != null) map.remove(channel) + else null.asInstanceOf } def forPublicPrivateChat(action: Mono[MessageChannel] => Mono[_]): Mono[_] = { @@ -154,7 +155,7 @@ object MCChatUtils { if (notEnabled) return Mono.empty val st = MCChatCustom.lastmsgCustom.stream.filter((clmd) => { def foo(clmd: CustomLMD): Boolean = { //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) eq 0)) return false //If null then allow + if (toggle != null && ((clmd.toggles & (1 << toggle.id)) eq 0)) return false //If null then allow if (sender == null) return true clmd.groupID.equals(clmd.mcchannel.getGroupID(sender)) } diff --git a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java deleted file mode 100644 index e4cd17d..0000000 --- a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.java +++ /dev/null @@ -1,144 +0,0 @@ -package buttondevteam.discordplugin.mccommands; - -import buttondevteam.discordplugin.DPUtils; -import buttondevteam.discordplugin.DiscordPlayer; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.discordplugin.DiscordSenderBase; -import buttondevteam.discordplugin.util.DPState; -import buttondevteam.lib.chat.Command2; -import buttondevteam.lib.chat.CommandClass; -import buttondevteam.lib.chat.ICommand2MC; -import buttondevteam.lib.player.ChromaGamerBase; -import buttondevteam.lib.player.TBMCPlayer; -import buttondevteam.lib.player.TBMCPlayerBase; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import reactor.core.publisher.Mono; - -import java.lang.reflect.Method; - -@CommandClass(path = "discord", helpText = { - "Discord", - "This command allows performing Discord-related actions." -}) -public class DiscordMCCommand extends ICommand2MC { - @Command2.Subcommand - public boolean accept(Player player) { - if (checkSafeMode(player)) return true; - String did = ConnectCommand.WaitingToConnect.get(player.getName()); - if (did == null) { - player.sendMessage("§cYou don't have a pending connection to Discord."); - return true; - } - DiscordPlayer dp = ChromaGamerBase.getUser(did, DiscordPlayer.class); - TBMCPlayer mcp = TBMCPlayerBase.getPlayer(player.getUniqueId(), TBMCPlayer.class); - dp.connectWith(mcp); - ConnectCommand.WaitingToConnect.remove(player.getName()); - MCChatUtils.UnconnectedSenders.remove(did); //Remove all unconnected, will be recreated where needed - player.sendMessage("§bAccounts connected."); - return true; - } - - @Command2.Subcommand - public boolean decline(Player player) { - if (checkSafeMode(player)) return true; - String did = ConnectCommand.WaitingToConnect.remove(player.getName()); - if (did == null) { - player.sendMessage("§cYou don't have a pending connection to Discord."); - return true; - } - player.sendMessage("§bPending connection declined."); - return true; - } - - @Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = { - "Reload Discord plugin", - "Reloads the config. To apply some changes, you may need to also run /discord restart." - }) - public void reload(CommandSender sender) { - if (DiscordPlugin.plugin.tryReloadConfig()) - sender.sendMessage("§bConfig reloaded."); - else - sender.sendMessage("§cFailed to reload config."); - } - - @Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = { - "Restart the plugin", // - "This command disables and then enables the plugin." // - }) - public void restart(CommandSender sender) { - Runnable task = () -> { - if (!DiscordPlugin.plugin.tryReloadConfig()) { - sender.sendMessage("§cFailed to reload config so not restarting. Check the console."); - return; - } - MinecraftChatModule.state = DPState.RESTARTING_PLUGIN; //Reset in MinecraftChatModule - sender.sendMessage("§bDisabling DiscordPlugin..."); - Bukkit.getPluginManager().disablePlugin(DiscordPlugin.plugin); - if (!(sender instanceof DiscordSenderBase)) //Sending to Discord errors - sender.sendMessage("§bEnabling DiscordPlugin..."); - Bukkit.getPluginManager().enablePlugin(DiscordPlugin.plugin); - if (!(sender instanceof DiscordSenderBase)) //Sending to Discord errors - sender.sendMessage("§bRestart finished!"); - }; - if (!Bukkit.getName().equals("Paper")) { - getPlugin().getLogger().warning("Async plugin events are not supported by the server, running on main thread"); - Bukkit.getScheduler().runTask(DiscordPlugin.plugin, task); - } else - Bukkit.getScheduler().runTaskAsynchronously(DiscordPlugin.plugin, task); - } - - @Command2.Subcommand(helpText = { - "Version command", - "Prints the plugin version" - }) - public void version(CommandSender sender) { - sender.sendMessage(VersionCommand.getVersion()); - } - - @Command2.Subcommand(helpText = { - "Invite", - "Shows an invite link to the server" - }) - public void invite(CommandSender sender) { - if (checkSafeMode(sender)) return; - String invi = DiscordPlugin.plugin.inviteLink.get(); - if (invi.length() > 0) { - sender.sendMessage("§bInvite link: " + invi); - return; - } - DiscordPlugin.mainServer.getInvites().limitRequest(1) - .switchIfEmpty(Mono.fromRunnable(() -> sender.sendMessage("§cNo invites found for the server."))) - .subscribe(inv -> sender.sendMessage("§bInvite link: https://discord.gg/" + inv.getCode()), - e -> sender.sendMessage("§cThe invite link is not set and the bot has no permission to get it.")); - } - - @Override - public String[] getHelpText(Method method, Command2.Subcommand ann) { - switch (method.getName()) { - case "accept": - return new String[]{ // - "Accept Discord connection", // - "Accept a pending connection between your Discord and Minecraft account.", // - "To start the connection process, do §b/connect §r in the " + DPUtils.botmention() + " channel on Discord", // - }; - case "decline": - return new String[]{ // - "Decline Discord connection", // - "Decline a pending connection between your Discord and Minecraft account.", // - "To start the connection process, do §b/connect §r in the " + DPUtils.botmention() + " channel on Discord", // - }; - default: - return super.getHelpText(method, ann); - } - } - - private boolean checkSafeMode(CommandSender sender) { - if (DiscordPlugin.SafeMode) { - sender.sendMessage("§cThe plugin isn't initialized. Check console for details."); - return true; - } - return false; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala new file mode 100644 index 0000000..76cb49d --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala @@ -0,0 +1,128 @@ +package buttondevteam.discordplugin.mccommands + +import buttondevteam.discordplugin.{DPUtils, DiscordPlayer, DiscordPlugin, DiscordSenderBase} +import buttondevteam.discordplugin.commands.{ConnectCommand, VersionCommand} +import buttondevteam.discordplugin.mcchat.{MCChatUtils, MinecraftChatModule} +import buttondevteam.discordplugin.util.DPState +import buttondevteam.lib.chat.{Command2, CommandClass, ICommand2MC} +import buttondevteam.lib.player.{ChromaGamerBase, TBMCPlayer, TBMCPlayerBase} +import discord4j.core.`object`.ExtendedInvite +import org.bukkit.Bukkit +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player +import reactor.core.publisher.Mono + +import java.lang.reflect.Method + +@CommandClass(path = "discord", helpText = Array(Array( + "Discord", + "This command allows performing Discord-related actions." +))) class DiscordMCCommand extends ICommand2MC { + @Command2.Subcommand def accept(player: Player): Boolean = { + if (checkSafeMode(player)) return true + val did = ConnectCommand.WaitingToConnect.get(player.getName) + if (did == null) { + player.sendMessage("§cYou don't have a pending connection to Discord.") + return true + } + val dp = ChromaGamerBase.getUser(did, classOf[DiscordPlayer]) + val mcp = TBMCPlayerBase.getPlayer(player.getUniqueId, classOf[TBMCPlayer]) + dp.connectWith(mcp) + ConnectCommand.WaitingToConnect.remove(player.getName) + MCChatUtils.UnconnectedSenders.remove(did) //Remove all unconnected, will be recreated where needed + player.sendMessage("§bAccounts connected.") + true + } + + @Command2.Subcommand def decline(player: Player): Boolean = { + if (checkSafeMode(player)) return true + val did = ConnectCommand.WaitingToConnect.remove(player.getName) + if (did == null) { + player.sendMessage("§cYou don't have a pending connection to Discord.") + return true + } + player.sendMessage("§bPending connection declined.") + true + } + + @Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = Array(Array( + "Reload Discord plugin", + "Reloads the config. To apply some changes, you may need to also run /discord restart." + ))) def reload(sender: CommandSender): Unit = + if (DiscordPlugin.plugin.tryReloadConfig) sender.sendMessage("§bConfig reloaded.") + else sender.sendMessage("§cFailed to reload config.") + + @Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = Array(Array( + "Restart the plugin", // + "This command disables and then enables the plugin." // + ))) def restart(sender: CommandSender): Unit = { + val task: Runnable = () => { + def foo(): Unit = { + if (!DiscordPlugin.plugin.tryReloadConfig) { + sender.sendMessage("§cFailed to reload config so not restarting. Check the console.") + return + } + MinecraftChatModule.state = DPState.RESTARTING_PLUGIN //Reset in MinecraftChatModule + sender.sendMessage("§bDisabling DiscordPlugin...") + Bukkit.getPluginManager.disablePlugin(DiscordPlugin.plugin) + if (!sender.isInstanceOf[DiscordSenderBase]) { //Sending to Discord errors + sender.sendMessage("§bEnabling DiscordPlugin...") + } + Bukkit.getPluginManager.enablePlugin(DiscordPlugin.plugin) + if (!sender.isInstanceOf[DiscordSenderBase]) sender.sendMessage("§bRestart finished!") + } + + foo() + } + + if (!(Bukkit.getName == "Paper")) { + getPlugin.getLogger.warning("Async plugin events are not supported by the server, running on main thread") + Bukkit.getScheduler.runTask(DiscordPlugin.plugin, task) + } + else { + Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, task) + } + } + + @Command2.Subcommand(helpText = Array(Array( + "Version command", + "Prints the plugin version"))) def version(sender: CommandSender): Unit = { + sender.sendMessage(VersionCommand.getVersion) + } + + @Command2.Subcommand(helpText = Array(Array( + "Invite", + "Shows an invite link to the server" + ))) def invite(sender: CommandSender): Unit = { + if (checkSafeMode(sender)) { + return + } + val invi: String = DiscordPlugin.plugin.inviteLink.get + if (invi.nonEmpty) { + sender.sendMessage("§bInvite link: " + invi) + return + } + DiscordPlugin.mainServer.getInvites.limitRequest(1) + .switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("§cNo invites found for the server."))) + .subscribe((inv: ExtendedInvite) => sender.sendMessage("§bInvite link: https://discord.gg/" + inv.getCode), _ => sender.sendMessage("§cThe invite link is not set and the bot has no permission to get it.")) + } + + override def getHelpText(method: Method, ann: Command2.Subcommand): Array[String] = { + method.getName match { + case "accept" => + Array[String]("Accept Discord connection", "Accept a pending connection between your Discord and Minecraft account.", "To start the connection process, do §b/connect §r in the " + DPUtils.botmention + " channel on Discord") + case "decline" => + Array[String]("Decline Discord connection", "Decline a pending connection between your Discord and Minecraft account.", "To start the connection process, do §b/connect §r in the " + DPUtils.botmention + " channel on Discord") + case _ => + super.getHelpText(method, ann) + } + } + + private def checkSafeMode(sender: CommandSender): Boolean = { + if (DiscordPlugin.SafeMode) { + sender.sendMessage("§cThe plugin isn't initialized. Check console for details.") + true + } + else false + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.java b/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.java deleted file mode 100644 index 116cade..0000000 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.java +++ /dev/null @@ -1,20 +0,0 @@ -package buttondevteam.discordplugin.playerfaker; - -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Delegate; -import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker; -import org.mockito.plugins.MockMaker; - -public class DelegatingMockMaker implements MockMaker { - @Getter - @Setter - @Delegate - private MockMaker mockMaker = new SubclassByteBuddyMockMaker(); - @Getter - private static DelegatingMockMaker instance; - - public DelegatingMockMaker() { - instance = this; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala b/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala new file mode 100644 index 0000000..0e14a89 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala @@ -0,0 +1,49 @@ +package buttondevteam.discordplugin.playerfaker + +import org.mockito.MockedConstruction +import org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker +import org.mockito.invocation.MockHandler +import org.mockito.mock.MockCreationSettings +import org.mockito.plugins.MockMaker + +import java.util.Optional + +object DelegatingMockMaker { + def getInstance: DelegatingMockMaker = DelegatingMockMaker.instance + + private var instance: DelegatingMockMaker = null +} + +class DelegatingMockMaker() extends MockMaker { + DelegatingMockMaker.instance = this + + override def createMock[T](settings: MockCreationSettings[T], handler: MockHandler[_]): T = + this.mockMaker.createMock(settings, handler) + + override def createSpy[T](settings: MockCreationSettings[T], handler: MockHandler[_], instance: T): Optional[T] = + this.mockMaker.createSpy(settings, handler, instance) + + override def getHandler(mock: Any): MockHandler[_] = + this.mockMaker.getHandler(mock) + + override def resetMock(mock: Any, newHandler: MockHandler[_], settings: MockCreationSettings[_]): Unit = { + this.mockMaker.resetMock(mock, newHandler, settings) + } + + override def isTypeMockable(`type`: Class[_]): MockMaker.TypeMockability = + this.mockMaker.isTypeMockable(`type`) + + override def createStaticMock[T](`type`: Class[T], settings: MockCreationSettings[T], handler: MockHandler[_]): MockMaker.StaticMockControl[T] = + this.mockMaker.createStaticMock(`type`, settings, handler) + + override def createConstructionMock[T](`type`: Class[T], settingsFactory: Function[MockedConstruction.Context, MockCreationSettings[T]], handlerFactory: Function[MockedConstruction.Context, MockHandler[T]], mockInitializer: MockedConstruction.MockInitializer[T]): MockMaker.ConstructionMockControl[T] = + this.mockMaker.createConstructionMock[T](`type`, settingsFactory: Function[MockedConstruction.Context, MockCreationSettings[T]], handlerFactory, mockInitializer) + + def setMockMaker(mockMaker: MockMaker): Unit = { + this.mockMaker = mockMaker + } + + def getMockMaker: MockMaker = this.mockMaker + + private var mockMaker: MockMaker = new SubclassByteBuddyMockMaker +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java b/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java deleted file mode 100644 index bf3051e..0000000 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.java +++ /dev/null @@ -1,96 +0,0 @@ -package buttondevteam.discordplugin.playerfaker; - -import com.destroystokyo.paper.profile.CraftPlayerProfile; -import lombok.RequiredArgsConstructor; -import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding; -import org.bukkit.Bukkit; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import org.mockito.Mockito; -import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker; - -import java.lang.reflect.Modifier; -import java.util.*; - -public class ServerWatcher { - private List playerList; - public final List fakePlayers = new ArrayList<>(); - private Server origServer; - - @IgnoreForBinding - public void enableDisable(boolean enable) throws Exception { - var serverField = Bukkit.class.getDeclaredField("server"); - serverField.setAccessible(true); - if (enable) { - var serverClass = Bukkit.getServer().getClass(); - var originalServer = serverField.get(null); - DelegatingMockMaker.getInstance().setMockMaker(new InlineByteBuddyMockMaker()); - var settings = Mockito.withSettings().stubOnly() - .defaultAnswer(invocation -> { - var method = invocation.getMethod(); - int pc = method.getParameterCount(); - Player player = null; - switch (method.getName()) { - case "getPlayer": - if (pc == 1 && method.getParameterTypes()[0] == UUID.class) - player = MCChatUtils.LoggedInPlayers.get(invocation.getArgument(0)); - break; - case "getPlayerExact": - if (pc == 1) { - final String argument = invocation.getArgument(0); - player = MCChatUtils.LoggedInPlayers.values().stream() - .filter(dcp -> dcp.getName().equalsIgnoreCase(argument)).findAny().orElse(null); - } - break; - /*case "getOnlinePlayers": - if (playerList == null) { - @SuppressWarnings("unchecked") var list = (List) method.invoke(origServer, invocation.getArguments()); - playerList = new AppendListView<>(list, fakePlayers); - } - Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should. - return playerList;*/ - case "createProfile": //Paper's method, casts the player to a CraftPlayer - if (pc == 2) { - UUID uuid = invocation.getArgument(0); - String name = invocation.getArgument(1); - player = uuid != null ? MCChatUtils.LoggedInPlayers.get(uuid) : null; - if (player == null && name != null) - player = MCChatUtils.LoggedInPlayers.values().stream() - .filter(dcp -> dcp.getName().equalsIgnoreCase(name)).findAny().orElse(null); - if (player != null) - return new CraftPlayerProfile(player.getUniqueId(), player.getName()); - } - break; - } - if (player != null) - return player; - return method.invoke(origServer, invocation.getArguments()); - }); - //var mock = mockMaker.createMock(settings, MockHandlerFactory.createMockHandler(settings)); - //thread.setContextClassLoader(cl); - var mock = Mockito.mock(serverClass, settings); - for (var field : serverClass.getFields()) //Copy public fields, private fields aren't accessible directly anyways - if (!Modifier.isFinal(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) - field.set(mock, field.get(originalServer)); - serverField.set(null, mock); - origServer = (Server) originalServer; - } else if (origServer != null) - serverField.set(null, origServer); - } - - @RequiredArgsConstructor - public static class AppendListView extends AbstractSequentialList { - private final List originalList; - private final List additionalList; - - @Override - public ListIterator listIterator(int i) { - int os = originalList.size(); - return i < os ? originalList.listIterator(i) : additionalList.listIterator(i - os); - } - - @Override - public int size() { - return originalList.size() + additionalList.size(); - } - } -} diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala b/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala new file mode 100644 index 0000000..ceb01b8 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala @@ -0,0 +1,91 @@ +package buttondevteam.discordplugin.playerfaker + +import buttondevteam.discordplugin.DiscordConnectedPlayer +import buttondevteam.discordplugin.mcchat.MCChatUtils +import com.destroystokyo.paper.profile.CraftPlayerProfile +import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding +import org.bukkit.{Bukkit, Server} +import org.bukkit.entity.Player +import org.mockito.Mockito +import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker +import org.mockito.invocation.InvocationOnMock + +import java.lang.reflect.Modifier +import java.util +import java.util._ + +object ServerWatcher { + + class AppendListView[T](private val originalList: java.util.List[T], private val additionalList: java.util.List[T]) extends java.util.AbstractSequentialList[T] { + + override def listIterator(i: Int): util.ListIterator[T] = { + val os = originalList.size + if (i < os) originalList.listIterator(i) + else additionalList.listIterator(i - os) + } + + override def size: Int = originalList.size + additionalList.size + } + +} + +class ServerWatcher { + final val fakePlayers = new util.ArrayList[Player] + private var origServer: Server = null + + @IgnoreForBinding + @throws[Exception] + def enableDisable(enable: Boolean): Unit = { + val serverField = classOf[Bukkit].getDeclaredField("server") + serverField.setAccessible(true) + if (enable) { + val serverClass = Bukkit.getServer.getClass + val originalServer = serverField.get(null) + DelegatingMockMaker.getInstance.setMockMaker(new InlineByteBuddyMockMaker) + val settings = Mockito.withSettings.stubOnly.defaultAnswer((invocation: InvocationOnMock) => { + def foo(invocation: InvocationOnMock): AnyRef = { + val method = invocation.getMethod + val pc = method.getParameterCount + var player: DiscordConnectedPlayer = null + method.getName match { + case "getPlayer" => + if (pc == 1 && (method.getParameterTypes()(0) eq classOf[UUID])) player = MCChatUtils.LoggedInPlayers.get(invocation.getArgument[UUID](0)) + case "getPlayerExact" => + if (pc == 1) { + val argument = invocation.getArgument(0) + player = MCChatUtils.LoggedInPlayers.values.stream.filter((dcp) => dcp.getName.equalsIgnoreCase(argument)).findAny.orElse(null) + } + + /*case "getOnlinePlayers": + if (playerList == null) { + @SuppressWarnings("unchecked") var list = (List) method.invoke(origServer, invocation.getArguments()); + playerList = new AppendListView<>(list, fakePlayers); + } - Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should. + return playerList;*/ case "createProfile" => //Paper's method, casts the player to a CraftPlayer + if (pc == 2) { + val uuid = invocation.getArgument(0) + val name = invocation.getArgument(1) + player = if (uuid != null) MCChatUtils.LoggedInPlayers.get(uuid) + else null + if (player == null && name != null) player = MCChatUtils.LoggedInPlayers.values.stream.filter((dcp) => dcp.getName.equalsIgnoreCase(name)).findAny.orElse(null) + if (player != null) return new CraftPlayerProfile(player.getUniqueId, player.getName) + } + } + if (player != null) return player + method.invoke(origServer, invocation.getArguments) + } + + foo(invocation) + }) + //var mock = mockMaker.createMock(settings, MockHandlerFactory.createMockHandler(settings)); + //thread.setContextClassLoader(cl); + val mock = Mockito.mock(serverClass, settings) + for (field <- serverClass.getFields) { //Copy public fields, private fields aren't accessible directly anyways + if (!Modifier.isFinal(field.getModifiers) && !Modifier.isStatic(field.getModifiers)) field.set(mock, field.get(originalServer)) + } + serverField.set(null, mock) + origServer = originalServer.asInstanceOf[Server] + } + else if (origServer != null) serverField.set(null, origServer) + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java index 7727673..6ff856b 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java @@ -2,6 +2,7 @@ package buttondevteam.discordplugin.playerfaker; import buttondevteam.discordplugin.DiscordSenderBase; import buttondevteam.discordplugin.IMCPlayer; +import buttondevteam.discordplugin.mcchat.MinecraftChatModule; import buttondevteam.lib.TBMCCoreAPI; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java b/src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java index c13b7dd..055c5e8 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java @@ -2,6 +2,7 @@ package buttondevteam.discordplugin.playerfaker.perm; import buttondevteam.discordplugin.DiscordConnectedPlayer; import buttondevteam.discordplugin.DiscordPlugin; +import buttondevteam.discordplugin.mcchat.MCChatUtils; import buttondevteam.lib.TBMCCoreAPI; import me.lucko.luckperms.bukkit.LPBukkitBootstrap; import me.lucko.luckperms.bukkit.LPBukkitPlugin; diff --git a/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java b/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java deleted file mode 100644 index c1a4079..0000000 --- a/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.java +++ /dev/null @@ -1,126 +0,0 @@ -package buttondevteam.discordplugin.role; - -import buttondevteam.core.ComponentManager; -import buttondevteam.discordplugin.DPUtils; -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.architecture.Component; -import buttondevteam.lib.architecture.ComponentMetadata; -import buttondevteam.lib.architecture.ReadOnlyConfigData; -import discord4j.core.event.domain.role.RoleCreateEvent; -import discord4j.core.event.domain.role.RoleDeleteEvent; -import discord4j.core.event.domain.role.RoleEvent; -import discord4j.core.event.domain.role.RoleUpdateEvent; -import discord4j.core.object.entity.Role; -import discord4j.core.object.entity.channel.MessageChannel; -import discord4j.rest.util.Color; -import lombok.val; -import org.bukkit.Bukkit; -import reactor.core.publisher.Mono; - -import java.util.Collections; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** - * Automatically collects roles with a certain color. - * Users can add these roles to themselves using the /role Discord command. - */ -@ComponentMetadata(enabledByDefault = false) -public class GameRoleModule extends Component { - public List GameRoles; - - private final RoleCommand command = new RoleCommand(this); - - @Override - protected void enable() { - getPlugin().getManager().registerCommand(command); - GameRoles = DiscordPlugin.mainServer.getRoles().filterWhen(this::isGameRole).map(Role::getName).collect(Collectors.toList()).block(); - } - - @Override - protected void disable() { - getPlugin().getManager().unregisterCommand(command); - } - - /** - * The channel where the bot logs when it detects a role change that results in a new game role or one being removed. - */ - private final ReadOnlyConfigData> logChannel = DPUtils.channelData(getConfig(), "logChannel"); - - /** - * The role color that is used by game roles. - * Defaults to the second to last in the upper row - #95a5a6. - */ - private final ReadOnlyConfigData roleColor = getConfig().getConfig("roleColor") - .def(Color.of(149, 165, 166)) - .getter(rgb -> Color.of(Integer.parseInt(((String) rgb).substring(1), 16))) - .setter(color -> String.format("#%08x", color.getRGB())).buildReadOnly(); - - public static void handleRoleEvent(RoleEvent roleEvent) { - val grm = ComponentManager.getIfEnabled(GameRoleModule.class); - if (grm == null) return; - val GameRoles = grm.GameRoles; - val logChannel = grm.logChannel.get(); - Predicate notMainServer = r -> r.getGuildId().asLong() != DiscordPlugin.mainServer.getId().asLong(); - if (roleEvent instanceof RoleCreateEvent) { - Bukkit.getScheduler().runTaskLaterAsynchronously(DiscordPlugin.plugin, () -> { - Role role = ((RoleCreateEvent) roleEvent).getRole(); - if (notMainServer.test(role)) - return; - grm.isGameRole(role).flatMap(b -> { - if (!b) - return Mono.empty(); //Deleted or not a game role - GameRoles.add(role.getName()); - if (logChannel != null) - return logChannel.flatMap(ch -> ch.createMessage("Added " + role.getName() + " as game role. If you don't want this, change the role's color from the game role color.")); - return Mono.empty(); - }).subscribe(); - }, 100); - } else if (roleEvent instanceof RoleDeleteEvent) { - Role role = ((RoleDeleteEvent) roleEvent).getRole().orElse(null); - if (role == null) return; - if (notMainServer.test(role)) - return; - if (GameRoles.remove(role.getName()) && logChannel != null) - logChannel.flatMap(ch -> ch.createMessage("Removed " + role.getName() + " as a game role.")).subscribe(); - } else if (roleEvent instanceof RoleUpdateEvent) { - val event = (RoleUpdateEvent) roleEvent; - if (!event.getOld().isPresent()) { - grm.logWarn("Old role not stored, cannot update game role!"); - return; - } - Role or = event.getOld().get(); - if (notMainServer.test(or)) - return; - grm.isGameRole(event.getCurrent()).flatMap(b -> { - if (!b) { - if (GameRoles.remove(or.getName()) && logChannel != null) - return logChannel.flatMap(ch -> ch.createMessage("Removed " + or.getName() + " as a game role because its color changed.")); - } else { - if (GameRoles.contains(or.getName()) && or.getName().equals(event.getCurrent().getName())) - return Mono.empty(); - boolean removed = GameRoles.remove(or.getName()); //Regardless of whether it was a game role - GameRoles.add(event.getCurrent().getName()); //Add it because it has no color - if (logChannel != null) { - if (removed) - return logChannel.flatMap(ch -> ch.createMessage("Changed game role from " + or.getName() + " to " + event.getCurrent().getName() + ".")); - else - return logChannel.flatMap(ch -> ch.createMessage("Added " + event.getCurrent().getName() + " as game role because it has the color of one.")); - } - } - return Mono.empty(); - }).subscribe(); - } - } - - private Mono isGameRole(Role r) { - if (r.getGuildId().asLong() != DiscordPlugin.mainServer.getId().asLong()) - return Mono.just(false); //Only allow on the main server - val rc = roleColor.get(); - return Mono.just(r.getColor().equals(rc)).filter(b -> b).flatMap(b -> - DiscordPlugin.dc.getSelf().flatMap(u -> u.asMember(DiscordPlugin.mainServer.getId())) - .flatMap(m -> m.hasHigherRoles(Collections.singleton(r.getId())))) //Below one of our roles - .defaultIfEmpty(false); - } -} diff --git a/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.scala b/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.scala new file mode 100644 index 0000000..95648ec --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.scala @@ -0,0 +1,107 @@ +package buttondevteam.discordplugin.role + +import buttondevteam.core.ComponentManager +import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} +import buttondevteam.lib.architecture.{Component, ComponentMetadata} +import discord4j.core.`object`.entity.Role +import discord4j.core.`object`.entity.channel.MessageChannel +import discord4j.core.event.domain.role.{RoleCreateEvent, RoleDeleteEvent, RoleEvent, RoleUpdateEvent} +import discord4j.rest.util.Color +import org.bukkit.Bukkit +import reactor.core.publisher.Mono + +import java.util.Collections +import java.util.stream.Collectors + +/** + * Automatically collects roles with a certain color. + * Users can add these roles to themselves using the /role Discord command. + */ +@ComponentMetadata(enabledByDefault = false) object GameRoleModule { + def handleRoleEvent(roleEvent: RoleEvent): Unit = { + val grm = ComponentManager.getIfEnabled(classOf[GameRoleModule]) + if (grm == null) return + val GameRoles = grm.GameRoles + val logChannel = grm.logChannel.get + val notMainServer = (r: Role) => r.getGuildId.asLong != DiscordPlugin.mainServer.getId.asLong + roleEvent match { + case roleCreateEvent: RoleCreateEvent => Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => { + def foo(): Unit = { + val role = roleCreateEvent.getRole + if (notMainServer(role)) return + grm.isGameRole(role).flatMap((b: Boolean) => { + def foo(b: Boolean): Mono[_] = { + if (!b) return Mono.empty //Deleted or not a game role + GameRoles.add(role.getName) + if (logChannel != null) return logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Added " + role.getName + " as game role. If you don't want this, change the role's color from the game role color.")) + Mono.empty + } + + foo(b) + }).subscribe + } + + foo() + }, 100) + case roleDeleteEvent: RoleDeleteEvent => + val role = roleDeleteEvent.getRole.orElse(null) + if (role == null) return + if (notMainServer(role)) return + if (GameRoles.remove(role.getName) && logChannel != null) logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Removed " + role.getName + " as a game role.")).subscribe + case roleUpdateEvent: RoleUpdateEvent => + if (!roleUpdateEvent.getOld.isPresent) { + grm.logWarn("Old role not stored, cannot update game role!") + return + } + val or = roleUpdateEvent.getOld.get + if (notMainServer(or)) return + val cr = roleUpdateEvent.getCurrent + grm.isGameRole(cr).flatMap((b: Boolean) => { + def foo(b: Boolean): Mono[_] = { + if (!b) if (GameRoles.remove(or.getName) && logChannel != null) return logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Removed " + or.getName + " as a game role because its color changed.")) + else { + if (GameRoles.contains(or.getName) && or.getName == cr.getName) return Mono.empty + val removed = GameRoles.remove(or.getName) //Regardless of whether it was a game role + GameRoles.add(cr.getName) //Add it because it has no color + if (logChannel != null) if (removed) return logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Changed game role from " + or.getName + " to " + cr.getName + ".")) + else return logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Added " + cr.getName + " as game role because it has the color of one.")) + } + Mono.empty + } + + foo(b) + }).subscribe + case _ => + } + } +} + +@ComponentMetadata(enabledByDefault = false) class GameRoleModule extends Component[DiscordPlugin] { + var GameRoles: java.util.List[String] = null + final private val command = new RoleCommand(this) + + override protected def enable(): Unit = { + getPlugin.manager.registerCommand(command) + GameRoles = DiscordPlugin.mainServer.getRoles.filterWhen(this.isGameRole _).map(_.getName).collect(Collectors.toList).block + } + + override protected def disable(): Unit = getPlugin.manager.unregisterCommand(command) + + /** + * The channel where the bot logs when it detects a role change that results in a new game role or one being removed. + */ + final private val logChannel = DPUtils.channelData(getConfig, "logChannel") + /** + * The role color that is used by game roles. + * Defaults to the second to last in the upper row - #95a5a6. + */ + final private val roleColor = getConfig.getConfig[Color]("roleColor").`def`(Color.of(149, 165, 166)).getter((rgb: Any) => Color.of(Integer.parseInt(rgb.asInstanceOf[String].substring(1), 16))).setter((color: Color) => String.format("#%08x", color.getRGB)).buildReadOnly + + private def isGameRole(r: Role): Mono[Boolean] = { + if (r.getGuildId.asLong != DiscordPlugin.mainServer.getId.asLong) return Mono.just(false) //Only allow on the main server + val rc = roleColor.get + if (r.getColor equals rc) + DiscordPlugin.dc.getSelf.flatMap((u) => u.asMember(DiscordPlugin.mainServer.getId)).flatMap((m) => m.hasHigherRoles(Collections.singleton(r.getId))).defaultIfEmpty(false) //Below one of our roles + else Mono.just(false) + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java deleted file mode 100755 index aac0aae..0000000 --- a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.java +++ /dev/null @@ -1,107 +0,0 @@ -package buttondevteam.discordplugin.role; - -import buttondevteam.discordplugin.DiscordPlugin; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.chat.Command2; -import buttondevteam.lib.chat.CommandClass; -import discord4j.core.object.entity.Role; -import lombok.val; -import reactor.core.publisher.Mono; - -import java.util.List; - -@CommandClass -public class RoleCommand extends ICommand2DC { - - private GameRoleModule grm; - - RoleCommand(GameRoleModule grm) { - this.grm = grm; - } - - @Command2.Subcommand(helpText = { - "Add role", - "This command adds a role to your account." - }) - public boolean add(Command2DCSender sender, @Command2.TextArg String rolename) { - final Role role = checkAndGetRole(sender, rolename); - if (role == null) - return true; - try { - sender.getMessage().getAuthorAsMember() - .flatMap(m -> m.addRole(role.getId()).switchIfEmpty(Mono.fromRunnable(() -> sender.sendMessage("added role.")))) - .subscribe(); - } catch (Exception e) { - TBMCCoreAPI.SendException("Error while adding role!", e, grm); - sender.sendMessage("an error occured while adding the role."); - } - return true; - } - - @Command2.Subcommand(helpText = { - "Remove role", - "This command removes a role from your account." - }) - public boolean remove(Command2DCSender sender, @Command2.TextArg String rolename) { - final Role role = checkAndGetRole(sender, rolename); - if (role == null) - return true; - try { - sender.getMessage().getAuthorAsMember() - .flatMap(m -> m.removeRole(role.getId()).switchIfEmpty(Mono.fromRunnable(() -> sender.sendMessage("removed role.")))) - .subscribe(); - } catch (Exception e) { - TBMCCoreAPI.SendException("Error while removing role!", e, grm); - sender.sendMessage("an error occured while removing the role."); - } - return true; - } - - @Command2.Subcommand - public void list(Command2DCSender sender) { - var sb = new StringBuilder(); - boolean b = false; - for (String role : (Iterable) grm.GameRoles.stream().sorted()::iterator) { - sb.append(role); - if (!b) - for (int j = 0; j < Math.max(1, 20 - role.length()); j++) - sb.append(" "); - else - sb.append("\n"); - b = !b; - } - if (sb.length() > 0 && sb.charAt(sb.length() - 1) != '\n') - sb.append('\n'); - sender.sendMessage("list of roles:\n```\n" + sb + "```"); - } - - private Role checkAndGetRole(Command2DCSender sender, String rolename) { - String rname = rolename; - if (!grm.GameRoles.contains(rolename)) { //If not found as-is, correct case - val orn = grm.GameRoles.stream().filter(r -> r.equalsIgnoreCase(rolename)).findAny(); - if (!orn.isPresent()) { - sender.sendMessage("that role cannot be found."); - list(sender); - return null; - } - rname = orn.get(); - } - val frname = rname; - final List roles = DiscordPlugin.mainServer.getRoles().filter(r -> r.getName().equals(frname)).collectList().block(); - if (roles == null) { - sender.sendMessage("an error occured."); - return null; - } - if (roles.size() == 0) { - sender.sendMessage("the specified role cannot be found on Discord! Removing from the list."); - grm.GameRoles.remove(rolename); - return null; - } - if (roles.size() > 1) { - sender.sendMessage("there are multiple roles with this name. Why are there multiple roles with this name?"); - return null; - } - return roles.get(0); - } - -} diff --git a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.scala b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.scala new file mode 100644 index 0000000..08db583 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.scala @@ -0,0 +1,84 @@ +package buttondevteam.discordplugin.role + +import buttondevteam.discordplugin.DiscordPlugin +import buttondevteam.discordplugin.commands.{Command2DCSender, ICommand2DC} +import buttondevteam.lib.TBMCCoreAPI +import buttondevteam.lib.chat.{Command2, CommandClass} +import discord4j.core.`object`.entity.Role +import reactor.core.publisher.Mono + +@CommandClass class RoleCommand private[role](var grm: GameRoleModule) extends ICommand2DC { + @Command2.Subcommand(helpText = Array(Array( + "Add role", + "This command adds a role to your account." + ))) def add(sender: Command2DCSender, @Command2.TextArg rolename: String): Boolean = { + val role = checkAndGetRole(sender, rolename) + if (role == null) return true + try sender.getMessage.getAuthorAsMember.flatMap(m => m.addRole(role.getId).switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("added role.")))).subscribe + catch { + case e: Exception => + TBMCCoreAPI.SendException("Error while adding role!", e, grm) + sender.sendMessage("an error occured while adding the role.") + } + true + } + + @Command2.Subcommand(helpText = Array(Array( + "Remove role", + "This command removes a role from your account." + ))) def remove(sender: Command2DCSender, @Command2.TextArg rolename: String): Boolean = { + val role = checkAndGetRole(sender, rolename) + if (role == null) return true + try sender.getMessage.getAuthorAsMember.flatMap(m => m.removeRole(role.getId).switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("removed role.")))).subscribe + catch { + case e: Exception => + TBMCCoreAPI.SendException("Error while removing role!", e, grm) + sender.sendMessage("an error occured while removing the role.") + } + true + } + + @Command2.Subcommand def list(sender: Command2DCSender): Unit = { + val sb = new StringBuilder + var b = false + for (role <- grm.GameRoles.stream.sorted.iterator.asInstanceOf[Iterable[String]]) { + sb.append(role) + if (!b) for (_ <- 0 until Math.max(1, 20 - role.length)) { + sb.append(" ") + } + else sb.append("\n") + b = !b + } + if (sb.nonEmpty && sb.charAt(sb.length - 1) != '\n') sb.append('\n') + sender.sendMessage("list of roles:\n```\n" + sb + "```") + } + + private def checkAndGetRole(sender: Command2DCSender, rolename: String): Role = { + var rname = rolename + if (!grm.GameRoles.contains(rolename)) { //If not found as-is, correct case + val orn = grm.GameRoles.stream.filter(r => r.equalsIgnoreCase(rolename)).findAny + if (!orn.isPresent) { + sender.sendMessage("that role cannot be found.") + list(sender) + return null + } + rname = orn.get + } + val frname = rname + val roles = DiscordPlugin.mainServer.getRoles.filter(r => r.getName.equals(frname)).collectList.block + if (roles == null) { + sender.sendMessage("an error occured.") + return null + } + if (roles.size == 0) { + sender.sendMessage("the specified role cannot be found on Discord! Removing from the list.") + grm.GameRoles.remove(rolename) + return null + } + if (roles.size > 1) { + sender.sendMessage("there are multiple roles with this name. Why are there multiple roles with this name?") + return null + } + roles.get(0) + } +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/util/DPState.java b/src/main/java/buttondevteam/discordplugin/util/DPState.java deleted file mode 100644 index b83d4ac..0000000 --- a/src/main/java/buttondevteam/discordplugin/util/DPState.java +++ /dev/null @@ -1,24 +0,0 @@ -package buttondevteam.discordplugin.util; - -public enum DPState { - /** - * Used from server start until anything else happens - */ - RUNNING, - /** - * Used when /restart is detected - */ - RESTARTING_SERVER, - /** - * Used when the plugin is disabled by outside forces - */ - STOPPING_SERVER, - /** - * Used when /discord restart is run - */ - RESTARTING_PLUGIN, - /** - * Used when the plugin is in the RUNNING state when the chat is disabled - */ - DISABLED_MCCHAT -} diff --git a/src/main/java/buttondevteam/discordplugin/util/DPState.scala b/src/main/java/buttondevteam/discordplugin/util/DPState.scala new file mode 100644 index 0000000..34bde89 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/util/DPState.scala @@ -0,0 +1,31 @@ +package buttondevteam.discordplugin.util + +object DPState extends Enumeration { + type DPState = Value + val + + /** + * Used from server start until anything else happens + */ + RUNNING, + + /** + * Used when /restart is detected + */ + RESTARTING_SERVER, + + /** + * Used when the plugin is disabled by outside forces + */ + STOPPING_SERVER, + + /** + * Used when /discord restart is run + */ + RESTARTING_PLUGIN, + + /** + * Used when the plugin is in the RUNNING state when the chat is disabled + */ + DISABLED_MCCHAT = Value +} \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/util/Timings.java b/src/main/java/buttondevteam/discordplugin/util/Timings.java deleted file mode 100644 index af91b0f..0000000 --- a/src/main/java/buttondevteam/discordplugin/util/Timings.java +++ /dev/null @@ -1,14 +0,0 @@ -package buttondevteam.discordplugin.util; - -public class Timings { - private long start; - - public Timings() { - start = System.nanoTime(); - } - - public void printElapsed(String message) { - CommonListeners.debug(message + " (" + (System.nanoTime() - start) / 1000000L + ")"); - start = System.nanoTime(); - } -} diff --git a/src/main/java/buttondevteam/discordplugin/util/Timings.scala b/src/main/java/buttondevteam/discordplugin/util/Timings.scala new file mode 100644 index 0000000..58702a0 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/util/Timings.scala @@ -0,0 +1,12 @@ +package buttondevteam.discordplugin.util + +import buttondevteam.discordplugin.listeners.CommonListeners + +class Timings() { + private var start = System.nanoTime + + def printElapsed(message: String): Unit = { + CommonListeners.debug(message + " (" + (System.nanoTime - start) / 1000000L + ")") + start = System.nanoTime + } +} \ No newline at end of file diff --git a/src/main/scala/Test.scala b/src/main/scala/Test.scala deleted file mode 100644 index 743d12a..0000000 --- a/src/main/scala/Test.scala +++ /dev/null @@ -1,5 +0,0 @@ -import buttondevteam.discordplugin.DiscordPlugin - -object Test extends App { - println(DiscordPlugin.plugin) -} \ No newline at end of file From fce6b91b97063b63df29b34c6481c1a71adef2f9 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 2 Mar 2021 01:18:20 +0100 Subject: [PATCH 04/23] Use Scala version of Reactor & data types --- pom.xml | 15 ---- .../buttondevteam/discordplugin/DPUtils.scala | 58 +++++++------- .../DiscordConnectedPlayer.scala | 62 ++++++--------- .../discordplugin/DiscordPlugin.scala | 25 +++--- .../discordplugin/DiscordSender.scala | 22 +++--- .../announcer/AnnouncerModule.scala | 3 +- .../commands/VersionCommand.scala | 3 +- .../exceptions/ExceptionListenerModule.scala | 7 +- .../discordplugin/fun/FunModule.scala | 2 +- .../listeners/CommandListener.scala | 45 ++++++----- .../listeners/CommonListeners.scala | 11 +-- .../mcchat/ChannelconCommand.scala | 58 +++++++------- .../discordplugin/mcchat/MCChatCustom.scala | 8 +- .../discordplugin/mcchat/MCChatListener.scala | 64 +++++++-------- .../discordplugin/mcchat/MCChatPrivate.scala | 77 ++++++++++--------- .../discordplugin/mcchat/MCChatUtils.scala | 16 ++-- .../playerfaker/VCMDWrapper.java | 66 ---------------- .../playerfaker/VCMDWrapper.scala | 54 +++++++++++++ 18 files changed, 282 insertions(+), 314 deletions(-) delete mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java create mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.scala diff --git a/pom.xml b/pom.xml index 38ff5f4..c1c5b3b 100644 --- a/pom.xml +++ b/pom.xml @@ -78,21 +78,6 @@ - - maven-compiler-plugin - - - pre-scala - - -proc:only - - generate-sources - - compile - - - - net.alchim31.maven scala-maven-plugin diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.scala b/src/main/java/buttondevteam/discordplugin/DPUtils.scala index f69db27..4841150 100644 --- a/src/main/java/buttondevteam/discordplugin/DPUtils.scala +++ b/src/main/java/buttondevteam/discordplugin/DPUtils.scala @@ -6,10 +6,10 @@ import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.`object`.entity.{Guild, Message, Role} import discord4j.core.spec.EmbedCreateSpec -import reactor.core.publisher.Mono +import reactor.core.scala.publisher.SMono import java.util -import java.util.{Comparator, Optional} +import java.util.Comparator import java.util.logging.Logger import java.util.regex.Pattern import javax.annotation.Nullable @@ -62,8 +62,8 @@ object DPUtils { return matcher.replaceAll(aFunctionalInterface); //Find nearest URL match and if it's not reaching to the char then escape*/ val sb = new StringBuffer while ( { matcher.find - }) matcher.appendReplacement(sb, if (Optional.ofNullable(ts.floor(Array[Int](matcher.start, 0))).map( //Find a URL start <= our start - (a: Array[Int]) => a(1)).orElse(-1) < matcher.start //Check if URL end < our start + }) matcher.appendReplacement(sb, if (Option(ts.floor(Array[Int](matcher.start, 0))).map( //Find a URL start <= our start + (a: Array[Int]) => a(1)).getOrElse(-1) < matcher.start //Check if URL end < our start ) "\\\\" + matcher.group else matcher.group) matcher.appendTail(sb) sb.toString @@ -74,22 +74,22 @@ object DPUtils { DiscordPlugin.plugin.getLogger } - def channelData(config: IHaveConfig, key: String): ReadOnlyConfigData[Mono[MessageChannel]] = + def channelData(config: IHaveConfig, key: String): ReadOnlyConfigData[SMono[MessageChannel]] = config.getReadOnlyDataPrimDef(key, 0L, (id: Any) => - getMessageChannel(key, Snowflake.of(id.asInstanceOf[Long])), (_: Mono[MessageChannel]) => 0L) //We can afford to search for the channel in the cache once (instead of using mainServer) - def roleData(config: IHaveConfig, key: String, defName: String): ReadOnlyConfigData[Mono[Role]] = - roleData(config, key, defName, Mono.just(DiscordPlugin.mainServer)) + getMessageChannel(key, Snowflake.of(id.asInstanceOf[Long])), (_: SMono[MessageChannel]) => 0L) //We can afford to search for the channel in the cache once (instead of using mainServer) + def roleData(config: IHaveConfig, key: String, defName: String): ReadOnlyConfigData[SMono[Role]] = + roleData(config, key, defName, SMono.just(DiscordPlugin.mainServer)) /** * Needs to be a {@link ConfigData} for checking if it's set */ - def roleData(config: IHaveConfig, key: String, defName: String, guild: Mono[Guild]): ReadOnlyConfigData[Mono[Role]] = config.getReadOnlyDataPrimDef(key, defName, (name: Any) => { - def foo(name: Any): Mono[Role] = { - if (!name.isInstanceOf[String] || name.asInstanceOf[String].isEmpty) return Mono.empty[Role] + def roleData(config: IHaveConfig, key: String, defName: String, guild: SMono[Guild]): ReadOnlyConfigData[SMono[Role]] = config.getReadOnlyDataPrimDef(key, defName, (name: Any) => { + def foo(name: Any): SMono[Role] = { + if (!name.isInstanceOf[String] || name.asInstanceOf[String].isEmpty) return SMono.empty[Role] guild.flatMapMany(_.getRoles).filter((r: Role) => r.getName == name).onErrorResume((e: Throwable) => { - def foo(e: Throwable): Mono[Role] = { + def foo(e: Throwable): SMono[Role] = { getLogger.warning("Failed to get role data for " + key + "=" + name + " - " + e.getMessage) - Mono.empty[Role] + SMono.empty[Role] } foo(e) @@ -97,7 +97,7 @@ object DPUtils { } foo(name) - }, (_: Mono[Role]) => defName) + }, (_: SMono[Role]) => defName) def snowflakeData(config: IHaveConfig, key: String, defID: Long): ReadOnlyConfigData[Snowflake] = config.getReadOnlyDataPrimDef(key, defID, (id: Any) => Snowflake.of(id.asInstanceOf[Long]), _.asLong) @@ -137,7 +137,7 @@ object DPUtils { */ def disableIfConfigErrorRes(@Nullable component: Component[DiscordPlugin], config: ConfigData[_], result: Any): Boolean = { //noinspection ConstantConditions - if (result == null || (result.isInstanceOf[Mono[_]] && !result.asInstanceOf[Mono[_]].hasElement.block)) { + if (result == null || (result.isInstanceOf[SMono[_]] && !result.asInstanceOf[SMono[_]].hasElement.block())) { var path: String = null try { if (component != null) Component.setComponentEnabled(component, false) @@ -157,26 +157,26 @@ object DPUtils { } /** - * Send a response in the form of "@User, message". Use Mono.empty() if you don't have a channel object. + * Send a response in the form of "@User, message". Use SMono.empty() if you don't have a channel object. * * @param original The original message to reply to * @param channel The channel to send the message in, defaults to the original * @param message The message to send * @return A mono to send the message */ - def reply(original: Message, @Nullable channel: MessageChannel, message: String): Mono[Message] = { - val ch = if (channel == null) original.getChannel - else Mono.just(channel) + def reply(original: Message, @Nullable channel: MessageChannel, message: String): SMono[Message] = { + val ch = if (channel == null) SMono(original.getChannel) + else SMono.just(channel) reply(original, ch, message) } /** * @see #reply(Message, MessageChannel, String) */ - def reply(original: Message, ch: Mono[MessageChannel], message: String): Mono[Message] = - ch.flatMap(_.createMessage((if (original.getAuthor.isPresent) + def reply(original: Message, ch: SMono[MessageChannel], message: String): SMono[Message] = + ch.flatMap(channel => SMono(channel.createMessage((if (original.getAuthor.isPresent) original.getAuthor.get.getMention + ", " - else "") + message)) + else "") + message))) def nickMention(userId: Snowflake): String = "<@!" + userId.asString + ">" @@ -189,21 +189,21 @@ object DPUtils { * @param id The channel ID * @return A message channel */ - def getMessageChannel(key: String, id: Snowflake): Mono[MessageChannel] = { - if (id.asLong == 0L) return Mono.empty[MessageChannel] + def getMessageChannel(key: String, id: Snowflake): SMono[MessageChannel] = { + if (id.asLong == 0L) return SMono.empty[MessageChannel] - DiscordPlugin.dc.getChannelById(id).onErrorResume(e => { + SMono(DiscordPlugin.dc.getChannelById(id)).onErrorResume(e => { def foo(e: Throwable) = { getLogger.warning("Failed to get channel data for " + key + "=" + id + " - " + e.getMessage) - Mono.empty + SMono.empty } foo(e) - }).filter(ch => ch.isInstanceOf[MessageChannel]).cast(classOf[MessageChannel]) + }).filter(ch => ch.isInstanceOf[MessageChannel]).cast[MessageChannel] } - def getMessageChannel(config: ConfigData[Snowflake]): Mono[MessageChannel] = + def getMessageChannel(config: ConfigData[Snowflake]): SMono[MessageChannel] = getMessageChannel(config.getPath, config.get) - def ignoreError[T](mono: Mono[T]): Mono[T] = mono.onErrorResume((_: Throwable) => Mono.empty) + def ignoreError[T](mono: SMono[T]): SMono[T] = mono.onErrorResume((_: Throwable) => SMono.empty) } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala index e627049..ee791a4 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala +++ b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala @@ -15,8 +15,8 @@ import org.bukkit.inventory.{Inventory, PlayerInventory} import org.bukkit.permissions.{PermissibleBase, Permission, PermissionAttachment, PermissionAttachmentInfo} import org.bukkit.plugin.Plugin import org.mockito.Answers.RETURNS_DEFAULTS -import org.mockito.{MockSettings, Mockito} import org.mockito.invocation.InvocationOnMock +import org.mockito.{MockSettings, Mockito} import java.lang.reflect.Modifier import java.net.InetSocketAddress @@ -52,7 +52,24 @@ object DiscordConnectedPlayer { }).stubOnly } -abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel) extends DiscordSenderBase(user, channel) with IMCPlayer[DiscordConnectedPlayer] { +/** + * @constructor The parameters must match with {@link #create ( User, MessageChannel, UUID, String, MinecraftChatModule)} + * @param user May be null. + * @param channel May not be null. + * @param uniqueId The UUID of the player. + * @param name The Minecraft name of the player. + * @param module The MinecraftChatModule or null if testing. + */ +abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel, val uniqueId: UUID, val name: String, val module: MinecraftChatModule) extends DiscordSenderBase(user, channel) with IMCPlayer[DiscordConnectedPlayer] { + private var loggedIn = false + private var displayName: String = name + + private var location: Location = if (module == null) null else Bukkit.getWorlds.get(0).getSpawnLocation + private val basePlayer: OfflinePlayer = if (module == null) null else Bukkit.getOfflinePlayer(uniqueId) + private var perm: PermissibleBase = if (module == null) null else new PermissibleBase(basePlayer) + private val origPerm: PermissibleBase = perm + private val vanillaCmdListener: VCMDWrapper = if (module == null) null else new VCMDWrapper(VCMDWrapper.createListener(this, module)) + override def isPermissionSet(name: String): Boolean = this.origPerm.isPermissionSet(name) override def isPermissionSet(perm: Permission): Boolean = this.origPerm.isPermissionSet(perm) @@ -98,40 +115,11 @@ abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel) exten override def getDisplayName: String = this.displayName - private var vanillaCmdListener: VCMDWrapper = null - private var loggedIn = false - private var origPerm: PermissibleBase = null - private var name: String = null - private var basePlayer: OfflinePlayer = null - private var perm: PermissibleBase = null - private var location: Location = null - final private var module: MinecraftChatModule = null - final private var uniqueId: UUID = null - final private var displayName: String = null - - /** - * The parameters must match with {@link #create ( User, MessageChannel, UUID, String, MinecraftChatModule)} - */ - def this(user: User, channel: MessageChannel, uuid: UUID, mcname: String, module: MinecraftChatModule) { - this(user, channel) - location = Bukkit.getWorlds.get(0).getSpawnLocation - perm = new PermissibleBase(basePlayer = Bukkit.getOfflinePlayer(uuid)) - origPerm = perm - name = mcname - this.module = module - uniqueId = uuid - displayName = mcname - vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, module)) - } - /** * For testing */ - def this(user: User, channel: MessageChannel) { - this(user, channel) - module = null - uniqueId = UUID.randomUUID - } + def this(user: User, channel: MessageChannel) = + this(user, channel, UUID.randomUUID(), "Test", null) override def setOp(value: Boolean): Unit = { //CraftPlayer-compatible implementation this.origPerm.setOp(value) @@ -214,7 +202,7 @@ abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel) exten override def getGameMode = GameMode.SPECTATOR //noinspection ScalaDeprecation - @SuppressWarnings(Array("deprecation")) final private val spigot: Spigot = new Spigot() { + @SuppressWarnings(Array("deprecation")) override def spigot: Spigot = new Spigot() { override def getRawAddress: InetSocketAddress = null override def playEffect(location: Location, effect: Effect, id: Int, data: Int, offsetX: Float, offsetY: Float, offsetZ: Float, speed: Float, particleCount: Int, radius: Int): Unit = { @@ -241,11 +229,9 @@ abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel) exten override def sendMessage(position: ChatMessageType, component: BaseComponent): Unit = sendMessage(component) //Ignore position - override def sendMessage(position: ChatMessageType, components: BaseComponent*) = - sendMessage(components) + override def sendMessage(position: ChatMessageType, components: BaseComponent*): Unit = + sendMessage(components: _*) override def isInvulnerable = true } - - override def spigot: Spigot = spigot } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala index 7624ec8..3d16fb5 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala @@ -15,12 +15,12 @@ import buttondevteam.lib.architecture._ import buttondevteam.lib.player.ChromaGamerBase import com.google.common.io.Files import discord4j.common.util.Snowflake -import discord4j.core.{DiscordClientBuilder, GatewayDiscordClient} import discord4j.core.`object`.entity.{ApplicationInfo, Guild, Role} import discord4j.core.`object`.presence.{Activity, Presence} import discord4j.core.`object`.reaction.ReactionEmoji import discord4j.core.event.domain.guild.GuildCreateEvent import discord4j.core.event.domain.lifecycle.ReadyEvent +import discord4j.core.{DiscordClientBuilder, GatewayDiscordClient} import discord4j.gateway.ShardInfo import discord4j.store.jdk.JdkStoreService import org.apache.logging.log4j.LogManager @@ -29,7 +29,7 @@ import org.bukkit.command.CommandSender import org.bukkit.configuration.file.YamlConfiguration import org.mockito.internal.util.MockUtil import reactor.core.Disposable -import reactor.core.publisher.Mono +import reactor.core.scala.publisher.SMono import java.io.File import java.nio.charset.StandardCharsets @@ -65,13 +65,16 @@ import java.util.Optional * The main server where the roles and other information is pulled from. It's automatically set to the first server the bot's invited to. */ private def mainServer = getIConfig.getDataPrimDef("mainServer", 0L, (id: Any) => { - def foo(id: Any) = { //It attempts to get the default as well - if (id.asInstanceOf[Long] == 0L) Optional.empty //Hack? - else DiscordPlugin.dc.getGuildById(Snowflake.of(id.asInstanceOf[Long])).onErrorResume((t: Throwable) => Mono.fromRunnable(() => getLogger.warning("Failed to get guild: " + t.getMessage))).blockOptional + def foo(id: Any): Option[Guild] = { //It attempts to get the default as well + if (id.asInstanceOf[Long] == 0L) Option.empty + else SMono.fromPublisher(DiscordPlugin.dc.getGuildById(Snowflake.of(id.asInstanceOf[Long]))) + .onErrorResume((t: Throwable) => { + getLogger.warning("Failed to get guild: " + t.getMessage); SMono.empty + }).blockOption() } foo(id) - }, (g: Optional[Guild]) => (g.map((gg: Guild) => gg.getId.asLong): Optional[Long]).orElse(0L)) + }, (g: Option[Guild]) => (g.map(_.getId.asLong): Option[Long]).getOrElse(0L)) /** * The (bot) channel to use for Discord commands like /role. @@ -81,7 +84,7 @@ import java.util.Optional * The role that allows using mod-only Discord commands. * If empty (''), then it will only allow for the owner. */ - var modRole: ReadOnlyConfigData[Mono[Role]] = null + var modRole: ReadOnlyConfigData[SMono[Role]] = null /** * The invite link to show by /discord invite. If empty, it defaults to the first invite if the bot has access. */ @@ -153,7 +156,9 @@ import java.util.Optional } private def stopStarting(): Unit = { - this synchronized (starting = false) + this synchronized { + starting = false + } notifyAll() } @@ -164,7 +169,7 @@ import java.util.Optional DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe //Update from the initial presence return } - DiscordPlugin.mainServer = mainServer.get.orElse(null) //Shouldn't change afterwards + DiscordPlugin.mainServer = mainServer.get.orNull //Shouldn't change afterwards if (DiscordPlugin.mainServer == null) { if (event.size == 0) { getLogger.severe("Main server not found! Invite the bot and do /discord restart") @@ -174,7 +179,7 @@ import java.util.Optional } DiscordPlugin.mainServer = event.get(0).getGuild getLogger.warning("Main server set to first one: " + DiscordPlugin.mainServer.getName) - mainServer.set(Optional.of(DiscordPlugin.mainServer)) //Save in config + mainServer.set(Option(DiscordPlugin.mainServer)) //Save in config } DiscordPlugin.SafeMode = false setupConfig() diff --git a/src/main/java/buttondevteam/discordplugin/DiscordSender.scala b/src/main/java/buttondevteam/discordplugin/DiscordSender.scala index 5e9c038..dadb809 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordSender.scala +++ b/src/main/java/buttondevteam/discordplugin/DiscordSender.scala @@ -2,28 +2,24 @@ package buttondevteam.discordplugin import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.MessageChannel -import org.bukkit.{Bukkit, Server} import org.bukkit.command.CommandSender import org.bukkit.permissions.{PermissibleBase, Permission, PermissionAttachment, PermissionAttachmentInfo} import org.bukkit.plugin.Plugin -import reactor.core.publisher.Mono +import org.bukkit.{Bukkit, Server} +import reactor.core.scala.publisher.SMono import java.util -class DiscordSender(user: User, channel: MessageChannel) extends DiscordSenderBase(user, channel) with CommandSender { +class DiscordSender(user: User, channel: MessageChannel, pname: String) extends DiscordSenderBase(user, channel) with CommandSender { private val perm = new PermissibleBase(this) - private var name: String = null + private val name: String = Option(pname) + .orElse(Option(user).map(u => SMono(u.asMember(DiscordPlugin.mainServer.getId)) + .onErrorResume(_ => SMono.empty).blockOption() + .map(u => u.getDisplayName))) + .getOrElse("Discord user") def this(user: User, channel: MessageChannel) { - this(user, channel) - val `def` = "Discord user" - name = if (user == null) `def` - else user.asMember(DiscordPlugin.mainServer.getId).onErrorResume((_: Throwable) => Mono.empty).blockOptional.map(_.getDisplayName).orElse(`def`) - } - - def this(user: User, channel: MessageChannel, name: String) { - this(user, channel) - this.name = name + this(user, channel, null) } override def isPermissionSet(name: String): Boolean = perm.isPermissionSet(name) diff --git a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala index 118ec9f..e54764b 100644 --- a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala +++ b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala @@ -90,7 +90,8 @@ import reactor.core.publisher.Flux } if (msgsb.length > 0) channel.get.flatMap((ch: MessageChannel) => ch.createMessage(msgsb.toString)).flatMap(Message.pin).subscribe if (modmsgsb.length > 0) modChannel.get.flatMap((ch: MessageChannel) => ch.createMessage(modmsgsb.toString)).flatMap(Message.pin).subscribe - if (lastAnnouncementTime.get ne lastanntime) lastAnnouncementTime.set(lastanntime) // If sending succeeded} catch { + if (lastAnnouncementTime.get ne lastanntime) lastAnnouncementTime.set(lastanntime) // If sending succeeded + } catch { case e: Exception => e.printStackTrace() } diff --git a/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala index 59be8e3..137f869 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala @@ -7,8 +7,7 @@ import buttondevteam.lib.chat.{Command2, CommandClass} object VersionCommand { def getVersion: Array[String] = { val desc = DiscordPlugin.plugin.getDescription - Array[String]( // - desc.getFullName, desc.getWebsite //) + Array[String](desc.getFullName, desc.getWebsite) } } diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala index 79f3e3e..14b9ba5 100644 --- a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala +++ b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala @@ -1,7 +1,6 @@ package buttondevteam.discordplugin.exceptions import buttondevteam.core.ComponentManager -import buttondevteam.discordplugin.exceptions.ExceptionListenerModule.SendException import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} import buttondevteam.lib.architecture.Component import buttondevteam.lib.{TBMCCoreAPI, TBMCExceptionEvent} @@ -12,6 +11,7 @@ import org.bukkit.Bukkit import org.bukkit.event.{EventHandler, Listener} import reactor.core.publisher.Mono +import java.util import java.util.stream.Collectors /** @@ -65,7 +65,10 @@ class ExceptionListenerModule extends Component[DiscordPlugin] with Listener { @EventHandler def onException(e: TBMCExceptionEvent): Unit = { if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(getClass)) return if (lastthrown.stream.anyMatch((ex: Throwable) => util.Arrays.equals(e.getException.getStackTrace, ex.getStackTrace) && (if (e.getException.getMessage == null) ex.getMessage == null - else e.getException.getMessage == ex.getMessage)) // e.Exception.Message==ex.Message && lastsourcemsg.contains(e.getSourceMessage)) { return } + else e.getException.getMessage == ex.getMessage)) // e.Exception.Message==ex.Message + && lastsourcemsg.contains(e.getSourceMessage)) { + return + } ExceptionListenerModule .SendException(e.getException, e.getSourceMessage) if (lastthrown.size >= 10) lastthrown.remove(0) diff --git a/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala b/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala index c5f20d1..4b71d87 100644 --- a/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala +++ b/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala @@ -59,7 +59,7 @@ object FunModule { return true //Handled } lastlistp = Bukkit.getOnlinePlayers.size.toShort //Didn't handle - if (!TBMCCoreAPI.IsTestServer && util.Arrays.stream(fm.serverReady.get.anyMatch(msglowercased.contains)) { + if (!TBMCCoreAPI.IsTestServer && util.Arrays.stream(fm.serverReady).get.anyMatch(msglowercased.contains _)) { var next = 0 if (usableServerReadyStrings.size == 0) fm.createUsableServerReadyStrings() next = usableServerReadyStrings.remove(serverReadyRandom.nextInt(usableServerReadyStrings.size)) diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala index 8f41efe..0ae0459 100644 --- a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala +++ b/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala @@ -6,7 +6,7 @@ import buttondevteam.lib.TBMCCoreAPI import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} import discord4j.core.`object`.entity.{Member, Message, Role, User} -import reactor.core.publisher.{Flux, Mono} +import reactor.core.scala.publisher.{SFlux, SMono} import java.util.concurrent.atomic.AtomicBoolean @@ -18,14 +18,14 @@ object CommandListener { * @param mentionedonly Only run the command if ChromaBot is mentioned at the start of the message * @return Whether it did not run the command */ - def runCommand(message: Message, commandChannelID: Snowflake, mentionedonly: Boolean): Mono[Boolean] = { + def runCommand(message: Message, commandChannelID: Snowflake, mentionedonly: Boolean): SMono[Boolean] = { val timings = CommonListeners.timings - val ret = Mono.just(true) + val ret = SMono.just(true) if (message.getContent.isEmpty) return ret //Pin messages and such, let the mcchat listener deal with it val content = message.getContent timings.printElapsed("A") - message.getChannel.flatMap((channel: MessageChannel) => { - def foo(channel: MessageChannel): Mono[Boolean] = { + SMono(message.getChannel).flatMap((channel: MessageChannel) => { + def foo(channel: MessageChannel): SMono[Boolean] = { var tmp = ret if (!mentionedonly) { //mentionedonly conditions are in CommonListeners timings.printElapsed("B") @@ -33,22 +33,24 @@ object CommandListener { return ret } timings.printElapsed("C") - tmp = ret.`then`(channel.`type`).thenReturn(true) // Fun (this true is ignored - x) + tmp = ret.`then`(SMono(channel.`type`)).`then`(ret) // Fun (this true is ignored - x) } val cmdwithargs = new StringBuilder(content) val gotmention = new AtomicBoolean timings.printElapsed("Before self") - tmp.flatMapMany((x: Boolean) => DiscordPlugin.dc.getSelf.flatMap((self: User) => self.asMember(DiscordPlugin.mainServer.getId)).flatMapMany((self: Member) => { - def foo(self: Member): Flux[String] = { - timings.printElapsed("D") - gotmention.set(checkanddeletemention(cmdwithargs, self.getMention, message)) - gotmention.set(checkanddeletemention(cmdwithargs, self.getNicknameMention, message) || gotmention.get) - val mentions = message.getRoleMentions - self.getRoles.filterWhen((r: Role) => mentions.any((rr: Role) => rr.getName == r.getName)).map(_.getMention) - } + tmp.flatMapMany((_: Boolean) => SMono(DiscordPlugin.dc.getSelf) + .flatMap((self: User) => SMono(self.asMember(DiscordPlugin.mainServer.getId))) + .flatMapMany((self: Member) => { + def foo(self: Member) = { + timings.printElapsed("D") + gotmention.set(checkanddeletemention(cmdwithargs, self.getMention, message)) + gotmention.set(checkanddeletemention(cmdwithargs, self.getNicknameMention, message) || gotmention.get) + val mentions = SFlux(message.getRoleMentions) + SFlux(self.getRoles).filterWhen((r: Role) => mentions.any((rr: Role) => rr.getName == r.getName)).map(_.getMention) + } - foo(self) - }).map((mentionRole: String) => { + foo(self) + }).map((mentionRole: String) => { def foo(mentionRole: String): Boolean = { timings.printElapsed("E") gotmention.set(checkanddeletemention(cmdwithargs, mentionRole, message) || gotmention.get) // Delete all mentions @@ -56,17 +58,20 @@ object CommandListener { } foo(mentionRole) - }: Boolean)[Mono[Boolean]].switchIfEmpty(Mono.fromSupplier[Boolean](() => !mentionedonly || gotmention.get)))[Mono[Boolean]].filter((b: Boolean) => b).last(false).filter((b: Boolean) => b).doOnNext((b: Boolean) => channel.`type`.subscribe).flatMap((b: Boolean) => { - def foo(): Mono[Boolean] = { + }).switchIfEmpty(SMono.fromCallable(() => !mentionedonly || gotmention.get))).filter(b => b) + .last(Option(false)).filter(b => b) + .doOnNext(_ => channel.`type`.subscribe).flatMap(_ => { + def foo(): SMono[Boolean] = { val cmdwithargsString = cmdwithargs.toString try { timings.printElapsed("F") - if (!DiscordPlugin.plugin.manager.handleCommand(new Command2DCSender(message), cmdwithargsString)) return DPUtils.reply(message, channel, "unknown command. Do " + DiscordPlugin.getPrefix + "help for help.").map((_: Message) => false) + if (!DiscordPlugin.plugin.manager.handleCommand(new Command2DCSender(message), cmdwithargsString)) + return DPUtils.reply(message, channel, "unknown command. Do " + DiscordPlugin.getPrefix + "help for help.").map(_ => false) } catch { case e: Exception => TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e, DiscordPlugin.plugin) } - Mono.just(false) //If the command succeeded or there was an error, return false + SMono.just(false) //If the command succeeded or there was an error, return false } foo() diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala index afb28f2..5927757 100644 --- a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala +++ b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala @@ -13,6 +13,7 @@ import discord4j.core.event.domain.PresenceUpdateEvent import discord4j.core.event.domain.message.MessageCreateEvent import discord4j.core.event.domain.role.{RoleCreateEvent, RoleDeleteEvent, RoleUpdateEvent} import reactor.core.publisher.Mono +import reactor.core.scala.publisher.SMono object CommonListeners { val timings = new Timings @@ -36,24 +37,24 @@ object CommonListeners { if (!author.isPresent || author.get.isBot) return `def` if (FunModule.executeMemes(event.getMessage)) return `def` val commandChannel = DiscordPlugin.plugin.commandChannel.get - event.getMessage.getChannel.map((mch: MessageChannel) => (commandChannel != null && mch.getId.asLong == commandChannel.asLong) //If mentioned, that's higher than chat + SMono(event.getMessage.getChannel).map((mch: MessageChannel) => (commandChannel != null && mch.getId.asLong == commandChannel.asLong) //If mentioned, that's higher than chat || mch.isInstanceOf[PrivateChannel] || event.getMessage.getContent.contains("channelcon")).flatMap( (shouldRun: Boolean) => { //Only 'channelcon' is allowed in other channels - def foo(shouldRun: Boolean): Mono[Boolean] = { //Only continue if this doesn't handle the event - if (!shouldRun) return Mono.just(true) //The condition is only for the first command execution, not mcchat + def foo(shouldRun: Boolean): SMono[Boolean] = { //Only continue if this doesn't handle the event + if (!shouldRun) return SMono.just(true) //The condition is only for the first command execution, not mcchat timings.printElapsed("Run command 1") CommandListener.runCommand(event.getMessage, commandChannel, mentionedonly = true) //#bot is handled here } foo(shouldRun) }).filterWhen((ch: Any) => { - def foo(): Mono[Boolean] = { + def foo() = { timings.printElapsed("mcchat") val mcchat = Component.getComponents.get(classOf[MinecraftChatModule]) if (mcchat != null && mcchat.isEnabled) { //ComponentManager.isEnabled() searches the component again return mcchat.asInstanceOf[MinecraftChatModule].getListener.handleDiscord(event) //Also runs Discord commands in chat channels } - Mono.just(true) //Wasn't handled, continue + SMono.just(true) //Wasn't handled, continue } foo() diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala index cb1df83..94ad744 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala @@ -1,27 +1,24 @@ package buttondevteam.discordplugin.mcchat -import buttondevteam.core.component.channel.Channel -import buttondevteam.core.component.channel.ChatRoom +import buttondevteam.core.component.channel.{Channel, ChatRoom} +import buttondevteam.discordplugin.ChannelconBroadcast.ChannelconBroadcast import buttondevteam.discordplugin._ import buttondevteam.discordplugin.commands.{Command2DCSender, ICommand2DC} import buttondevteam.lib.TBMCSystemChatEvent -import buttondevteam.lib.chat.Command2 -import buttondevteam.lib.chat.CommandClass -import buttondevteam.lib.player.TBMCPlayer +import buttondevteam.lib.chat.{Command2, CommandClass} +import buttondevteam.lib.player.{ChromaGamerBase, TBMCPlayer} import discord4j.core.`object`.entity.Message import discord4j.core.`object`.entity.channel.{GuildChannel, MessageChannel} import discord4j.rest.util.{Permission, PermissionSet} -import lombok.RequiredArgsConstructor import org.bukkit.Bukkit -import org.bukkit.command.CommandSender -import reactor.core.publisher.Mono +import reactor.core.scala.publisher.SMono -import javax.annotation.Nullable import java.lang.reflect.Method import java.util -import java.util.{Objects, Optional} import java.util.function.Supplier import java.util.stream.Collectors +import java.util.{Objects, Optional} +import javax.annotation.Nullable @SuppressWarnings(Array("SimplifyOptionalCallChains")) //Java 11 @CommandClass(helpText = Array(Array("Channel connect", // @@ -37,11 +34,11 @@ import java.util.stream.Collectors class ChannelconCommand(private val module: MinecraftChatModule) extends ICommand2DC { @Command2.Subcommand def remove(sender: Command2DCSender): Boolean = { val message = sender.getMessage - if (checkPerms(message, null)) true + if (checkPerms(message, null)) return true else if (MCChatCustom.removeCustomChat(message.getChannelId)) - DPUtils.reply(message, Mono.empty, "channel connection removed.").subscribe + DPUtils.reply(message, SMono.empty, "channel connection removed.").subscribe else - DPUtils.reply(message, Mono.empty, "this channel isn't connected.").subscribe + DPUtils.reply(message, SMono.empty, "this channel isn't connected.").subscribe true } @@ -54,31 +51,30 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman if (cc == null) { return respond(sender, "this channel isn't connected.") } - val togglesString: Supplier[String] = () => util.Arrays.stream(ChannelconBroadcast.values) - .map((t: ChannelconBroadcast) => - t.toString.toLowerCase + ": " + (if ((cc.toggles & t.flag) == 0) "disabled" else "enabled")) - .collect(Collectors.joining("\n")) + "\n\n" + + val togglesString: Supplier[String] = () => ChannelconBroadcast.values + .map(t => t.toString.toLowerCase + ": " + (if ((cc.toggles & (1 << t.id)) == 0) "disabled" else "enabled")) + .mkString("\n") + "\n\n" + TBMCSystemChatEvent.BroadcastTarget.stream.map((target: TBMCSystemChatEvent.BroadcastTarget) => target.getName + ": " + (if (cc.brtoggles.contains(target)) "enabled" else "disabled")) .collect(Collectors.joining("\n")) if (toggle == null) { - DPUtils.reply(message, Mono.empty, "toggles:\n" + togglesString.get).subscribe + DPUtils.reply(message, SMono.empty, "toggles:\n" + togglesString.get).subscribe return true } val arg: String = toggle.toUpperCase - val b: Optional[ChannelconBroadcast] = util.Arrays.stream(ChannelconBroadcast.values).filter((t: ChannelconBroadcast) => t.toString == arg).findAny - if (!b.isPresent) { + val b = ChannelconBroadcast.values.find((t: ChannelconBroadcast) => t.toString == arg) + if (b.isEmpty) { val bt: TBMCSystemChatEvent.BroadcastTarget = TBMCSystemChatEvent.BroadcastTarget.get(arg) if (bt == null) { - DPUtils.reply(message, Mono.empty, "cannot find toggle. Toggles:\n" + togglesString.get).subscribe + DPUtils.reply(message, SMono.empty, "cannot find toggle. Toggles:\n" + togglesString.get).subscribe return true } val add: Boolean = !(cc.brtoggles.contains(bt)) if (add) { - cc.brtoggles.add(bt) + cc.brtoggles += bt } else { - cc.brtoggles.remove(bt) + cc.brtoggles -= bt } return respond(sender, "'" + bt.getName + "' " + (if (add) "en" else "dis") + "abled") } @@ -89,10 +85,10 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman //1 0 | 1 //1 1 | 0 // XOR - cc.toggles ^= b.get.flag - DPUtils.reply(message, Mono.empty, "'" + b.get.toString.toLowerCase + "' " - + (if ((cc.toggles & b.get.flag) == 0) "disabled" else "enabled")).subscribe - return true + cc.toggles ^= (1 << b.get.id) + DPUtils.reply(message, SMono.empty, "'" + b.get.toString.toLowerCase + "' " + + (if ((cc.toggles & (1 << b.get.id)) == 0) "disabled" else "enabled")).subscribe + true } @Command2.Subcommand def `def`(sender: Command2DCSender, channelID: String): Boolean = { @@ -139,14 +135,14 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman return true; }*/ //TODO: "Channel admins" that can connect channels? - MCChatCustom.addCustomChat(channel, groupid, chan.get, author, dcp, 0, new util.HashSet[TBMCSystemChatEvent.BroadcastTarget]) + MCChatCustom.addCustomChat(channel, groupid, chan.get, author, dcp, 0, Set()) if (chan.get.isInstanceOf[ChatRoom]) { DPUtils.reply(message, channel, "alright, connection made to the room!").subscribe } else { DPUtils.reply(message, channel, "alright, connection made to group `" + groupid + "`!").subscribe } - return true + true } @SuppressWarnings(Array("ConstantConditions")) @@ -167,7 +163,7 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman false } - def getHelpText(method: Method, ann: Command2.Subcommand): Array[String] = + override def getHelpText(method: Method, ann: Command2.Subcommand): Array[String] = Array[String]( "Channel connect", "This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).", @@ -178,6 +174,6 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman "To remove a connection use @ChromaBot channelcon remove in the channel.", "Mentioning the bot is needed in this case because the " + DiscordPlugin.getPrefix + " prefix only works in " + DPUtils.botmention + ".", "Invite link: info.getId.asString).blockOption().getOrElse("Unknown") + "&scope=bot&permissions=268509264>") } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala index c6fbcec..25eb781 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala @@ -18,7 +18,7 @@ object MCChatCustom { */ private[mcchat] val lastmsgCustom = new util.ArrayList[MCChatCustom.CustomLMD] - def addCustomChat(channel: MessageChannel, groupid: String, mcchannel: Channel, user: User, dcp: DiscordConnectedPlayer, toggles: Int, brtoggles: util.Set[TBMCSystemChatEvent.BroadcastTarget]): Boolean = { + def addCustomChat(channel: MessageChannel, groupid: String, mcchannel: Channel, user: User, dcp: DiscordConnectedPlayer, toggles: Int, brtoggles: Set[TBMCSystemChatEvent.BroadcastTarget]): Boolean = { lastmsgCustom synchronized { var gid: String = null mcchannel match { @@ -58,9 +58,9 @@ object MCChatCustom { def getCustomChats: util.List[CustomLMD] = Collections.unmodifiableList(lastmsgCustom) - class CustomLMD private(@NonNull channel: MessageChannel, @NonNull user: User, val groupID: String, - @NonNull val mcchannel: Channel, val dcp: DiscordConnectedPlayer, var toggles: Int, - var brtoggles: Set[TBMCSystemChatEvent.BroadcastTarget]) extends MCChatUtils.LastMsgData(channel, user) { + class CustomLMD private[mcchat](@NonNull channel: MessageChannel, @NonNull user: User, val groupID: String, + @NonNull mcchannel: Channel, val dcp: DiscordConnectedPlayer, var toggles: Int, + var brtoggles: Set[TBMCSystemChatEvent.BroadcastTarget]) extends MCChatUtils.LastMsgData(channel, user, mcchannel) { } } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala index 62eeef6..067d960 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala @@ -21,6 +21,7 @@ import org.bukkit.entity.Player import org.bukkit.event.{EventHandler, Listener} import org.bukkit.scheduler.BukkitTask import reactor.core.publisher.Mono +import reactor.core.scala.publisher.{SFlux, SMono} import java.time.Instant import java.util @@ -244,30 +245,30 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { private var recthread: Thread = null // Discord - def handleDiscord(ev: MessageCreateEvent): Mono[Boolean] = { + def handleDiscord(ev: MessageCreateEvent): SMono[Boolean] = { val timings: Timings = CommonListeners.timings timings.printElapsed("Chat event") - val author: Optional[User] = ev.getMessage.getAuthor - val hasCustomChat: Boolean = MCChatCustom.hasCustomChat(ev.getMessage.getChannelId) - val prefix: Char = DiscordPlugin.getPrefix - return ev.getMessage.getChannel.filter((channel: MessageChannel) => { - def foo(channel: MessageChannel) = { - timings.printElapsed("Filter 1") - return !((ev.getMessage.getChannelId.asLong != module.chatChannel.get.asLong && !((channel.isInstanceOf[PrivateChannel] && author.map((u: User) => MCChatPrivate.isMinecraftChatEnabled(u.getId.asString)).orElse(false))) && !(hasCustomChat))) //Chat isn't enabled on this channel - } + val author = Option(ev.getMessage.getAuthor.orElse(null)) + val hasCustomChat = MCChatCustom.hasCustomChat(ev.getMessage.getChannelId) + val prefix = DiscordPlugin.getPrefix + SMono(ev.getMessage.getChannel).filter(channel => { + def hasPrivateChat = channel.isInstanceOf[PrivateChannel] && + author.exists((u: User) => MCChatPrivate.isMinecraftChatEnabled(u.getId.asString)) - foo(channel) - }).filter((channel: MessageChannel) => { - def foo(channel: MessageChannel) = { - timings.printElapsed("Filter 2") - return !((channel.isInstanceOf[PrivateChannel] //Only in private chat && ev.getMessage.getContent.length < "/mcchat<>".length && ev.getMessage.getContent.replace(prefix + "", "").equalsIgnoreCase("mcchat")))//Either mcchat or /mcchat - //Allow disabling the chat if needed - } + def hasPublicChat = ev.getMessage.getChannelId.asLong == module.chatChannel.get.asLong - foo(channel) - }).filterWhen((channel: MessageChannel) => CommandListener.runCommand(ev.getMessage, DiscordPlugin.plugin.commandChannel.get, true)).filter //Allow running commands in chat channels - ((channel: MessageChannel) => { - def foo(channel: MessageChannel) = { + timings.printElapsed("Filter 1") + val chatEnabled = hasPublicChat || hasPrivateChat || hasCustomChat + chatEnabled + }).filter(channel => { + timings.printElapsed("Filter 2") + !(channel.isInstanceOf[PrivateChannel] //Only in private chat + && ev.getMessage.getContent.length < "/mcchat<>".length + && ev.getMessage.getContent.replace(prefix + "", "").equalsIgnoreCase("mcchat")) //Either mcchat or /mcchat + //Allow disabling the chat if needed + }).filterWhen(_ => + CommandListener.runCommand(ev.getMessage, DiscordPlugin.plugin.commandChannel.get, mentionedonly = true)) //Allow running commands in chat channels + .filter(channel => { MCChatUtils.resetLastMessage(channel) recevents.add(ev) timings.printElapsed("Message event added") @@ -275,22 +276,15 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { return true } recrun = () => { - def foo() = { //Don't return in a while loop next time - recthread = Thread.currentThread - processDiscordToMC() - if (DiscordPlugin.plugin.isEnabled && !(stop)) { - rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Continue message processing - } + recthread = Thread.currentThread + processDiscordToMC() + if (DiscordPlugin.plugin.isEnabled && !(stop)) { + rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Continue message processing } - - foo() } rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Start message processing return true - } - - foo(channel) - }).map((b: MessageChannel) => false).defaultIfEmpty(true) + }).map(_ => false).defaultIfEmpty(true) } private def processDiscordToMC(): Unit = { @@ -307,7 +301,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { val dsender: DiscordSenderBase = MCChatUtils.getSender(event.getMessage.getChannelId, sender) val user: DiscordPlayer = dsender.getChromaUser - for (u <- event.getMessage.getUserMentions.toIterable) { //TODO: Role mentions + for (u <- SFlux(event.getMessage.getUserMentions).toIterable()) { //TODO: Role mentions dmessage = dmessage.replace(u.getMention, "@" + u.getUsername) // TODO: IG Formatting val m: Optional[Member] = u.asMember(DiscordPlugin.mainServer.getId).onErrorResume((t: Throwable) => Mono.empty).blockOptional if (m.isPresent) { @@ -317,7 +311,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { } } - for (ch <- event.getGuild.flux.flatMap(_.getChannels).toIterable) { + for (ch <- SFlux(event.getGuild.flux).flatMap(_.getChannels).toIterable()) { dmessage = dmessage.replace(ch.getMention, "#" + ch.getName) } dmessage = EmojiParser.parseToAliases(dmessage, EmojiParser.FitzpatrickAction.PARSE) //Converts emoji to text- TODO: Add option to disable (resource pack?) @@ -388,7 +382,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { } react = true } - return react + react } private def handleIngameCommand(event: MessageCreateEvent, dmessage: String, dsender: DiscordSenderBase, user: DiscordPlayer, clmd: MCChatCustom.CustomLMD, isPrivate: Boolean): Boolean = { diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala index e2e023e..1acd129 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala @@ -1,6 +1,7 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.ComponentManager +import buttondevteam.discordplugin.mcchat.MCChatUtils.LastMsgData import buttondevteam.discordplugin.{DiscordConnectedPlayer, DiscordPlayer, DiscordPlugin, DiscordSenderBase} import buttondevteam.lib.player.TBMCPlayer import discord4j.common.util.Snowflake @@ -8,52 +9,53 @@ import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} import org.bukkit.Bukkit -import java.util +import scala.collection.mutable.ListBuffer object MCChatPrivate { /** * Used for messages in PMs (mcchat). */ - private[mcchat] val lastmsgPerUser = new util.ArrayList[MCChatUtils.LastMsgData] + private[mcchat] var lastmsgPerUser: ListBuffer[LastMsgData] = ListBuffer() def privateMCChat(channel: MessageChannel, start: Boolean, user: User, dp: DiscordPlayer): Unit = { - MCChatUtils.ConnectedSenders synchronized - val mcp = dp.getAs(classOf[TBMCPlayer]) - 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) - val mcm = ComponentManager.getIfEnabled(classOf[MinecraftChatModule]) - if (start) { - val sender = DiscordConnectedPlayer.create(user, channel, mcp.getUUID, op.getName, mcm) - MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender) - MCChatUtils.LoggedInPlayers.put(mcp.getUUID, sender) - if (p == null) { // Player is offline - If the player is online, that takes precedence - MCChatUtils.callLoginEvents(sender) + MCChatUtils.ConnectedSenders synchronized { + val mcp = dp.getAs(classOf[TBMCPlayer]) + 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) + val mcm = ComponentManager.getIfEnabled(classOf[MinecraftChatModule]) + if (start) { + val sender = DiscordConnectedPlayer.create(user, channel, mcp.getUUID, op.getName, mcm) + MCChatUtils.addSender(MCChatUtils.ConnectedSenders, user, sender) + MCChatUtils.LoggedInPlayers.put(mcp.getUUID, sender) + if (p == null) { // Player is offline - If the player is online, that takes precedence + MCChatUtils.callLoginEvents(sender) + } } - } - else { - val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId, user) - assert(sender != null) - Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => { - def foo(): Unit = { - if ((p == null || p.isInstanceOf[DiscordSenderBase]) // Player is offline - If the player is online, that takes precedence - && sender.isLoggedIn) { //Don't call the quit event if login failed - MCChatUtils.callLogoutEvent(sender, false) //The next line has to run *after* this one, so can't use the needsSync parameter + else { + val sender = MCChatUtils.removeSender(MCChatUtils.ConnectedSenders, channel.getId, user) + assert(sender != null) + Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => { + def foo(): Unit = { + if ((p == null || p.isInstanceOf[DiscordSenderBase]) // Player is offline - If the player is online, that takes precedence + && sender.isLoggedIn) { //Don't call the quit event if login failed + MCChatUtils.callLogoutEvent(sender, false) //The next line has to run *after* this one, so can't use the needsSync parameter + } + + MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId) + sender.setLoggedIn(false) } - MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId) - sender.setLoggedIn(false) + foo() } - - foo() + ) } - ) + // ---- PermissionsEx warning is normal on logout ---- } - // ---- PermissionsEx warning is normal on logout ---- + if (!start) MCChatUtils.lastmsgfromd.remove(channel.getId.asLong) + if (start) lastmsgPerUser += new MCChatUtils.LastMsgData(channel, user) // Doesn't support group DMs + else lastmsgPerUser.filterInPlace(_.channel.getId.asLong != channel.getId.asLong) //Remove } - if (!start) MCChatUtils.lastmsgfromd.remove(channel.getId.asLong) - if (start) lastmsgPerUser.add(new MCChatUtils.LastMsgData(channel, user)) // Doesn't support group DMs - else lastmsgPerUser.removeIf((lmd: MCChatUtils.LastMsgData) => lmd.channel.getId.asLong == channel.getId.asLong) } def isMinecraftChatEnabled(dp: DiscordPlayer): Boolean = isMinecraftChatEnabled(dp.getDiscordID) @@ -64,11 +66,12 @@ object MCChatPrivate { } def logoutAll(): Unit = { - MCChatUtils.ConnectedSenders synchronized - for (entry <- MCChatUtils.ConnectedSenders.entrySet) { - for (valueEntry <- entry.getValue.entrySet) { - if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey, valueEntry.getValue.getUser) == null) { //If the player is online then the fake player was already logged out - MCChatUtils.callLogoutEvent(valueEntry.getValue, !Bukkit.isPrimaryThread) + MCChatUtils.ConnectedSenders synchronized { + for (entry <- asScala(MCChatUtils.ConnectedSenders.entrySet)) { + for (valueEntry <- entry.getValue.entrySet) { + if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey, valueEntry.getValue.getUser) == null) { //If the player is online then the fake player was already logged out + MCChatUtils.callLogoutEvent(valueEntry.getValue, !Bukkit.isPrimaryThread) + } } } } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala index 7eaae1e..1926cab 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala @@ -30,15 +30,16 @@ import java.util.function.Supplier import java.util.logging.Level import java.util.stream.{Collectors, Stream} import javax.annotation.Nullable +import scala.jdk.javaapi.CollectionConverters.asScala object MCChatUtils { /** * May contain P<DiscordID> as key for public chat */ - val UnconnectedSenders = new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordSender]] - val ConnectedSenders = new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordConnectedPlayer]] - val OnlineSenders = new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordPlayerSender]] - val LoggedInPlayers = new ConcurrentHashMap[UUID, DiscordConnectedPlayer] + val UnconnectedSenders = asScala(new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordSender]]) + val ConnectedSenders = asScala(new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordConnectedPlayer]]) + val OnlineSenders = asScala(new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordPlayerSender]]) + val LoggedInPlayers = asScala(new ConcurrentHashMap[UUID, DiscordConnectedPlayer]) @Nullable private[mcchat] var lastmsgdata: MCChatUtils.LastMsgData = null private[mcchat] val lastmsgfromd = new LongObjectHashMap[Message] // Last message sent by a Discord user, used for clearing checkmarks private var module: MinecraftChatModule = null @@ -361,10 +362,15 @@ object MCChatUtils { private[mcchat] def callEventSync(event: Event) = Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => callEventExcludingSome(event)) class LastMsgData(val channel: MessageChannel, val user: User) { - var message: String = null + var message: Message = null var time = 0L var content: String = null var mcchannel: component.channel.Channel = null + + protected def this(channel: MessageChannel, user: User, mcchannel: component.channel.Channel) = { + this(channel, user) + this.mcchannel = mcchannel + } } } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java deleted file mode 100644 index 6ff856b..0000000 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.java +++ /dev/null @@ -1,66 +0,0 @@ -package buttondevteam.discordplugin.playerfaker; - -import buttondevteam.discordplugin.DiscordSenderBase; -import buttondevteam.discordplugin.IMCPlayer; -import buttondevteam.discordplugin.mcchat.MinecraftChatModule; -import buttondevteam.lib.TBMCCoreAPI; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import javax.annotation.Nullable; - -@RequiredArgsConstructor -public class VCMDWrapper { - @Getter //Needed to mock the player - @Nullable - private final Object listener; - - /** - * This constructor will only send raw vanilla messages to the sender in plain text. - * - * @param player The Discord sender player (the wrapper) - */ - public static > Object createListener(T player, MinecraftChatModule module) { - return createListener(player, null, module); - } - - /** - * 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 - * @param module The Minecraft chat module - */ - public static > Object createListener(T player, Player bukkitplayer, MinecraftChatModule module) { - try { - Object ret; - String mcpackage = Bukkit.getServer().getClass().getPackage().getName(); - if (mcpackage.contains("1_12")) - ret = new VanillaCommandListener<>(player, bukkitplayer); - else if (mcpackage.contains("1_14")) - ret = new VanillaCommandListener14<>(player, bukkitplayer); - else if (mcpackage.contains("1_15") || mcpackage.contains("1_16")) - ret = VanillaCommandListener15.create(player, bukkitplayer); //bukkitplayer may be null but that's fine - else - ret = null; - if (ret == null) - compatWarning(module); - return ret; - } catch (NoClassDefFoundError | Exception e) { - compatWarning(module); - TBMCCoreAPI.SendException("Failed to create vanilla command listener", e, module); - return null; - } - } - - private static void compatWarning(MinecraftChatModule module) { - module.logWarn("Vanilla commands won't be available from Discord due to a compatibility error. Disable vanilla command support to remove this message."); - } - - static boolean compatResponse(DiscordSenderBase dsender) { - dsender.sendMessage("Vanilla commands are not supported on this Minecraft version."); - return true; - } -} diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.scala b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.scala new file mode 100644 index 0000000..4c96d51 --- /dev/null +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.scala @@ -0,0 +1,54 @@ +package buttondevteam.discordplugin.playerfaker + +import buttondevteam.discordplugin.{DiscordSenderBase, IMCPlayer} +import buttondevteam.discordplugin.mcchat.MinecraftChatModule +import buttondevteam.lib.TBMCCoreAPI +import org.bukkit.Bukkit +import org.bukkit.entity.Player + +object VCMDWrapper { + /** + * This constructor will only send raw vanilla messages to the sender in plain text. + * + * @param player The Discord sender player (the wrapper) + */ + def createListener[T <: DiscordSenderBase with IMCPlayer[T]](player: T, module: MinecraftChatModule): AnyRef = + createListener(player, null, module) + + /** + * 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 + * @param module The Minecraft chat module + */ + def createListener[T <: DiscordSenderBase with IMCPlayer[T]](player: T, bukkitplayer: Player, module: MinecraftChatModule): AnyRef = try { + var ret: AnyRef = null + val mcpackage = Bukkit.getServer.getClass.getPackage.getName + if (mcpackage.contains("1_12")) ret = new VanillaCommandListener[T](player, bukkitplayer) + else if (mcpackage.contains("1_14")) ret = new VanillaCommandListener14[T](player, bukkitplayer) + else if (mcpackage.contains("1_15") || mcpackage.contains("1_16")) ret = VanillaCommandListener15.create(player, bukkitplayer) //bukkitplayer may be null but that's fine + else ret = null + if (ret == null) compatWarning(module) + ret + } catch { + case e@(_: NoClassDefFoundError | _: Exception) => + compatWarning(module) + TBMCCoreAPI.SendException("Failed to create vanilla command listener", e, module) + null + } + + private def compatWarning(module: MinecraftChatModule): Unit = + module.logWarn("Vanilla commands won't be available from Discord due to a compatibility error. Disable vanilla command support to remove this message.") + + private[playerfaker] def compatResponse(dsender: DiscordSenderBase) = { + dsender.sendMessage("Vanilla commands are not supported on this Minecraft version.") + true + } +} + +class VCMDWrapper(private val listener: AnyRef) { + @javax.annotation.Nullable def getListener: AnyRef = listener + + //Needed to mock the player @Nullable +} \ No newline at end of file From d416eef1448a053fd7b9cfecd533e33b14a8a88e Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 2 Mar 2021 01:40:58 +0100 Subject: [PATCH 05/23] Make some small functions --- .../listeners/CommonListeners.scala | 31 ++++--------- .../discordplugin/mcchat/MCChatListener.scala | 45 +++++++++---------- 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala index 5927757..57f6d8b 100644 --- a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala +++ b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala @@ -12,7 +12,6 @@ import discord4j.core.event.EventDispatcher import discord4j.core.event.domain.PresenceUpdateEvent import discord4j.core.event.domain.message.MessageCreateEvent import discord4j.core.event.domain.role.{RoleCreateEvent, RoleDeleteEvent, RoleUpdateEvent} -import reactor.core.publisher.Mono import reactor.core.scala.publisher.SMono object CommonListeners { @@ -29,9 +28,9 @@ object CommonListeners { */ def register(dispatcher: EventDispatcher) = { dispatcher.on(classOf[MessageCreateEvent]).flatMap((event: MessageCreateEvent) => { - def foo(event: MessageCreateEvent) = { + def foo(event: MessageCreateEvent): SMono[Boolean] = { timings.printElapsed("Message received") - val `def` = Mono.empty + val `def` = SMono.empty if (DiscordPlugin.SafeMode) return `def` val author = event.getMessage.getAuthor if (!author.isPresent || author.get.isBot) return `def` @@ -42,30 +41,18 @@ object CommonListeners { (shouldRun: Boolean) => { //Only 'channelcon' is allowed in other channels def foo(shouldRun: Boolean): SMono[Boolean] = { //Only continue if this doesn't handle the event if (!shouldRun) return SMono.just(true) //The condition is only for the first command execution, not mcchat - timings.printElapsed("Run command 1") CommandListener.runCommand(event.getMessage, commandChannel, mentionedonly = true) //#bot is handled here } foo(shouldRun) - }).filterWhen((ch: Any) => { - def foo() = { - timings.printElapsed("mcchat") - val mcchat = Component.getComponents.get(classOf[MinecraftChatModule]) - if (mcchat != null && mcchat.isEnabled) { //ComponentManager.isEnabled() searches the component again - return mcchat.asInstanceOf[MinecraftChatModule].getListener.handleDiscord(event) //Also runs Discord commands in chat channels - } - SMono.just(true) //Wasn't handled, continue + }).filterWhen(_ => { + timings.printElapsed("mcchat") + val mcchat = Component.getComponents.get(classOf[MinecraftChatModule]) + if (mcchat != null && mcchat.isEnabled) { //ComponentManager.isEnabled() searches the component again + return mcchat.asInstanceOf[MinecraftChatModule].getListener.handleDiscord(event) //Also runs Discord commands in chat channels } - - foo() - }).filterWhen((ch: Any) => { - def foo(ch: Any) = { - timings.printElapsed("Run command 2") - CommandListener.runCommand(event.getMessage, commandChannel, mentionedonly = false) - } - - foo(ch) - }) + SMono.just(true) //Wasn't handled, continue + }).filterWhen(_ => CommandListener.runCommand(event.getMessage, commandChannel, mentionedonly = false)) } foo(event) diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala index 067d960..d4c837b 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala @@ -3,9 +3,8 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.ComponentManager import buttondevteam.core.component.channel.Channel import buttondevteam.discordplugin._ -import buttondevteam.discordplugin.listeners.{CommandListener, CommonListeners} +import buttondevteam.discordplugin.listeners.CommandListener import buttondevteam.discordplugin.playerfaker.{VanillaCommandListener, VanillaCommandListener14, VanillaCommandListener15} -import buttondevteam.discordplugin.util.Timings import buttondevteam.lib._ import buttondevteam.lib.chat.{ChatMessage, TBMCChatAPI} import buttondevteam.lib.player.TBMCPlayer @@ -246,32 +245,16 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { // Discord def handleDiscord(ev: MessageCreateEvent): SMono[Boolean] = { - val timings: Timings = CommonListeners.timings - timings.printElapsed("Chat event") val author = Option(ev.getMessage.getAuthor.orElse(null)) val hasCustomChat = MCChatCustom.hasCustomChat(ev.getMessage.getChannelId) val prefix = DiscordPlugin.getPrefix - SMono(ev.getMessage.getChannel).filter(channel => { - def hasPrivateChat = channel.isInstanceOf[PrivateChannel] && - author.exists((u: User) => MCChatPrivate.isMinecraftChatEnabled(u.getId.asString)) - - def hasPublicChat = ev.getMessage.getChannelId.asLong == module.chatChannel.get.asLong - - timings.printElapsed("Filter 1") - val chatEnabled = hasPublicChat || hasPrivateChat || hasCustomChat - chatEnabled - }).filter(channel => { - timings.printElapsed("Filter 2") - !(channel.isInstanceOf[PrivateChannel] //Only in private chat - && ev.getMessage.getContent.length < "/mcchat<>".length - && ev.getMessage.getContent.replace(prefix + "", "").equalsIgnoreCase("mcchat")) //Either mcchat or /mcchat - //Allow disabling the chat if needed - }).filterWhen(_ => - CommandListener.runCommand(ev.getMessage, DiscordPlugin.plugin.commandChannel.get, mentionedonly = true)) //Allow running commands in chat channels + SMono(ev.getMessage.getChannel) + .filter(channel => isChatEnabled(channel, author, hasCustomChat)) + .filter(channel => !isRunningMCChatCommand(channel, ev.getMessage.getContent, prefix)) + .filterWhen(_ => CommandListener.runCommand(ev.getMessage, DiscordPlugin.plugin.commandChannel.get, mentionedonly = true)) //Allow running commands in chat channels .filter(channel => { MCChatUtils.resetLastMessage(channel) recevents.add(ev) - timings.printElapsed("Message event added") if (rectask != null) { return true } @@ -287,11 +270,27 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { }).map(_ => false).defaultIfEmpty(true) } + private def isChatEnabled(channel: MessageChannel, author: Option[User], hasCustomChat: Boolean) = { + def hasPrivateChat = channel.isInstanceOf[PrivateChannel] && + author.exists((u: User) => MCChatPrivate.isMinecraftChatEnabled(u.getId.asString)) + + def hasPublicChat = channel.getId.asLong == module.chatChannel.get.asLong + + hasPublicChat || hasPrivateChat || hasCustomChat + } + + private def isRunningMCChatCommand(channel: MessageChannel, content: String, prefix: Char) = { + (channel.isInstanceOf[PrivateChannel] //Only in private chat + && content.length < "/mcchat<>".length + && content.replace(prefix + "", "").equalsIgnoreCase("mcchat")) //Either mcchat or /mcchat + //Allow disabling the chat if needed + } + private def processDiscordToMC(): Unit = { var event: MessageCreateEvent = null try event = recevents.take catch { - case e1: InterruptedException => + case _: InterruptedException => rectask.cancel() return } From 7296ebd2f8fce1d8b2d0f2030f9c78e6001e1b07 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sat, 6 Mar 2021 01:27:21 +0100 Subject: [PATCH 06/23] Tailrec announcer method, fix some compile issues --- .../discordplugin/DiscordSender.scala | 2 +- .../discordplugin/DiscordSenderBase.scala | 24 +-- .../announcer/AnnouncerModule.scala | 56 +++--- .../commands/Command2DCSender.scala | 4 +- .../commands/ConnectCommand.scala | 5 +- .../discordplugin/commands/DebugCommand.scala | 29 +-- .../discordplugin/commands/HelpCommand.scala | 4 +- .../commands/UserinfoCommand.scala | 29 ++- .../commands/VersionCommand.scala | 4 +- .../exceptions/ExceptionListenerModule.scala | 54 +++--- .../discordplugin/fun/FunModule.scala | 49 +++-- .../listeners/CommonListeners.scala | 20 +- .../mcchat/ChannelconCommand.scala | 4 +- .../discordplugin/mcchat/MCChatCommand.scala | 6 +- .../discordplugin/mcchat/MCChatCustom.scala | 31 ++-- .../discordplugin/mcchat/MCChatListener.scala | 63 ++++--- .../discordplugin/mcchat/MCChatUtils.scala | 171 ++++++++---------- .../mcchat/MinecraftChatModule.scala | 9 +- 18 files changed, 272 insertions(+), 292 deletions(-) diff --git a/src/main/java/buttondevteam/discordplugin/DiscordSender.scala b/src/main/java/buttondevteam/discordplugin/DiscordSender.scala index dadb809..4dca813 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordSender.scala +++ b/src/main/java/buttondevteam/discordplugin/DiscordSender.scala @@ -13,7 +13,7 @@ import java.util class DiscordSender(user: User, channel: MessageChannel, pname: String) extends DiscordSenderBase(user, channel) with CommandSender { private val perm = new PermissibleBase(this) private val name: String = Option(pname) - .orElse(Option(user).map(u => SMono(u.asMember(DiscordPlugin.mainServer.getId)) + .orElse(Option(user).flatMap(u => SMono(u.asMember(DiscordPlugin.mainServer.getId)) .onErrorResume(_ => SMono.empty).blockOption() .map(u => u.getDisplayName))) .getOrElse("Discord user") diff --git a/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.scala b/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.scala index 83121aa..63592fa 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.scala +++ b/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.scala @@ -44,21 +44,23 @@ abstract class DiscordSenderBase protected(var user: User, var channel: MessageC return } val sendmsg = DPUtils.sanitizeString(message) - this synchronized msgtosend += "\n" + sendmsg - if (sendtask == null) sendtask = Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => { - def foo(): Unit = { - channel.createMessage((if (user != null) user.getMention + "\n" - else "") + msgtosend.trim).subscribe - sendtask = null - msgtosend = "" - } + this synchronized { + msgtosend += "\n" + sendmsg + if (sendtask == null) sendtask = Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => { + def foo(): Unit = { + channel.createMessage((if (user != null) user.getMention + "\n" + else "") + msgtosend.trim).subscribe + sendtask = null + msgtosend = "" + } - foo() - }, 4) // Waits a 0.2 second to gather all/most of the different messages + foo() + }, 4) // Waits a 0.2 second to gather all/most of the different messages + } } catch { case e: Exception => TBMCCoreAPI.SendException("An error occured while sending message to DiscordSender", e, DiscordPlugin.plugin) } - override def sendMessage(messages: Array[String]): Unit = sendMessage(String.join("\n", messages)) + override def sendMessage(messages: Array[String]): Unit = sendMessage(String.join("\n", messages: _*)) } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala index e54764b..31cb524 100644 --- a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala +++ b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala @@ -5,9 +5,10 @@ import buttondevteam.lib.TBMCCoreAPI import buttondevteam.lib.architecture.{Component, ComponentMetadata} import buttondevteam.lib.player.ChromaGamerBase import com.google.gson.JsonParser -import discord4j.core.`object`.entity.Message import discord4j.core.`object`.entity.channel.MessageChannel -import reactor.core.publisher.Flux +import reactor.core.scala.publisher.SMono + +import scala.annotation.tailrec /** * Posts new posts from Reddit to the specified channel(s). It will pin the regular posts (not the mod posts). @@ -39,23 +40,19 @@ import reactor.core.publisher.Flux override protected def enable(): Unit = { if (DPUtils.disableIfConfigError(this, channel, modChannel)) return AnnouncerModule.stop = false //If not the first time - val kp = keepPinned.get - if (kp eq 0) return - val msgs: Flux[Message] = channel.get.flatMapMany(_.getPinnedMessages).takeLast(kp) + val kp: Short = keepPinned.get + if (kp <= 0) return + val msgs = channel.get.flatMapMany(_.getPinnedMessages).takeLast(kp) msgs.subscribe(_.unpin) new Thread(() => this.AnnouncementGetterThreadMethod()).start() } override protected def disable(): Unit = AnnouncerModule.stop = true - private def AnnouncementGetterThreadMethod(): Unit = while ( { - !AnnouncerModule.stop - }) { - try { - if (!isEnabled) { //noinspection BusyWait - Thread.sleep(10000) - continue //todo: continue is not supported - } + @tailrec + private def AnnouncementGetterThreadMethod() { + if (AnnouncerModule.stop) return + if (isEnabled) try { //If not enabled, just wait val body = TBMCCoreAPI.DownloadString(subredditURL.get + "/new/.json?limit=10") val json = new JsonParser().parse(body).getAsJsonObject.get("data").getAsJsonObject.get("children").getAsJsonArray val msgsb = new StringBuilder @@ -66,30 +63,32 @@ import reactor.core.publisher.Flux val data = item.get("data").getAsJsonObject var author = data.get("author").getAsString val distinguishedjson = data.get("distinguished") - var distinguished = null - if (distinguishedjson.isJsonNull) distinguished = null - else distinguished = distinguishedjson.getAsString + val distinguished = if (distinguishedjson.isJsonNull) null else distinguishedjson.getAsString val permalink = "https://www.reddit.com" + data.get("permalink").getAsString val date = data.get("created_utc").getAsLong if (date > lastSeenTime.get) lastSeenTime.set(date) else if (date > lastAnnouncementTime.get) { //noinspection ConstantConditions - do { + { val reddituserclass = ChromaGamerBase.getTypeForFolder("reddit") - if (reddituserclass == null) break //todo: break is not supported - val user = ChromaGamerBase.getUser(author, reddituserclass) - val id = user.getConnectedID(classOf[DiscordPlayer]) - if (id != null) author = "<@" + id + ">" - } while ( { - false - }) + if (reddituserclass != null) { + val user = ChromaGamerBase.getUser(author, reddituserclass) + val id = user.getConnectedID(classOf[DiscordPlayer]) + if (id != null) author = "<@" + id + ">" + } + } if (!author.startsWith("<")) author = "/u/" + author - (if (distinguished != null && distinguished == "moderator") modmsgsb - else msgsb).append("A new post was submitted to the subreddit by ").append(author).append("\n").append(permalink).append("\n") + (if (distinguished != null && distinguished == "moderator") modmsgsb else msgsb) + .append("A new post was submitted to the subreddit by ").append(author).append("\n") + .append(permalink).append("\n") lastanntime = date } } - if (msgsb.length > 0) channel.get.flatMap((ch: MessageChannel) => ch.createMessage(msgsb.toString)).flatMap(Message.pin).subscribe - if (modmsgsb.length > 0) modChannel.get.flatMap((ch: MessageChannel) => ch.createMessage(modmsgsb.toString)).flatMap(Message.pin).subscribe + + def sendMsg(ch: SMono[MessageChannel], msg: String) = + ch.asJava().flatMap(c => c.createMessage(msg)).flatMap(_.pin).subscribe + + if (msgsb.nonEmpty) sendMsg(channel.get(), msgsb.toString()) + if (modmsgsb.nonEmpty) sendMsg(modChannel.get(), modmsgsb.toString()) if (lastAnnouncementTime.get ne lastanntime) lastAnnouncementTime.set(lastanntime) // If sending succeeded } catch { case e: Exception => @@ -100,5 +99,6 @@ import reactor.core.publisher.Flux case ex: InterruptedException => Thread.currentThread.interrupt() } + AnnouncementGetterThreadMethod() } } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala b/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala index f898fe8..6b7a4f9 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala +++ b/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala @@ -16,7 +16,7 @@ import lombok.RequiredArgsConstructor this.message.getChannel.flatMap((ch: MessageChannel) => ch.createMessage(this.message.getAuthor.map((u: User) => DPUtils.nickMention(u.getId) + ", ").orElse("") + msg)).subscribe } - override def sendMessage(message: Array[String]): Unit = sendMessage(String.join("\n", message)) + override def sendMessage(message: Array[String]): Unit = sendMessage(String.join("\n", message: _*)) - override def getName = message.getAuthor.map(_.getUsername).orElse("Discord") + override def getName: String = Option(message.getAuthor.orElse(null)).map(_.getUsername).getOrElse("Discord") } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala index db1d9a9..09a51ad 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala @@ -7,8 +7,9 @@ import com.google.common.collect.HashBiMap import org.bukkit.Bukkit import org.bukkit.entity.Player -@CommandClass(helpText = Array(Array("Connect command", // - "This command lets you connect your account with a Minecraft account. This allows using the private Minecraft chat and other things."))) object ConnectCommand { +@CommandClass(helpText = Array("Connect command", // + "This command lets you connect your account with a Minecraft account." + + " This allows using the private Minecraft chat and other things.")) object ConnectCommand { /** * Key: Minecraft name
* Value: Discord ID diff --git a/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala index 67fee6e..79f3d9c 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala @@ -3,24 +3,27 @@ package buttondevteam.discordplugin.commands import buttondevteam.discordplugin.DiscordPlugin import buttondevteam.discordplugin.listeners.CommonListeners import buttondevteam.lib.chat.{Command2, CommandClass} +import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.{Member, User} -import reactor.core.publisher.Mono +import reactor.core.scala.publisher.SMono -@CommandClass(helpText = Array(Array("Switches debug mode."))) +@CommandClass(helpText = Array("Switches debug mode.")) class DebugCommand extends ICommand2DC { @Command2.Subcommand override def `def`(sender: Command2DCSender): Boolean = { - sender.getMessage.getAuthorAsMember.switchIfEmpty(sender.getMessage.getAuthor.map //Support DMs - ((u: User) => u.asMember(DiscordPlugin.mainServer.getId)).orElse(Mono.empty)).flatMap((m: Member) => DiscordPlugin.plugin.modRole.get.map((mr) => m.getRoleIds.stream.anyMatch((r: Snowflake) => r == mr.getId)).switchIfEmpty(Mono.fromSupplier(() => DiscordPlugin.mainServer.getOwnerId.asLong eq m.getId.asLong))) - .onErrorReturn(false) //Role not found - .subscribe((success: Any) => { - def foo(success: Any) = { - if (success) sender.sendMessage("debug " + (if (CommonListeners.debug) "enabled" - else "disabled")) - else sender.sendMessage("you need to be a moderator to use this command.") - } - - foo(success) + SMono(sender.getMessage.getAuthorAsMember) + .switchIfEmpty(Option(sender.getMessage.getAuthor.orElse(null)) //Support DMs + .map((u: User) => SMono(u.asMember(DiscordPlugin.mainServer.getId))).getOrElse(SMono.empty)) + .flatMap((m: Member) => DiscordPlugin.plugin.modRole.get + .map(mr => m.getRoleIds.stream.anyMatch((r: Snowflake) => r == mr.getId)) + .switchIfEmpty(SMono.fromCallable(() => DiscordPlugin.mainServer.getOwnerId.asLong eq m.getId.asLong))) + .onErrorResume(_ => SMono.just(false)) //Role not found + .subscribe(success => { + if (success) { + CommonListeners.debug = !CommonListeners.debug; + sender.sendMessage("debug " + (if (CommonListeners.debug) "enabled" else "disabled")) + } else + sender.sendMessage("you need to be a moderator to use this command.") }) true } diff --git a/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.scala index c3d1029..77d51df 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.scala @@ -2,8 +2,8 @@ package buttondevteam.discordplugin.commands import buttondevteam.lib.chat.{Command2, CommandClass} -@CommandClass(helpText = Array(Array("Help command", // - "Shows some info about a command or lists the available commands."))) +@CommandClass(helpText = Array("Help command", // + "Shows some info about a command or lists the available commands.")) class HelpCommand extends ICommand2DC { @Command2.Subcommand def `def`(sender: Command2DCSender, @Command2.TextArg @Command2.OptionalArg args: String): Boolean = { diff --git a/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.scala index c9e7203..7768b44 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.scala @@ -4,13 +4,12 @@ import buttondevteam.discordplugin.{DiscordPlayer, DiscordPlugin} import buttondevteam.lib.chat.{Command2, CommandClass} import buttondevteam.lib.player.ChromaGamerBase import buttondevteam.lib.player.ChromaGamerBase.InfoTarget -import discord4j.core.`object`.entity.{Member, Message, User} +import discord4j.core.`object`.entity.{Message, User} +import reactor.core.scala.publisher.SFlux -import java.util - -@CommandClass(helpText = Array(Array("User information", // +@CommandClass(helpText = Array("User information", // "Shows some information about users, from Discord, from Minecraft or from Reddit if they have these accounts connected.", - "If used without args, shows your info."))) + "If used without args, shows your info.")) class UserinfoCommand extends ICommand2DC { @Command2.Subcommand def `def`(sender: Command2DCSender, @Command2.OptionalArg @Command2.TextArg user: String): Boolean = { @@ -25,16 +24,11 @@ class UserinfoCommand extends ICommand2DC { else if (user.contains("#")) { val targettag = user.split("#") val targets = getUsers(message, targettag(0)) - if (targets.size == 0) { + if (targets.isEmpty) { channel.createMessage("The user cannot be found (by name): " + user).subscribe return true } - for (ptarget <- targets) { - if (ptarget.getDiscriminator.equalsIgnoreCase(targettag(1))) { - target = ptarget - break //todo: break is not supported - } - } + targets.collectFirst(_.getDiscriminator.equalsIgnoreCase(targettag(1))) if (target == null) { channel.createMessage("The user cannot be found (by discriminator): " + user + "(Found " + targets.size + " users with the name.)").subscribe return true @@ -42,7 +36,7 @@ class UserinfoCommand extends ICommand2DC { } else { val targets = getUsers(message, user) - if (targets.size == 0) { + if (targets.isEmpty) { channel.createMessage("The user cannot be found on Discord: " + user).subscribe return true } @@ -50,7 +44,7 @@ class UserinfoCommand extends ICommand2DC { channel.createMessage("Multiple users found with that (nick)name. Please specify the whole tag, like ChromaBot#6338 or use a ping.").subscribe return true } - target = targets.get(0) + target = targets.head } } if (target == null) { @@ -65,12 +59,11 @@ class UserinfoCommand extends ICommand2DC { } private def getUsers(message: Message, args: String) = { - var targets: util.List[User] val guild = message.getGuild.block if (guild == null) { //Private channel - targets = DiscordPlugin.dc.getUsers.filter((u) => u.getUsername.equalsIgnoreCase(args)).collectList.block + SFlux(DiscordPlugin.dc.getUsers).filter(u => u.getUsername.equalsIgnoreCase(args)).collectSeq().block() } - else targets = guild.getMembers.filter((m: Member) => m.getUsername.equalsIgnoreCase(args)).map((m: Member) => m.asInstanceOf[User]).collectList.block - targets + else + SFlux(guild.getMembers).filter(_.getUsername.equalsIgnoreCase(args)).map(_.asInstanceOf[User]).collectSeq().block() } } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala index 137f869..9f8544c 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala @@ -3,7 +3,7 @@ package buttondevteam.discordplugin.commands import buttondevteam.discordplugin.DiscordPlugin import buttondevteam.lib.chat.{Command2, CommandClass} -@CommandClass(helpText = Array(Array("Version", "Returns the plugin's version"))) +@CommandClass(helpText = Array("Version", "Returns the plugin's version")) object VersionCommand { def getVersion: Array[String] = { val desc = DiscordPlugin.plugin.getDescription @@ -11,7 +11,7 @@ object VersionCommand { } } -@CommandClass(helpText = Array(Array("Version", "Returns the plugin's version"))) +@CommandClass(helpText = Array("Version", "Returns the plugin's version")) class VersionCommand extends ICommand2DC { @Command2.Subcommand override def `def`(sender: Command2DCSender): Boolean = { sender.sendMessage(VersionCommand.getVersion) diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala index 14b9ba5..fdf04ca 100644 --- a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala +++ b/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala @@ -9,7 +9,7 @@ import discord4j.core.`object`.entity.{Guild, Role} import org.apache.commons.lang.exception.ExceptionUtils import org.bukkit.Bukkit import org.bukkit.event.{EventHandler, Listener} -import reactor.core.publisher.Mono +import reactor.core.scala.publisher.SMono import java.util import java.util.stream.Collectors @@ -20,29 +20,24 @@ import java.util.stream.Collectors object ExceptionListenerModule { private def SendException(e: Throwable, sourcemessage: String): Unit = { if (instance == null) return - try getChannel.flatMap((channel: MessageChannel) => { - def foo(channel: MessageChannel) = { - var coderRole: Mono[Role] = channel match { - case ch: GuildChannel => instance.pingRole(ch.getGuild).get - case _ => Mono.empty - } - coderRole.map((role: Role) => if (TBMCCoreAPI.IsTestServer) new StringBuilder - else new StringBuilder(role.getMention).append("\n")).defaultIfEmpty(new StringBuilder).flatMap((sb: StringBuilder) => { - def foo(sb: StringBuilder) = { - sb.append(sourcemessage).append("\n") - sb.append("```").append("\n") - var stackTrace = util.Arrays.stream(ExceptionUtils.getStackTrace(e).split("\\n")).filter((s: String) => !s.contains("\tat ") || s.contains("\tat buttondevteam.")).collect(Collectors.joining("\n")) - if (sb.length + stackTrace.length >= 1980) stackTrace = stackTrace.substring(0, 1980 - sb.length) - sb.append(stackTrace).append("\n") - sb.append("```") - channel.createMessage(sb.toString) - } - - foo(sb) - }) + try getChannel.flatMap(channel => { + val coderRole = channel match { + case ch: GuildChannel => instance.pingRole(SMono(ch.getGuild)).get + case _ => SMono.empty } - - foo(channel) + coderRole.map((role: Role) => if (TBMCCoreAPI.IsTestServer) new StringBuilder + else new StringBuilder(role.getMention).append("\n")) + .defaultIfEmpty(new StringBuilder).flatMap(sb => { + sb.append(sourcemessage).append("\n") + sb.append("```").append("\n") + var stackTrace = util.Arrays.stream(ExceptionUtils.getStackTrace(e).split("\\n")) + .filter(s => !s.contains("\tat ") || s.contains("\tat buttondevteam.")) + .collect(Collectors.joining("\n")) + if (sb.length + stackTrace.length >= 1980) stackTrace = stackTrace.substring(0, 1980 - sb.length) + sb.append(stackTrace).append("\n") + sb.append("```") + SMono(channel.createMessage(sb.toString)) + }) }).subscribe catch { case ex: Exception => @@ -52,9 +47,9 @@ object ExceptionListenerModule { private var instance: ExceptionListenerModule = null - def getChannel: Mono[MessageChannel] = { + def getChannel: SMono[MessageChannel] = { if (instance != null) return instance.channel.get - Mono.empty + SMono.empty } } @@ -64,13 +59,12 @@ class ExceptionListenerModule extends Component[DiscordPlugin] with Listener { @EventHandler def onException(e: TBMCExceptionEvent): Unit = { if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(getClass)) return - if (lastthrown.stream.anyMatch((ex: Throwable) => util.Arrays.equals(e.getException.getStackTrace, ex.getStackTrace) && (if (e.getException.getMessage == null) ex.getMessage == null - else e.getException.getMessage == ex.getMessage)) // e.Exception.Message==ex.Message + if (lastthrown.stream.anyMatch(ex => e.getException.getStackTrace.sameElements(ex.getStackTrace) + && (if (e.getException.getMessage == null) ex.getMessage == null else e.getException.getMessage == ex.getMessage)) && lastsourcemsg.contains(e.getSourceMessage)) { return } - ExceptionListenerModule - .SendException(e.getException, e.getSourceMessage) + ExceptionListenerModule.SendException(e.getException, e.getSourceMessage) if (lastthrown.size >= 10) lastthrown.remove(0) if (lastsourcemsg.size >= 10) lastsourcemsg.remove(0) lastthrown.add(e.getException) @@ -86,7 +80,7 @@ class ExceptionListenerModule extends Component[DiscordPlugin] with Listener { /** * The role to ping if an error occurs. Set to empty ('') to disable. */ - private def pingRole(guild: Mono[Guild]) = DPUtils.roleData(getConfig, "pingRole", "Coder", guild) + private def pingRole(guild: SMono[Guild]) = DPUtils.roleData(getConfig, "pingRole", "Coder", guild) override protected def enable(): Unit = { if (DPUtils.disableIfConfigError(this, channel)) return diff --git a/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala b/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala index 4b71d87..f919932 100644 --- a/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala +++ b/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala @@ -5,16 +5,15 @@ import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} import buttondevteam.lib.TBMCCoreAPI import buttondevteam.lib.architecture.{Component, ConfigData} import com.google.common.collect.Lists -import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.channel.{GuildChannel, MessageChannel} -import discord4j.core.`object`.entity.{Guild, Member, Message, Role} -import discord4j.core.`object`.presence.{Presence, Status} +import discord4j.core.`object`.entity.{Guild, Message} +import discord4j.core.`object`.presence.Status import discord4j.core.event.domain.PresenceUpdateEvent import discord4j.core.spec.{EmbedCreateSpec, MessageCreateSpec} import org.bukkit.Bukkit import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.{EventHandler, Listener} -import reactor.core.publisher.Mono +import reactor.core.scala.publisher.{SFlux, SMono} import java.util import java.util.Calendar @@ -50,41 +49,47 @@ object FunModule { lastlist = 0 } if (msglowercased == "/list" && Bukkit.getOnlinePlayers.size == lastlistp && { - ListC += 1; + ListC += 1 ListC - 1 } > 2) { // Lowered already - DPUtils.reply(message, Mono.empty, "stop it. You know the answer.").subscribe + DPUtils.reply(message, SMono.empty, "stop it. You know the answer.").subscribe lastlist = 0 lastlistp = Bukkit.getOnlinePlayers.size.toShort return true //Handled } lastlistp = Bukkit.getOnlinePlayers.size.toShort //Didn't handle - if (!TBMCCoreAPI.IsTestServer && util.Arrays.stream(fm.serverReady).get.anyMatch(msglowercased.contains _)) { + if (!TBMCCoreAPI.IsTestServer && fm.serverReady.get.exists(msglowercased.contains)) { var next = 0 if (usableServerReadyStrings.size == 0) fm.createUsableServerReadyStrings() next = usableServerReadyStrings.remove(serverReadyRandom.nextInt(usableServerReadyStrings.size)) - DPUtils.reply(message, Mono.empty, fm.serverReadyAnswers.get.get(next)).subscribe + DPUtils.reply(message, SMono.empty, fm.serverReadyAnswers.get.get(next)).subscribe return false //Still process it as a command/mcchat if needed } - false + false } - private var lasttime = 0 + private var lasttime: Long = 0 def handleFullHouse(event: PresenceUpdateEvent): Unit = { val fm = ComponentManager.getIfEnabled(classOf[FunModule]) if (fm == null) return if (Calendar.getInstance.get(Calendar.DAY_OF_MONTH) % 5 != 0) return + if (!Option(event.getOld.orElse(null)).exists(_.getStatus == Status.OFFLINE) + || event.getCurrent.getStatus == Status.OFFLINE) + return //If it's not an offline -> online change fm.fullHouseChannel.get.filter((ch: MessageChannel) => ch.isInstanceOf[GuildChannel]) - .flatMap((channel: MessageChannel) => fm.fullHouseDevRole(channel.asInstanceOf[GuildChannel].getGuild).get.filter((role: Role) => event.getOld.map((p: Presence) => p.getStatus == Status.OFFLINE).orElse(false)).filter((role: Role) => !(event.getCurrent.getStatus == Status.OFFLINE)).filterWhen((devrole: Role) => event.getMember.flatMap((m: Member) => m.getRoles.any((r: Role) => r.getId.asLong == devrole.getId.asLong))).filterWhen((devrole: Role) => event.getGuild.flatMapMany((g: Guild) => g.getMembers.filter((m: Member) => m.getRoleIds.stream.anyMatch((s: Snowflake) => s == devrole.getId))).flatMap(Member.getPresence).all((pr: Presence) => !(pr.getStatus == Status.OFFLINE))).filter((devrole: Role) => lasttime + 10 < TimeUnit.NANOSECONDS.toHours(System.nanoTime)).flatMap //This should stay so it checks this last - ((devrole: Role) => { - def foo(devrole: Role) = { + .flatMap(channel => fm.fullHouseDevRole(SMono(channel.asInstanceOf[GuildChannel].getGuild)).get + .filterWhen(devrole => SMono(event.getMember) + .flatMap(m => SFlux(m.getRoles).any(_.getId.asLong == devrole.getId.asLong))) + .filterWhen(devrole => SMono(event.getGuild) + .flatMapMany(g => SFlux(g.getMembers).filter(_.getRoleIds.stream.anyMatch(_ == devrole.getId))) + .flatMap(_.getPresence).all(_.getStatus != Status.OFFLINE)) + .filter(_ => lasttime + 10 < TimeUnit.NANOSECONDS.toHours(System.nanoTime)) //This should stay so it checks this last + .flatMap(_ => { lasttime = TimeUnit.NANOSECONDS.toHours(System.nanoTime) - channel.createMessage((mcs: MessageCreateSpec) => mcs.setContent("Full house!").setEmbed((ecs: EmbedCreateSpec) => ecs.setImage("https://cdn.discordapp.com/attachments/249295547263877121/249687682618359808/poker-hand-full-house-aces-kings-playing-cards-15553791.png"))) - } - - foo(devrole) - })).subscribe + SMono(channel.createMessage((mcs: MessageCreateSpec) => mcs.setContent("Full house!") + .setEmbed((ecs: EmbedCreateSpec) => ecs.setImage("https://cdn.discordapp.com/attachments/249295547263877121/249687682618359808/poker-hand-full-house-aces-kings-playing-cards-15553791.png")))) + })).subscribe } } @@ -109,14 +114,18 @@ class FunModule extends Component[DiscordPlugin] with Listener { override protected def enable(): Unit = registerListener(this) - override protected def disable(): Unit = FunModule.lastlist = FunModule.lastlistp = FunModule.ListC = 0 + override protected def disable(): Unit = { + FunModule.lastlist = 0 + FunModule.lastlistp = 0 + FunModule.ListC = 0 + } @EventHandler def onPlayerJoin(event: PlayerJoinEvent): Unit = FunModule.ListC = 0 /** * If all of the people who have this role are online, the bot will post a full house. */ - private def fullHouseDevRole(guild: Mono[Guild]) = DPUtils.roleData(getConfig, "fullHouseDevRole", "Developer", guild) + private def fullHouseDevRole(guild: SMono[Guild]) = DPUtils.roleData(getConfig, "fullHouseDevRole", "Developer", guild) /** * The channel to post the full house to. diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala index 57f6d8b..5c776ca 100644 --- a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala +++ b/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala @@ -12,7 +12,7 @@ import discord4j.core.event.EventDispatcher import discord4j.core.event.domain.PresenceUpdateEvent import discord4j.core.event.domain.message.MessageCreateEvent import discord4j.core.event.domain.role.{RoleCreateEvent, RoleDeleteEvent, RoleUpdateEvent} -import reactor.core.scala.publisher.SMono +import reactor.core.scala.publisher.{SFlux, SMono} object CommonListeners { val timings = new Timings @@ -56,25 +56,19 @@ object CommonListeners { } foo(event) - }).onErrorContinue((err: Throwable, obj: Any) => TBMCCoreAPI.SendException("An error occured while handling a message!", err, DiscordPlugin.plugin)).subscribe + }).onErrorContinue((err: Throwable, _) => TBMCCoreAPI.SendException("An error occured while handling a message!", err, DiscordPlugin.plugin)).subscribe dispatcher.on(classOf[PresenceUpdateEvent]).subscribe((event: PresenceUpdateEvent) => { - def foo(event: PresenceUpdateEvent) = { - if (DiscordPlugin.SafeMode) return + if (!DiscordPlugin.SafeMode) FunModule.handleFullHouse(event) - } - - foo(event) }) - dispatcher.on(classOf[RoleCreateEvent]).subscribe(GameRoleModule.handleRoleEvent _) - dispatcher.on(classOf[RoleDeleteEvent]).subscribe(GameRoleModule.handleRoleEvent _) - dispatcher.on(classOf[RoleUpdateEvent]).subscribe(GameRoleModule.handleRoleEvent _) + SFlux(dispatcher.on(classOf[RoleCreateEvent])).subscribe(GameRoleModule.handleRoleEvent _) + SFlux(dispatcher.on(classOf[RoleDeleteEvent])).subscribe(GameRoleModule.handleRoleEvent _) + SFlux(dispatcher.on(classOf[RoleUpdateEvent])).subscribe(GameRoleModule.handleRoleEvent _) } - private var debug = false + var debug = false def debug(debug: String): Unit = if (CommonListeners.debug) { //Debug DPUtils.getLogger.info(debug) } - - def debug(): Unit = debug = !debug } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala index 94ad744..e69fb53 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala @@ -21,7 +21,7 @@ import java.util.{Objects, Optional} import javax.annotation.Nullable @SuppressWarnings(Array("SimplifyOptionalCallChains")) //Java 11 -@CommandClass(helpText = Array(Array("Channel connect", // +@CommandClass(helpText = Array("Channel connect", // "This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).", "You need to have access to the MC channel and have manage permissions on the Discord channel.", "You also need to have your Minecraft account connected. In #bot use /connect .", @@ -30,7 +30,7 @@ import javax.annotation.Nullable "To remove a connection use @ChromaBot channelcon remove in the channel.", "Mentioning the bot is needed in this case because the / prefix only works in #bot.", "Invite link: " // -))) +)) class ChannelconCommand(private val module: MinecraftChatModule) extends ICommand2DC { @Command2.Subcommand def remove(sender: Command2DCSender): Boolean = { val message = sender.getMessage diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.scala index 7b76572..78e7085 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.scala @@ -1,17 +1,17 @@ package buttondevteam.discordplugin.mcchat -import buttondevteam.discordplugin.{DPUtils, DiscordPlayer, DiscordPlugin} import buttondevteam.discordplugin.commands.{Command2DCSender, ICommand2DC} +import buttondevteam.discordplugin.{DPUtils, DiscordPlayer, DiscordPlugin} import buttondevteam.lib.chat.{Command2, CommandClass} import buttondevteam.lib.player.ChromaGamerBase import discord4j.core.`object`.entity.channel.PrivateChannel -@CommandClass(helpText = Array(Array( +@CommandClass(helpText = Array( "MC Chat", "This command 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." // -))) +)) class MCChatCommand(private val module: MinecraftChatModule) extends ICommand2DC { @Command2.Subcommand override def `def`(sender: Command2DCSender): Boolean = { if (!(module.allowPrivateChat.get)) { diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala index 25eb781..163b6ec 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala @@ -8,15 +8,14 @@ import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.MessageChannel import lombok.NonNull -import java.util -import java.util.Collections import javax.annotation.Nullable +import scala.collection.mutable.ListBuffer object MCChatCustom { /** * Used for town or nation chats or anything else */ - private[mcchat] val lastmsgCustom = new util.ArrayList[MCChatCustom.CustomLMD] + private[mcchat] val lastmsgCustom = new ListBuffer[MCChatCustom.CustomLMD] def addCustomChat(channel: MessageChannel, groupid: String, mcchannel: Channel, user: User, dcp: DiscordConnectedPlayer, toggles: Int, brtoggles: Set[TBMCSystemChatEvent.BroadcastTarget]): Boolean = { lastmsgCustom synchronized { @@ -29,34 +28,32 @@ object MCChatCustom { gid = groupid } val lmd = new MCChatCustom.CustomLMD(channel, user, gid, mcchannel, dcp, toggles, brtoggles) - lastmsgCustom.add(lmd) + lastmsgCustom += lmd } true } def hasCustomChat(channel: Snowflake): Boolean = - lastmsgCustom.stream.anyMatch((lmd: MCChatCustom.CustomLMD) => lmd.channel.getId.asLong == channel.asLong) + lastmsgCustom.exists(_.channel.getId.asLong == channel.asLong) @Nullable def getCustomChat(channel: Snowflake): CustomLMD = - lastmsgCustom.stream.filter((lmd: MCChatCustom.CustomLMD) => lmd.channel.getId.asLong == channel.asLong).findAny.orElse(null) + lastmsgCustom.find(_.channel.getId.asLong == channel.asLong).orNull - def removeCustomChat(channel: Snowflake): Boolean = { - lastmsgCustom synchronized MCChatUtils.lastmsgfromd.remove(channel.asLong) - lastmsgCustom.removeIf((lmd: MCChatCustom.CustomLMD) => { - def foo(lmd: MCChatCustom.CustomLMD): Boolean = { - if (lmd.channel.getId.asLong != channel.asLong) return false + def removeCustomChat(channel: Snowflake): Unit = { + lastmsgCustom synchronized { + MCChatUtils.lastmsgfromd.remove(channel.asLong) + lastmsgCustom.filterInPlace(lmd => { + if (lmd.channel.getId.asLong != channel.asLong) return true lmd.mcchannel match { case room: ChatRoom => room.leaveRoom(lmd.dcp) case _ => } - true - } - - foo(lmd) - }) + false + }) + } } - def getCustomChats: util.List[CustomLMD] = Collections.unmodifiableList(lastmsgCustom) + def getCustomChats: List[CustomLMD] = lastmsgCustom.toList class CustomLMD private[mcchat](@NonNull channel: MessageChannel, @NonNull user: User, val groupID: String, @NonNull mcchannel: Channel, val dcp: DiscordConnectedPlayer, var toggles: Int, diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala index d4c837b..c5bd052 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala @@ -28,6 +28,7 @@ import java.util.Optional import java.util.concurrent.{LinkedBlockingQueue, TimeoutException} import java.util.function.{Consumer, Function, Predicate} import java.util.stream.Collectors +import scala.jdk.OptionConverters.RichOptional object MCChatListener { @@ -111,21 +112,21 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { foo(ecs) } val nanoTime: Long = System.nanoTime - val doit: MCChatListener.InterruptibleConsumer[MCChatUtils.LastMsgData] = (lastmsgdata: MCChatUtils.LastMsgData) => { - def foo(lastmsgdata: MCChatUtils.LastMsgData): Unit = { - if (lastmsgdata.message == null || !(authorPlayer == lastmsgdata.message.getEmbeds.get(0).getAuthor.map(_.getName).orElse(null)) || lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120 || !(lastmsgdata.mcchannel.ID == e.getChannel.ID) || lastmsgdata.content.length + e.getMessage.length + 1 > 2048) { - lastmsgdata.message = lastmsgdata.channel.createEmbed(embed).block - lastmsgdata.time = nanoTime - lastmsgdata.mcchannel = e.getChannel - lastmsgdata.content = e.getMessage - } - else { - lastmsgdata.content = lastmsgdata.content + "\n" + e.getMessage // The message object doesn't get updated - lastmsgdata.message.edit((mes: MessageEditSpec) => mes.setEmbed(embed.andThen((ecs: EmbedCreateSpec) => ecs.setDescription(lastmsgdata.content)))).block - } + val doit = (lastmsgdata: MCChatUtils.LastMsgData) => { + if (lastmsgdata.message == null + || authorPlayer != lastmsgdata.message.getEmbeds.get(0).getAuthor.toScala.map(_.getName).orNull + || lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120 + || !(lastmsgdata.mcchannel.ID == e.getChannel.ID) + || lastmsgdata.content.length + e.getMessage.length + 1 > 2048) { + lastmsgdata.message = lastmsgdata.channel.createEmbed(embed).block + lastmsgdata.time = nanoTime + lastmsgdata.mcchannel = e.getChannel + lastmsgdata.content = e.getMessage + } + else { + lastmsgdata.content = lastmsgdata.content + "\n" + e.getMessage // The message object doesn't get updated + lastmsgdata.message.edit((mes: MessageEditSpec) => mes.setEmbed(embed.andThen((ecs: EmbedCreateSpec) => ecs.setDescription(lastmsgdata.content)))).block } - - foo(lastmsgdata) } // Checks if the given channel is different than where the message was sent from // Or if it was from MC @@ -133,31 +134,29 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { if (e.getChannel.isGlobal && (e.isFromCommand || isdifferentchannel.test(module.chatChannel.get))) { if (MCChatUtils.lastmsgdata == null) MCChatUtils.lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono.block, null) - doit.accept(MCChatUtils.lastmsgdata) + doit(MCChatUtils.lastmsgdata) } for (data <- MCChatPrivate.lastmsgPerUser) { if ((e.isFromCommand || isdifferentchannel.test(data.channel.getId)) && e.shouldSendTo(MCChatUtils.getSender(data.channel.getId, data.user))) { - doit.accept(data) + doit(data) } } - MCChatCustom.lastmsgCustom synchronized - val iterator = MCChatCustom.lastmsgCustom.iterator - while ( { - iterator.hasNext - }) { - val lmd = iterator.next - if ((e.isFromCommand || isdifferentchannel.test(lmd.channel.getId)) //Test if msg is from Discord - && e.getChannel.ID == lmd.mcchannel.ID //If it's from a command, the command msg has been deleted, so we need to send it - && e.getGroupID == 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) + MCChatCustom.lastmsgCustom synchronized { + MCChatCustom.lastmsgCustom.filterInPlace(lmd => { + if ((e.isFromCommand || isdifferentchannel.test(lmd.channel.getId)) //Test if msg is from Discord + && e.getChannel.ID == lmd.mcchannel.ID //If it's from a command, the command msg has been deleted, so we need to send it + && e.getGroupID == lmd.groupID) { //Check if this is the group we want to test - #58 + if (e.shouldSendTo(lmd.dcp)) { //Check original user's permissions + doit(lmd) + } + else { + lmd.channel.createMessage("The user no longer has permission to view the channel, connection removed.").subscribe + return false //If the user no longer has permission, remove the connection + } } - else { - iterator.remove() //If the user no longer has permission, remove the connection - lmd.channel.createMessage("The user no longer has permission to view the channel, connection removed.").subscribe - } - } + true + }) } } catch { case ex: InterruptedException => diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala index 1926cab..4f10b2d 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala @@ -19,17 +19,20 @@ import org.bukkit.event.Event import org.bukkit.event.player.{AsyncPlayerPreLoginEvent, PlayerJoinEvent, PlayerLoginEvent, PlayerQuitEvent} import org.bukkit.plugin.AuthorNagException import org.reactivestreams.Publisher -import reactor.core.publisher.Mono +import reactor.core.scala.publisher.SMono import java.net.InetAddress import java.util import java.util._ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger -import java.util.function.Supplier import java.util.logging.Level -import java.util.stream.{Collectors, Stream} +import java.util.stream.Collectors import javax.annotation.Nullable +import scala.collection.concurrent +import scala.collection.convert.ImplicitConversions.`map AsJavaMap` +import scala.collection.mutable.ListBuffer +import scala.jdk.CollectionConverters.CollectionHasAsScala import scala.jdk.javaapi.CollectionConverters.asScala object MCChatUtils { @@ -43,13 +46,13 @@ object MCChatUtils { @Nullable private[mcchat] var lastmsgdata: MCChatUtils.LastMsgData = null private[mcchat] val lastmsgfromd = new LongObjectHashMap[Message] // Last message sent by a Discord user, used for clearing checkmarks private var module: MinecraftChatModule = null - private val staticExcludedPlugins = new util.HashMap[Class[_ <: Event], util.HashSet[String]] + private val staticExcludedPlugins = Map[Class[_ <: Event], util.HashSet[String]]() def updatePlayerList(): Unit = { val mod = getModule if (mod == null || !mod.showPlayerListOnDC.get) return if (lastmsgdata != null) updatePL(lastmsgdata) - MCChatCustom.lastmsgCustom.forEach(MCChatUtils.updatePL) + MCChatCustom.lastmsgCustom.foreach(MCChatUtils.updatePL) } private def notEnabled = (module == null || !module.disabling) && getModule == null //Allow using things while disabling the module @@ -85,7 +88,8 @@ object MCChatUtils { .filter(_ => C.incrementAndGet > 0) //Always true .map((p) => DPUtils.sanitizeString(p.getDisplayName)).collect(Collectors.joining(", ")) s(0) = C + " player" + (if (C.get != 1) "s" else "") + " online" - lmd.channel.asInstanceOf[TextChannel].edit((tce: TextChannelEditSpec) => tce.setTopic(String.join("\n----\n", s)).setReason("Player list update")).subscribe //Don't wait + lmd.channel.asInstanceOf[TextChannel].edit((tce: TextChannelEditSpec) => + tce.setTopic(String.join("\n----\n", s: _*)).setReason("Player list update")).subscribe //Don't wait } private[mcchat] def checkEssentials(p: Player): Boolean = { @@ -94,12 +98,12 @@ object MCChatUtils { !ess.getUser(p).isHidden } - def addSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], user: User, sender: T): T = + def addSender[T <: DiscordSenderBase](senders: concurrent.Map[String, ConcurrentHashMap[Snowflake, T]], user: User, sender: T): T = addSender(senders, user.getId.asString, sender) - def addSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], did: String, sender: T): T = { - var map = senders.get(did) - if (map == null) map = new ConcurrentHashMap[Snowflake, T] + def addSender[T <: DiscordSenderBase](senders: concurrent.Map[String, ConcurrentHashMap[Snowflake, T]], did: String, sender: T): T = { + val origMap = senders.get(did) + val map = if (origMap.isEmpty) new ConcurrentHashMap[Snowflake, T] else origMap.get map.put(sender.getChannel.getId, sender) senders.put(did, map) sender @@ -117,15 +121,12 @@ object MCChatUtils { else null.asInstanceOf } - def forPublicPrivateChat(action: Mono[MessageChannel] => Mono[_]): Mono[_] = { - if (notEnabled) return Mono.empty - val list = new util.ArrayList[Mono[_]] - list.add(action.apply(module.chatChannelMono)) - for (data <- MCChatPrivate.lastmsgPerUser) { - list.add(action.apply(Mono.just(data.channel))) - } + def forPublicPrivateChat(action: SMono[MessageChannel] => SMono[_]): SMono[_] = { + if (notEnabled) return SMono.empty + val list = MCChatPrivate.lastmsgPerUser.map(data => action(SMono.just(data.channel))) + .prepend(action(module.chatChannelMono)) // lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat - Mono.whenDelayError(list) + SMono.whenDelayError(list) } /** @@ -135,14 +136,14 @@ object MCChatUtils { * @param toggle The toggle to check * @param hookmsg Whether the message is also sent from the hook */ - def forCustomAndAllMCChat(action: Mono[MessageChannel] => Mono[_], @Nullable toggle: ChannelconBroadcast, hookmsg: Boolean): Mono[_] = { - if (notEnabled) return Mono.empty - val list = new util.ArrayList[Publisher[_]] - if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) list.add(forPublicPrivateChat(action)) - val customLMDFunction = (cc: MCChatCustom.CustomLMD) => action.apply(Mono.just(cc.channel)) - if (toggle == null) MCChatCustom.lastmsgCustom.stream.map(customLMDFunction).forEach(list.add(_)) - else MCChatCustom.lastmsgCustom.stream.filter((cc) => (cc.toggles & toggle.flag) ne 0).map(customLMDFunction).forEach(list.add(_)) - Mono.whenDelayError(list) + def forCustomAndAllMCChat(action: SMono[MessageChannel] => SMono[_], @Nullable toggle: ChannelconBroadcast, hookmsg: Boolean): SMono[_] = { + if (notEnabled) return SMono.empty + val list = new ListBuffer[Publisher[_]] + if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) list.append(forPublicPrivateChat(action)) + val customLMDFunction = (cc: MCChatCustom.CustomLMD) => action(SMono.just(cc.channel)) + if (toggle == null) MCChatCustom.lastmsgCustom.map(customLMDFunction).foreach(list.append(_)) + else MCChatCustom.lastmsgCustom.filter((cc) => (cc.toggles & (1 << toggle.id)) ne 0).map(customLMDFunction).foreach(list.append(_)) + SMono.whenDelayError(list) } /** @@ -152,18 +153,15 @@ object MCChatUtils { * @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 */ - def forAllowedCustomMCChat(action: Mono[MessageChannel] => Mono[_], @Nullable sender: CommandSender, @Nullable toggle: ChannelconBroadcast): Mono[_] = { - if (notEnabled) return Mono.empty - val st = MCChatCustom.lastmsgCustom.stream.filter((clmd) => { - def foo(clmd: CustomLMD): Boolean = { //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 & (1 << toggle.id)) eq 0)) return false //If null then allow - if (sender == null) return true - clmd.groupID.equals(clmd.mcchannel.getGroupID(sender)) - } - - foo(clmd) - }).map((cc) => action.apply(Mono.just(cc.channel))) //TODO: Send error messages on channel connect - Mono.whenDelayError(st.iterator) //Can't convert as an iterator or inside the stream, but I can convert it as a stream + def forAllowedCustomMCChat(action: SMono[MessageChannel] => SMono[_], @Nullable sender: CommandSender, @Nullable toggle: ChannelconBroadcast): SMono[_] = { + if (notEnabled) return SMono.empty + val st = MCChatCustom.lastmsgCustom.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 & (1 << toggle.id)) eq 0)) false //If null then allow + else if (sender == null) true + else clmd.groupID.equals(clmd.mcchannel.getGroupID(sender)) + }).map(cc => action.apply(SMono.just(cc.channel))) //TODO: Send error messages on channel connect + //Mono.whenDelayError((() => st.iterator).asInstanceOf[java.lang.Iterable[Mono[_]]]) //Can't convert as an iterator or inside the stream, but I can convert it as a stream + SMono.whenDelayError(st) } /** @@ -174,47 +172,45 @@ object MCChatUtils { * @param toggle The toggle to check or null to send to all allowed * @param hookmsg Whether the message is also sent from the hook */ - def forAllowedCustomAndAllMCChat(action: Mono[MessageChannel] => Mono[_], @Nullable sender: CommandSender, @Nullable toggle: ChannelconBroadcast, hookmsg: Boolean): Mono[_] = { - if (notEnabled) return Mono.empty + def forAllowedCustomAndAllMCChat(action: SMono[MessageChannel] => SMono[_], @Nullable sender: CommandSender, @Nullable toggle: ChannelconBroadcast, hookmsg: Boolean): SMono[_] = { + if (notEnabled) return SMono.empty val cc = forAllowedCustomMCChat(action, sender, toggle) - if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) return Mono.whenDelayError(forPublicPrivateChat(action), cc) - Mono.whenDelayError(cc) + if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) return SMono.whenDelayError(Array(forPublicPrivateChat(action), cc)) + SMono.whenDelayError(Array(cc)) } - def send(message: String): Mono[MessageChannel] => Mono[_] = (ch: Mono[MessageChannel]) => ch.flatMap((mc: MessageChannel) => { - def foo(mc: MessageChannel) = { - resetLastMessage(mc) - mc.createMessage(DPUtils.sanitizeString(message)) - } - - foo(mc) + def send(message: String): SMono[MessageChannel] => SMono[_] = _.flatMap((mc: MessageChannel) => { + resetLastMessage(mc) + SMono(mc.createMessage(DPUtils.sanitizeString(message))) }) - def forAllowedMCChat(action: Mono[MessageChannel] => Mono[_], event: TBMCSystemChatEvent): Mono[_] = { - if (notEnabled) return Mono.empty - val list = new util.ArrayList[Mono[_]] - if (event.getChannel.isGlobal) list.add(action.apply(module.chatChannelMono)) + def forAllowedMCChat(action: SMono[MessageChannel] => SMono[_], event: TBMCSystemChatEvent): SMono[_] = { + if (notEnabled) return SMono.empty + val list = new ListBuffer[SMono[_]] + if (event.getChannel.isGlobal) list.append(action(module.chatChannelMono)) for (data <- MCChatPrivate.lastmsgPerUser) - if (event.shouldSendTo(getSender(data.channel.getId, data.user))) list.add(action.apply(Mono.just(data.channel))) //TODO: Only store ID?} - MCChatCustom.lastmsgCustom.stream.filter((clmd) => { - def foo(clmd: CustomLMD): Boolean = { - clmd.brtoggles.contains(event.getTarget) && event.shouldSendTo(clmd.dcp) - } - - foo(clmd) - }).map((clmd) => action.apply(Mono.just(clmd.channel))).forEach(list.add(_)) - Mono.whenDelayError(list) + if (event.shouldSendTo(getSender(data.channel.getId, data.user))) + list.append(action(SMono.just(data.channel))) //TODO: Only store ID? + MCChatCustom.lastmsgCustom.filter(clmd => + clmd.brtoggles.contains(event.getTarget) && event.shouldSendTo(clmd.dcp)) + .map(clmd => action(SMono.just(clmd.channel))).forEach(elem => { + list.append(elem) + () + }) + SMono.whenDelayError(list) } /** * 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[mcchat] def getSender(channel: Snowflake, author: User) = { //noinspection OptionalGetWithoutIsPresent - Stream.of[Supplier[Optional[DiscordSenderBase]]]( // 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, DiscordPlugin.dc.getChannelById(channel).block.asInstanceOf[MessageChannel])))).map(_.get).filter(_.isPresent).map(_.get).findFirst.get + private[mcchat] def getSender(channel: Snowflake, author: User): DiscordSenderBase = { //noinspection OptionalGetWithoutIsPresent + List[() => DiscordSenderBase]( // https://stackoverflow.com/a/28833677/2703239 + () => getSender[DiscordSenderBase](OnlineSenders, channel, author), // Find first non-null + () => getSender[DiscordSenderBase](ConnectedSenders, channel, author), // This doesn't support the public chat, but it'll always return null for it + () => getSender[DiscordSenderBase](UnconnectedSenders, channel, author), // + () => addSender[DiscordSenderBase](UnconnectedSenders, author, + new DiscordSender(author, SMono(DiscordPlugin.dc.getChannelById(channel)).block().asInstanceOf[MessageChannel]))) + .map(_.apply()).find(sender => sender != null).get } /** @@ -226,7 +222,7 @@ object MCChatUtils { def resetLastMessage(channel: Channel): Unit = { if (notEnabled) return if (channel.getId.asLong == module.chatChannel.get.asLong) { - if (lastmsgdata == null) lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono.block, null) + if (lastmsgdata == null) lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono.block(), null) else lastmsgdata.message = null return } // Don't set the whole object to null, the player and channel information should be preserved @@ -241,25 +237,23 @@ object MCChatUtils { } def addStaticExcludedPlugin(event: Class[_ <: Event], plugin: String): util.HashSet[String] = - staticExcludedPlugins.compute(event, (e: Class[_ <: Event], hs: util.HashSet[String]) => + staticExcludedPlugins.compute(event, (_, hs: util.HashSet[String]) => if (hs == null) Sets.newHashSet(plugin) else if (hs.add(plugin)) hs else hs) def callEventExcludingSome(event: Event): Unit = { if (notEnabled) return val second = staticExcludedPlugins.get(event.getClass) val first = module.excludedPlugins.get - val both = if (second == null) first + val both = if (second.isEmpty) first else util.Arrays.copyOf(first, first.length + second.size) var i = first.length - if (second != null) { - for (plugin <- second) { - both({ - i += 1; - i - 1 - }) = plugin + if (second.nonEmpty) { + for (plugin <- second.get.asScala) { + both(i) = plugin + i += 1 } } - callEventExcluding(event, false, both) + callEventExcluding(event, false, both: _*) } /** @@ -275,21 +269,18 @@ object MCChatUtils { 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) + fireEventExcluding(event, only, plugins: _*) } - else Bukkit.getPluginManager synchronized fireEventExcluding(event, only, plugins) + else Bukkit.getPluginManager synchronized fireEventExcluding(event, only, plugins: _*) } private def fireEventExcluding(event: Event, only: Boolean, plugins: String*): Unit = { val handlers = event.getHandlers // Code taken from SimplePluginManager in Spigot-API val listeners = handlers.getRegisteredListeners val server = Bukkit.getServer - for (registration <- listeners) { - if (!registration.getPlugin.isEnabled || util.Arrays.stream(plugins).anyMatch((p: String) => only ^ p.equalsIgnoreCase(registration.getPlugin.getName))) { - continue //todo: continue is not supported - // Modified to exclude plugins - } - try registration.callEvent(event) + for (registration <- listeners) { // Modified to exclude plugins + if (registration.getPlugin.isEnabled + && !plugins.exists(only ^ _.equalsIgnoreCase(registration.getPlugin.getName))) try registration.callEvent(event) catch { case ex: AuthorNagException => val plugin = registration.getPlugin @@ -308,12 +299,8 @@ object MCChatUtils { */ def callLoginEvents(dcp: DiscordConnectedPlayer): Unit = { val loginFail = (kickMsg: String) => { - def foo(kickMsg: String): Unit = { - dcp.sendMessage("Minecraft chat disabled, as the login failed: " + kickMsg) - MCChatPrivate.privateMCChat(dcp.getChannel, start = false, dcp.getUser, dcp.getChromaUser) - } - - foo(kickMsg) + dcp.sendMessage("Minecraft chat disabled, as the login failed: " + kickMsg) + MCChatPrivate.privateMCChat(dcp.getChannel, start = false, dcp.getUser, dcp.getChromaUser) } //Probably also happens if the user is banned or so val event = new AsyncPlayerPreLoginEvent(dcp.getName, InetAddress.getLoopbackAddress, dcp.getUniqueId) callEventExcludingSome(event) diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala index 5b8aa3f..f469019 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala @@ -1,22 +1,23 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.component.channel.Channel -import buttondevteam.discordplugin.{ChannelconBroadcast, DPUtils, DiscordConnectedPlayer, DiscordPlugin} import buttondevteam.discordplugin.playerfaker.ServerWatcher import buttondevteam.discordplugin.playerfaker.perm.LPInjector import buttondevteam.discordplugin.util.DPState -import buttondevteam.lib.{TBMCCoreAPI, TBMCSystemChatEvent} +import buttondevteam.discordplugin.{ChannelconBroadcast, DPUtils, DiscordConnectedPlayer, DiscordPlugin} import buttondevteam.lib.architecture.{Component, ConfigData, ReadOnlyConfigData} +import buttondevteam.lib.{TBMCCoreAPI, TBMCSystemChatEvent} import com.google.common.collect.Lists import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.rest.util.Color import org.bukkit.Bukkit import reactor.core.publisher.Mono +import reactor.core.scala.publisher.SMono import java.util -import java.util.{Objects, UUID} import java.util.stream.Collectors +import java.util.{Objects, UUID} /** * Provides Minecraft chat connection to Discord. Commands may be used either in a public chat (limited) or in a DM. @@ -45,7 +46,7 @@ class MinecraftChatModule extends Component[DiscordPlugin] { */ val chatChannel: ReadOnlyConfigData[Snowflake] = DPUtils.snowflakeData(getConfig, "chatChannel", 0L) - def chatChannelMono: Mono[MessageChannel] = DPUtils.getMessageChannel(chatChannel.getPath, chatChannel.get) + def chatChannelMono: SMono[MessageChannel] = DPUtils.getMessageChannel(chatChannel.getPath, chatChannel.get) /** * The channel where the plugin can log when it mutes a player on Discord because of a Minecraft mute From a0a7f756c4f68fd53e0969481374a8563d90c28e Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 9 Mar 2021 02:47:11 +0100 Subject: [PATCH 07/23] Implicit classes for conversion, more fixing Added 'extension methods' to convert to Scala-friendly formats easily --- .../discordplugin/ChromaBot.scala | 10 +- .../buttondevteam/discordplugin/DPUtils.scala | 18 +- .../announcer/AnnouncerModule.scala | 2 +- .../commands/ConnectCommand.scala | 4 +- .../discordplugin/commands/DebugCommand.scala | 2 +- .../exceptions/DebugMessageListener.scala | 3 +- .../discordplugin/fun/FunModule.scala | 2 +- .../discordplugin/mcchat/MCChatCustom.scala | 4 +- .../discordplugin/mcchat/MCChatListener.scala | 287 +++++++++--------- .../discordplugin/mcchat/MCChatPrivate.scala | 11 +- .../discordplugin/mcchat/MCChatUtils.scala | 45 ++- .../discordplugin/mcchat/MCListener.scala | 68 ++--- .../mcchat/MinecraftChatModule.scala | 34 +-- .../mccommands/DiscordMCCommand.scala | 22 +- .../playerfaker/DelegatingMockMaker.scala | 6 +- .../playerfaker/ServerWatcher.scala | 20 +- .../discordplugin/role/RoleCommand.scala | 8 +- 17 files changed, 286 insertions(+), 260 deletions(-) diff --git a/src/main/java/buttondevteam/discordplugin/ChromaBot.scala b/src/main/java/buttondevteam/discordplugin/ChromaBot.scala index 187c7f8..1353645 100644 --- a/src/main/java/buttondevteam/discordplugin/ChromaBot.scala +++ b/src/main/java/buttondevteam/discordplugin/ChromaBot.scala @@ -4,7 +4,7 @@ import buttondevteam.discordplugin.ChannelconBroadcast.ChannelconBroadcast import buttondevteam.discordplugin.mcchat.MCChatUtils import discord4j.core.`object`.entity.Message import discord4j.core.`object`.entity.channel.MessageChannel -import reactor.core.publisher.Mono +import reactor.core.scala.publisher.SMono import javax.annotation.Nullable @@ -20,8 +20,8 @@ object ChromaBot { * * @param message The message to send, duh (use {@link MessageChannel# createMessage ( String )}) */ - def sendMessage(message: java.util.function.Function[Mono[MessageChannel], Mono[Message]]): Unit = - MCChatUtils.forPublicPrivateChat(message.apply(_)).subscribe + def sendMessage(message: SMono[MessageChannel] => SMono[Message]): Unit = + MCChatUtils.forPublicPrivateChat(message).subscribe /** * Send a message to the chat channels, private chats and custom chats. @@ -29,8 +29,8 @@ object ChromaBot { * @param message The message to send, duh * @param toggle The toggle type for channelcon */ - def sendMessageCustomAsWell(message: Function[Mono[MessageChannel], Mono[Message]], @Nullable toggle: ChannelconBroadcast): Unit = - MCChatUtils.forCustomAndAllMCChat(message.apply, toggle, false).subscribe + def sendMessageCustomAsWell(message: SMono[MessageChannel] => SMono[Message], @Nullable toggle: ChannelconBroadcast): Unit = + MCChatUtils.forCustomAndAllMCChat(message.apply, toggle, hookmsg = false).subscribe def updatePlayerList(): Unit = MCChatUtils.updatePlayerList() diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.scala b/src/main/java/buttondevteam/discordplugin/DPUtils.scala index 4841150..c22cbe9 100644 --- a/src/main/java/buttondevteam/discordplugin/DPUtils.scala +++ b/src/main/java/buttondevteam/discordplugin/DPUtils.scala @@ -5,8 +5,9 @@ import buttondevteam.lib.architecture.{Component, ConfigData, IHaveConfig, ReadO import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.`object`.entity.{Guild, Message, Role} -import discord4j.core.spec.EmbedCreateSpec -import reactor.core.scala.publisher.SMono +import discord4j.core.spec.{EmbedCreateSpec, Spec} +import reactor.core.publisher.{Flux, Mono} +import reactor.core.scala.publisher.{SFlux, SMono} import java.util import java.util.Comparator @@ -206,4 +207,17 @@ object DPUtils { getMessageChannel(config.getPath, config.get) def ignoreError[T](mono: SMono[T]): SMono[T] = mono.onErrorResume((_: Throwable) => SMono.empty) + + implicit class MonoExtensions[T](mono: Mono[T]) { + def ^^(): SMono[T] = SMono(mono) + } + + implicit class FluxExtensions[T](flux: Flux[T]) { + def ^^(): SFlux[T] = SFlux(flux) + } + + implicit class SpecExtensions[T <: Spec[_]](spec: T) { + def ^^(): Unit = () + } + } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala index 31cb524..34c9bb2 100644 --- a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala +++ b/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala @@ -89,7 +89,7 @@ import scala.annotation.tailrec if (msgsb.nonEmpty) sendMsg(channel.get(), msgsb.toString()) if (modmsgsb.nonEmpty) sendMsg(modChannel.get(), modmsgsb.toString()) - if (lastAnnouncementTime.get ne lastanntime) lastAnnouncementTime.set(lastanntime) // If sending succeeded + if (lastAnnouncementTime.get != lastanntime) lastAnnouncementTime.set(lastanntime) // If sending succeeded } catch { case e: Exception => e.printStackTrace() diff --git a/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala index 09a51ad..17b46d7 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala @@ -17,7 +17,9 @@ import org.bukkit.entity.Player var WaitingToConnect: HashBiMap[String, String] = HashBiMap.create } -@CommandClass(helpText = Array(Array("Connect command", "This command lets you connect your account with a Minecraft account. This allows using the private Minecraft chat and other things."))) class ConnectCommand extends ICommand2DC { +@CommandClass(helpText = Array("Connect command", + "This command lets you connect your account with a Minecraft account." + + " This allows using the private Minecraft chat and other things.")) class ConnectCommand extends ICommand2DC { @Command2.Subcommand def `def`(sender: Command2DCSender, Minecraftname: String): Boolean = { val message = sender.getMessage val channel = message.getChannel.block diff --git a/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala b/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala index 79f3d9c..637c14b 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala @@ -16,7 +16,7 @@ class DebugCommand extends ICommand2DC { .map((u: User) => SMono(u.asMember(DiscordPlugin.mainServer.getId))).getOrElse(SMono.empty)) .flatMap((m: Member) => DiscordPlugin.plugin.modRole.get .map(mr => m.getRoleIds.stream.anyMatch((r: Snowflake) => r == mr.getId)) - .switchIfEmpty(SMono.fromCallable(() => DiscordPlugin.mainServer.getOwnerId.asLong eq m.getId.asLong))) + .switchIfEmpty(SMono.fromCallable(() => DiscordPlugin.mainServer.getOwnerId.asLong == m.getId.asLong))) .onErrorResume(_ => SMono.just(false)) //Role not found .subscribe(success => { if (success) { diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala b/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala index 265cdb6..95895d4 100644 --- a/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala +++ b/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala @@ -5,6 +5,7 @@ import buttondevteam.discordplugin.DiscordPlugin import buttondevteam.lib.TBMCDebugMessageEvent import discord4j.core.`object`.entity.channel.MessageChannel import org.bukkit.event.{EventHandler, Listener} +import reactor.core.scala.publisher.SMono object DebugMessageListener { private def SendMessage(message: String): Unit = { @@ -16,7 +17,7 @@ object DebugMessageListener { sb.append("```").append("\n") sb.append(if (message.length > 2000) message.substring(0, 2000) else message).append("\n") sb.append("```") - mc.flatMap((ch: MessageChannel) => ch.createMessage(sb.toString)).subscribe + mc.flatMap((ch: MessageChannel) => SMono(ch.createMessage(sb.toString))).subscribe } catch { case ex: Exception => ex.printStackTrace() diff --git a/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala b/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala index f919932..fa95622 100644 --- a/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala +++ b/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala @@ -107,7 +107,7 @@ class FunModule extends Component[DiscordPlugin] with Listener { * Answers for a recognized question. Selected randomly. */ final private val serverReadyAnswers: ConfigData[util.ArrayList[String]] = - getConfig.getData("serverReadyAnswers", () => Lists.newArrayList(FunModule.serverReadyStrings)) + getConfig.getData("serverReadyAnswers", () => Lists.newArrayList(FunModule.serverReadyStrings): _*) private def createUsableServerReadyStrings(): Unit = IntStream.range(0, serverReadyAnswers.get.size).forEach((i: Int) => FunModule.usableServerReadyStrings.add(i.toShort)) diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala index 163b6ec..a9b4dec 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala @@ -39,9 +39,10 @@ object MCChatCustom { @Nullable def getCustomChat(channel: Snowflake): CustomLMD = lastmsgCustom.find(_.channel.getId.asLong == channel.asLong).orNull - def removeCustomChat(channel: Snowflake): Unit = { + def removeCustomChat(channel: Snowflake): Boolean = { lastmsgCustom synchronized { MCChatUtils.lastmsgfromd.remove(channel.asLong) + val count = lastmsgCustom.size lastmsgCustom.filterInPlace(lmd => { if (lmd.channel.getId.asLong != channel.asLong) return true lmd.mcchannel match { @@ -50,6 +51,7 @@ object MCChatCustom { } false }) + lastmsgCustom.size < count } } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala index c5bd052..9339529 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala @@ -1,7 +1,6 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.ComponentManager -import buttondevteam.core.component.channel.Channel import buttondevteam.discordplugin._ import buttondevteam.discordplugin.listeners.CommandListener import buttondevteam.discordplugin.playerfaker.{VanillaCommandListener, VanillaCommandListener14, VanillaCommandListener15} @@ -24,10 +23,10 @@ import reactor.core.scala.publisher.{SFlux, SMono} import java.time.Instant import java.util -import java.util.Optional import java.util.concurrent.{LinkedBlockingQueue, TimeoutException} -import java.util.function.{Consumer, Function, Predicate} +import java.util.function.{Consumer, Predicate} import java.util.stream.Collectors +import scala.jdk.CollectionConverters.SetHasAsScala import scala.jdk.OptionConverters.RichOptional object MCChatListener { @@ -133,7 +132,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { val isdifferentchannel: Predicate[Snowflake] = (id: Snowflake) => !((e.getSender.isInstanceOf[DiscordSenderBase])) || (e.getSender.asInstanceOf[DiscordSenderBase]).getChannel.getId.asLong != id.asLong if (e.getChannel.isGlobal && (e.isFromCommand || isdifferentchannel.test(module.chatChannel.get))) { if (MCChatUtils.lastmsgdata == null) - MCChatUtils.lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono.block, null) + MCChatUtils.lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono.block(), null) doit(MCChatUtils.lastmsgdata) } @@ -146,7 +145,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { MCChatCustom.lastmsgCustom.filterInPlace(lmd => { if ((e.isFromCommand || isdifferentchannel.test(lmd.channel.getId)) //Test if msg is from Discord && e.getChannel.ID == lmd.mcchannel.ID //If it's from a command, the command msg has been deleted, so we need to send it - && e.getGroupID == lmd.groupID) { //Check if this is the group we want to test - #58 + && e.getGroupID() == lmd.groupID) { //Check if this is the group we want to test - #58 if (e.shouldSendTo(lmd.dcp)) { //Check original user's permissions doit(lmd) } @@ -254,18 +253,17 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { .filter(channel => { MCChatUtils.resetLastMessage(channel) recevents.add(ev) - if (rectask != null) { - return true - } - recrun = () => { - recthread = Thread.currentThread - processDiscordToMC() - if (DiscordPlugin.plugin.isEnabled && !(stop)) { - rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Continue message processing + if (rectask == null) { + recrun = () => { + recthread = Thread.currentThread + processDiscordToMC() + if (DiscordPlugin.plugin.isEnabled && !(stop)) { + rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Continue message processing + } } + rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Start message processing } - rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Start message processing - return true + true }).map(_ => false).defaultIfEmpty(true) } @@ -299,47 +297,44 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { val dsender: DiscordSenderBase = MCChatUtils.getSender(event.getMessage.getChannelId, sender) val user: DiscordPlayer = dsender.getChromaUser - for (u <- SFlux(event.getMessage.getUserMentions).toIterable()) { //TODO: Role mentions - dmessage = dmessage.replace(u.getMention, "@" + u.getUsername) // TODO: IG Formatting - val m: Optional[Member] = u.asMember(DiscordPlugin.mainServer.getId).onErrorResume((t: Throwable) => Mono.empty).blockOptional - if (m.isPresent) { - val mm: Member = m.get - val nick: String = mm.getDisplayName - dmessage = dmessage.replace(mm.getNicknameMention, "@" + nick) + def replaceUserMentions(): Unit = { + for (u <- SFlux(event.getMessage.getUserMentions).toIterable()) { //TODO: Role mentions + dmessage = dmessage.replace(u.getMention, "@" + u.getUsername) // TODO: IG Formatting + val m = u.asMember(DiscordPlugin.mainServer.getId).onErrorResume(_ => Mono.empty).blockOptional + if (m.isPresent) { + val mm: Member = m.get + val nick: String = mm.getDisplayName + dmessage = dmessage.replace(mm.getNicknameMention, "@" + nick) + } } } - for (ch <- SFlux(event.getGuild.flux).flatMap(_.getChannels).toIterable()) { - dmessage = dmessage.replace(ch.getMention, "#" + ch.getName) - } - dmessage = EmojiParser.parseToAliases(dmessage, EmojiParser.FitzpatrickAction.PARSE) //Converts emoji to text- TODO: Add option to disable (resource pack?) - dmessage = dmessage.replaceAll(":(\\S+)\\|type_(?:(\\d)|(1)_2):", ":$1::skin-tone-$2:") //Convert to Discord's format so it still shows up - dmessage = dmessage.replaceAll("", ":$1:") //We don't need info about the custom emojis, just display their text - val getChatMessage: Function[String, String] = (msg: String) => // - msg + (if (event.getMessage.getAttachments.size > 0) { - "\n" + event.getMessage.getAttachments.stream.map(_.getUrl).collect(Collectors.joining("\n")) - } - else { - "" - }) - val clmd: MCChatCustom.CustomLMD = MCChatCustom.getCustomChat(event.getMessage.getChannelId) - var react: Boolean = false - val sendChannel: MessageChannel = event.getMessage.getChannel.block - val isPrivate: Boolean = sendChannel.isInstanceOf[PrivateChannel] - if (dmessage.startsWith("/")) { // Ingame command - if (handleIngameCommand(event, dmessage, dsender, user, clmd, isPrivate)) { - return + replaceUserMentions() + + def replaceChannelMentions(): Unit = { + for (ch <- SFlux(event.getGuild.flux).flatMap(_.getChannels).toIterable()) { + dmessage = dmessage.replace(ch.getMention, "#" + ch.getName) } } - else { // Not a command - react = handleIngameMessage(event, dmessage, dsender, user, getChatMessage, clmd, isPrivate) + + replaceChannelMentions() + + def replaceEmojis(): Unit = { + dmessage = EmojiParser.parseToAliases(dmessage, EmojiParser.FitzpatrickAction.PARSE) //Converts emoji to text- TODO: Add option to disable (resource pack?) + dmessage = dmessage.replaceAll(":(\\S+)\\|type_(?:(\\d)|(1)_2):", ":$1::skin-tone-$2:") //Convert to Discord's format so it still shows up + dmessage = dmessage.replaceAll("", ":$1:") //We don't need info about the custom emojis, just display their text } - if (react) { + + replaceEmojis() + val clmd = MCChatCustom.getCustomChat(event.getMessage.getChannelId) + val sendChannel = event.getMessage.getChannel.block + val isPrivate = sendChannel.isInstanceOf[PrivateChannel] + + def addCheckmark() = { try { - val lmfd: Message = MCChatUtils.lastmsgfromd.get(event.getMessage.getChannelId.asLong) - if (lmfd != null) { + val lmfd = MCChatUtils.lastmsgfromd.get(event.getMessage.getChannelId.asLong) + if (lmfd != null) lmfd.removeSelfReaction(DiscordPlugin.DELIVERED_REACTION).subscribe // Remove it no matter what, we know it's there 99.99% of the time - } } catch { case e: Exception => TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e, module) @@ -347,123 +342,133 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { MCChatUtils.lastmsgfromd.put(event.getMessage.getChannelId.asLong, event.getMessage) event.getMessage.addReaction(DiscordPlugin.DELIVERED_REACTION).subscribe } + + if (dmessage.startsWith("/")) // Ingame command + handleIngameCommand(event, dmessage, dsender, user, clmd, isPrivate) + else if (handleIngameMessage(event, dmessage, dsender, user, clmd, isPrivate)) // Not a command + addCheckmark() } catch { case e: Exception => TBMCCoreAPI.SendException("An error occured while handling message \"" + dmessage + "\"!", e, module) } } - private def handleIngameMessage(event: MessageCreateEvent, dmessage: String, dsender: DiscordSenderBase, user: DiscordPlayer, getChatMessage: Function[String, String], clmd: MCChatCustom.CustomLMD, isPrivate: Boolean): Boolean = { - var react: Boolean = false - if (dmessage.isEmpty && event.getMessage.getAttachments.size == 0 && !(isPrivate) && (event.getMessage.getType eq Message.Type.CHANNEL_PINNED_MESSAGE)) { - val rtr: Channel.RecipientTestResult = if (clmd != null) { - clmd.mcchannel.getRTR(clmd.dcp) - } - else { - dsender.getChromaUser.channel.get.getRTR(dsender) - } - TBMCChatAPI.SendSystemMessage(if (clmd != null) clmd.mcchannel else dsender.getChromaUser.channel.get, rtr, - (dsender match { - case player: Player => - player.getDisplayName - case _ => - dsender.getName - }) + " pinned a message on Discord.", TBMCSystemChatEvent.BroadcastTarget.ALL) + /** + * Handles a message coming from Discord to Minecraft. + * + * @param event The Discord event + * @param dmessage The message itself + * @param dsender The sender who sent it + * @param user The Chroma user of the sender + * @param clmd Custom chat last message data (if in a custom chat) + * @param isPrivate Whether the chat is private + * @return Whether the bot should react with a checkmark + */ + private def handleIngameMessage(event: MessageCreateEvent, dmessage: String, dsender: DiscordSenderBase, user: DiscordPlayer, + clmd: MCChatCustom.CustomLMD, isPrivate: Boolean): Boolean = { + def getAttachmentText = { + val att = event.getMessage.getAttachments.asScala + if (att.nonEmpty) att map (_.getUrl) mkString "\n" + else "" + } + + if (event.getMessage.getType eq Message.Type.CHANNEL_PINNED_MESSAGE) { + val mcchannel = if (clmd != null) clmd.mcchannel else dsender.getChromaUser.channel.get + val rtr = mcchannel getRTR (if (clmd != null) clmd.dcp else dsender) + TBMCChatAPI.SendSystemMessage(mcchannel, rtr, (dsender match { + case player: Player => player.getDisplayName + case _ => dsender.getName + }) + " pinned a message on Discord.", TBMCSystemChatEvent.BroadcastTarget.ALL) + false } else { - val cmb: ChatMessage.ChatMessageBuilder = ChatMessage.builder(dsender, user, getChatMessage.apply(dmessage)).fromCommand(false) - if (clmd != null) { + val cmb = ChatMessage.builder(dsender, user, dmessage + getAttachmentText()).fromCommand(false) + if (clmd != null) TBMCChatAPI.SendChatMessage(cmb.permCheck(clmd.dcp).build, clmd.mcchannel) - } - else { + else TBMCChatAPI.SendChatMessage(cmb.build) - } - react = true + true } - react } - private def handleIngameCommand(event: MessageCreateEvent, dmessage: String, dsender: DiscordSenderBase, user: DiscordPlayer, clmd: MCChatCustom.CustomLMD, isPrivate: Boolean): Boolean = { - if (!(isPrivate)) { + /** + * Handle a Minecraft command coming from Discord. + * + * @param event The Discord event + * @param dmessage The Discord mewsage, starting with a slash + * @param dsender The sender who sent it + * @param user The Chroma user of the sender + * @param clmd The custom last message data (if in a custom chat) + * @param isPrivate Whether the chat is private + * @return + */ + private def handleIngameCommand(event: MessageCreateEvent, dmessage: String, dsender: DiscordSenderBase, user: DiscordPlayer, + clmd: MCChatCustom.CustomLMD, isPrivate: Boolean): Unit = { + def notWhitelisted(cmd: String) = module.whitelistedCommands.get.stream + .noneMatch(s => cmd == s || cmd.startsWith(s + " ")) + + def whitelistedCommands = module.whitelistedCommands.get.stream + .map("/" + _).collect(Collectors.joining(", ")) + + if (!isPrivate) event.getMessage.delete.subscribe - } - val cmd: String = dmessage.substring(1) - val cmdlowercased: String = cmd.toLowerCase - if (dsender.isInstanceOf[DiscordSender] && module.whitelistedCommands.get.stream.noneMatch((s: String) => cmdlowercased == s || cmdlowercased.startsWith(s + " "))) { // Command not whitelisted - dsender.sendMessage("Sorry, you can only access these commands from here:\n" + module.whitelistedCommands.get.stream.map((uc: String) => "/" + uc).collect(Collectors.joining(", ")) + (if (user.getConnectedID(classOf[TBMCPlayer]) == null) { - "\nTo access your commands, first please connect your accounts, using /connect in " + DPUtils.botmention + "\nThen y" - } - else { - "\nY" - }) + "ou can access all of your regular commands (even offline) in private chat: DM me `mcchat`!") - return true + val cmd = dmessage.substring(1) + val cmdlowercased = cmd.toLowerCase + if (dsender.isInstanceOf[DiscordSender] && notWhitelisted(cmdlowercased)) { // Command not whitelisted + dsender.sendMessage("Sorry, you can only access these commands from here:\n" + whitelistedCommands() + + (if (user.getConnectedID(classOf[TBMCPlayer]) == null) + "\nTo access your commands, first please connect your accounts, using /connect in " + DPUtils.botmention + + "\nThen y" else "\nY") + "ou can access all of your regular commands (even offline) in private chat: DM me `mcchat`!") + return } module.log(dsender.getName + " ran from DC: /" + cmd) if (dsender.isInstanceOf[DiscordSender] && runCustomCommand(dsender, cmdlowercased)) { - return true + return } - val channel: Channel = if (clmd == null) { - user.channel.get - } - else { - clmd.mcchannel - } - val ev: TBMCCommandPreprocessEvent = new TBMCCommandPreprocessEvent(dsender, channel, dmessage, if (clmd == null) { - dsender - } - else { - clmd.dcp + val channel = if (clmd == null) user.channel.get else clmd.mcchannel + val ev = new TBMCCommandPreprocessEvent(dsender, channel, dmessage, if (clmd == null) dsender else clmd.dcp) + Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => { //Commands need to be run sync + Bukkit.getPluginManager.callEvent(ev) + if (!ev.isCancelled) + runMCCommand(dsender, cmd) }) - Bukkit.getScheduler.runTask(DiscordPlugin.plugin, //Commands need to be run sync - () => { - def foo(): Unit = { - Bukkit.getPluginManager.callEvent(ev) - if (ev.isCancelled) { - return - } - try { - val mcpackage: String = Bukkit.getServer.getClass.getPackage.getName - if (!(module.enableVanillaCommands.get)) { - Bukkit.dispatchCommand(dsender, cmd) - } - else { - if (mcpackage.contains("1_12")) { - VanillaCommandListener.runBukkitOrVanillaCommand(dsender, cmd) - } - else { - if (mcpackage.contains("1_14")) { - VanillaCommandListener14.runBukkitOrVanillaCommand(dsender, cmd) - } - else { - if (mcpackage.contains("1_15") || mcpackage.contains("1_16")) { - VanillaCommandListener15.runBukkitOrVanillaCommand(dsender, cmd) - } - else { - Bukkit.dispatchCommand(dsender, cmd) - } - } - } - } - } catch { - case e: NoClassDefFoundError => - TBMCCoreAPI.SendException("A class is not found when trying to run command " + cmd + "!", e, module) - case e: Exception => - TBMCCoreAPI.SendException("An error occurred when trying to run command " + cmd + "! Vanilla commands are only supported in some MC versions.", e, module) - } - } - - foo() - }) - return true } + private def runMCCommand(dsender: DiscordSenderBase, cmd: String): Unit = { + try { + val mcpackage = Bukkit.getServer.getClass.getPackage.getName + if (!module.enableVanillaCommands.get) + Bukkit.dispatchCommand(dsender, cmd) + else if (mcpackage.contains("1_12")) + VanillaCommandListener.runBukkitOrVanillaCommand(dsender, cmd) + else if (mcpackage.contains("1_14")) + VanillaCommandListener14.runBukkitOrVanillaCommand(dsender, cmd) + else if (mcpackage.contains("1_15") || mcpackage.contains("1_16")) + VanillaCommandListener15.runBukkitOrVanillaCommand(dsender, cmd) + else + Bukkit.dispatchCommand(dsender, cmd) + } catch { + case e: NoClassDefFoundError => + TBMCCoreAPI.SendException("A class is not found when trying to run command " + cmd + "!", e, module) + case e: Exception => + TBMCCoreAPI.SendException("An error occurred when trying to run command " + cmd + "! Vanilla commands are only supported in some MC versions.", e, module) + } + } + + /** + * Handles custom public commands. Used to hide sensitive information in public chats. + * + * @param dsender The Discord sender + * @param cmdlowercased The command, lowercased + * @return Whether the command was a custom command + */ private def runCustomCommand(dsender: DiscordSenderBase, cmdlowercased: String): Boolean = { if (cmdlowercased.startsWith("list")) { - val players: util.Collection[_ <: Player] = Bukkit.getOnlinePlayers + val players = Bukkit.getOnlinePlayers dsender.sendMessage("There are " + players.stream.filter(MCChatUtils.checkEssentials).count + " out of " + Bukkit.getMaxPlayers + " players online.") - dsender.sendMessage("Players: " + players.stream.filter(MCChatUtils.checkEssentials).map(Player.getDisplayName).collect(Collectors.joining(", "))) - return true + dsender.sendMessage("Players: " + players.stream.filter(MCChatUtils.checkEssentials).map(_.getDisplayName).collect(Collectors.joining(", "))) + true } - return false + else false } } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala index 1acd129..773113f 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala @@ -4,12 +4,12 @@ import buttondevteam.core.ComponentManager import buttondevteam.discordplugin.mcchat.MCChatUtils.LastMsgData import buttondevteam.discordplugin.{DiscordConnectedPlayer, DiscordPlayer, DiscordPlugin, DiscordSenderBase} import buttondevteam.lib.player.TBMCPlayer -import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} import org.bukkit.Bukkit import scala.collection.mutable.ListBuffer +import scala.jdk.javaapi.CollectionConverters.asScala object MCChatPrivate { /** @@ -39,7 +39,7 @@ object MCChatPrivate { def foo(): Unit = { if ((p == null || p.isInstanceOf[DiscordSenderBase]) // Player is offline - If the player is online, that takes precedence && sender.isLoggedIn) { //Don't call the quit event if login failed - MCChatUtils.callLogoutEvent(sender, false) //The next line has to run *after* this one, so can't use the needsSync parameter + MCChatUtils.callLogoutEvent(sender, needsSync = false) //The next line has to run *after* this one, so can't use the needsSync parameter } MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId) @@ -61,14 +61,13 @@ object MCChatPrivate { def isMinecraftChatEnabled(dp: DiscordPlayer): Boolean = isMinecraftChatEnabled(dp.getDiscordID) def isMinecraftChatEnabled(did: String): Boolean = { // Don't load the player data just for this - lastmsgPerUser.stream.anyMatch((lmd: MCChatUtils.LastMsgData) => - lmd.channel.asInstanceOf[PrivateChannel].getRecipientIds.stream.anyMatch((u: Snowflake) => u.asString == did)) + lastmsgPerUser.exists(_.channel.asInstanceOf[PrivateChannel].getRecipientIds.stream.anyMatch(u => u.asString == did)) } def logoutAll(): Unit = { MCChatUtils.ConnectedSenders synchronized { - for (entry <- asScala(MCChatUtils.ConnectedSenders.entrySet)) { - for (valueEntry <- entry.getValue.entrySet) { + for ((_, userMap) <- MCChatUtils.ConnectedSenders) { + for (valueEntry <- asScala(userMap.entrySet)) { if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey, valueEntry.getValue.getUser) == null) { //If the player is online then the fake player was already logged out MCChatUtils.callLogoutEvent(valueEntry.getValue, !Bukkit.isPrimaryThread) } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala index 4f10b2d..019493f 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala @@ -18,7 +18,6 @@ import org.bukkit.entity.Player import org.bukkit.event.Event import org.bukkit.event.player.{AsyncPlayerPreLoginEvent, PlayerJoinEvent, PlayerLoginEvent, PlayerQuitEvent} import org.bukkit.plugin.AuthorNagException -import org.reactivestreams.Publisher import reactor.core.scala.publisher.SMono import java.net.InetAddress @@ -46,7 +45,7 @@ object MCChatUtils { @Nullable private[mcchat] var lastmsgdata: MCChatUtils.LastMsgData = null private[mcchat] val lastmsgfromd = new LongObjectHashMap[Message] // Last message sent by a Discord user, used for clearing checkmarks private var module: MinecraftChatModule = null - private val staticExcludedPlugins = Map[Class[_ <: Event], util.HashSet[String]]() + private val staticExcludedPlugins: concurrent.Map[Class[_ <: Event], util.HashSet[String]] = concurrent.TrieMap() def updatePlayerList(): Unit = { val mod = getModule @@ -102,22 +101,22 @@ object MCChatUtils { addSender(senders, user.getId.asString, sender) def addSender[T <: DiscordSenderBase](senders: concurrent.Map[String, ConcurrentHashMap[Snowflake, T]], did: String, sender: T): T = { - val origMap = senders.get(did) - val map = if (origMap.isEmpty) new ConcurrentHashMap[Snowflake, T] else origMap.get + val mapOpt = senders.get(did) + val map = if (mapOpt.isEmpty) new ConcurrentHashMap[Snowflake, T] else mapOpt.get map.put(sender.getChannel.getId, sender) senders.put(did, map) sender } - def getSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = { - val map = senders.get(user.getId.asString) - if (map != null) map.get(channel) + def getSender[T <: DiscordSenderBase](senders: concurrent.Map[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = { + val mapOpt = senders.get(user.getId.asString) + if (mapOpt.nonEmpty) mapOpt.get.get(channel) else null.asInstanceOf } - def removeSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = { - val map = senders.get(user.getId.asString) - if (map != null) map.remove(channel) + def removeSender[T <: DiscordSenderBase](senders: concurrent.Map[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = { + val mapOpt = senders.get(user.getId.asString) + if (mapOpt.nonEmpty) mapOpt.get.remove(channel) else null.asInstanceOf } @@ -138,11 +137,12 @@ object MCChatUtils { */ def forCustomAndAllMCChat(action: SMono[MessageChannel] => SMono[_], @Nullable toggle: ChannelconBroadcast, hookmsg: Boolean): SMono[_] = { if (notEnabled) return SMono.empty - val list = new ListBuffer[Publisher[_]] - if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) list.append(forPublicPrivateChat(action)) - val customLMDFunction = (cc: MCChatCustom.CustomLMD) => action(SMono.just(cc.channel)) - if (toggle == null) MCChatCustom.lastmsgCustom.map(customLMDFunction).foreach(list.append(_)) - else MCChatCustom.lastmsgCustom.filter((cc) => (cc.toggles & (1 << toggle.id)) ne 0).map(customLMDFunction).foreach(list.append(_)) + val list = + List(if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) + forPublicPrivateChat(action) else SMono.empty) ++ + (if (toggle == null) MCChatCustom.lastmsgCustom + else MCChatCustom.lastmsgCustom.filter(cc => (cc.toggles & (1 << toggle.id)) != 0)) + .map(_.channel).map(SMono.just).map(action) SMono.whenDelayError(list) } @@ -156,7 +156,7 @@ object MCChatUtils { def forAllowedCustomMCChat(action: SMono[MessageChannel] => SMono[_], @Nullable sender: CommandSender, @Nullable toggle: ChannelconBroadcast): SMono[_] = { if (notEnabled) return SMono.empty val st = MCChatCustom.lastmsgCustom.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 & (1 << toggle.id)) eq 0)) false //If null then allow + if (toggle != null && ((clmd.toggles & (1 << toggle.id)) == 0)) false //If null then allow else if (sender == null) true else clmd.groupID.equals(clmd.mcchannel.getGroupID(sender)) }).map(cc => action.apply(SMono.just(cc.channel))) //TODO: Send error messages on channel connect @@ -204,13 +204,12 @@ object MCChatUtils { * 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[mcchat] def getSender(channel: Snowflake, author: User): DiscordSenderBase = { //noinspection OptionalGetWithoutIsPresent - List[() => DiscordSenderBase]( // https://stackoverflow.com/a/28833677/2703239 - () => getSender[DiscordSenderBase](OnlineSenders, channel, author), // Find first non-null - () => getSender[DiscordSenderBase](ConnectedSenders, channel, author), // This doesn't support the public chat, but it'll always return null for it - () => getSender[DiscordSenderBase](UnconnectedSenders, channel, author), // - () => addSender[DiscordSenderBase](UnconnectedSenders, author, - new DiscordSender(author, SMono(DiscordPlugin.dc.getChannelById(channel)).block().asInstanceOf[MessageChannel]))) - .map(_.apply()).find(sender => sender != null).get + Option(getSender(OnlineSenders, channel, author)) // Find first non-null + .orElse(Option(getSender(ConnectedSenders, channel, author))) // This doesn't support the public chat, but it'll always return null for it + .orElse(Option(getSender(UnconnectedSenders, channel, author))) // + .orElse(Option(addSender(UnconnectedSenders, author, + new DiscordSender(author, SMono(DiscordPlugin.dc.getChannelById(channel)).block().asInstanceOf[MessageChannel])))) + .get } /** diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala index 57b1e14..d3b2ea3 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala @@ -1,22 +1,22 @@ package buttondevteam.discordplugin.mcchat +import buttondevteam.discordplugin.DPUtils.FluxExtensions import buttondevteam.discordplugin._ import buttondevteam.lib.TBMCSystemChatEvent import buttondevteam.lib.player.{TBMCPlayer, TBMCPlayerBase, TBMCYEEHAWEvent} import discord4j.common.util.Snowflake +import discord4j.core.`object`.entity.Role import discord4j.core.`object`.entity.channel.MessageChannel -import discord4j.core.`object`.entity.{Member, Role, User} import net.ess3.api.events.{AfkStatusChangeEvent, MuteStatusChangeEvent, NickChangeEvent, VanishStatusChangeEvent} import org.bukkit.Bukkit import org.bukkit.entity.Player -import org.bukkit.event.{EventHandler, EventPriority, Listener} import org.bukkit.event.entity.PlayerDeathEvent import org.bukkit.event.player.PlayerLoginEvent.Result import org.bukkit.event.player._ import org.bukkit.event.server.{BroadcastMessageEvent, TabCompleteEvent} -import reactor.core.publisher.{Flux, Mono} - -import java.util.Optional +import org.bukkit.event.{EventHandler, EventPriority, Listener} +import reactor.core.publisher.Flux +import reactor.core.scala.publisher.SMono class MCListener(val module: MinecraftChatModule) extends Listener { final private val muteRole = DPUtils.roleData(module.getConfig, "muteRole", "Muted") @@ -25,7 +25,7 @@ class MCListener(val module: MinecraftChatModule) extends Listener { if (e.getResult ne Result.ALLOWED) return if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return val dcp = MCChatUtils.LoggedInPlayers.get(e.getPlayer.getUniqueId) - if (dcp != null) MCChatUtils.callLogoutEvent(dcp, needsSync = false) + if (dcp.nonEmpty) MCChatUtils.callLogoutEvent(dcp.get, needsSync = false) } @EventHandler(priority = EventPriority.MONITOR) def onPlayerJoin(e: PlayerJoinEvent): Unit = { @@ -34,15 +34,13 @@ class MCListener(val module: MinecraftChatModule) extends Listener { def foo(): Unit = { val p = e.getPlayer val dp = TBMCPlayerBase.getPlayer(p.getUniqueId, classOf[TBMCPlayer]).getAs(classOf[DiscordPlayer]) - if (dp != null) DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID)).flatMap((user) => user.getPrivateChannel.flatMap((chan) => module.chatChannelMono.flatMap((cc: MessageChannel) => { - def foo(cc: MessageChannel) = { - MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, chan, p, module)) - MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, cc, p, module)) //Stored per-channel - Mono.empty - } - - foo(cc) - }))).subscribe + if (dp != null) + DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID)).flatMap(user => + user.getPrivateChannel.flatMap(chan => module.chatChannelMono.flatMap(cc => { + MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, chan, p, module)) + MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, cc, p, module)) //Stored per-channel + SMono.empty + }))).subscribe val message = e.getJoinMessage sendJoinLeaveMessage(message, e.getPlayer) ChromaBot.updatePlayerList() @@ -58,8 +56,8 @@ class MCListener(val module: MinecraftChatModule) extends Listener { @EventHandler(priority = EventPriority.MONITOR) def onPlayerLeave(e: PlayerQuitEvent): Unit = { if (e.getPlayer.isInstanceOf[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.runTaskAsynchronously(DiscordPlugin.plugin, () => Optional.ofNullable(MCChatUtils.LoggedInPlayers.get(e.getPlayer.getUniqueId)).ifPresent(MCChatUtils.callLoginEvents)) + MCChatUtils.OnlineSenders.filterInPlace((_, userMap) => userMap.entrySet.stream.noneMatch(_.getValue.getUniqueId.equals(e.getPlayer.getUniqueId))) + Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, () => MCChatUtils.LoggedInPlayers.get(e.getPlayer.getUniqueId).foreach(MCChatUtils.callLoginEvents)) Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => ChromaBot.updatePlayerList(), 5) val message = e.getQuitMessage sendJoinLeaveMessage(message, e.getPlayer) @@ -89,20 +87,22 @@ class MCListener(val module: MinecraftChatModule) extends Listener { if (!source.isPlayer) return val p = TBMCPlayerBase.getPlayer(source.getPlayer.getUniqueId, classOf[TBMCPlayer]).getAs(classOf[DiscordPlayer]) if (p == null) return - DPUtils.ignoreError(DiscordPlugin.dc.getUserById(Snowflake.of(p.getDiscordID)).flatMap((user: User) => user.asMember(DiscordPlugin.mainServer.getId)).flatMap((user: Member) => role.flatMap((r: Role) => { - def foo(r: Role): Mono[_] = { - if (e.getValue) user.addRole(r.getId) - else user.removeRole(r.getId) - val modlog = module.modlogChannel.get - val msg = (if (e.getValue) "M" - else "Unm") + "uted user: " + user.getUsername + "#" + user.getDiscriminator - module.log(msg) - if (modlog != null) return modlog.flatMap((ch: MessageChannel) => ch.createMessage(msg)) - Mono.empty - } + DPUtils.ignoreError(SMono(DiscordPlugin.dc.getUserById(Snowflake.of(p.getDiscordID))) + .flatMap(user => SMono(user.asMember(DiscordPlugin.mainServer.getId))) + .flatMap(user => role.flatMap((r: Role) => { + def foo(r: Role): SMono[_] = { + if (e.getValue) user.addRole(r.getId) + else user.removeRole(r.getId) + val modlog = module.modlogChannel.get + val msg = (if (e.getValue) "M" + else "Unm") + "uted user: " + user.getUsername + "#" + user.getDiscriminator + module.log(msg) + if (modlog != null) return modlog.flatMap((ch: MessageChannel) => SMono(ch.createMessage(msg))) + SMono.empty + } - foo(r) - }))).subscribe + foo(r) + }))).subscribe } @EventHandler def onChatSystemMessage(event: TBMCSystemChatEvent): Unit = @@ -117,10 +117,10 @@ class MCListener(val module: MinecraftChatModule) extends Listener { case _ => event.getSender.getName } //Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO - DiscordPlugin.mainServer.getEmojis.filter(e => "YEEHAW" == e.getName).take(1).singleOrEmpty - .map(Optional.of(_)).defaultIfEmpty(Optional.empty) - .flatMap((yeehaw) => MCChatUtils.forPublicPrivateChat(MCChatUtils.send(name + - yeehaw.map((guildEmoji) => " <:YEEHAW:" + guildEmoji.getId.asString + ">s").orElse(" YEEHAWs")))).subscribe + DiscordPlugin.mainServer.getEmojis.^^().filter(e => "YEEHAW" == e.getName).take(1).singleOrEmpty + .map(Option.apply).defaultIfEmpty(Option.empty) + .flatMap(yeehaw => MCChatUtils.forPublicPrivateChat(MCChatUtils.send(name + + yeehaw.map(guildEmoji => " <:YEEHAW:" + guildEmoji.getId.asString + ">s").getOrElse(" YEEHAWs")))).subscribe } @EventHandler def onNickChange(event: NickChangeEvent): Unit = MCChatUtils.updatePlayerList() diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala index f469019..ea51472 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala @@ -1,6 +1,7 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.component.channel.Channel +import buttondevteam.discordplugin.DPUtils.{MonoExtensions, SpecExtensions} import buttondevteam.discordplugin.playerfaker.ServerWatcher import buttondevteam.discordplugin.playerfaker.perm.LPInjector import buttondevteam.discordplugin.util.DPState @@ -12,12 +13,12 @@ import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.rest.util.Color import org.bukkit.Bukkit -import reactor.core.publisher.Mono import reactor.core.scala.publisher.SMono import java.util import java.util.stream.Collectors import java.util.{Objects, UUID} +import scala.jdk.CollectionConverters.IterableHasAsScala /** * Provides Minecraft chat connection to Discord. Commands may be used either in a public chat (limited) or in a DM. @@ -51,7 +52,7 @@ class MinecraftChatModule extends Component[DiscordPlugin] { /** * The channel where the plugin can log when it mutes a player on Discord because of a Minecraft mute */ - val modlogChannel: ReadOnlyConfigData[Mono[MessageChannel]] = DPUtils.channelData(getConfig, "modlogChannel") + val modlogChannel: ReadOnlyConfigData[SMono[MessageChannel]] = DPUtils.channelData(getConfig, "modlogChannel") /** * The plugins to exclude from fake player events used for the 'mcchat' command - some plugins may crash, add them here */ @@ -113,7 +114,7 @@ class MinecraftChatModule extends Component[DiscordPlugin] { } if (chcons != null) { val chconkeys = chcons.getKeys(false) - for (chconkey <- chconkeys) { + for (chconkey <- chconkeys.asScala) { val chcon = chcons.getConfigurationSection(chconkey) val mcch = Channel.getChannels.filter((ch: Channel) => ch.ID == chcon.getString("mcchid")).findAny val ch = DiscordPlugin.dc.getChannelById(Snowflake.of(chcon.getLong("chid"))).block @@ -122,15 +123,14 @@ class MinecraftChatModule extends Component[DiscordPlugin] { val groupid = chcon.getString("groupid") val toggles = chcon.getInt("toggles") val brtoggles = chcon.getStringList("brtoggles") - if (!mcch.isPresent || ch == null || user == null || groupid == null) continue //todo: continue is not supported - Bukkit.getScheduler.runTask(getPlugin, () => { - def foo() = { //<-- Needed because of occasional ConcurrentModificationExceptions when creating the player (PermissibleBase) - val dcp = DiscordConnectedPlayer.create(user, ch.asInstanceOf[MessageChannel], UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname"), this) - MCChatCustom.addCustomChat(ch.asInstanceOf[MessageChannel], groupid, mcch.get, user, dcp, toggles, brtoggles.stream.map(TBMCSystemChatEvent.BroadcastTarget.get).filter(Objects.nonNull).collect(Collectors.toSet)) - } - - foo() - }) + if (mcch.isPresent && ch != null && user != null && groupid != null) { + Bukkit.getScheduler.runTask(getPlugin, () => { //<-- Needed because of occasional ConcurrentModificationExceptions when creating the player (PermissibleBase) + val dcp = DiscordConnectedPlayer.create(user, ch.asInstanceOf[MessageChannel], + UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname"), this) + MCChatCustom.addCustomChat(ch.asInstanceOf[MessageChannel], groupid, mcch.get, user, dcp, toggles, + brtoggles.asScala.map(TBMCSystemChatEvent.BroadcastTarget.get).filter(Objects.nonNull).toSet) + }) + } } } try if (lpInjector == null) lpInjector = new LPInjector(DiscordPlugin.plugin) @@ -205,7 +205,7 @@ class MinecraftChatModule extends Component[DiscordPlugin] { chconc.set("mcname", chcon.dcp.getName) chconc.set("groupid", chcon.groupID) chconc.set("toggles", chcon.toggles) - chconc.set("brtoggles", chcon.brtoggles.stream.map(_.getName).collect(Collectors.toList)) + chconc.set("brtoggles", chcon.brtoggles.map(_.getName).toList) } if (listener != null) { //Can be null if disabled because of a config error listener.stop(true) @@ -219,10 +219,10 @@ class MinecraftChatModule extends Component[DiscordPlugin] { * It will block to make sure all messages are sent */ private def sendStateMessage(color: Color, message: String) = - MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(_.setColor(color).setTitle(message))), - ChannelconBroadcast.RESTART, hookmsg = false).block + MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(spec => spec.setColor(color).setTitle(message)).^^()), + ChannelconBroadcast.RESTART, hookmsg = false).block() private def sendStateMessage(color: Color, message: String, extra: String) = - MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(_.setColor(color).setTitle(message).setDescription(extra)) - .onErrorResume((_: Throwable) => Mono.empty)), ChannelconBroadcast.RESTART, hookmsg = false).block + MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(_.setColor(color).setTitle(message).setDescription(extra).^^()).^^() + .onErrorResume(_ => SMono.empty)), ChannelconBroadcast.RESTART, hookmsg = false).block() } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala index 76cb49d..ea7749f 100644 --- a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala @@ -1,9 +1,9 @@ package buttondevteam.discordplugin.mccommands -import buttondevteam.discordplugin.{DPUtils, DiscordPlayer, DiscordPlugin, DiscordSenderBase} import buttondevteam.discordplugin.commands.{ConnectCommand, VersionCommand} import buttondevteam.discordplugin.mcchat.{MCChatUtils, MinecraftChatModule} import buttondevteam.discordplugin.util.DPState +import buttondevteam.discordplugin.{DPUtils, DiscordPlayer, DiscordPlugin, DiscordSenderBase} import buttondevteam.lib.chat.{Command2, CommandClass, ICommand2MC} import buttondevteam.lib.player.{ChromaGamerBase, TBMCPlayer, TBMCPlayerBase} import discord4j.core.`object`.ExtendedInvite @@ -14,10 +14,10 @@ import reactor.core.publisher.Mono import java.lang.reflect.Method -@CommandClass(path = "discord", helpText = Array(Array( +@CommandClass(path = "discord", helpText = Array( "Discord", "This command allows performing Discord-related actions." -))) class DiscordMCCommand extends ICommand2MC { +)) class DiscordMCCommand extends ICommand2MC { @Command2.Subcommand def accept(player: Player): Boolean = { if (checkSafeMode(player)) return true val did = ConnectCommand.WaitingToConnect.get(player.getName) @@ -45,17 +45,17 @@ import java.lang.reflect.Method true } - @Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = Array(Array( + @Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = Array( "Reload Discord plugin", "Reloads the config. To apply some changes, you may need to also run /discord restart." - ))) def reload(sender: CommandSender): Unit = + )) def reload(sender: CommandSender): Unit = if (DiscordPlugin.plugin.tryReloadConfig) sender.sendMessage("§bConfig reloaded.") else sender.sendMessage("§cFailed to reload config.") - @Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = Array(Array( + @Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = Array( "Restart the plugin", // "This command disables and then enables the plugin." // - ))) def restart(sender: CommandSender): Unit = { + )) def restart(sender: CommandSender): Unit = { val task: Runnable = () => { def foo(): Unit = { if (!DiscordPlugin.plugin.tryReloadConfig) { @@ -84,16 +84,16 @@ import java.lang.reflect.Method } } - @Command2.Subcommand(helpText = Array(Array( + @Command2.Subcommand(helpText = Array( "Version command", - "Prints the plugin version"))) def version(sender: CommandSender): Unit = { + "Prints the plugin version")) def version(sender: CommandSender): Unit = { sender.sendMessage(VersionCommand.getVersion) } - @Command2.Subcommand(helpText = Array(Array( + @Command2.Subcommand(helpText = Array( "Invite", "Shows an invite link to the server" - ))) def invite(sender: CommandSender): Unit = { + )) def invite(sender: CommandSender): Unit = { if (checkSafeMode(sender)) { return } diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala b/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala index 0e14a89..b9f7ca7 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala @@ -36,8 +36,10 @@ class DelegatingMockMaker() extends MockMaker { override def createStaticMock[T](`type`: Class[T], settings: MockCreationSettings[T], handler: MockHandler[_]): MockMaker.StaticMockControl[T] = this.mockMaker.createStaticMock(`type`, settings, handler) - override def createConstructionMock[T](`type`: Class[T], settingsFactory: Function[MockedConstruction.Context, MockCreationSettings[T]], handlerFactory: Function[MockedConstruction.Context, MockHandler[T]], mockInitializer: MockedConstruction.MockInitializer[T]): MockMaker.ConstructionMockControl[T] = - this.mockMaker.createConstructionMock[T](`type`, settingsFactory: Function[MockedConstruction.Context, MockCreationSettings[T]], handlerFactory, mockInitializer) + override def createConstructionMock[T](`type`: Class[T], settingsFactory: java.util.function.Function[MockedConstruction.Context, + MockCreationSettings[T]], handlerFactory: java.util.function.Function[MockedConstruction.Context, + MockHandler[T]], mockInitializer: MockedConstruction.MockInitializer[T]): MockMaker.ConstructionMockControl[T] = + this.mockMaker.createConstructionMock[T](`type`, settingsFactory, handlerFactory, mockInitializer) def setMockMaker(mockMaker: MockMaker): Unit = { this.mockMaker = mockMaker diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala b/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala index ceb01b8..c080d50 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala @@ -4,8 +4,8 @@ import buttondevteam.discordplugin.DiscordConnectedPlayer import buttondevteam.discordplugin.mcchat.MCChatUtils import com.destroystokyo.paper.profile.CraftPlayerProfile import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding -import org.bukkit.{Bukkit, Server} import org.bukkit.entity.Player +import org.bukkit.{Bukkit, Server} import org.mockito.Mockito import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker import org.mockito.invocation.InvocationOnMock @@ -46,14 +46,15 @@ class ServerWatcher { def foo(invocation: InvocationOnMock): AnyRef = { val method = invocation.getMethod val pc = method.getParameterCount - var player: DiscordConnectedPlayer = null + var player = Option.empty[DiscordConnectedPlayer] method.getName match { case "getPlayer" => - if (pc == 1 && (method.getParameterTypes()(0) eq classOf[UUID])) player = MCChatUtils.LoggedInPlayers.get(invocation.getArgument[UUID](0)) + if (pc == 1 && (method.getParameterTypes()(0) == classOf[UUID])) + player = MCChatUtils.LoggedInPlayers.get(invocation.getArgument[UUID](0)) case "getPlayerExact" => if (pc == 1) { val argument = invocation.getArgument(0) - player = MCChatUtils.LoggedInPlayers.values.stream.filter((dcp) => dcp.getName.equalsIgnoreCase(argument)).findAny.orElse(null) + player = MCChatUtils.LoggedInPlayers.values.find(_.getName.equalsIgnoreCase(argument)) } /*case "getOnlinePlayers": @@ -65,13 +66,14 @@ class ServerWatcher { if (pc == 2) { val uuid = invocation.getArgument(0) val name = invocation.getArgument(1) - player = if (uuid != null) MCChatUtils.LoggedInPlayers.get(uuid) - else null - if (player == null && name != null) player = MCChatUtils.LoggedInPlayers.values.stream.filter((dcp) => dcp.getName.equalsIgnoreCase(name)).findAny.orElse(null) - if (player != null) return new CraftPlayerProfile(player.getUniqueId, player.getName) + player = if (uuid != null) MCChatUtils.LoggedInPlayers.get(uuid) else Option.empty + if (player.isEmpty && name != null) + player = MCChatUtils.LoggedInPlayers.values.find(_.getName.equalsIgnoreCase(name)) + if (player.nonEmpty) + return new CraftPlayerProfile(player.get.getUniqueId, player.get.getName) } } - if (player != null) return player + if (player.nonEmpty) return player.get method.invoke(origServer, invocation.getArguments) } diff --git a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.scala b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.scala index 08db583..e0a6bce 100644 --- a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.scala +++ b/src/main/java/buttondevteam/discordplugin/role/RoleCommand.scala @@ -8,10 +8,10 @@ import discord4j.core.`object`.entity.Role import reactor.core.publisher.Mono @CommandClass class RoleCommand private[role](var grm: GameRoleModule) extends ICommand2DC { - @Command2.Subcommand(helpText = Array(Array( + @Command2.Subcommand(helpText = Array( "Add role", "This command adds a role to your account." - ))) def add(sender: Command2DCSender, @Command2.TextArg rolename: String): Boolean = { + )) def add(sender: Command2DCSender, @Command2.TextArg rolename: String): Boolean = { val role = checkAndGetRole(sender, rolename) if (role == null) return true try sender.getMessage.getAuthorAsMember.flatMap(m => m.addRole(role.getId).switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("added role.")))).subscribe @@ -23,10 +23,10 @@ import reactor.core.publisher.Mono true } - @Command2.Subcommand(helpText = Array(Array( + @Command2.Subcommand(helpText = Array( "Remove role", "This command removes a role from your account." - ))) def remove(sender: Command2DCSender, @Command2.TextArg rolename: String): Boolean = { + )) def remove(sender: Command2DCSender, @Command2.TextArg rolename: String): Boolean = { val role = checkAndGetRole(sender, rolename) if (role == null) return true try sender.getMessage.getAuthorAsMember.flatMap(m => m.removeRole(role.getId).switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("removed role.")))).subscribe From a84cd4e8e35bf3c9aeb98bfdf75a792f51fcf464 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 9 Mar 2021 03:41:04 +0100 Subject: [PATCH 08/23] Fix all Scala errors! --- .../discordplugin/DiscordPlayerSender.scala | 2 +- .../broadcaster/PlayerListWatcher.scala | 2 +- .../discordplugin/fun/FunModule.scala | 2 +- .../discordplugin/mcchat/MCChatCustom.scala | 12 +-- .../discordplugin/mcchat/MCChatListener.scala | 9 ++- .../discordplugin/mcchat/MCChatUtils.scala | 13 ++-- .../discordplugin/mcchat/MCListener.scala | 13 ++-- .../mcchat/MinecraftChatModule.scala | 1 + .../discordplugin/role/GameRoleModule.scala | 75 ++++++++++--------- 9 files changed, 69 insertions(+), 60 deletions(-) diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.scala b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.scala index 5994da7..4f3f035 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.scala +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.scala @@ -24,7 +24,7 @@ object DiscordPlayerSender { }).useConstructor(user, channel, player, module)) } -abstract class DiscordPlayerSender(val user: User, val channel: MessageChannel, var player: Player, val module: Nothing) extends DiscordSenderBase(user, channel) with IMCPlayer[DiscordPlayerSender] { +abstract class DiscordPlayerSender(user: User, channel: MessageChannel, var player: Player, val module: Nothing) extends DiscordSenderBase(user, channel) with IMCPlayer[DiscordPlayerSender] { val vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, player, module)) override def getVanillaCmdListener: VCMDWrapper = this.vanillaCmdListener diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala b/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala index 7e563d1..55f409c 100644 --- a/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala +++ b/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala @@ -72,7 +72,7 @@ object PlayerListWatcher { else lookupConstructor = null mock = Mockito.mock(dplc, Mockito.withSettings.defaultAnswer(new Answer[AnyRef]() { // Cannot call super constructor @throws[Throwable] - override def answer(invocation: InvocationOnMock): Any = { + override def answer(invocation: InvocationOnMock): AnyRef = { val method = invocation.getMethod if (!(method.getName == "sendMessage")) { if (method.getName == "sendAll") { diff --git a/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala b/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala index fa95622..0cd0c6c 100644 --- a/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala +++ b/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala @@ -107,7 +107,7 @@ class FunModule extends Component[DiscordPlugin] with Listener { * Answers for a recognized question. Selected randomly. */ final private val serverReadyAnswers: ConfigData[util.ArrayList[String]] = - getConfig.getData("serverReadyAnswers", () => Lists.newArrayList(FunModule.serverReadyStrings): _*) + getConfig.getData("serverReadyAnswers", () => Lists.newArrayList(FunModule.serverReadyStrings: _*)) private def createUsableServerReadyStrings(): Unit = IntStream.range(0, serverReadyAnswers.get.size).forEach((i: Int) => FunModule.usableServerReadyStrings.add(i.toShort)) diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala index a9b4dec..e84b9d9 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala @@ -44,12 +44,14 @@ object MCChatCustom { MCChatUtils.lastmsgfromd.remove(channel.asLong) val count = lastmsgCustom.size lastmsgCustom.filterInPlace(lmd => { - if (lmd.channel.getId.asLong != channel.asLong) return true - lmd.mcchannel match { - case room: ChatRoom => room.leaveRoom(lmd.dcp) - case _ => + if (lmd.channel.getId.asLong != channel.asLong) true + else { + lmd.mcchannel match { + case room: ChatRoom => room.leaveRoom(lmd.dcp) + case _ => + } + false } - false }) lastmsgCustom.size < count } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala index 9339529..9b65bb7 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala @@ -148,13 +148,14 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { && e.getGroupID() == lmd.groupID) { //Check if this is the group we want to test - #58 if (e.shouldSendTo(lmd.dcp)) { //Check original user's permissions doit(lmd) + true } else { lmd.channel.createMessage("The user no longer has permission to view the channel, connection removed.").subscribe - return false //If the user no longer has permission, remove the connection + false //If the user no longer has permission, remove the connection } } - true + else true }) } } catch { @@ -382,7 +383,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { false } else { - val cmb = ChatMessage.builder(dsender, user, dmessage + getAttachmentText()).fromCommand(false) + val cmb = ChatMessage.builder(dsender, user, dmessage + getAttachmentText).fromCommand(false) if (clmd != null) TBMCChatAPI.SendChatMessage(cmb.permCheck(clmd.dcp).build, clmd.mcchannel) else @@ -415,7 +416,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { val cmd = dmessage.substring(1) val cmdlowercased = cmd.toLowerCase if (dsender.isInstanceOf[DiscordSender] && notWhitelisted(cmdlowercased)) { // Command not whitelisted - dsender.sendMessage("Sorry, you can only access these commands from here:\n" + whitelistedCommands() + + dsender.sendMessage("Sorry, you can only access these commands from here:\n" + whitelistedCommands + (if (user.getConnectedID(classOf[TBMCPlayer]) == null) "\nTo access your commands, first please connect your accounts, using /connect in " + DPUtils.botmention + "\nThen y" else "\nY") + "ou can access all of your regular commands (even offline) in private chat: DM me `mcchat`!") diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala index 019493f..d285553 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala @@ -22,7 +22,7 @@ import reactor.core.scala.publisher.SMono import java.net.InetAddress import java.util -import java.util._ +import java.util.UUID import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger import java.util.logging.Level @@ -137,12 +137,11 @@ object MCChatUtils { */ def forCustomAndAllMCChat(action: SMono[MessageChannel] => SMono[_], @Nullable toggle: ChannelconBroadcast, hookmsg: Boolean): SMono[_] = { if (notEnabled) return SMono.empty - val list = - List(if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) - forPublicPrivateChat(action) else SMono.empty) ++ - (if (toggle == null) MCChatCustom.lastmsgCustom - else MCChatCustom.lastmsgCustom.filter(cc => (cc.toggles & (1 << toggle.id)) != 0)) - .map(_.channel).map(SMono.just).map(action) + val list = List(if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) + forPublicPrivateChat(action) else SMono.empty) ++ + (if (toggle == null) MCChatCustom.lastmsgCustom + else MCChatCustom.lastmsgCustom.filter(cc => (cc.toggles & (1 << toggle.id)) != 0)) + .map(_.channel).map(SMono.just).map(action) SMono.whenDelayError(list) } diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala index d3b2ea3..21377c8 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala @@ -1,6 +1,6 @@ package buttondevteam.discordplugin.mcchat -import buttondevteam.discordplugin.DPUtils.FluxExtensions +import buttondevteam.discordplugin.DPUtils.{FluxExtensions, MonoExtensions} import buttondevteam.discordplugin._ import buttondevteam.lib.TBMCSystemChatEvent import buttondevteam.lib.player.{TBMCPlayer, TBMCPlayerBase, TBMCYEEHAWEvent} @@ -15,8 +15,7 @@ import org.bukkit.event.player.PlayerLoginEvent.Result import org.bukkit.event.player._ import org.bukkit.event.server.{BroadcastMessageEvent, TabCompleteEvent} import org.bukkit.event.{EventHandler, EventPriority, Listener} -import reactor.core.publisher.Flux -import reactor.core.scala.publisher.SMono +import reactor.core.scala.publisher.{SFlux, SMono} class MCListener(val module: MinecraftChatModule) extends Listener { final private val muteRole = DPUtils.roleData(module.getConfig, "muteRole", "Muted") @@ -35,8 +34,8 @@ class MCListener(val module: MinecraftChatModule) extends Listener { val p = e.getPlayer val dp = TBMCPlayerBase.getPlayer(p.getUniqueId, classOf[TBMCPlayer]).getAs(classOf[DiscordPlayer]) if (dp != null) - DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID)).flatMap(user => - user.getPrivateChannel.flatMap(chan => module.chatChannelMono.flatMap(cc => { + DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID)).^^().flatMap(user => + user.getPrivateChannel.^^().flatMap(chan => module.chatChannelMono.flatMap(cc => { MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, chan, p, module)) MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, cc, p, module)) //Stored per-channel SMono.empty @@ -130,8 +129,8 @@ class MCListener(val module: MinecraftChatModule) extends Listener { val t = event.getBuffer.substring(i + 1) //0 if not found if (!t.startsWith("@")) return val token = t.substring(1) - val x = DiscordPlugin.mainServer.getMembers.flatMap((m) => Flux.just(m.getUsername, m.getNickname.orElse(""))) - .filter((s) => s.startsWith(token)).map((s) => "@" + s).doOnNext(event.getCompletions.add(_)).blockLast + val x = DiscordPlugin.mainServer.getMembers.^^().flatMap(m => SFlux.just(m.getUsername, m.getNickname.orElse(""))) + .filter(_.startsWith(token)).map("@" + _).doOnNext(event.getCompletions.add(_)).blockLast() } @EventHandler def onCommandSend(event: PlayerCommandSendEvent): Boolean = event.getCommands.add("g") diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala index ea51472..121130a 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala @@ -129,6 +129,7 @@ class MinecraftChatModule extends Component[DiscordPlugin] { UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname"), this) MCChatCustom.addCustomChat(ch.asInstanceOf[MessageChannel], groupid, mcch.get, user, dcp, toggles, brtoggles.asScala.map(TBMCSystemChatEvent.BroadcastTarget.get).filter(Objects.nonNull).toSet) + () }) } } diff --git a/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.scala b/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.scala index 95648ec..25e7e61 100644 --- a/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.scala +++ b/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.scala @@ -1,6 +1,7 @@ package buttondevteam.discordplugin.role import buttondevteam.core.ComponentManager +import buttondevteam.discordplugin.DPUtils.{FluxExtensions, MonoExtensions} import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} import buttondevteam.lib.architecture.{Component, ComponentMetadata} import discord4j.core.`object`.entity.Role @@ -8,10 +9,10 @@ import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.event.domain.role.{RoleCreateEvent, RoleDeleteEvent, RoleEvent, RoleUpdateEvent} import discord4j.rest.util.Color import org.bukkit.Bukkit -import reactor.core.publisher.Mono +import reactor.core.scala.publisher.SMono import java.util.Collections -import java.util.stream.Collectors +import scala.jdk.CollectionConverters.SeqHasAsJava /** * Automatically collects roles with a certain color. @@ -23,31 +24,31 @@ import java.util.stream.Collectors if (grm == null) return val GameRoles = grm.GameRoles val logChannel = grm.logChannel.get - val notMainServer = (r: Role) => r.getGuildId.asLong != DiscordPlugin.mainServer.getId.asLong + val notMainServer = (_: Role).getGuildId.asLong != DiscordPlugin.mainServer.getId.asLong roleEvent match { case roleCreateEvent: RoleCreateEvent => Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => { - def foo(): Unit = { - val role = roleCreateEvent.getRole - if (notMainServer(role)) return - grm.isGameRole(role).flatMap((b: Boolean) => { - def foo(b: Boolean): Mono[_] = { - if (!b) return Mono.empty //Deleted or not a game role + val role = roleCreateEvent.getRole + if (!notMainServer(role)) { + grm.isGameRole(role).flatMap(b => { + if (!b) SMono.empty //Deleted or not a game role + else { GameRoles.add(role.getName) - if (logChannel != null) return logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Added " + role.getName + " as game role. If you don't want this, change the role's color from the game role color.")) - Mono.empty + if (logChannel != null) + logChannel.flatMap(_.createMessage("Added " + role.getName + " as game role." + + " If you don't want this, change the role's color from the game role color.").^^()) + else + SMono.empty } - - foo(b) }).subscribe + () } - - foo() }, 100) case roleDeleteEvent: RoleDeleteEvent => val role = roleDeleteEvent.getRole.orElse(null) if (role == null) return if (notMainServer(role)) return - if (GameRoles.remove(role.getName) && logChannel != null) logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Removed " + role.getName + " as a game role.")).subscribe + if (GameRoles.remove(role.getName) && logChannel != null) + logChannel.flatMap(_.createMessage("Removed " + role.getName + " as a game role.").^^()).subscribe case roleUpdateEvent: RoleUpdateEvent => if (!roleUpdateEvent.getOld.isPresent) { grm.logWarn("Old role not stored, cannot update game role!") @@ -56,20 +57,25 @@ import java.util.stream.Collectors val or = roleUpdateEvent.getOld.get if (notMainServer(or)) return val cr = roleUpdateEvent.getCurrent - grm.isGameRole(cr).flatMap((b: Boolean) => { - def foo(b: Boolean): Mono[_] = { - if (!b) if (GameRoles.remove(or.getName) && logChannel != null) return logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Removed " + or.getName + " as a game role because its color changed.")) - else { - if (GameRoles.contains(or.getName) && or.getName == cr.getName) return Mono.empty - val removed = GameRoles.remove(or.getName) //Regardless of whether it was a game role - GameRoles.add(cr.getName) //Add it because it has no color - if (logChannel != null) if (removed) return logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Changed game role from " + or.getName + " to " + cr.getName + ".")) - else return logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Added " + cr.getName + " as game role because it has the color of one.")) - } - Mono.empty + grm.isGameRole(cr).flatMap(isGameRole => { + if (!isGameRole) + if (GameRoles.remove(or.getName) && logChannel != null) + logChannel.flatMap(_.createMessage("Removed " + or.getName + " as a game role because its color changed.").^^()) + else + SMono.empty + else if (GameRoles.contains(or.getName) && or.getName == cr.getName) + SMono.empty + else { + val removed = GameRoles.remove(or.getName) //Regardless of whether it was a game role + GameRoles.add(cr.getName) //Add it because it has no color + if (logChannel != null) + if (removed) + logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Changed game role from " + or.getName + " to " + cr.getName + ".").^^()) + else + logChannel.flatMap((ch: MessageChannel) => ch.createMessage("Added " + cr.getName + " as game role because it has the color of one.").^^()) + else + SMono.empty } - - foo(b) }).subscribe case _ => } @@ -82,7 +88,7 @@ import java.util.stream.Collectors override protected def enable(): Unit = { getPlugin.manager.registerCommand(command) - GameRoles = DiscordPlugin.mainServer.getRoles.filterWhen(this.isGameRole _).map(_.getName).collect(Collectors.toList).block + GameRoles = DiscordPlugin.mainServer.getRoles.^^().filterWhen(this.isGameRole).map(_.getName).collectSeq().block().asJava } override protected def disable(): Unit = getPlugin.manager.unregisterCommand(command) @@ -97,11 +103,12 @@ import java.util.stream.Collectors */ final private val roleColor = getConfig.getConfig[Color]("roleColor").`def`(Color.of(149, 165, 166)).getter((rgb: Any) => Color.of(Integer.parseInt(rgb.asInstanceOf[String].substring(1), 16))).setter((color: Color) => String.format("#%08x", color.getRGB)).buildReadOnly - private def isGameRole(r: Role): Mono[Boolean] = { - if (r.getGuildId.asLong != DiscordPlugin.mainServer.getId.asLong) return Mono.just(false) //Only allow on the main server + private def isGameRole(r: Role): SMono[Boolean] = { + if (r.getGuildId.asLong != DiscordPlugin.mainServer.getId.asLong) return SMono.just(false) //Only allow on the main server val rc = roleColor.get if (r.getColor equals rc) - DiscordPlugin.dc.getSelf.flatMap((u) => u.asMember(DiscordPlugin.mainServer.getId)).flatMap((m) => m.hasHigherRoles(Collections.singleton(r.getId))).defaultIfEmpty(false) //Below one of our roles - else Mono.just(false) + DiscordPlugin.dc.getSelf.flatMap(u => u.asMember(DiscordPlugin.mainServer.getId)).^^() + .flatMap(_.hasHigherRoles(Collections.singleton(r.getId)).^^().asInstanceOf).defaultIfEmpty(false) //Below one of our roles + else SMono.just(false) } } \ No newline at end of file From 1b1a592a1efd9de541b37fcdea027eb06c98fe3c Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 9 Mar 2021 23:55:02 +0100 Subject: [PATCH 09/23] It compiles! Remove LPInjector for now The IDE and the Scala compiler don't agree on what is or isn't needed --- pom.xml | 12 +- .../DiscordConnectedPlayer.scala | 8 +- .../discordplugin/DiscordSender.scala | 2 +- .../mcchat/MinecraftChatModule.scala | 2 +- .../playerfaker/VanillaCommandListener.java | 102 ------------- .../playerfaker/VanillaCommandListener.scala | 85 +++++++++++ .../playerfaker/VanillaCommandListener14.java | 108 -------------- .../VanillaCommandListener14.scala | 85 +++++++++++ .../playerfaker/VanillaCommandListener15.java | 138 ------------------ .../VanillaCommandListener15.scala | 123 ++++++++++++++++ .../playerfaker/perm/LPInjector.java | 41 +----- 11 files changed, 303 insertions(+), 403 deletions(-) delete mode 100755 src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.java create mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.java create mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala delete mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener15.java create mode 100644 src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener15.scala diff --git a/pom.xml b/pom.xml index c1c5b3b..7201620 100644 --- a/pom.xml +++ b/pom.xml @@ -84,7 +84,7 @@ 4.4.0 - generate-sources + compile compile @@ -92,9 +92,6 @@ - target/generated-sourcess - src/main/java - src/main/scala
@@ -208,13 +205,6 @@ 2.17.1 provided - - - org.projectlombok - lombok - 1.18.10 - provided - - src/main/java - - - src/main/resources - true - - - Chroma-Discord - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.1 - - - package - - shade - - - - - - - - - io.netty - btndvtm.dp.io.netty - - - - - com.fasterxml - btndvtm.dp.com.fasterxml - - - org.mockito - btndvtm.dp.org.mockito - - - org.slf4j - btndvtm.dp.org.slf4j - - - - - - - - maven-surefire-plugin - 2.4.2 - - false - - - - - net.alchim31.maven - scala-maven-plugin - 4.4.0 - - - compile - - compile - - - - - - - - - - - - UTF-8 - 1.0.0 - - - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - - jcenter - http://jcenter.bintray.com - - - jitpack.io - https://jitpack.io - - - - Essentials - https://ci.ender.zone/plugin/repository/everything/ - - - projectlombok.org - http://projectlombok.org/mavenrepo - - - - - papermc - https://papermc.io/repo/repository/maven-public/ - - - - true - - ossSonatypeSnapshot - OSS Sonatype Snapshots - https://oss.sonatype.org/content/repositories/snapshots/ - default - - - - - - junit - junit - 4.13.1 - test - - - org.spigotmc - spigot-api - 1.12.2-R0.1-SNAPSHOT - provided - - - org.spigotmc - spigot - 1.12.2-R0.1-SNAPSHOT - provided - - - org.spigotmc. - spigot - 1.14.4-R0.1-SNAPSHOT - provided - - - com.destroystokyo.paper - paper - 1.16.3-R0.1-SNAPSHOT - provided - - - - com.discord4j - discord4j-core - 3.1.3 - - - - org.slf4j - slf4j-jdk14 - 1.7.21 - - - com.github.TBMCPlugins.ChromaCore - Chroma-Core - v1.0.0 - provided - - - net.ess3 - EssentialsX - 2.17.1 - provided - - - - com.vdurmont - emoji-java - 4.0.0 - - - - - com.github.lucko.LuckPerms - bukkit - master-SNAPSHOT - provided - - - - org.mockito - mockito-core - 3.5.13 - - - org.scala-lang - scala-library - 2.13.1 - - - io.projectreactor - reactor-scala-extensions_2.13 - 0.7.0 - - - diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..edb57e9 --- /dev/null +++ b/project/build.properties @@ -0,0 +1,2 @@ +sbt.version=1.4.7 +scala.version=1.13.1 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..a317875 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") +//addSbtPlugin("com.sandinh" % "sbt-shade" % "0.1.2") diff --git a/src/main/java/buttondevteam/discordplugin/ChromaBot.scala b/src/main/java/buttondevteam/discordplugin/ChromaBot.scala index 1353645..c604c48 100644 --- a/src/main/java/buttondevteam/discordplugin/ChromaBot.scala +++ b/src/main/java/buttondevteam/discordplugin/ChromaBot.scala @@ -18,7 +18,7 @@ object ChromaBot { /** * Send a message to the chat channels and private chats. * - * @param message The message to send, duh (use {@link MessageChannel# createMessage ( String )}) + * @param message The message to send, duh (use [[MessageChannel.createMessage]]) */ def sendMessage(message: SMono[MessageChannel] => SMono[Message]): Unit = MCChatUtils.forPublicPrivateChat(message).subscribe diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.scala b/src/main/java/buttondevteam/discordplugin/DPUtils.scala index c22cbe9..455ff46 100644 --- a/src/main/java/buttondevteam/discordplugin/DPUtils.scala +++ b/src/main/java/buttondevteam/discordplugin/DPUtils.scala @@ -82,7 +82,7 @@ object DPUtils { roleData(config, key, defName, SMono.just(DiscordPlugin.mainServer)) /** - * Needs to be a {@link ConfigData} for checking if it's set + * Needs to be a [[ConfigData]] for checking if it's set */ def roleData(config: IHaveConfig, key: String, defName: String, guild: SMono[Guild]): ReadOnlyConfigData[SMono[Role]] = config.getReadOnlyDataPrimDef(key, defName, (name: Any) => { def foo(name: Any): SMono[Role] = { diff --git a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala index 459e9b9..fdfd590 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala +++ b/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala @@ -49,7 +49,7 @@ object DiscordConnectedPlayer { } /** - * @constructor The parameters must match with {@link #create ( User, MessageChannel, UUID, String, MinecraftChatModule)} + * @constructor The parameters must match with [[DiscordConnectedPlayer.create]] * @param user May be null. * @param channel May not be null. * @param uniqueId The UUID of the player. diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayer.scala b/src/main/java/buttondevteam/discordplugin/DiscordPlayer.scala index 017e12a..4cfca44 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlayer.scala +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlayer.scala @@ -14,7 +14,7 @@ import buttondevteam.lib.player.{ChromaGamerBase, UserClass} /** * Returns true if player has the private Minecraft chat enabled. For setting the value, see - * {@link MCChatPrivate# privateMCChat ( MessageChannel, boolean, User, DiscordPlayer)} + * [[MCChatPrivate.privateMCChat]] */ def isMinecraftChatEnabled: Boolean = MCChatPrivate.isMinecraftChatEnabled(this) } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala index 3d16fb5..b7cf4c7 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala +++ b/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala @@ -22,6 +22,7 @@ import discord4j.core.event.domain.guild.GuildCreateEvent import discord4j.core.event.domain.lifecycle.ReadyEvent import discord4j.core.{DiscordClientBuilder, GatewayDiscordClient} import discord4j.gateway.ShardInfo +import discord4j.rest.interaction.Interactions import discord4j.store.jdk.JdkStoreService import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.core.Logger @@ -216,6 +217,8 @@ import java.util.Optional blw.start() LogManager.getRootLogger.asInstanceOf[Logger].addAppender(blw) logWatcher = blw + Interactions.create().onCommand("teszt", Interactions.createHandler() + .guild(gi => gi.acknowledge().withFollowup(_.createFollowupMessage("Teszt"))).build()); if (!TBMCCoreAPI.IsTestServer) DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe else DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("testing"))).subscribe getLogger.info("Loaded!") diff --git a/src/main/java/buttondevteam/discordplugin/DiscordSender.scala b/src/main/java/buttondevteam/discordplugin/DiscordSender.scala index 207103a..5e17693 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordSender.scala +++ b/src/main/java/buttondevteam/discordplugin/DiscordSender.scala @@ -57,4 +57,5 @@ class DiscordSender(user: User, channel: MessageChannel, pname: String) extends override def getName: String = name //override def spigot(): CommandSender.Spigot = new CommandSender.Spigot + override def spigot(): CommandSender.Spigot = ??? } \ No newline at end of file diff --git a/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala b/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala index 6b7a4f9..b31d3bf 100644 --- a/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala +++ b/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala @@ -4,9 +4,8 @@ import buttondevteam.discordplugin.DPUtils import buttondevteam.lib.chat.Command2Sender import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.`object`.entity.{Message, User} -import lombok.RequiredArgsConstructor -@RequiredArgsConstructor class Command2DCSender(val message: Message) extends Command2Sender { +class Command2DCSender(val message: Message) extends Command2Sender { def getMessage: Message = this.message override def sendMessage(message: String): Unit = { diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala index e84b9d9..df29986 100644 --- a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala +++ b/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala @@ -6,7 +6,6 @@ import buttondevteam.lib.TBMCSystemChatEvent import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.MessageChannel -import lombok.NonNull import javax.annotation.Nullable import scala.collection.mutable.ListBuffer @@ -59,8 +58,8 @@ object MCChatCustom { def getCustomChats: List[CustomLMD] = lastmsgCustom.toList - class CustomLMD private[mcchat](@NonNull channel: MessageChannel, @NonNull user: User, val groupID: String, - @NonNull mcchannel: Channel, val dcp: DiscordConnectedPlayer, var toggles: Int, + class CustomLMD private[mcchat](channel: MessageChannel, user: User, val groupID: String, + mcchannel: Channel, val dcp: DiscordConnectedPlayer, var toggles: Int, var brtoggles: Set[TBMCSystemChatEvent.BroadcastTarget]) extends MCChatUtils.LastMsgData(channel, user, mcchannel) { } diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordInventory.java b/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordInventory.java index 1c0ebb3..b730cc8 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordInventory.java +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordInventory.java @@ -1,7 +1,5 @@ package buttondevteam.discordplugin.playerfaker; -import lombok.Getter; -import lombok.Setter; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.HumanEntity; @@ -16,8 +14,7 @@ import java.util.stream.IntStream; public class DiscordInventory implements Inventory { private ItemStack[] items = new ItemStack[27]; private List itemStacks = Arrays.asList(items); - @Getter - @Setter + public int maxStackSize; private static ItemStack emptyStack = new ItemStack(Material.AIR, 0); @@ -26,6 +23,16 @@ public class DiscordInventory implements Inventory { return items.length; } + @Override + public int getMaxStackSize() { + return maxStackSize; + } + + @Override + public void setMaxStackSize(int maxStackSize) { + this.maxStackSize = maxStackSize; + } + @Override public String getName() { return "Discord inventory"; diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala index 36e464c..c192e52 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala @@ -1,7 +1,6 @@ package buttondevteam.discordplugin.playerfaker import buttondevteam.discordplugin.{DiscordSenderBase, IMCPlayer} -import lombok.Getter import net.minecraft.server.v1_12_R1._ import org.bukkit.Bukkit import org.bukkit.craftbukkit.v1_12_R1.command.VanillaCommandWrapper @@ -40,7 +39,7 @@ object VanillaCommandListener { class VanillaCommandListener[T <: DiscordSenderBase with IMCPlayer[T]] extends ICommandListener { def getPlayer: T = this.player - @Getter private var player: T = null.asInstanceOf + private var player: T = null.asInstanceOf private var bukkitplayer: Player = null /** diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala index b44fa69..df70378 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala @@ -1,7 +1,6 @@ package buttondevteam.discordplugin.playerfaker import buttondevteam.discordplugin.{DiscordSenderBase, IMCPlayer} -import lombok.Getter import net.minecraft.server.v1_14_R1._ import org.bukkit.Bukkit import org.bukkit.command.CommandSender @@ -44,7 +43,7 @@ object VanillaCommandListener14 { class VanillaCommandListener14[T <: DiscordSenderBase with IMCPlayer[T]] extends ICommandListener { def getPlayer: T = this.player - @Getter private var player: T = null.asInstanceOf + private var player: T = null.asInstanceOf private var bukkitplayer: Player = null /** From 860dd664314db5f109805131242ef7764d89617e Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Mon, 5 Apr 2021 02:45:28 +0200 Subject: [PATCH 11/23] Add task to read source files --- build.sbt | 57 ++++++++----------- commenter/src/Commenter.scala | 3 + project/plugins.sbt | 18 +++++- .../VanillaCommandListener14.scala | 2 +- 4 files changed, 44 insertions(+), 36 deletions(-) create mode 100644 commenter/src/Commenter.scala diff --git a/build.sbt b/build.sbt index ed24d8a..be162f4 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,9 @@ +import scala.io.Source +import scala.util.Using + name := "Chroma-Discord" -version := "0.1" +version := "1.1" scalaVersion := "2.13.5" @@ -25,45 +28,13 @@ libraryDependencies ++= Seq( "com.github.lucko.LuckPerms" % "bukkit" % "master-SNAPSHOT" % Provided, ) - -/*val myAssemblySettings = inTask(assembly)( - Seq( - assemblyShadeRules := libraryDependencies.value.filter(!_.configurations.exists(_ contains "provided")) - .map { _.organization } - .map { p => - ShadeRule.rename(s"$p.**" -> "btndvtm.dp.@0").inAll - }, - assemblyMergeStrategy := { - case PathList("META-INF", "io.netty.versions.properties") => MergeStrategy.concat - // https://stackoverflow.com/a/55557287/457612 - case "module-info.class" => MergeStrategy.discard - case x => assemblyMergeStrategy.value(x) - }, - /*shadeResourceTransformers ++= Seq( - Rename( - "libnetty_tcnative_linux_x86_64.so" -> "libcom_couchbase_client_core_deps_netty_tcnative_linux_x86_64.so", - "libnetty_tcnative_osx_x86_64.jnilib" -> "libcom_couchbase_client_core_deps_netty_tcnative_osx_x86_64.jnilib", - "netty_tcnative_windows_x86_64.dll" -> "com_couchbase_client_core_deps_netty_tcnative_windows_x86_64.dll" - ).inDir("META-INF/native"), - Discard( - "com.fasterxml.jackson.core.JsonFactory", - "com.fasterxml.jackson.core.ObjectCodec", - "com.fasterxml.jackson.databind.Module" - ).inDir("META-INF/services") - )*/ - ) -)*/ - assemblyJarName in assembly := "Chroma-Discord.jar" -//assemblyShadeRules in assembly := libraryDependencies.value.filter(!_.configurations.exists(_ contains "provided")) assemblyShadeRules in assembly := Seq( "io.netty", "com.fasterxml", "org.mockito", "org.slf4j" ).map { p => ShadeRule.rename(s"$p.**" -> "btndvtm.dp.@0").inAll } -//logLevel in assembly := Level.Debug - assemblyMergeStrategy in assembly := { case PathList("META-INF", "io.netty.versions.properties") => MergeStrategy.concat // https://stackoverflow.com/a/55557287/457612 @@ -71,4 +42,22 @@ assemblyMergeStrategy in assembly := { case x => (assemblyMergeStrategy in assembly).value(x) } -//lazy val `Chroma-Discord` = project.settings(myAssemblySettings) +lazy val commenter = project.settings(Seq( + name := "Chroma-Commenter", + version := "1.0", + organization := "com.github.TBMCPlugins" +)) + +val teszt = TaskKey[Unit]("teszt") +teszt := { + //val tv = target.value + val sv = (Compile / sources).value + for (file <- sv) { + Using(Source.fromFile(file)) { src => + for (line <- src.getLines) { + if (line.contains("class")) + println(line) + } + }.recover[Unit]({ case t => t.printStackTrace() }) + } +} diff --git a/commenter/src/Commenter.scala b/commenter/src/Commenter.scala new file mode 100644 index 0000000..bf51882 --- /dev/null +++ b/commenter/src/Commenter.scala @@ -0,0 +1,3 @@ +class Commenter { + +} diff --git a/project/plugins.sbt b/project/plugins.sbt index a317875..116cd07 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,2 +1,18 @@ + + addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") -//addSbtPlugin("com.sandinh" % "sbt-shade" % "0.1.2") + +/*lazy val commenter = project.settings(Seq( + name := "Chroma-Commenter", + version := "1.0", + organization := "com.github.TBMCPlugins" +)) + +lazy val root = (project in file(".")).dependsOn(commenter)*/ + +//addSbtPlugin("com.github.TBMCPlugins" % "Chroma-Commenter" % "1.0") +/*val Teszt = config("teszt").extend(Compile) +val teszt = TaskKey[Unit]("teszt") +teszt := target map { target => //teszt := { x.value } + println("Teszt: " + target) +}*/ diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala index df70378..ff5e1c0 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala +++ b/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala @@ -70,7 +70,7 @@ class VanillaCommandListener14[T <: DiscordSenderBase with IMCPlayer[T]] extends if (bukkitplayer != null && !bukkitplayer.isInstanceOf[CraftPlayer]) throw new ClassCastException("bukkitplayer must be a Bukkit player!") } - override def sendMessage(arg0: IChatBaseComponent): Unit = { + override def sendMessage(arg0: IChatBaseComponent): scala.Unit = { player.sendMessage(arg0.getString) if (bukkitplayer != null) bukkitplayer.asInstanceOf[CraftPlayer].getHandle.sendMessage(arg0) } From 5146fdf368643b91dbf20c495de386f36543b3db Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Mon, 5 Apr 2021 03:47:56 +0200 Subject: [PATCH 12/23] Add Reflections --- build.sbt | 9 +-------- commenter/src/Commenter.scala | 3 --- project/build.sbt | 10 ++++++++++ project/commenter/src/main/scala/Commenter.scala | 10 ++++++++++ 4 files changed, 21 insertions(+), 11 deletions(-) delete mode 100644 commenter/src/Commenter.scala create mode 100644 project/build.sbt create mode 100644 project/commenter/src/main/scala/Commenter.scala diff --git a/build.sbt b/build.sbt index be162f4..d87bd51 100644 --- a/build.sbt +++ b/build.sbt @@ -42,21 +42,14 @@ assemblyMergeStrategy in assembly := { case x => (assemblyMergeStrategy in assembly).value(x) } -lazy val commenter = project.settings(Seq( - name := "Chroma-Commenter", - version := "1.0", - organization := "com.github.TBMCPlugins" -)) - val teszt = TaskKey[Unit]("teszt") teszt := { - //val tv = target.value val sv = (Compile / sources).value for (file <- sv) { Using(Source.fromFile(file)) { src => for (line <- src.getLines) { if (line.contains("class")) - println(line) + println(line + "") } }.recover[Unit]({ case t => t.printStackTrace() }) } diff --git a/commenter/src/Commenter.scala b/commenter/src/Commenter.scala deleted file mode 100644 index bf51882..0000000 --- a/commenter/src/Commenter.scala +++ /dev/null @@ -1,3 +0,0 @@ -class Commenter { - -} diff --git a/project/build.sbt b/project/build.sbt new file mode 100644 index 0000000..592ef7e --- /dev/null +++ b/project/build.sbt @@ -0,0 +1,10 @@ +lazy val commenter = project.settings(Seq( + name := "Chroma-Commenter", + version := "1.0", + organization := "com.github.TBMCPlugins", + + resolvers += Resolver.mavenLocal, + + libraryDependencies += "org.reflections" % "reflections" % "0.9.12", + libraryDependencies += "com.github.TBMCPlugins.ChromaCore" % "ButtonProcessor" % "master-SNAPSHOT" +)) diff --git a/project/commenter/src/main/scala/Commenter.scala b/project/commenter/src/main/scala/Commenter.scala new file mode 100644 index 0000000..03b25ea --- /dev/null +++ b/project/commenter/src/main/scala/Commenter.scala @@ -0,0 +1,10 @@ + + +object Commenter extends App { + val ref = new Reflections("buttondevteam.discordplugin") + val types: Set[Class[_]] = ref.getTypesAnnotatedWith(HasConfig, true).asScala + for (ty <- types) { + ty + . + } +} From 470212411dac9081334331104ff36d5994554a3e Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Mon, 5 Apr 2021 18:57:40 +0200 Subject: [PATCH 13/23] Successfully made an unnecessary subproject work --- commenter/.idea/.gitignore | 8 ++++++++ commenter/build.sbt | 10 ++++++++++ commenter/src/main/scala/Commenter.scala | 18 ++++++++++++++++++ project/build.sbt | 12 ++---------- .../commenter/src/main/scala/Commenter.scala | 10 ---------- project/plugins.sbt | 17 ----------------- .../discordplugin/BukkitLogWatcher.scala | 2 +- .../discordplugin/ChannelconBroadcast.scala | 0 .../discordplugin/ChromaBot.scala | 0 .../buttondevteam/discordplugin/DPUtils.scala | 0 .../discordplugin/DiscordConnectedPlayer.scala | 0 .../discordplugin/DiscordPlayer.scala | 0 .../discordplugin/DiscordPlayerSender.scala | 0 .../discordplugin/DiscordPlugin.scala | 3 ++- .../discordplugin/DiscordSender.scala | 0 .../discordplugin/DiscordSenderBase.scala | 0 .../discordplugin/IMCPlayer.scala | 0 .../announcer/AnnouncerModule.scala | 0 .../GeneralEventBroadcasterModule.scala | 0 .../broadcaster/PlayerListWatcher.scala | 0 .../discordplugin/commands/Command2DC.scala | 0 .../commands/Command2DCSender.scala | 0 .../commands/ConnectCommand.scala | 0 .../discordplugin/commands/DebugCommand.scala | 0 .../discordplugin/commands/HelpCommand.scala | 0 .../discordplugin/commands/ICommand2DC.scala | 0 .../commands/UserinfoCommand.scala | 0 .../commands/VersionCommand.scala | 0 .../exceptions/DebugMessageListener.scala | 0 .../exceptions/ExceptionListenerModule.scala | 0 .../discordplugin/fun/FunModule.scala | 0 .../listeners/CommandListener.scala | 0 .../listeners/CommonListeners.scala | 0 .../discordplugin/listeners/MCListener.scala | 0 .../mcchat/ChannelconCommand.scala | 0 .../discordplugin/mcchat/MCChatCommand.scala | 0 .../discordplugin/mcchat/MCChatCustom.scala | 0 .../discordplugin/mcchat/MCChatListener.scala | 0 .../discordplugin/mcchat/MCChatPrivate.scala | 0 .../discordplugin/mcchat/MCChatUtils.scala | 0 .../discordplugin/mcchat/MCListener.scala | 0 .../mcchat/MinecraftChatModule.scala | 0 .../mccommands/DiscordMCCommand.scala | 0 .../playerfaker/DelegatingMockMaker.scala | 0 .../playerfaker/DiscordInventory.java | 0 .../playerfaker/ServerWatcher.scala | 0 .../playerfaker/VCMDWrapper.scala | 2 +- .../playerfaker/VanillaCommandListener.scala | 0 .../playerfaker/VanillaCommandListener14.scala | 0 .../playerfaker/VanillaCommandListener15.scala | 0 .../playerfaker/perm/LPInjector.java | 0 .../discordplugin/role/GameRoleModule.scala | 0 .../discordplugin/role/RoleCommand.scala | 0 .../discordplugin/util/DPState.scala | 0 .../discordplugin/util/Timings.scala | 0 55 files changed, 42 insertions(+), 40 deletions(-) create mode 100644 commenter/.idea/.gitignore create mode 100644 commenter/build.sbt create mode 100644 commenter/src/main/scala/Commenter.scala delete mode 100644 project/commenter/src/main/scala/Commenter.scala rename src/main/{java => scala}/buttondevteam/discordplugin/BukkitLogWatcher.scala (99%) rename src/main/{java => scala}/buttondevteam/discordplugin/ChannelconBroadcast.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/ChromaBot.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/DPUtils.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/DiscordConnectedPlayer.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/DiscordPlayer.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/DiscordPlayerSender.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/DiscordPlugin.scala (99%) rename src/main/{java => scala}/buttondevteam/discordplugin/DiscordSender.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/DiscordSenderBase.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/IMCPlayer.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/announcer/AnnouncerModule.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/commands/Command2DC.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/commands/Command2DCSender.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/commands/ConnectCommand.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/commands/DebugCommand.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/commands/HelpCommand.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/commands/ICommand2DC.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/commands/UserinfoCommand.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/commands/VersionCommand.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/fun/FunModule.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/listeners/CommandListener.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/listeners/CommonListeners.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/listeners/MCListener.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/mcchat/MCChatCommand.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/mcchat/MCChatCustom.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/mcchat/MCChatListener.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/mcchat/MCChatUtils.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/mcchat/MCListener.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/playerfaker/DiscordInventory.java (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/playerfaker/VCMDWrapper.scala (99%) rename src/main/{java => scala}/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/playerfaker/VanillaCommandListener15.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/role/GameRoleModule.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/role/RoleCommand.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/util/DPState.scala (100%) rename src/main/{java => scala}/buttondevteam/discordplugin/util/Timings.scala (100%) diff --git a/commenter/.idea/.gitignore b/commenter/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/commenter/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/commenter/build.sbt b/commenter/build.sbt new file mode 100644 index 0000000..edf8f98 --- /dev/null +++ b/commenter/build.sbt @@ -0,0 +1,10 @@ +name := "Chroma-Commenter" +version := "1.0" +organization := "com.github.TBMCPlugins" +scalaVersion := "2.13.4" + +resolvers += Resolver.mavenLocal + +libraryDependencies += "org.reflections" % "reflections" % "0.9.12" +libraryDependencies += "com.github.TBMCPlugins.ChromaCore" % "ButtonProcessor" % "master-SNAPSHOT" +libraryDependencies += "com.github.TBMCPlugins.ChromaCore" % "Chroma-Core" % "v1.0.0" diff --git a/commenter/src/main/scala/Commenter.scala b/commenter/src/main/scala/Commenter.scala new file mode 100644 index 0000000..e9c653d --- /dev/null +++ b/commenter/src/main/scala/Commenter.scala @@ -0,0 +1,18 @@ +import buttondevteam.buttonproc.HasConfig +import buttondevteam.lib.architecture.ConfigData +import org.reflections.Reflections + +import scala.jdk.javaapi.CollectionConverters.asScala + +object Commenter extends App { + val ref = new Reflections("buttondevteam.discordplugin") + val types = asScala(ref.getTypesAnnotatedWith(classOf[HasConfig], true)) + for (ty <- types) { + val path = if (ty.getAnnotation(classOf[HasConfig]).global()) "global" + else s"components.${ty.getSimpleName}" + val cdcl = classOf[ConfigData[_]] + ty.getDeclaredMethods.filter(m => cdcl.isAssignableFrom(m.getReturnType)) + .concat(ty.getDeclaredFields.filter(f => cdcl.isAssignableFrom(f.getType))) + .map(path + "." + _.getName) + } +} diff --git a/project/build.sbt b/project/build.sbt index 592ef7e..e8c3569 100644 --- a/project/build.sbt +++ b/project/build.sbt @@ -1,10 +1,2 @@ -lazy val commenter = project.settings(Seq( - name := "Chroma-Commenter", - version := "1.0", - organization := "com.github.TBMCPlugins", - - resolvers += Resolver.mavenLocal, - - libraryDependencies += "org.reflections" % "reflections" % "0.9.12", - libraryDependencies += "com.github.TBMCPlugins.ChromaCore" % "ButtonProcessor" % "master-SNAPSHOT" -)) +lazy val commenter = RootProject(file("../commenter")) +lazy val root = (project in file(".")).dependsOn(commenter) diff --git a/project/commenter/src/main/scala/Commenter.scala b/project/commenter/src/main/scala/Commenter.scala deleted file mode 100644 index 03b25ea..0000000 --- a/project/commenter/src/main/scala/Commenter.scala +++ /dev/null @@ -1,10 +0,0 @@ - - -object Commenter extends App { - val ref = new Reflections("buttondevteam.discordplugin") - val types: Set[Class[_]] = ref.getTypesAnnotatedWith(HasConfig, true).asScala - for (ty <- types) { - ty - . - } -} diff --git a/project/plugins.sbt b/project/plugins.sbt index 116cd07..72477a2 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,18 +1 @@ - - addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") - -/*lazy val commenter = project.settings(Seq( - name := "Chroma-Commenter", - version := "1.0", - organization := "com.github.TBMCPlugins" -)) - -lazy val root = (project in file(".")).dependsOn(commenter)*/ - -//addSbtPlugin("com.github.TBMCPlugins" % "Chroma-Commenter" % "1.0") -/*val Teszt = config("teszt").extend(Compile) -val teszt = TaskKey[Unit]("teszt") -teszt := target map { target => //teszt := { x.value } - println("Teszt: " + target) -}*/ diff --git a/src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.scala b/src/main/scala/buttondevteam/discordplugin/BukkitLogWatcher.scala similarity index 99% rename from src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.scala rename to src/main/scala/buttondevteam/discordplugin/BukkitLogWatcher.scala index 7fee830..ecec820 100644 --- a/src/main/java/buttondevteam/discordplugin/BukkitLogWatcher.scala +++ b/src/main/scala/buttondevteam/discordplugin/BukkitLogWatcher.scala @@ -3,10 +3,10 @@ package buttondevteam.discordplugin import buttondevteam.discordplugin.mcchat.MinecraftChatModule import buttondevteam.discordplugin.util.DPState import org.apache.logging.log4j.Level -import org.apache.logging.log4j.core.{Filter, LogEvent} import org.apache.logging.log4j.core.appender.AbstractAppender import org.apache.logging.log4j.core.filter.LevelRangeFilter import org.apache.logging.log4j.core.layout.PatternLayout +import org.apache.logging.log4j.core.{Filter, LogEvent} class BukkitLogWatcher private[discordplugin]() extends AbstractAppender("ChromaDiscord", LevelRangeFilter.createFilter(Level.INFO, Level.INFO, Filter.Result.ACCEPT, Filter.Result.DENY), diff --git a/src/main/java/buttondevteam/discordplugin/ChannelconBroadcast.scala b/src/main/scala/buttondevteam/discordplugin/ChannelconBroadcast.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/ChannelconBroadcast.scala rename to src/main/scala/buttondevteam/discordplugin/ChannelconBroadcast.scala diff --git a/src/main/java/buttondevteam/discordplugin/ChromaBot.scala b/src/main/scala/buttondevteam/discordplugin/ChromaBot.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/ChromaBot.scala rename to src/main/scala/buttondevteam/discordplugin/ChromaBot.scala diff --git a/src/main/java/buttondevteam/discordplugin/DPUtils.scala b/src/main/scala/buttondevteam/discordplugin/DPUtils.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/DPUtils.scala rename to src/main/scala/buttondevteam/discordplugin/DPUtils.scala diff --git a/src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala b/src/main/scala/buttondevteam/discordplugin/DiscordConnectedPlayer.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/DiscordConnectedPlayer.scala rename to src/main/scala/buttondevteam/discordplugin/DiscordConnectedPlayer.scala diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayer.scala b/src/main/scala/buttondevteam/discordplugin/DiscordPlayer.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/DiscordPlayer.scala rename to src/main/scala/buttondevteam/discordplugin/DiscordPlayer.scala diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.scala b/src/main/scala/buttondevteam/discordplugin/DiscordPlayerSender.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/DiscordPlayerSender.scala rename to src/main/scala/buttondevteam/discordplugin/DiscordPlayerSender.scala diff --git a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala similarity index 99% rename from src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala rename to src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala index b7cf4c7..421e3e2 100644 --- a/src/main/java/buttondevteam/discordplugin/DiscordPlugin.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala @@ -70,7 +70,8 @@ import java.util.Optional if (id.asInstanceOf[Long] == 0L) Option.empty else SMono.fromPublisher(DiscordPlugin.dc.getGuildById(Snowflake.of(id.asInstanceOf[Long]))) .onErrorResume((t: Throwable) => { - getLogger.warning("Failed to get guild: " + t.getMessage); SMono.empty + getLogger.warning("Failed to get guild: " + t.getMessage); + SMono.empty }).blockOption() } diff --git a/src/main/java/buttondevteam/discordplugin/DiscordSender.scala b/src/main/scala/buttondevteam/discordplugin/DiscordSender.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/DiscordSender.scala rename to src/main/scala/buttondevteam/discordplugin/DiscordSender.scala diff --git a/src/main/java/buttondevteam/discordplugin/DiscordSenderBase.scala b/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/DiscordSenderBase.scala rename to src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala diff --git a/src/main/java/buttondevteam/discordplugin/IMCPlayer.scala b/src/main/scala/buttondevteam/discordplugin/IMCPlayer.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/IMCPlayer.scala rename to src/main/scala/buttondevteam/discordplugin/IMCPlayer.scala diff --git a/src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala b/src/main/scala/buttondevteam/discordplugin/announcer/AnnouncerModule.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/announcer/AnnouncerModule.scala rename to src/main/scala/buttondevteam/discordplugin/announcer/AnnouncerModule.scala diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala b/src/main/scala/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala rename to src/main/scala/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala diff --git a/src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala b/src/main/scala/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala rename to src/main/scala/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala diff --git a/src/main/java/buttondevteam/discordplugin/commands/Command2DC.scala b/src/main/scala/buttondevteam/discordplugin/commands/Command2DC.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/commands/Command2DC.scala rename to src/main/scala/buttondevteam/discordplugin/commands/Command2DC.scala diff --git a/src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala b/src/main/scala/buttondevteam/discordplugin/commands/Command2DCSender.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/commands/Command2DCSender.scala rename to src/main/scala/buttondevteam/discordplugin/commands/Command2DCSender.scala diff --git a/src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala b/src/main/scala/buttondevteam/discordplugin/commands/ConnectCommand.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/commands/ConnectCommand.scala rename to src/main/scala/buttondevteam/discordplugin/commands/ConnectCommand.scala diff --git a/src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala b/src/main/scala/buttondevteam/discordplugin/commands/DebugCommand.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/commands/DebugCommand.scala rename to src/main/scala/buttondevteam/discordplugin/commands/DebugCommand.scala diff --git a/src/main/java/buttondevteam/discordplugin/commands/HelpCommand.scala b/src/main/scala/buttondevteam/discordplugin/commands/HelpCommand.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/commands/HelpCommand.scala rename to src/main/scala/buttondevteam/discordplugin/commands/HelpCommand.scala diff --git a/src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.scala b/src/main/scala/buttondevteam/discordplugin/commands/ICommand2DC.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/commands/ICommand2DC.scala rename to src/main/scala/buttondevteam/discordplugin/commands/ICommand2DC.scala diff --git a/src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.scala b/src/main/scala/buttondevteam/discordplugin/commands/UserinfoCommand.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/commands/UserinfoCommand.scala rename to src/main/scala/buttondevteam/discordplugin/commands/UserinfoCommand.scala diff --git a/src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala b/src/main/scala/buttondevteam/discordplugin/commands/VersionCommand.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/commands/VersionCommand.scala rename to src/main/scala/buttondevteam/discordplugin/commands/VersionCommand.scala diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala b/src/main/scala/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala rename to src/main/scala/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala diff --git a/src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala b/src/main/scala/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala rename to src/main/scala/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala diff --git a/src/main/java/buttondevteam/discordplugin/fun/FunModule.scala b/src/main/scala/buttondevteam/discordplugin/fun/FunModule.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/fun/FunModule.scala rename to src/main/scala/buttondevteam/discordplugin/fun/FunModule.scala diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala b/src/main/scala/buttondevteam/discordplugin/listeners/CommandListener.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/listeners/CommandListener.scala rename to src/main/scala/buttondevteam/discordplugin/listeners/CommandListener.scala diff --git a/src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala b/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/listeners/CommonListeners.scala rename to src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala diff --git a/src/main/java/buttondevteam/discordplugin/listeners/MCListener.scala b/src/main/scala/buttondevteam/discordplugin/listeners/MCListener.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/listeners/MCListener.scala rename to src/main/scala/buttondevteam/discordplugin/listeners/MCListener.scala diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala rename to src/main/scala/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatCommand.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/mcchat/MCChatCommand.scala rename to src/main/scala/buttondevteam/discordplugin/mcchat/MCChatCommand.scala diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatCustom.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/mcchat/MCChatCustom.scala rename to src/main/scala/buttondevteam/discordplugin/mcchat/MCChatCustom.scala diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/mcchat/MCChatListener.scala rename to src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala rename to src/main/scala/buttondevteam/discordplugin/mcchat/MCChatPrivate.scala diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/mcchat/MCChatUtils.scala rename to src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCListener.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/mcchat/MCListener.scala rename to src/main/scala/buttondevteam/discordplugin/mcchat/MCListener.scala diff --git a/src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala rename to src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala diff --git a/src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala b/src/main/scala/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala rename to src/main/scala/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala b/src/main/scala/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala rename to src/main/scala/buttondevteam/discordplugin/playerfaker/DelegatingMockMaker.scala diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/DiscordInventory.java b/src/main/scala/buttondevteam/discordplugin/playerfaker/DiscordInventory.java similarity index 100% rename from src/main/java/buttondevteam/discordplugin/playerfaker/DiscordInventory.java rename to src/main/scala/buttondevteam/discordplugin/playerfaker/DiscordInventory.java diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala b/src/main/scala/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala rename to src/main/scala/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.scala b/src/main/scala/buttondevteam/discordplugin/playerfaker/VCMDWrapper.scala similarity index 99% rename from src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.scala rename to src/main/scala/buttondevteam/discordplugin/playerfaker/VCMDWrapper.scala index 4c96d51..15a3653 100644 --- a/src/main/java/buttondevteam/discordplugin/playerfaker/VCMDWrapper.scala +++ b/src/main/scala/buttondevteam/discordplugin/playerfaker/VCMDWrapper.scala @@ -1,7 +1,7 @@ package buttondevteam.discordplugin.playerfaker -import buttondevteam.discordplugin.{DiscordSenderBase, IMCPlayer} import buttondevteam.discordplugin.mcchat.MinecraftChatModule +import buttondevteam.discordplugin.{DiscordSenderBase, IMCPlayer} import buttondevteam.lib.TBMCCoreAPI import org.bukkit.Bukkit import org.bukkit.entity.Player diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala b/src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala rename to src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala b/src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala rename to src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener15.scala b/src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener15.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/playerfaker/VanillaCommandListener15.scala rename to src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener15.scala diff --git a/src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java b/src/main/scala/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java similarity index 100% rename from src/main/java/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java rename to src/main/scala/buttondevteam/discordplugin/playerfaker/perm/LPInjector.java diff --git a/src/main/java/buttondevteam/discordplugin/role/GameRoleModule.scala b/src/main/scala/buttondevteam/discordplugin/role/GameRoleModule.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/role/GameRoleModule.scala rename to src/main/scala/buttondevteam/discordplugin/role/GameRoleModule.scala diff --git a/src/main/java/buttondevteam/discordplugin/role/RoleCommand.scala b/src/main/scala/buttondevteam/discordplugin/role/RoleCommand.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/role/RoleCommand.scala rename to src/main/scala/buttondevteam/discordplugin/role/RoleCommand.scala diff --git a/src/main/java/buttondevteam/discordplugin/util/DPState.scala b/src/main/scala/buttondevteam/discordplugin/util/DPState.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/util/DPState.scala rename to src/main/scala/buttondevteam/discordplugin/util/DPState.scala diff --git a/src/main/java/buttondevteam/discordplugin/util/Timings.scala b/src/main/scala/buttondevteam/discordplugin/util/Timings.scala similarity index 100% rename from src/main/java/buttondevteam/discordplugin/util/Timings.scala rename to src/main/scala/buttondevteam/discordplugin/util/Timings.scala From d80703ac68d8c752d9e35488d268a2d69bfaab37 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 6 Apr 2021 00:35:59 +0200 Subject: [PATCH 14/23] Obtain config comments from sources --- build.sbt | 46 ++++++++++++++++++++++++++++++++++++++++++---- project/build.sbt | 4 ++-- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index d87bd51..a9b0272 100644 --- a/build.sbt +++ b/build.sbt @@ -1,3 +1,4 @@ +import java.util.regex.Pattern import scala.io.Source import scala.util.Using @@ -42,14 +43,51 @@ assemblyMergeStrategy in assembly := { case x => (assemblyMergeStrategy in assembly).value(x) } -val teszt = TaskKey[Unit]("teszt") -teszt := { +val getConfigComments = TaskKey[Unit]("getConfigComments") +getConfigComments := { val sv = (Compile / sources).value + //val cdataRegex = Pattern.compile("(?:def|val|var) \\w*ConfigData\\w*(?:\\[\\w+])? (\\w+)") + val cdataRegex = Pattern.compile("(?:def|val|var) (\\w+)(?::[^=]+)? = get(?:I)?Config") + val clRegex = Pattern.compile("class (\\w+) extends (\\w+)") for (file <- sv) { Using(Source.fromFile(file)) { src => + var pkg: String = null + var cl: String = null + var comment: String = null + var justCommented: Boolean = false + var isComponent: Boolean = false for (line <- src.getLines) { - if (line.contains("class")) - println(line + "") + val clMatcher = clRegex.matcher(line) + if (line.startsWith("package")) { + pkg = line.substring("package ".length) + //println("Found package: " + pkg) + } else if (line.contains("class") && pkg != null && cl == null && clMatcher.find()) { //First occurrence + //cl = line.substring(line.indexOf("class") + "class ".length) + cl = clMatcher.group(1) + isComponent = clMatcher.group(2).contains("Component") + //println("Found class: " + cl) + } else if (line.contains("/**") && cl != null) { + comment = "" + justCommented = false + //println("Found comment start") + } else if (line.contains("*/") && comment != null) { + justCommented = true + //println("Found comment end") + } else if (comment != null) { + if (justCommented) { + //println("Just commented") + //println(s"line: $line") + val matcher = cdataRegex.matcher(line) + if (matcher.find()) + println(s"$pkg.$cl.${matcher.group(1)} comment:\n" + comment) + justCommented = false + comment = null + } + else { + comment += line.replaceFirst("^\\s*\\*\\s+", "") + //println("Adding to comment") + } + } } }.recover[Unit]({ case t => t.printStackTrace() }) } diff --git a/project/build.sbt b/project/build.sbt index e8c3569..1b60820 100644 --- a/project/build.sbt +++ b/project/build.sbt @@ -1,2 +1,2 @@ -lazy val commenter = RootProject(file("../commenter")) -lazy val root = (project in file(".")).dependsOn(commenter) +//lazy val commenter = RootProject(file("../commenter")) +//lazy val root = (project in file(".")).dependsOn(commenter) From 74bce1ecf97f254d5bb690f03c7d03377efad25c Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 6 Apr 2021 01:05:58 +0200 Subject: [PATCH 15/23] Save config comments (not all of them apparently) --- build.sbt | 36 +++++++++++++++--------------------- project/build.sbt | 4 ++++ 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/build.sbt b/build.sbt index a9b0272..d555667 100644 --- a/build.sbt +++ b/build.sbt @@ -1,3 +1,5 @@ +import org.bukkit.configuration.file.YamlConfiguration + import java.util.regex.Pattern import scala.io.Source import scala.util.Using @@ -46,49 +48,41 @@ assemblyMergeStrategy in assembly := { val getConfigComments = TaskKey[Unit]("getConfigComments") getConfigComments := { val sv = (Compile / sources).value - //val cdataRegex = Pattern.compile("(?:def|val|var) \\w*ConfigData\\w*(?:\\[\\w+])? (\\w+)") val cdataRegex = Pattern.compile("(?:def|val|var) (\\w+)(?::[^=]+)? = get(?:I)?Config") val clRegex = Pattern.compile("class (\\w+) extends (\\w+)") + val config = new YamlConfiguration() for (file <- sv) { Using(Source.fromFile(file)) { src => var pkg: String = null - var cl: String = null + var clKey: String = null var comment: String = null var justCommented: Boolean = false - var isComponent: Boolean = false for (line <- src.getLines) { val clMatcher = clRegex.matcher(line) if (line.startsWith("package")) { pkg = line.substring("package ".length) - //println("Found package: " + pkg) - } else if (line.contains("class") && pkg != null && cl == null && clMatcher.find()) { //First occurrence - //cl = line.substring(line.indexOf("class") + "class ".length) - cl = clMatcher.group(1) - isComponent = clMatcher.group(2).contains("Component") - //println("Found class: " + cl) - } else if (line.contains("/**") && cl != null) { + } else if (line.contains("class") && pkg != null && clKey == null && clMatcher.find()) { //First occurrence + clKey = if (clMatcher.group(2).contains("Component")) + "component." + clMatcher.group(1) + else + "global" + } else if (line.contains("/**") && clKey != null) { comment = "" justCommented = false - //println("Found comment start") - } else if (line.contains("*/") && comment != null) { + } else if (line.contains("*/") && comment != null) justCommented = true - //println("Found comment end") - } else if (comment != null) { + else if (comment != null) { if (justCommented) { - //println("Just commented") - //println(s"line: $line") val matcher = cdataRegex.matcher(line) if (matcher.find()) - println(s"$pkg.$cl.${matcher.group(1)} comment:\n" + comment) + config.set(s"$clKey.${matcher.group(1)}", comment.trim) justCommented = false comment = null } - else { - comment += line.replaceFirst("^\\s*\\*\\s+", "") - //println("Adding to comment") - } + else comment += line.replaceFirst("^\\s*\\*\\s+", "") + "\n" } } + config.save("configHelp.yml") }.recover[Unit]({ case t => t.printStackTrace() }) } } diff --git a/project/build.sbt b/project/build.sbt index 1b60820..c98cb35 100644 --- a/project/build.sbt +++ b/project/build.sbt @@ -1,2 +1,6 @@ //lazy val commenter = RootProject(file("../commenter")) //lazy val root = (project in file(".")).dependsOn(commenter) + +resolvers += Resolver.mavenLocal + +libraryDependencies += "org.spigotmc" % "spigot-api" % "1.12.2-R0.1-SNAPSHOT" From ad3bd451ba03a9b71c12e3327e22510fbced0d96 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 6 Apr 2021 02:25:23 +0200 Subject: [PATCH 16/23] Save all of the config comments and include in JAR --- build.sbt | 43 +++++++++++++------ .../mcchat/MinecraftChatModule.scala | 5 +-- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/build.sbt b/build.sbt index d555667..8909643 100644 --- a/build.sbt +++ b/build.sbt @@ -45,44 +45,61 @@ assemblyMergeStrategy in assembly := { case x => (assemblyMergeStrategy in assembly).value(x) } -val getConfigComments = TaskKey[Unit]("getConfigComments") -getConfigComments := { +val saveConfigComments = TaskKey[Seq[File]]("saveConfigComments") +saveConfigComments := { val sv = (Compile / sources).value - val cdataRegex = Pattern.compile("(?:def|val|var) (\\w+)(?::[^=]+)? = get(?:I)?Config") + val cdataRegex = Pattern.compile("(?:def|val|var) (\\w+)(?::[^=]+)? = (?:(?:get(?:I)?Config)|(?:DPUtils.\\w+Data))") //Hack: DPUtils val clRegex = Pattern.compile("class (\\w+) extends (\\w+)") + val objRegex = Pattern.compile("object (\\w+)") val config = new YamlConfiguration() for (file <- sv) { Using(Source.fromFile(file)) { src => - var pkg: String = null var clKey: String = null var comment: String = null var justCommented: Boolean = false for (line <- src.getLines) { val clMatcher = clRegex.matcher(line) - if (line.startsWith("package")) { - pkg = line.substring("package ".length) - } else if (line.contains("class") && pkg != null && clKey == null && clMatcher.find()) { //First occurrence + if (clKey == null && clMatcher.find()) { //First occurrence clKey = if (clMatcher.group(2).contains("Component")) - "component." + clMatcher.group(1) + "components." + clMatcher.group(1) else "global" - } else if (line.contains("/**") && clKey != null) { + /*println("Class: "+clKey) + println("Comment: "+comment) + println("Just commented: "+justCommented) + if (comment != null) { //Not checking justCommented because the object may have the comment and not extend anything + config.set(s"$clKey.generalDescriptionInsteadOfAConfig", comment.trim) + justCommented = false + comment = null + println("Found class comment for " + clKey) + }*/ + } else if (line.contains("/**")) { comment = "" justCommented = false } else if (line.contains("*/") && comment != null) justCommented = true else if (comment != null) { if (justCommented) { - val matcher = cdataRegex.matcher(line) - if (matcher.find()) - config.set(s"$clKey.${matcher.group(1)}", comment.trim) + if (clKey != null) { + val matcher = cdataRegex.matcher(line) + if (matcher.find()) + config.set(s"$clKey.${matcher.group(1)}", comment.trim) + } + else { + val matcher = objRegex.matcher(line) + if (matcher.find()) + config.set(s"${matcher.group(1)}.generalDescriptionInsteadOfAConfig", comment.trim) + } justCommented = false comment = null } else comment += line.replaceFirst("^\\s*\\*\\s+", "") + "\n" } } - config.save("configHelp.yml") + config.save("target/configHelp.yml") }.recover[Unit]({ case t => t.printStackTrace() }) } + Seq(file("target/configHelp.yml")) } + +resourceGenerators in Compile += saveConfigComments diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala index d032ea3..77e35d5 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala @@ -38,9 +38,8 @@ class MinecraftChatModule extends Component[DiscordPlugin] { /** * A list of commands that can be used in public chats - Warning: Some plugins will treat players as OPs, always test before allowing a command! */ - val whitelistedCommands: ConfigData[util.ArrayList[String]] = - getConfig.getData("whitelistedCommands", - () => Lists.newArrayList("list", "u", "shrug", "tableflip", "unflip", "mwiki", "yeehaw", "lenny", "rp", "plugins")) + val whitelistedCommands: ConfigData[util.ArrayList[String]] = getConfig.getData("whitelistedCommands", + () => Lists.newArrayList("list", "u", "shrug", "tableflip", "unflip", "mwiki", "yeehaw", "lenny", "rp", "plugins")) /** * The channel to use as the public Minecraft chat - everything public gets broadcasted here From fd63e995ff7cbb1845577707ce745c7e1984efa7 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 6 Apr 2021 22:32:12 +0200 Subject: [PATCH 17/23] Add command parameter name saving --- build.sbt | 103 +++++++++++++++-------- commenter/.idea/.gitignore | 8 -- commenter/build.sbt | 10 --- commenter/src/main/scala/Commenter.scala | 18 ---- 4 files changed, 68 insertions(+), 71 deletions(-) delete mode 100644 commenter/.idea/.gitignore delete mode 100644 commenter/build.sbt delete mode 100644 commenter/src/main/scala/Commenter.scala diff --git a/build.sbt b/build.sbt index 8909643..8b710ca 100644 --- a/build.sbt +++ b/build.sbt @@ -51,50 +51,83 @@ saveConfigComments := { val cdataRegex = Pattern.compile("(?:def|val|var) (\\w+)(?::[^=]+)? = (?:(?:get(?:I)?Config)|(?:DPUtils.\\w+Data))") //Hack: DPUtils val clRegex = Pattern.compile("class (\\w+) extends (\\w+)") val objRegex = Pattern.compile("object (\\w+)") + val subRegex = Pattern.compile("def `?(\\w+)`?\\((?:((?:\\w|\\d)+): ((?:\\w|[\\[\\].]|\\d)+),?\\s*)+\\)") val config = new YamlConfiguration() + + def getConfigComments(line: String, clKey: String, comment: String, justCommented: Boolean): (String, String, Boolean) = { + val clMatcher = clRegex.matcher(line) + if (clKey == null && clMatcher.find()) { //First occurrence + (if (clMatcher.group(2).contains("Component")) + "components." + clMatcher.group(1) + else "global", comment, justCommented) + } else if (line.contains("/**")) { + (clKey, "", false) + } else if (line.contains("*/") && comment != null) + (clKey, comment, true) + else if (comment != null) { + if (justCommented) { + if (clKey != null) { + val matcher = cdataRegex.matcher(line) + if (matcher.find()) + config.set(s"$clKey.${matcher.group(1)}", comment.trim) + } + else { + val matcher = objRegex.matcher(line) + if (matcher.find()) { + val clName = matcher.group(1) + val compKey = if (clName.contains("Module")) s"component.$clName" + else if (clName.contains("Plugin")) "global" + else null + if (compKey != null) + config.set(s"${compKey}.generalDescriptionInsteadOfAConfig", comment.trim) + } + } + (clKey, null, false) + } + else (clKey, comment + line.replaceFirst("^\\s*\\*\\s+", "") + "\n", justCommented) + } + else (clKey, comment, justCommented) + } + for (file <- sv) { Using(Source.fromFile(file)) { src => var clKey: String = null var comment: String = null var justCommented: Boolean = false + + var subCommand = false + var pkg: String = null + var clName: String = null for (line <- src.getLines) { + val (clk, c, jc) = getConfigComments(line, clKey, comment, justCommented) + clKey = clk; comment = c; justCommented = jc + + val objMatcher = objRegex.matcher(line) val clMatcher = clRegex.matcher(line) - if (clKey == null && clMatcher.find()) { //First occurrence - clKey = if (clMatcher.group(2).contains("Component")) - "components." + clMatcher.group(1) - else - "global" - /*println("Class: "+clKey) - println("Comment: "+comment) - println("Just commented: "+justCommented) - if (comment != null) { //Not checking justCommented because the object may have the comment and not extend anything - config.set(s"$clKey.generalDescriptionInsteadOfAConfig", comment.trim) - justCommented = false - comment = null - println("Found class comment for " + clKey) - }*/ - } else if (line.contains("/**")) { - comment = "" - justCommented = false - } else if (line.contains("*/") && comment != null) - justCommented = true - else if (comment != null) { - if (justCommented) { - if (clKey != null) { - val matcher = cdataRegex.matcher(line) - if (matcher.find()) - config.set(s"$clKey.${matcher.group(1)}", comment.trim) - } - else { - val matcher = objRegex.matcher(line) - if (matcher.find()) - config.set(s"${matcher.group(1)}.generalDescriptionInsteadOfAConfig", comment.trim) - } - justCommented = false - comment = null + if (pkg == null && line.startsWith("package ")) + pkg = line.substring("package ".length) + /*else if (clName == null && (line.contains("object") || line.contains("class")) + && !line.contains("import")) { + val i = line.indexOf("class") + val j = if (i == -1) line.indexOf("object") + "object ".length else i + "class ".length + clName = line.substring(j) + }*/ + else if (clName == null && objMatcher.find()) + clName = objMatcher.group(1) + else if (clName == null && clMatcher.find()) + clName = clMatcher.group(1) + val subMatcher = subRegex.matcher(line) + val sub = line.contains("@Subcommand") || line.contains("@Command2.Subcommand") + if (subCommand || sub) //This line or the previous one had the annotation + if (subMatcher.find()) { + val groups = (2 to subMatcher.groupCount()).map(subMatcher.group) + val pairs = for (i <- groups.indices by 2) yield (groups(i), groups(i + 1)) + val mname = subMatcher.group(1) + print(s"$pkg.$clName.$mname(") + for ((name, ty) <- pairs) print(s"$name: $ty, ") + println(")") } - else comment += line.replaceFirst("^\\s*\\*\\s+", "") + "\n" - } + subCommand = sub } config.save("target/configHelp.yml") }.recover[Unit]({ case t => t.printStackTrace() }) diff --git a/commenter/.idea/.gitignore b/commenter/.idea/.gitignore deleted file mode 100644 index 73f69e0..0000000 --- a/commenter/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/commenter/build.sbt b/commenter/build.sbt deleted file mode 100644 index edf8f98..0000000 --- a/commenter/build.sbt +++ /dev/null @@ -1,10 +0,0 @@ -name := "Chroma-Commenter" -version := "1.0" -organization := "com.github.TBMCPlugins" -scalaVersion := "2.13.4" - -resolvers += Resolver.mavenLocal - -libraryDependencies += "org.reflections" % "reflections" % "0.9.12" -libraryDependencies += "com.github.TBMCPlugins.ChromaCore" % "ButtonProcessor" % "master-SNAPSHOT" -libraryDependencies += "com.github.TBMCPlugins.ChromaCore" % "Chroma-Core" % "v1.0.0" diff --git a/commenter/src/main/scala/Commenter.scala b/commenter/src/main/scala/Commenter.scala deleted file mode 100644 index e9c653d..0000000 --- a/commenter/src/main/scala/Commenter.scala +++ /dev/null @@ -1,18 +0,0 @@ -import buttondevteam.buttonproc.HasConfig -import buttondevteam.lib.architecture.ConfigData -import org.reflections.Reflections - -import scala.jdk.javaapi.CollectionConverters.asScala - -object Commenter extends App { - val ref = new Reflections("buttondevteam.discordplugin") - val types = asScala(ref.getTypesAnnotatedWith(classOf[HasConfig], true)) - for (ty <- types) { - val path = if (ty.getAnnotation(classOf[HasConfig]).global()) "global" - else s"components.${ty.getSimpleName}" - val cdcl = classOf[ConfigData[_]] - ty.getDeclaredMethods.filter(m => cdcl.isAssignableFrom(m.getReturnType)) - .concat(ty.getDeclaredFields.filter(f => cdcl.isAssignableFrom(f.getType))) - .map(path + "." + _.getName) - } -} From c49fac5ce51938fbfec53f6c9018d46e19f7eca6 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 6 Apr 2021 23:17:31 +0200 Subject: [PATCH 18/23] Fix command parameter name saving and other things --- build.sbt | 36 +++++++++---------- .../discordplugin/DiscordPlugin.scala | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/build.sbt b/build.sbt index 8b710ca..d41921a 100644 --- a/build.sbt +++ b/build.sbt @@ -49,10 +49,12 @@ val saveConfigComments = TaskKey[Seq[File]]("saveConfigComments") saveConfigComments := { val sv = (Compile / sources).value val cdataRegex = Pattern.compile("(?:def|val|var) (\\w+)(?::[^=]+)? = (?:(?:get(?:I)?Config)|(?:DPUtils.\\w+Data))") //Hack: DPUtils - val clRegex = Pattern.compile("class (\\w+) extends (\\w+)") + val clRegex = Pattern.compile("class (\\w+).* extends ((?:\\w|\\d)+)") val objRegex = Pattern.compile("object (\\w+)") - val subRegex = Pattern.compile("def `?(\\w+)`?\\((?:((?:\\w|\\d)+): ((?:\\w|[\\[\\].]|\\d)+),?\\s*)+\\)") - val config = new YamlConfiguration() + val subRegex = Pattern.compile("def `?(\\w+)`?") + val subParamRegex = Pattern.compile("((?:\\w|\\d)+): ((?:\\w|\\d)+)") + val configConfig = new YamlConfiguration() + val commandConfig = new YamlConfiguration() def getConfigComments(line: String, clKey: String, comment: String, justCommented: Boolean): (String, String, Boolean) = { val clMatcher = clRegex.matcher(line) @@ -69,7 +71,7 @@ saveConfigComments := { if (clKey != null) { val matcher = cdataRegex.matcher(line) if (matcher.find()) - config.set(s"$clKey.${matcher.group(1)}", comment.trim) + configConfig.set(s"$clKey.${matcher.group(1)}", comment.trim) } else { val matcher = objRegex.matcher(line) @@ -79,7 +81,7 @@ saveConfigComments := { else if (clName.contains("Plugin")) "global" else null if (compKey != null) - config.set(s"${compKey}.generalDescriptionInsteadOfAConfig", comment.trim) + configConfig.set(s"${compKey}.generalDescriptionInsteadOfAConfig", comment.trim) } } (clKey, null, false) @@ -106,33 +108,31 @@ saveConfigComments := { val clMatcher = clRegex.matcher(line) if (pkg == null && line.startsWith("package ")) pkg = line.substring("package ".length) - /*else if (clName == null && (line.contains("object") || line.contains("class")) - && !line.contains("import")) { - val i = line.indexOf("class") - val j = if (i == -1) line.indexOf("object") + "object ".length else i + "class ".length - clName = line.substring(j) - }*/ else if (clName == null && objMatcher.find()) clName = objMatcher.group(1) else if (clName == null && clMatcher.find()) clName = clMatcher.group(1) val subMatcher = subRegex.matcher(line) + val subParamMatcher = subParamRegex.matcher(line) val sub = line.contains("@Subcommand") || line.contains("@Command2.Subcommand") if (subCommand || sub) //This line or the previous one had the annotation if (subMatcher.find()) { - val groups = (2 to subMatcher.groupCount()).map(subMatcher.group) - val pairs = for (i <- groups.indices by 2) yield (groups(i), groups(i + 1)) + /*val groups = (2 to subMatcher.groupCount()).map(subMatcher.group) + val pairs = for (i <- groups.indices by 2) yield (groups(i), groups(i + 1))*/ val mname = subMatcher.group(1) - print(s"$pkg.$clName.$mname(") - for ((name, ty) <- pairs) print(s"$name: $ty, ") - println(")") + val params = Iterator.continually(()).takeWhile(_ => subParamMatcher.find()) + .map(_ => subParamMatcher.group(1)).drop(1) + val section = commandConfig.createSection(s"$pkg.$clName.$mname") + section.set("method", s"$mname()") + section.set("params", params.mkString(" ")) } subCommand = sub } - config.save("target/configHelp.yml") + configConfig.save("target/configHelp.yml") + commandConfig.save("target/commands.yml") }.recover[Unit]({ case t => t.printStackTrace() }) } - Seq(file("target/configHelp.yml")) + Seq(file("target/configHelp.yml"), file("target/commands.yml")) } resourceGenerators in Compile += saveConfigComments diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala index 421e3e2..c133df2 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala @@ -160,8 +160,8 @@ import java.util.Optional private def stopStarting(): Unit = { this synchronized { starting = false + notifyAll() } - notifyAll() } private def handleReady(event: java.util.List[GuildCreateEvent]): Unit = { //System.out.println("Got ready event"); From 7b27ec0ea374c47d4b6f2ec065b9656b25c3cd66 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Thu, 8 Jul 2021 23:04:17 +0200 Subject: [PATCH 19/23] Update to Scala 3.0.0 and update dependencies --- .gitignore | 451 +++++++++--------- build.sbt | 30 +- project/build.properties | 4 +- .../DiscordConnectedPlayer.scala | 6 +- .../discordplugin/DiscordPlugin.scala | 15 +- .../discordplugin/DiscordSender.scala | 2 +- .../announcer/AnnouncerModule.scala | 4 +- .../commands/UserinfoCommand.scala | 4 +- .../discordplugin/fun/FunModule.scala | 2 +- .../discordplugin/listeners/MCListener.scala | 2 +- .../discordplugin/mcchat/MCChatListener.scala | 6 +- .../discordplugin/mcchat/MCChatUtils.scala | 6 +- .../playerfaker/ServerWatcher.scala | 5 +- .../playerfaker/VanillaCommandListener.scala | 6 +- .../VanillaCommandListener14.scala | 6 +- 15 files changed, 276 insertions(+), 273 deletions(-) diff --git a/.gitignore b/.gitignore index 1d227f7..9ab5846 100755 --- a/.gitignore +++ b/.gitignore @@ -1,225 +1,226 @@ -################# -## Eclipse -################# - -*.pydevproject -.metadata/ -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.classpath -.settings/ -.loadpath -target/ -.project - -# External tool builders -.externalToolBuilders/ - -# Locally stored "Eclipse launch configurations" -*.launch - -# CDT-specific -.cproject - -# PDT-specific -.buildpath - - -################# -## Visual Studio -################# - -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.sln.docstates - -# Build results - -[Dd]ebug/ -[Rr]elease/ -x64/ -build/ -[Bb]in/ -[Oo]bj/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -*_i.c -*_p.c -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.log -*.scc - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -*.ncrunch* -.*crunch*.local.xml - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.Publish.xml -*.pubxml -*.publishproj - -# NuGet Packages Directory -## TO!DO: If you have NuGet Package Restore enabled, uncomment the next line -#packages/ - -# Windows Azure Build Output -csx -*.build.csdef - -# Windows Store app package directory -AppPackages/ - -# Others -sql/ -*.Cache -ClientBin/ -[Ss]tyle[Cc]op.* -~$* -*~ -*.dbmdl -*.[Pp]ublish.xml -*.pfx -*.publishsettings - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file to a newer -# Visual Studio version. Backup files are not needed, because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -App_Data/*.mdf -App_Data/*.ldf - -############# -## Windows detritus -############# - -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Mac crap -.DS_Store - - -############# -## Python -############# - -*.py[cod] - -# Packages -*.egg -*.egg-info -dist/ -build/ -eggs/ -parts/ -var/ -sdist/ -develop-eggs/ -.installed.cfg - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.coverage -.tox - -#Translations -*.mo - -#Mr Developer -.mr.developer.cfg -.metadata/* -TheButtonAutoFlair/out/artifacts/Autoflair/Autoflair.jar -*.iml -*.name -.idea/compiler.xml -*.xml - -Token.txt +################# +## Eclipse +################# + +*.pydevproject +.metadata/ +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath +target/ +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml +*.publishproj + +# NuGet Packages Directory +## TO!DO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + + +############# +## Python +############# + +*.py[cod] + +# Packages +*.egg +*.egg-info +dist/ +build/ +eggs/ +parts/ +var/ +sdist/ +develop-eggs/ +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg +.metadata/* +TheButtonAutoFlair/out/artifacts/Autoflair/Autoflair.jar +*.iml +*.name +.idea/compiler.xml +*.xml + +Token.txt +.bsp diff --git a/build.sbt b/build.sbt index d41921a..d139cc5 100644 --- a/build.sbt +++ b/build.sbt @@ -8,7 +8,7 @@ name := "Chroma-Discord" version := "1.1" -scalaVersion := "2.13.5" +scalaVersion := "3.0.0" resolvers += "spigot-repo" at "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" resolvers += "jitpack.io" at "https://jitpack.io" @@ -20,35 +20,37 @@ libraryDependencies ++= Seq( "org.spigotmc." % "spigot" % "1.14.4-R0.1-SNAPSHOT" % Provided, "com.destroystokyo.paper" % "paper" % "1.16.3-R0.1-SNAPSHOT" % Provided, - "com.discord4j" % "discord4j-core" % "3.1.4", - "org.slf4j" % "slf4j-jdk14" % "1.7.21", - "com.vdurmont" % "emoji-java" % "4.0.0", - "org.mockito" % "mockito-core" % "3.5.13", - "io.projectreactor" %% "reactor-scala-extensions" % "0.7.0", + "com.discord4j" % "discord4j-core" % "3.1.6", + "org.slf4j" % "slf4j-jdk14" % "1.7.31", + "com.vdurmont" % "emoji-java" % "5.1.1", + "org.mockito" % "mockito-core" % "3.11.1", + "io.projectreactor" % "reactor-scala-extensions_2.13" % "0.8.0", + // https://mvnrepository.com/artifact/org.immutables/value + "org.immutables" % "value" % "2.8.8" % "provided", "com.github.TBMCPlugins.ChromaCore" % "Chroma-Core" % "v1.0.0" % Provided, "net.ess3" % "EssentialsX" % "2.17.1" % Provided, - "com.github.lucko.LuckPerms" % "bukkit" % "master-SNAPSHOT" % Provided, + "net.luckperms" % "api" % "5.3" % Provided, ) -assemblyJarName in assembly := "Chroma-Discord.jar" -assemblyShadeRules in assembly := Seq( +assembly / assemblyJarName := "Chroma-Discord.jar" +assembly / assemblyShadeRules := Seq( "io.netty", "com.fasterxml", "org.mockito", "org.slf4j" ).map { p => ShadeRule.rename(s"$p.**" -> "btndvtm.dp.@0").inAll } -assemblyMergeStrategy in assembly := { +assembly / assemblyMergeStrategy := { case PathList("META-INF", "io.netty.versions.properties") => MergeStrategy.concat // https://stackoverflow.com/a/55557287/457612 case "module-info.class" => MergeStrategy.discard - case x => (assemblyMergeStrategy in assembly).value(x) + case x => (assembly / assemblyMergeStrategy).value(x) } val saveConfigComments = TaskKey[Seq[File]]("saveConfigComments") saveConfigComments := { val sv = (Compile / sources).value - val cdataRegex = Pattern.compile("(?:def|val|var) (\\w+)(?::[^=]+)? = (?:(?:get(?:I)?Config)|(?:DPUtils.\\w+Data))") //Hack: DPUtils + val cdataRegex = Pattern.compile("(?:def|val|var) (\\w+)(?::[^=]+)? = (?:getI?Config|DPUtils.\\w+Data)") //Hack: DPUtils val clRegex = Pattern.compile("class (\\w+).* extends ((?:\\w|\\d)+)") val objRegex = Pattern.compile("object (\\w+)") val subRegex = Pattern.compile("def `?(\\w+)`?") @@ -81,7 +83,7 @@ saveConfigComments := { else if (clName.contains("Plugin")) "global" else null if (compKey != null) - configConfig.set(s"${compKey}.generalDescriptionInsteadOfAConfig", comment.trim) + configConfig.set(s"$compKey.generalDescriptionInsteadOfAConfig", comment.trim) } } (clKey, null, false) @@ -135,4 +137,4 @@ saveConfigComments := { Seq(file("target/configHelp.yml"), file("target/commands.yml")) } -resourceGenerators in Compile += saveConfigComments +Compile / resourceGenerators += saveConfigComments diff --git a/project/build.properties b/project/build.properties index edb57e9..000265f 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,2 +1,2 @@ -sbt.version=1.4.7 -scala.version=1.13.1 +sbt.version=1.5.4 +scala.version=3.0.0 \ No newline at end of file diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordConnectedPlayer.scala b/src/main/scala/buttondevteam/discordplugin/DiscordConnectedPlayer.scala index fdfd590..71585e4 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordConnectedPlayer.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordConnectedPlayer.scala @@ -4,7 +4,7 @@ import buttondevteam.discordplugin.mcchat.MinecraftChatModule import buttondevteam.discordplugin.playerfaker.{DiscordInventory, VCMDWrapper} import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.channel.MessageChannel -import org.bukkit._ +import org.bukkit.* import org.bukkit.attribute.{Attribute, AttributeInstance, AttributeModifier} import org.bukkit.entity.{Entity, Player} import org.bukkit.event.player.{AsyncPlayerChatEvent, PlayerTeleportEvent} @@ -17,7 +17,7 @@ import org.mockito.{MockSettings, Mockito} import java.lang.reflect.Modifier import java.util -import java.util._ +import java.util.* object DiscordConnectedPlayer { def create(user: User, channel: MessageChannel, uuid: UUID, mcname: String, module: MinecraftChatModule): DiscordConnectedPlayer = @@ -170,7 +170,7 @@ abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel, val u override def getEyeLocation: Location = getLocation - @deprecated override def getMaxHealth = 20 + @deprecated override def getMaxHealth = 20d override def getPlayer: DiscordConnectedPlayer = this diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala index c133df2..62b826a 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala @@ -2,7 +2,7 @@ package buttondevteam.discordplugin import buttondevteam.discordplugin.announcer.AnnouncerModule import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule -import buttondevteam.discordplugin.commands._ +import buttondevteam.discordplugin.commands.* import buttondevteam.discordplugin.exceptions.ExceptionListenerModule import buttondevteam.discordplugin.fun.FunModule import buttondevteam.discordplugin.listeners.{CommonListeners, MCListener} @@ -11,7 +11,7 @@ import buttondevteam.discordplugin.mccommands.DiscordMCCommand import buttondevteam.discordplugin.role.GameRoleModule import buttondevteam.discordplugin.util.{DPState, Timings} import buttondevteam.lib.TBMCCoreAPI -import buttondevteam.lib.architecture._ +import buttondevteam.lib.architecture.* import buttondevteam.lib.player.ChromaGamerBase import com.google.common.io.Files import discord4j.common.util.Snowflake @@ -142,13 +142,10 @@ import java.util.Optional foo(t) }).subscribe((dc: GatewayDiscordClient) => { - def foo(dc: GatewayDiscordClient): Disposable = { //System.out.println("Login successful, got dc: " + dc); - DiscordPlugin.dc = dc //Set to gateway client - dc.on(classOf[ReadyEvent]).map(_.getGuilds.size).flatMap(dc.on(classOf[GuildCreateEvent]).take(_).collectList) - .doOnError(_ => stopStarting()).subscribe(this.handleReady _) // Take all received GuildCreateEvents and make it a List - } - - foo(dc) + DiscordPlugin.dc = dc //Set to gateway client + dc.on(classOf[ReadyEvent]).map(_.getGuilds.size).flatMap(dc.on(classOf[GuildCreateEvent]).take(_).collectList) + .doOnError(_ => stopStarting()).subscribe(this.handleReady _) // Take all received GuildCreateEvents and make it a List + () }) /* All guilds have been received, client is fully connected */ } catch { case e: Exception => diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordSender.scala b/src/main/scala/buttondevteam/discordplugin/DiscordSender.scala index 5e17693..0462990 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordSender.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordSender.scala @@ -18,7 +18,7 @@ class DiscordSender(user: User, channel: MessageChannel, pname: String) extends .map(u => u.getDisplayName))) .getOrElse("Discord user") - def this(user: User, channel: MessageChannel) { + def this(user: User, channel: MessageChannel) = { this(user, channel, null) } diff --git a/src/main/scala/buttondevteam/discordplugin/announcer/AnnouncerModule.scala b/src/main/scala/buttondevteam/discordplugin/announcer/AnnouncerModule.scala index 34c9bb2..2cbb585 100644 --- a/src/main/scala/buttondevteam/discordplugin/announcer/AnnouncerModule.scala +++ b/src/main/scala/buttondevteam/discordplugin/announcer/AnnouncerModule.scala @@ -50,8 +50,8 @@ import scala.annotation.tailrec override protected def disable(): Unit = AnnouncerModule.stop = true @tailrec - private def AnnouncementGetterThreadMethod() { - if (AnnouncerModule.stop) return + private def AnnouncementGetterThreadMethod(): Unit = { + if (AnnouncerModule.stop) return () if (isEnabled) try { //If not enabled, just wait val body = TBMCCoreAPI.DownloadString(subredditURL.get + "/new/.json?limit=10") val json = new JsonParser().parse(body).getAsJsonObject.get("data").getAsJsonObject.get("children").getAsJsonArray diff --git a/src/main/scala/buttondevteam/discordplugin/commands/UserinfoCommand.scala b/src/main/scala/buttondevteam/discordplugin/commands/UserinfoCommand.scala index 7768b44..d21f4f1 100644 --- a/src/main/scala/buttondevteam/discordplugin/commands/UserinfoCommand.scala +++ b/src/main/scala/buttondevteam/discordplugin/commands/UserinfoCommand.scala @@ -28,7 +28,9 @@ class UserinfoCommand extends ICommand2DC { channel.createMessage("The user cannot be found (by name): " + user).subscribe return true } - targets.collectFirst(_.getDiscriminator.equalsIgnoreCase(targettag(1))) + targets.collectFirst { + case user => user.getDiscriminator.equalsIgnoreCase(targettag(1)) + } if (target == null) { channel.createMessage("The user cannot be found (by discriminator): " + user + "(Found " + targets.size + " users with the name.)").subscribe return true diff --git a/src/main/scala/buttondevteam/discordplugin/fun/FunModule.scala b/src/main/scala/buttondevteam/discordplugin/fun/FunModule.scala index 0cd0c6c..0a1a5ea 100644 --- a/src/main/scala/buttondevteam/discordplugin/fun/FunModule.scala +++ b/src/main/scala/buttondevteam/discordplugin/fun/FunModule.scala @@ -87,7 +87,7 @@ object FunModule { .filter(_ => lasttime + 10 < TimeUnit.NANOSECONDS.toHours(System.nanoTime)) //This should stay so it checks this last .flatMap(_ => { lasttime = TimeUnit.NANOSECONDS.toHours(System.nanoTime) - SMono(channel.createMessage((mcs: MessageCreateSpec) => mcs.setContent("Full house!") + SMono(channel.createMessage(_.setContent("Full house!") .setEmbed((ecs: EmbedCreateSpec) => ecs.setImage("https://cdn.discordapp.com/attachments/249295547263877121/249687682618359808/poker-hand-full-house-aces-kings-playing-cards-15553791.png")))) })).subscribe } diff --git a/src/main/scala/buttondevteam/discordplugin/listeners/MCListener.scala b/src/main/scala/buttondevteam/discordplugin/listeners/MCListener.scala index d464d84..f45f78d 100644 --- a/src/main/scala/buttondevteam/discordplugin/listeners/MCListener.scala +++ b/src/main/scala/buttondevteam/discordplugin/listeners/MCListener.scala @@ -37,7 +37,7 @@ class MCListener extends Listener { e.addInfo(pr.getStatus.toString) if (pr.getActivity.isPresent) { val activity = pr.getActivity.get - e.addInfo(activity.getType + ": " + activity.getName) + e.addInfo(s"${activity.getType}: ${activity.getName}") } } diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala index 9b65bb7..f493760 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala @@ -1,10 +1,10 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.ComponentManager -import buttondevteam.discordplugin._ +import buttondevteam.discordplugin.* import buttondevteam.discordplugin.listeners.CommandListener import buttondevteam.discordplugin.playerfaker.{VanillaCommandListener, VanillaCommandListener14, VanillaCommandListener15} -import buttondevteam.lib._ +import buttondevteam.lib.* import buttondevteam.lib.chat.{ChatMessage, TBMCChatAPI} import buttondevteam.lib.player.TBMCPlayer import com.vdurmont.emoji.EmojiParser @@ -44,7 +44,7 @@ object MCChatListener { @FunctionalInterface private trait InterruptibleConsumer[T] { @throws[TimeoutException] @throws[InterruptedException] - def accept(value: T) + def accept(value: T): Unit } } diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala index d285553..bdb8f61 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala @@ -1,8 +1,8 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.{ComponentManager, MainPlugin, component} +import buttondevteam.discordplugin.* import buttondevteam.discordplugin.ChannelconBroadcast.ChannelconBroadcast -import buttondevteam.discordplugin._ import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule import buttondevteam.discordplugin.mcchat.MCChatCustom.CustomLMD import buttondevteam.lib.{TBMCCoreAPI, TBMCSystemChatEvent} @@ -86,7 +86,7 @@ object MCChatUtils { ).filter(MCChatUtils.checkEssentials) //If they can see it .filter(_ => C.incrementAndGet > 0) //Always true .map((p) => DPUtils.sanitizeString(p.getDisplayName)).collect(Collectors.joining(", ")) - s(0) = C + " player" + (if (C.get != 1) "s" else "") + " online" + s(0) = s"$C player${if (C.get != 1) "s" else ""} online" lmd.channel.asInstanceOf[TextChannel].edit((tce: TextChannelEditSpec) => tce.setTopic(String.join("\n----\n", s: _*)).setReason("Player list update")).subscribe //Don't wait } @@ -192,7 +192,7 @@ object MCChatUtils { list.append(action(SMono.just(data.channel))) //TODO: Only store ID? MCChatCustom.lastmsgCustom.filter(clmd => clmd.brtoggles.contains(event.getTarget) && event.shouldSendTo(clmd.dcp)) - .map(clmd => action(SMono.just(clmd.channel))).forEach(elem => { + .map(clmd => action(SMono.just(clmd.channel))).foreach(elem => { list.append(elem) () }) diff --git a/src/main/scala/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala b/src/main/scala/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala index c080d50..8ab2f74 100644 --- a/src/main/scala/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala +++ b/src/main/scala/buttondevteam/discordplugin/playerfaker/ServerWatcher.scala @@ -12,7 +12,7 @@ import org.mockito.invocation.InvocationOnMock import java.lang.reflect.Modifier import java.util -import java.util._ +import java.util.* object ServerWatcher { @@ -62,7 +62,8 @@ class ServerWatcher { @SuppressWarnings("unchecked") var list = (List) method.invoke(origServer, invocation.getArguments()); playerList = new AppendListView<>(list, fakePlayers); } - Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should. - return playerList;*/ case "createProfile" => //Paper's method, casts the player to a CraftPlayer + return playerList;*/ + case "createProfile" => //Paper's method, casts the player to a CraftPlayer if (pc == 2) { val uuid = invocation.getArgument(0) val name = invocation.getArgument(1) diff --git a/src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala b/src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala index c192e52..3622871 100644 --- a/src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala +++ b/src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener.scala @@ -1,7 +1,7 @@ package buttondevteam.discordplugin.playerfaker import buttondevteam.discordplugin.{DiscordSenderBase, IMCPlayer} -import net.minecraft.server.v1_12_R1._ +import net.minecraft.server.v1_12_R1.* import org.bukkit.Bukkit import org.bukkit.craftbukkit.v1_12_R1.command.VanillaCommandWrapper import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer @@ -47,7 +47,7 @@ class VanillaCommandListener[T <: DiscordSenderBase with IMCPlayer[T]] extends I * * @param player The Discord sender player (the wrapper) */ - def this(player: T) { + def this(player: T) = { this() this.player = player this.bukkitplayer = null @@ -59,7 +59,7 @@ class VanillaCommandListener[T <: DiscordSenderBase with IMCPlayer[T]] extends I * @param player The Discord sender player (the wrapper) * @param bukkitplayer The Bukkit player to send the raw message to */ - def this(player: T, bukkitplayer: Player) { + def this(player: T, bukkitplayer: Player) = { this() this.player = player this.bukkitplayer = bukkitplayer diff --git a/src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala b/src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala index ff5e1c0..e7062c8 100644 --- a/src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala +++ b/src/main/scala/buttondevteam/discordplugin/playerfaker/VanillaCommandListener14.scala @@ -1,7 +1,7 @@ package buttondevteam.discordplugin.playerfaker import buttondevteam.discordplugin.{DiscordSenderBase, IMCPlayer} -import net.minecraft.server.v1_14_R1._ +import net.minecraft.server.v1_14_R1.* import org.bukkit.Bukkit import org.bukkit.command.CommandSender import org.bukkit.craftbukkit.v1_14_R1.command.{ProxiedNativeCommandSender, VanillaCommandWrapper} @@ -51,7 +51,7 @@ class VanillaCommandListener14[T <: DiscordSenderBase with IMCPlayer[T]] extends * * @param player The Discord sender player (the wrapper) */ - def this(player: T) { + def this(player: T) = { this() this.player = player this.bukkitplayer = null @@ -63,7 +63,7 @@ class VanillaCommandListener14[T <: DiscordSenderBase with IMCPlayer[T]] extends * @param player The Discord sender player (the wrapper) * @param bukkitplayer The Bukkit player to send the raw message to */ - def this(player: T, bukkitplayer: Player) { + def this(player: T, bukkitplayer: Player) = { this() this.player = player this.bukkitplayer = bukkitplayer From 263c652d68844216b467962392df4aace6767782 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Thu, 26 Aug 2021 02:03:06 +0200 Subject: [PATCH 20/23] Fix subcommand detection, fix returns --- build.sbt | 25 ++++++++++--------- .../discordplugin/mcchat/MCChatUtils.scala | 5 ++-- .../mcchat/MinecraftChatModule.scala | 14 +++++++---- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/build.sbt b/build.sbt index d139cc5..104e354 100644 --- a/build.sbt +++ b/build.sbt @@ -117,18 +117,19 @@ saveConfigComments := { val subMatcher = subRegex.matcher(line) val subParamMatcher = subParamRegex.matcher(line) val sub = line.contains("@Subcommand") || line.contains("@Command2.Subcommand") - if (subCommand || sub) //This line or the previous one had the annotation - if (subMatcher.find()) { - /*val groups = (2 to subMatcher.groupCount()).map(subMatcher.group) - val pairs = for (i <- groups.indices by 2) yield (groups(i), groups(i + 1))*/ - val mname = subMatcher.group(1) - val params = Iterator.continually(()).takeWhile(_ => subParamMatcher.find()) - .map(_ => subParamMatcher.group(1)).drop(1) - val section = commandConfig.createSection(s"$pkg.$clName.$mname") - section.set("method", s"$mname()") - section.set("params", params.mkString(" ")) - } - subCommand = sub + if (sub) subCommand = true + else if (line.contains("}")) subCommand = false + if (subCommand && subMatcher.find()) { + /*val groups = (2 to subMatcher.groupCount()).map(subMatcher.group) + val pairs = for (i <- groups.indices by 2) yield (groups(i), groups(i + 1))*/ + val mname = subMatcher.group(1) + val params = Iterator.continually(()).takeWhile(_ => subParamMatcher.find()) + .map(_ => subParamMatcher.group(1)).drop(1) + val section = commandConfig.createSection(s"$pkg.$clName.$mname") + section.set("method", s"$mname()") + section.set("params", params.mkString(" ")) + subCommand = false + } } configConfig.save("target/configHelp.yml") commandConfig.save("target/commands.yml") diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala index bdb8f61..3f0438d 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala @@ -49,12 +49,13 @@ object MCChatUtils { def updatePlayerList(): Unit = { val mod = getModule - if (mod == null || !mod.showPlayerListOnDC.get) return + if (mod == null || !mod.showPlayerListOnDC.get) return () if (lastmsgdata != null) updatePL(lastmsgdata) MCChatCustom.lastmsgCustom.foreach(MCChatUtils.updatePL) } private def notEnabled = (module == null || !module.disabling) && getModule == null //Allow using things while disabling the module + private def getModule = { if (module == null || !module.isEnabled) module = ComponentManager.getIfEnabled(classOf[MinecraftChatModule]) //If disabled, it will try to get it again because another instance may be enabled - useful for /discord restart @@ -218,7 +219,7 @@ object MCChatUtils { * @param channel The channel to reset in - the process is slightly different for the public, private and custom chats */ def resetLastMessage(channel: Channel): Unit = { - if (notEnabled) return + if (notEnabled) return () if (channel.getId.asLong == module.chatChannel.get.asLong) { if (lastmsgdata == null) lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono.block(), null) else lastmsgdata.message = null diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala index 77e35d5..fa2c467 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala @@ -101,7 +101,7 @@ class MinecraftChatModule extends Component[DiscordPlugin] { final private val channelconCommand = new ChannelconCommand(this) override protected def enable(): Unit = { - if (DPUtils.disableIfConfigErrorRes(this, chatChannel, chatChannelMono)) return + if (DPUtils.disableIfConfigErrorRes(this, chatChannel, chatChannelMono)) return () listener = new MCChatListener(this) TBMCCoreAPI.RegisterEventsForExceptions(listener, getPlugin) TBMCCoreAPI.RegisterEventsForExceptions(new MCListener(this), getPlugin) //These get undone if restarting/resetting - it will ignore events if disabled @@ -219,10 +219,14 @@ class MinecraftChatModule extends Component[DiscordPlugin] { * It will block to make sure all messages are sent */ private def sendStateMessage(color: Color, message: String) = - MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(spec => spec.setColor(color).setTitle(message)).^^()), - ChannelconBroadcast.RESTART, hookmsg = false).block() + MCChatUtils.forCustomAndAllMCChat(_.flatMap( + _.createEmbed(_.setColor(color).setTitle(message)).^^() + .onErrorResume(_ => SMono.empty) + ), ChannelconBroadcast.RESTART, hookmsg = false).block() private def sendStateMessage(color: Color, message: String, extra: String) = - MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(_.setColor(color).setTitle(message).setDescription(extra).^^()).^^() - .onErrorResume(_ => SMono.empty)), ChannelconBroadcast.RESTART, hookmsg = false).block() + MCChatUtils.forCustomAndAllMCChat(_.flatMap( + _.createEmbed(_.setColor(color).setTitle(message).setDescription(extra).^^()).^^() + .onErrorResume(_ => SMono.empty) + ), ChannelconBroadcast.RESTART, hookmsg = false).block() } \ No newline at end of file From 9f3ca37929882d35bc95402131b2c68ed0f76d68 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Thu, 26 Aug 2021 02:03:06 +0200 Subject: [PATCH 21/23] Fix compilation issues and startup exceptions - Removed test class because it errors, and I don't know how to fix it - Updated dependencies - Fixed SMono.whenDelayError() causing a crash on Scala 3.0.0 - Fixed subscribe method calls being ambiguous - Fixed returns returning the wrong things - Converted onGetInfo() to use no returns --- build.sbt | 6 +-- project/build.properties | 2 +- .../discordplugin/ChromaBot.scala | 4 +- .../buttondevteam/discordplugin/DPUtils.scala | 6 +-- .../discordplugin/DiscordPlugin.scala | 18 ++++----- .../discordplugin/DiscordSenderBase.scala | 4 +- .../announcer/AnnouncerModule.scala | 8 ++-- .../GeneralEventBroadcasterModule.scala | 2 +- .../broadcaster/PlayerListWatcher.scala | 2 +- .../commands/Command2DCSender.scala | 4 +- .../commands/ConnectCommand.scala | 8 ++-- .../commands/UserinfoCommand.scala | 16 ++++---- .../exceptions/DebugMessageListener.scala | 6 +-- .../exceptions/ExceptionListenerModule.scala | 12 +++--- .../discordplugin/fun/FunModule.scala | 16 ++++---- .../listeners/CommonListeners.scala | 2 +- .../discordplugin/listeners/MCListener.scala | 35 ++++++++-------- .../mcchat/ChannelconCommand.scala | 28 ++++++------- .../discordplugin/mcchat/MCChatCommand.scala | 4 +- .../discordplugin/mcchat/MCChatListener.scala | 33 +++++++-------- .../discordplugin/mcchat/MCChatUtils.scala | 31 +++++++------- .../discordplugin/mcchat/MCListener.scala | 36 ++++++++--------- .../mcchat/MinecraftChatModule.scala | 2 +- .../mccommands/DiscordMCCommand.scala | 6 +-- .../discordplugin/role/GameRoleModule.scala | 16 ++++---- .../discordplugin/role/RoleCommand.scala | 4 +- .../buttondevteam/DiscordPlugin/AppTest.java | 40 ------------------- 27 files changed, 156 insertions(+), 195 deletions(-) delete mode 100755 src/test/java/buttondevteam/DiscordPlugin/AppTest.java diff --git a/build.sbt b/build.sbt index 104e354..19c60c3 100644 --- a/build.sbt +++ b/build.sbt @@ -20,10 +20,10 @@ libraryDependencies ++= Seq( "org.spigotmc." % "spigot" % "1.14.4-R0.1-SNAPSHOT" % Provided, "com.destroystokyo.paper" % "paper" % "1.16.3-R0.1-SNAPSHOT" % Provided, - "com.discord4j" % "discord4j-core" % "3.1.6", - "org.slf4j" % "slf4j-jdk14" % "1.7.31", + "com.discord4j" % "discord4j-core" % "3.2.1", + "org.slf4j" % "slf4j-jdk14" % "1.7.32", "com.vdurmont" % "emoji-java" % "5.1.1", - "org.mockito" % "mockito-core" % "3.11.1", + "org.mockito" % "mockito-core" % "4.2.0", "io.projectreactor" % "reactor-scala-extensions_2.13" % "0.8.0", // https://mvnrepository.com/artifact/org.immutables/value "org.immutables" % "value" % "2.8.8" % "provided", diff --git a/project/build.properties b/project/build.properties index 000265f..7cde0be 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1,2 +1,2 @@ -sbt.version=1.5.4 +sbt.version=1.5.8 scala.version=3.0.0 \ No newline at end of file diff --git a/src/main/scala/buttondevteam/discordplugin/ChromaBot.scala b/src/main/scala/buttondevteam/discordplugin/ChromaBot.scala index c604c48..af6ae9f 100644 --- a/src/main/scala/buttondevteam/discordplugin/ChromaBot.scala +++ b/src/main/scala/buttondevteam/discordplugin/ChromaBot.scala @@ -21,7 +21,7 @@ object ChromaBot { * @param message The message to send, duh (use [[MessageChannel.createMessage]]) */ def sendMessage(message: SMono[MessageChannel] => SMono[Message]): Unit = - MCChatUtils.forPublicPrivateChat(message).subscribe + MCChatUtils.forPublicPrivateChat(message).subscribe() /** * Send a message to the chat channels, private chats and custom chats. @@ -30,7 +30,7 @@ object ChromaBot { * @param toggle The toggle type for channelcon */ def sendMessageCustomAsWell(message: SMono[MessageChannel] => SMono[Message], @Nullable toggle: ChannelconBroadcast): Unit = - MCChatUtils.forCustomAndAllMCChat(message.apply, toggle, hookmsg = false).subscribe + MCChatUtils.forCustomAndAllMCChat(message.apply, toggle, hookmsg = false).subscribe() def updatePlayerList(): Unit = MCChatUtils.updatePlayerList() diff --git a/src/main/scala/buttondevteam/discordplugin/DPUtils.scala b/src/main/scala/buttondevteam/discordplugin/DPUtils.scala index 455ff46..b024832 100644 --- a/src/main/scala/buttondevteam/discordplugin/DPUtils.scala +++ b/src/main/scala/buttondevteam/discordplugin/DPUtils.scala @@ -5,7 +5,7 @@ import buttondevteam.lib.architecture.{Component, ConfigData, IHaveConfig, ReadO import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.`object`.entity.{Guild, Message, Role} -import discord4j.core.spec.{EmbedCreateSpec, Spec} +import discord4j.core.spec.legacy.{LegacyEmbedCreateSpec, LegacySpec} import reactor.core.publisher.{Flux, Mono} import reactor.core.scala.publisher.{SFlux, SMono} @@ -19,7 +19,7 @@ object DPUtils { private val URL_PATTERN = Pattern.compile("https?://\\S*") private val FORMAT_PATTERN = Pattern.compile("[*_~]") - def embedWithHead(ecs: EmbedCreateSpec, displayname: String, playername: String, profileUrl: String): EmbedCreateSpec = + def embedWithHead(ecs: LegacyEmbedCreateSpec, displayname: String, playername: String, profileUrl: String): LegacyEmbedCreateSpec = ecs.setAuthor(displayname, profileUrl, "https://minotar.net/avatar/" + playername + "/32.png") /** @@ -216,7 +216,7 @@ object DPUtils { def ^^(): SFlux[T] = SFlux(flux) } - implicit class SpecExtensions[T <: Spec[_]](spec: T) { + implicit class SpecExtensions[T <: LegacySpec[_]](spec: T) { def ^^(): Unit = () } diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala index 62b826a..921a59b 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala @@ -16,7 +16,7 @@ import buttondevteam.lib.player.ChromaGamerBase import com.google.common.io.Files import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.{ApplicationInfo, Guild, Role} -import discord4j.core.`object`.presence.{Activity, Presence} +import discord4j.core.`object`.presence.{Activity, ClientActivity, ClientPresence, Presence} import discord4j.core.`object`.reaction.ReactionEmoji import discord4j.core.event.domain.guild.GuildCreateEvent import discord4j.core.event.domain.lifecycle.ReadyEvent @@ -124,15 +124,15 @@ import java.util.Optional getLogger.severe("Token not found! Please set it in private.yml then do /discord restart") getLogger.severe("You need to have a bot account to use with your server.") getLogger.severe("If you don't have one, go to https://discordapp.com/developers/applications/ and create an application, then create a bot for it and copy the bot token.") - return + return () } } starting = true //System.out.println("This line should show up for sure"); val cb = DiscordClientBuilder.create(token).build.gateway //System.out.println("Got gateway bootstrap"); - cb.setInitialStatus((si: ShardInfo) => Presence.doNotDisturb(Activity.playing("booting"))) - cb.setStoreService(new JdkStoreService) //The default doesn't work for some reason - it's waaay faster now + cb.setInitialPresence((si: ShardInfo) => ClientPresence.doNotDisturb(ClientActivity.playing("booting"))) + //cb.setStore(new JdkStoreService) //The default doesn't work for some reason - it's waaay faster now //System.out.println("Initial status and store service set"); cb.login.doOnError((t: Throwable) => { def foo(t: Throwable): Unit = { @@ -165,8 +165,8 @@ import java.util.Optional try { if (DiscordPlugin.mainServer != null) { //This is not the first ready event getLogger.info("Ready event already handled") //TODO: It should probably handle disconnections - DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe //Update from the initial presence - return + DiscordPlugin.dc.updatePresence(ClientPresence.online(ClientActivity.playing("Minecraft"))).subscribe //Update from the initial presence + return () } DiscordPlugin.mainServer = mainServer.get.orNull //Shouldn't change afterwards if (DiscordPlugin.mainServer == null) { @@ -217,8 +217,8 @@ import java.util.Optional logWatcher = blw Interactions.create().onCommand("teszt", Interactions.createHandler() .guild(gi => gi.acknowledge().withFollowup(_.createFollowupMessage("Teszt"))).build()); - if (!TBMCCoreAPI.IsTestServer) DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe - else DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("testing"))).subscribe + if (!TBMCCoreAPI.IsTestServer) DiscordPlugin.dc.updatePresence(ClientPresence.online(ClientActivity.playing("Minecraft"))).subscribe() + else DiscordPlugin.dc.updatePresence(ClientPresence.online(ClientActivity.playing("testing"))).subscribe() getLogger.info("Loaded!") } catch { case e: Exception => @@ -247,7 +247,7 @@ import java.util.Optional override def pluginDisable(): Unit = { val timings = new Timings timings.printElapsed("Actual disable start (logout)") - if (!ChromaBot.enabled) return + if (!ChromaBot.enabled) return () try { DiscordPlugin.SafeMode = true // Stop interacting with Discord ChromaBot.enabled = false diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala b/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala index 63592fa..c5625ad 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala @@ -41,7 +41,7 @@ abstract class DiscordSenderBase protected(var user: User, var channel: MessageC override def sendMessage(message: String): Unit = try { val broadcast = new Exception().getStackTrace()(2).getMethodName.contains("broadcast") if (broadcast) { //We're catching broadcasts using the Bukkit event - return + return () } val sendmsg = DPUtils.sanitizeString(message) this synchronized { @@ -49,7 +49,7 @@ abstract class DiscordSenderBase protected(var user: User, var channel: MessageC if (sendtask == null) sendtask = Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => { def foo(): Unit = { channel.createMessage((if (user != null) user.getMention + "\n" - else "") + msgtosend.trim).subscribe + else "") + msgtosend.trim).subscribe() sendtask = null msgtosend = "" } diff --git a/src/main/scala/buttondevteam/discordplugin/announcer/AnnouncerModule.scala b/src/main/scala/buttondevteam/discordplugin/announcer/AnnouncerModule.scala index 2cbb585..5c72574 100644 --- a/src/main/scala/buttondevteam/discordplugin/announcer/AnnouncerModule.scala +++ b/src/main/scala/buttondevteam/discordplugin/announcer/AnnouncerModule.scala @@ -38,10 +38,10 @@ import scala.annotation.tailrec final private val subredditURL = getConfig.getData("subredditURL", "https://www.reddit.com/r/ChromaGamers") override protected def enable(): Unit = { - if (DPUtils.disableIfConfigError(this, channel, modChannel)) return + if (DPUtils.disableIfConfigError(this, channel, modChannel)) return () AnnouncerModule.stop = false //If not the first time - val kp: Short = keepPinned.get - if (kp <= 0) return + val kp = keepPinned.get + if (kp <= 0) return () val msgs = channel.get.flatMapMany(_.getPinnedMessages).takeLast(kp) msgs.subscribe(_.unpin) new Thread(() => this.AnnouncementGetterThreadMethod()).start() @@ -85,7 +85,7 @@ import scala.annotation.tailrec } def sendMsg(ch: SMono[MessageChannel], msg: String) = - ch.asJava().flatMap(c => c.createMessage(msg)).flatMap(_.pin).subscribe + ch.asJava().flatMap(c => c.createMessage(msg)).flatMap(_.pin).subscribe() if (msgsb.nonEmpty) sendMsg(channel.get(), msgsb.toString()) if (modmsgsb.nonEmpty) sendMsg(modChannel.get(), modmsgsb.toString()) diff --git a/src/main/scala/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala b/src/main/scala/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala index 99e0ac4..0072c77 100644 --- a/src/main/scala/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala +++ b/src/main/scala/buttondevteam/discordplugin/broadcaster/GeneralEventBroadcasterModule.scala @@ -27,7 +27,7 @@ import buttondevteam.lib.architecture.{Component, ComponentMetadata} } override protected def disable(): Unit = try { - if (!GeneralEventBroadcasterModule.hooked) return + if (!GeneralEventBroadcasterModule.hooked) return () if (PlayerListWatcher.hookUpDown(false, this)) log("Finished unhooking the player list!") else log("Didn't have the player list hooked.") GeneralEventBroadcasterModule.hooked = false diff --git a/src/main/scala/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala b/src/main/scala/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala index 55f409c..49c2a01 100644 --- a/src/main/scala/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala +++ b/src/main/scala/buttondevteam/discordplugin/broadcaster/PlayerListWatcher.scala @@ -130,7 +130,7 @@ object PlayerListWatcher { if (packet.getClass eq ppoc) { val msgf = ppoc.getDeclaredField("a") msgf.setAccessible(true) - MCChatUtils.forPublicPrivateChat(MCChatUtils.send(toPlainText.invoke(msgf.get(packet)).asInstanceOf[String])).subscribe + MCChatUtils.forPublicPrivateChat(MCChatUtils.send(toPlainText.invoke(msgf.get(packet)).asInstanceOf[String])).subscribe() } } catch { case e: Exception => diff --git a/src/main/scala/buttondevteam/discordplugin/commands/Command2DCSender.scala b/src/main/scala/buttondevteam/discordplugin/commands/Command2DCSender.scala index b31d3bf..5cac3bf 100644 --- a/src/main/scala/buttondevteam/discordplugin/commands/Command2DCSender.scala +++ b/src/main/scala/buttondevteam/discordplugin/commands/Command2DCSender.scala @@ -9,10 +9,10 @@ class Command2DCSender(val message: Message) extends Command2Sender { def getMessage: Message = this.message override def sendMessage(message: String): Unit = { - if (message.isEmpty) return + if (message.isEmpty) return () var msg = DPUtils.sanitizeString(message) msg = Character.toLowerCase(message.charAt(0)) + message.substring(1) - this.message.getChannel.flatMap((ch: MessageChannel) => ch.createMessage(this.message.getAuthor.map((u: User) => DPUtils.nickMention(u.getId) + ", ").orElse("") + msg)).subscribe + this.message.getChannel.flatMap((ch: MessageChannel) => ch.createMessage(this.message.getAuthor.map((u: User) => DPUtils.nickMention(u.getId) + ", ").orElse("") + msg)).subscribe() } override def sendMessage(message: Array[String]): Unit = sendMessage(String.join("\n", message: _*)) diff --git a/src/main/scala/buttondevteam/discordplugin/commands/ConnectCommand.scala b/src/main/scala/buttondevteam/discordplugin/commands/ConnectCommand.scala index 17b46d7..dc05087 100644 --- a/src/main/scala/buttondevteam/discordplugin/commands/ConnectCommand.scala +++ b/src/main/scala/buttondevteam/discordplugin/commands/ConnectCommand.scala @@ -26,23 +26,23 @@ import org.bukkit.entity.Player val author = message.getAuthor.orElse(null) if (author == null || channel == null) return true if (ConnectCommand.WaitingToConnect.inverse.containsKey(author.getId.asString)) { - channel.createMessage("Replacing " + ConnectCommand.WaitingToConnect.inverse.get(author.getId.asString) + " with " + Minecraftname).subscribe + channel.createMessage("Replacing " + ConnectCommand.WaitingToConnect.inverse.get(author.getId.asString) + " with " + Minecraftname).subscribe() ConnectCommand.WaitingToConnect.inverse.remove(author.getId.asString) } //noinspection ScalaDeprecation val p = Bukkit.getOfflinePlayer(Minecraftname) if (p == null) { - channel.createMessage("The specified Minecraft player cannot be found").subscribe + channel.createMessage("The specified Minecraft player cannot be found").subscribe() return true } val pl = TBMCPlayerBase.getPlayer(p.getUniqueId, classOf[TBMCPlayer]) val dp = pl.getAs(classOf[DiscordPlayer]) if (dp != null && author.getId.asString == dp.getDiscordID) { - channel.createMessage("You already have this account connected.").subscribe + channel.createMessage("You already have this account connected.").subscribe() return true } ConnectCommand.WaitingToConnect.put(p.getName, author.getId.asString) - channel.createMessage("Alright! Now accept the connection in Minecraft from the account " + Minecraftname + " before the next server restart. You can also adjust the Minecraft name you want to connect to by running this command again.").subscribe + channel.createMessage("Alright! Now accept the connection in Minecraft from the account " + Minecraftname + " before the next server restart. You can also adjust the Minecraft name you want to connect to by running this command again.").subscribe() if (p.isOnline) p.asInstanceOf[Player].sendMessage("§bTo connect with the Discord account " + author.getUsername + "#" + author.getDiscriminator + " do /discord accept") true } diff --git a/src/main/scala/buttondevteam/discordplugin/commands/UserinfoCommand.scala b/src/main/scala/buttondevteam/discordplugin/commands/UserinfoCommand.scala index d21f4f1..5fa100d 100644 --- a/src/main/scala/buttondevteam/discordplugin/commands/UserinfoCommand.scala +++ b/src/main/scala/buttondevteam/discordplugin/commands/UserinfoCommand.scala @@ -7,6 +7,8 @@ import buttondevteam.lib.player.ChromaGamerBase.InfoTarget import discord4j.core.`object`.entity.{Message, User} import reactor.core.scala.publisher.SFlux +import scala.jdk.CollectionConverters.ListHasAsScala + @CommandClass(helpText = Array("User information", // "Shows some information about users, from Discord, from Minecraft or from Reddit if they have these accounts connected.", "If used without args, shows your info.")) @@ -19,31 +21,31 @@ class UserinfoCommand extends ICommand2DC { assert(channel != null) if (user == null || user.isEmpty) target = message.getAuthor.orElse(null) else { - val firstmention = message.getUserMentions.filter((m: User) => !(m.getId.asString == DiscordPlugin.dc.getSelfId.asString)).blockFirst - if (firstmention != null) target = firstmention + val firstmention = message.getUserMentions.asScala.find((m: User) => !(m.getId.asString == DiscordPlugin.dc.getSelfId.asString)) + if (firstmention.isDefined) target = firstmention.get else if (user.contains("#")) { val targettag = user.split("#") val targets = getUsers(message, targettag(0)) if (targets.isEmpty) { - channel.createMessage("The user cannot be found (by name): " + user).subscribe + channel.createMessage("The user cannot be found (by name): " + user).subscribe() return true } targets.collectFirst { case user => user.getDiscriminator.equalsIgnoreCase(targettag(1)) } if (target == null) { - channel.createMessage("The user cannot be found (by discriminator): " + user + "(Found " + targets.size + " users with the name.)").subscribe + channel.createMessage("The user cannot be found (by discriminator): " + user + "(Found " + targets.size + " users with the name.)").subscribe() return true } } else { val targets = getUsers(message, user) if (targets.isEmpty) { - channel.createMessage("The user cannot be found on Discord: " + user).subscribe + channel.createMessage("The user cannot be found on Discord: " + user).subscribe() return true } if (targets.size > 1) { - channel.createMessage("Multiple users found with that (nick)name. Please specify the whole tag, like ChromaBot#6338 or use a ping.").subscribe + channel.createMessage("Multiple users found with that (nick)name. Please specify the whole tag, like ChromaBot#6338 or use a ping.").subscribe() return true } target = targets.head @@ -56,7 +58,7 @@ class UserinfoCommand extends ICommand2DC { val dp = ChromaGamerBase.getUser(target.getId.asString, classOf[DiscordPlayer]) val uinfo = new StringBuilder("User info for ").append(target.getUsername).append(":\n") uinfo.append(dp.getInfo(InfoTarget.Discord)) - channel.createMessage(uinfo.toString).subscribe + channel.createMessage(uinfo.toString).subscribe() true } diff --git a/src/main/scala/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala b/src/main/scala/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala index 95895d4..d6bf7aa 100644 --- a/src/main/scala/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala +++ b/src/main/scala/buttondevteam/discordplugin/exceptions/DebugMessageListener.scala @@ -9,15 +9,15 @@ import reactor.core.scala.publisher.SMono object DebugMessageListener { private def SendMessage(message: String): Unit = { - if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(classOf[ExceptionListenerModule])) return + if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(classOf[ExceptionListenerModule])) return () try { val mc = ExceptionListenerModule.getChannel - if (mc == null) return + if (mc == null) return () val sb = new StringBuilder sb.append("```").append("\n") sb.append(if (message.length > 2000) message.substring(0, 2000) else message).append("\n") sb.append("```") - mc.flatMap((ch: MessageChannel) => SMono(ch.createMessage(sb.toString))).subscribe + mc.flatMap((ch: MessageChannel) => SMono(ch.createMessage(sb.toString))).subscribe() } catch { case ex: Exception => ex.printStackTrace() diff --git a/src/main/scala/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala b/src/main/scala/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala index fdf04ca..bd5826f 100644 --- a/src/main/scala/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala +++ b/src/main/scala/buttondevteam/discordplugin/exceptions/ExceptionListenerModule.scala @@ -19,7 +19,7 @@ import java.util.stream.Collectors */ object ExceptionListenerModule { private def SendException(e: Throwable, sourcemessage: String): Unit = { - if (instance == null) return + if (instance == null) return () try getChannel.flatMap(channel => { val coderRole = channel match { case ch: GuildChannel => instance.pingRole(SMono(ch.getGuild)).get @@ -31,14 +31,14 @@ object ExceptionListenerModule { sb.append(sourcemessage).append("\n") sb.append("```").append("\n") var stackTrace = util.Arrays.stream(ExceptionUtils.getStackTrace(e).split("\\n")) - .filter(s => !s.contains("\tat ") || s.contains("\tat buttondevteam.")) + .filter(s => !s.contains("\tat ") || s.contains("buttondevteam.")) .collect(Collectors.joining("\n")) if (sb.length + stackTrace.length >= 1980) stackTrace = stackTrace.substring(0, 1980 - sb.length) sb.append(stackTrace).append("\n") sb.append("```") SMono(channel.createMessage(sb.toString)) }) - }).subscribe + }).subscribe() catch { case ex: Exception => ex.printStackTrace() @@ -58,11 +58,11 @@ class ExceptionListenerModule extends Component[DiscordPlugin] with Listener { final private val lastsourcemsg = new util.ArrayList[String] @EventHandler def onException(e: TBMCExceptionEvent): Unit = { - if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(getClass)) return + if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(getClass)) return () if (lastthrown.stream.anyMatch(ex => e.getException.getStackTrace.sameElements(ex.getStackTrace) && (if (e.getException.getMessage == null) ex.getMessage == null else e.getException.getMessage == ex.getMessage)) && lastsourcemsg.contains(e.getSourceMessage)) { - return + return () } ExceptionListenerModule.SendException(e.getException, e.getSourceMessage) if (lastthrown.size >= 10) lastthrown.remove(0) @@ -83,7 +83,7 @@ class ExceptionListenerModule extends Component[DiscordPlugin] with Listener { private def pingRole(guild: SMono[Guild]) = DPUtils.roleData(getConfig, "pingRole", "Coder", guild) override protected def enable(): Unit = { - if (DPUtils.disableIfConfigError(this, channel)) return + if (DPUtils.disableIfConfigError(this, channel)) return () ExceptionListenerModule.instance = this Bukkit.getPluginManager.registerEvents(new ExceptionListenerModule, getPlugin) TBMCCoreAPI.RegisterEventsForExceptions(new DebugMessageListener, getPlugin) diff --git a/src/main/scala/buttondevteam/discordplugin/fun/FunModule.scala b/src/main/scala/buttondevteam/discordplugin/fun/FunModule.scala index 0a1a5ea..d924c09 100644 --- a/src/main/scala/buttondevteam/discordplugin/fun/FunModule.scala +++ b/src/main/scala/buttondevteam/discordplugin/fun/FunModule.scala @@ -9,7 +9,7 @@ import discord4j.core.`object`.entity.channel.{GuildChannel, MessageChannel} import discord4j.core.`object`.entity.{Guild, Message} import discord4j.core.`object`.presence.Status import discord4j.core.event.domain.PresenceUpdateEvent -import discord4j.core.spec.{EmbedCreateSpec, MessageCreateSpec} +import discord4j.core.spec.legacy.{LegacyEmbedCreateSpec, LegacyMessageCreateSpec} import org.bukkit.Bukkit import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.{EventHandler, Listener} @@ -52,7 +52,7 @@ object FunModule { ListC += 1 ListC - 1 } > 2) { // Lowered already - DPUtils.reply(message, SMono.empty, "stop it. You know the answer.").subscribe + DPUtils.reply(message, SMono.empty, "stop it. You know the answer.").subscribe() lastlist = 0 lastlistp = Bukkit.getOnlinePlayers.size.toShort return true //Handled @@ -62,7 +62,7 @@ object FunModule { var next = 0 if (usableServerReadyStrings.size == 0) fm.createUsableServerReadyStrings() next = usableServerReadyStrings.remove(serverReadyRandom.nextInt(usableServerReadyStrings.size)) - DPUtils.reply(message, SMono.empty, fm.serverReadyAnswers.get.get(next)).subscribe + DPUtils.reply(message, SMono.empty, fm.serverReadyAnswers.get.get(next)).subscribe() return false //Still process it as a command/mcchat if needed } false @@ -72,11 +72,11 @@ object FunModule { def handleFullHouse(event: PresenceUpdateEvent): Unit = { val fm = ComponentManager.getIfEnabled(classOf[FunModule]) - if (fm == null) return - if (Calendar.getInstance.get(Calendar.DAY_OF_MONTH) % 5 != 0) return + if (fm == null) return () + if (Calendar.getInstance.get(Calendar.DAY_OF_MONTH) % 5 != 0) return () if (!Option(event.getOld.orElse(null)).exists(_.getStatus == Status.OFFLINE) || event.getCurrent.getStatus == Status.OFFLINE) - return //If it's not an offline -> online change + return () //If it's not an offline -> online change fm.fullHouseChannel.get.filter((ch: MessageChannel) => ch.isInstanceOf[GuildChannel]) .flatMap(channel => fm.fullHouseDevRole(SMono(channel.asInstanceOf[GuildChannel].getGuild)).get .filterWhen(devrole => SMono(event.getMember) @@ -88,8 +88,8 @@ object FunModule { .flatMap(_ => { lasttime = TimeUnit.NANOSECONDS.toHours(System.nanoTime) SMono(channel.createMessage(_.setContent("Full house!") - .setEmbed((ecs: EmbedCreateSpec) => ecs.setImage("https://cdn.discordapp.com/attachments/249295547263877121/249687682618359808/poker-hand-full-house-aces-kings-playing-cards-15553791.png")))) - })).subscribe + .setEmbed((ecs: LegacyEmbedCreateSpec) => ecs.setImage("https://cdn.discordapp.com/attachments/249295547263877121/249687682618359808/poker-hand-full-house-aces-kings-playing-cards-15553791.png")))) + })).subscribe() } } diff --git a/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala b/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala index 5c776ca..467ee8b 100644 --- a/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala +++ b/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala @@ -56,7 +56,7 @@ object CommonListeners { } foo(event) - }).onErrorContinue((err: Throwable, _) => TBMCCoreAPI.SendException("An error occured while handling a message!", err, DiscordPlugin.plugin)).subscribe + }).onErrorContinue((err: Throwable, _) => TBMCCoreAPI.SendException("An error occured while handling a message!", err, DiscordPlugin.plugin)).subscribe() dispatcher.on(classOf[PresenceUpdateEvent]).subscribe((event: PresenceUpdateEvent) => { if (!DiscordPlugin.SafeMode) FunModule.handleFullHouse(event) diff --git a/src/main/scala/buttondevteam/discordplugin/listeners/MCListener.scala b/src/main/scala/buttondevteam/discordplugin/listeners/MCListener.scala index f45f78d..a68a48e 100644 --- a/src/main/scala/buttondevteam/discordplugin/listeners/MCListener.scala +++ b/src/main/scala/buttondevteam/discordplugin/listeners/MCListener.scala @@ -10,35 +10,32 @@ import discord4j.common.util.Snowflake import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.{EventHandler, Listener} import reactor.core.publisher.Mono +import reactor.core.scala.publisher.javaOptional2ScalaOption class MCListener extends Listener { @EventHandler def onPlayerJoin(e: PlayerJoinEvent): Unit = if (ConnectCommand.WaitingToConnect.containsKey(e.getPlayer.getName)) { @SuppressWarnings(Array("ConstantConditions")) val user = DiscordPlugin.dc.getUserById(Snowflake.of(ConnectCommand.WaitingToConnect.get(e.getPlayer.getName))).block - if (user == null) return + if (user == null) return () e.getPlayer.sendMessage("§bTo connect with the Discord account @" + user.getUsername + "#" + user.getDiscriminator + " do /discord accept") e.getPlayer.sendMessage("§bIf it wasn't you, do /discord decline") } @EventHandler def onGetInfo(e: TBMCPlayerGetInfoEvent): Unit = { - if (DiscordPlugin.SafeMode) return - val dp = e.getPlayer.getAs(classOf[DiscordPlayer]) - if (dp == null || dp.getDiscordID == null || dp.getDiscordID == "") return - val userOpt = DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID)).onErrorResume(_ => Mono.empty).blockOptional - if (!userOpt.isPresent) return - val user = userOpt.get - e.addInfo("Discord tag: " + user.getUsername + "#" + user.getDiscriminator) - val memberOpt = user.asMember(DiscordPlugin.mainServer.getId).onErrorResume((t: Throwable) => Mono.empty).blockOptional - if (!memberOpt.isPresent) return - val member = memberOpt.get - val prOpt = member.getPresence.blockOptional - if (!prOpt.isPresent) return - val pr = prOpt.get - e.addInfo(pr.getStatus.toString) - if (pr.getActivity.isPresent) { - val activity = pr.getActivity.get - e.addInfo(s"${activity.getType}: ${activity.getName}") - } + Option(DiscordPlugin.SafeMode).filterNot(identity).flatMap(_ => Option(e.getPlayer.getAs(classOf[DiscordPlayer]))) + .flatMap(dp => Option(dp.getDiscordID)).filter(_.nonEmpty) + .map(Snowflake.of).flatMap(id => DiscordPlugin.dc.getUserById(id).onErrorResume(_ => Mono.empty).blockOptional()) + .map(user => { + e.addInfo("Discord tag: " + user.getUsername + "#" + user.getDiscriminator) + user + }) + .flatMap(user => user.asMember(DiscordPlugin.mainServer.getId).onErrorResume(t => Mono.empty).blockOptional()) + .flatMap(member => member.getPresence.blockOptional()) + .map(pr => { + e.addInfo(pr.getStatus.toString) + pr + }) + .flatMap(_.getActivity).foreach(activity => e.addInfo(s"${activity.getType}: ${activity.getName}")) } /*@EventHandler diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala index e69fb53..aa77081 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/ChannelconCommand.scala @@ -1,8 +1,8 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.component.channel.{Channel, ChatRoom} +import buttondevteam.discordplugin.* import buttondevteam.discordplugin.ChannelconBroadcast.ChannelconBroadcast -import buttondevteam.discordplugin._ import buttondevteam.discordplugin.commands.{Command2DCSender, ICommand2DC} import buttondevteam.lib.TBMCSystemChatEvent import buttondevteam.lib.chat.{Command2, CommandClass} @@ -36,9 +36,9 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman val message = sender.getMessage if (checkPerms(message, null)) return true else if (MCChatCustom.removeCustomChat(message.getChannelId)) - DPUtils.reply(message, SMono.empty, "channel connection removed.").subscribe + DPUtils.reply(message, SMono.empty, "channel connection removed.").subscribe() else - DPUtils.reply(message, SMono.empty, "this channel isn't connected.").subscribe + DPUtils.reply(message, SMono.empty, "this channel isn't connected.").subscribe() true } @@ -58,7 +58,7 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman target.getName + ": " + (if (cc.brtoggles.contains(target)) "enabled" else "disabled")) .collect(Collectors.joining("\n")) if (toggle == null) { - DPUtils.reply(message, SMono.empty, "toggles:\n" + togglesString.get).subscribe + DPUtils.reply(message, SMono.empty, "toggles:\n" + togglesString.get).subscribe() return true } val arg: String = toggle.toUpperCase @@ -66,7 +66,7 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman if (b.isEmpty) { val bt: TBMCSystemChatEvent.BroadcastTarget = TBMCSystemChatEvent.BroadcastTarget.get(arg) if (bt == null) { - DPUtils.reply(message, SMono.empty, "cannot find toggle. Toggles:\n" + togglesString.get).subscribe + DPUtils.reply(message, SMono.empty, "cannot find toggle. Toggles:\n" + togglesString.get).subscribe() return true } val add: Boolean = !(cc.brtoggles.contains(bt)) @@ -87,7 +87,7 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman // XOR cc.toggles ^= (1 << b.get.id) DPUtils.reply(message, SMono.empty, "'" + b.get.toString.toLowerCase + "' " - + (if ((cc.toggles & (1 << b.get.id)) == 0) "disabled" else "enabled")).subscribe + + (if ((cc.toggles & (1 << b.get.id)) == 0) "disabled" else "enabled")).subscribe() true } @@ -106,7 +106,7 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman } val chan: Optional[Channel] = Channel.getChannels.filter((ch: Channel) => ch.ID.equalsIgnoreCase(channelID) || (util.Arrays.stream(ch.IDs.get).anyMatch((cid: String) => cid.equalsIgnoreCase(channelID)))).findAny if (!(chan.isPresent)) { //TODO: Red embed that disappears over time (kinda like the highlight messages in OW) - DPUtils.reply(message, channel, "MC channel with ID '" + channelID + "' not found! The ID is the command for it without the /.").subscribe + DPUtils.reply(message, channel, "MC channel with ID '" + channelID + "' not found! The ID is the command for it without the /.").subscribe() return true } if (!(message.getAuthor.isPresent)) { @@ -116,18 +116,18 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman val dp: DiscordPlayer = ChromaGamerBase.getUser(author.getId.asString, classOf[DiscordPlayer]) val chp: TBMCPlayer = dp.getAs(classOf[TBMCPlayer]) if (chp == null) { - DPUtils.reply(message, channel, "you need to connect your Minecraft account. On the main server in " + DPUtils.botmention + " do " + DiscordPlugin.getPrefix + "connect ").subscribe + DPUtils.reply(message, channel, "you need to connect your Minecraft account. On the main server in " + DPUtils.botmention + " do " + DiscordPlugin.getPrefix + "connect ").subscribe() return true } val dcp: DiscordConnectedPlayer = DiscordConnectedPlayer.create(message.getAuthor.get, channel, chp.getUUID, Bukkit.getOfflinePlayer(chp.getUUID).getName, module) //Using a fake player with no login/logout, should be fine for this event val groupid: String = chan.get.getGroupID(dcp) if (groupid == null && !((chan.get.isInstanceOf[ChatRoom]))) { //ChatRooms don't allow it unless the user joins, which happens later - DPUtils.reply(message, channel, "sorry, you cannot use that Minecraft channel.").subscribe + DPUtils.reply(message, channel, "sorry, you cannot use that Minecraft channel.").subscribe() return true } if (chan.get.isInstanceOf[ChatRoom]) { //ChatRooms don't work well - DPUtils.reply(message, channel, "chat rooms are not supported yet.").subscribe + DPUtils.reply(message, channel, "chat rooms are not supported yet.").subscribe() return true } /*if (MCChatListener.getCustomChats().stream().anyMatch(cc -> cc.groupID.equals(groupid) && cc.mcchannel.ID.equals(chan.get().ID))) { @@ -137,10 +137,10 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman //TODO: "Channel admins" that can connect channels? MCChatCustom.addCustomChat(channel, groupid, chan.get, author, dcp, 0, Set()) if (chan.get.isInstanceOf[ChatRoom]) { - DPUtils.reply(message, channel, "alright, connection made to the room!").subscribe + DPUtils.reply(message, channel, "alright, connection made to the room!").subscribe() } else { - DPUtils.reply(message, channel, "alright, connection made to group `" + groupid + "`!").subscribe + DPUtils.reply(message, channel, "alright, connection made to group `" + groupid + "`!").subscribe() } true } @@ -151,13 +151,13 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman return checkPerms(message, message.getChannel.block) } if (!((channel.isInstanceOf[GuildChannel]))) { - DPUtils.reply(message, channel, "you can only use this command in a server!").subscribe + DPUtils.reply(message, channel, "you can only use this command in a server!").subscribe() return true } //noinspection OptionalGetWithoutIsPresent val perms: PermissionSet = (channel.asInstanceOf[GuildChannel]).getEffectivePermissions(message.getAuthor.map(_.getId).get).block if (!(perms.contains(Permission.ADMINISTRATOR)) && !(perms.contains(Permission.MANAGE_CHANNELS))) { - DPUtils.reply(message, channel, "you need to have manage permissions for this channel!").subscribe + DPUtils.reply(message, channel, "you need to have manage permissions for this channel!").subscribe() return true } false diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatCommand.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatCommand.scala index 78e7085..9b84215 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatCommand.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatCommand.scala @@ -22,7 +22,7 @@ class MCChatCommand(private val module: MinecraftChatModule) extends ICommand2DC val channel = message.getChannel.block @SuppressWarnings(Array("OptionalGetWithoutIsPresent")) val author = message.getAuthor.get if (!((channel.isInstanceOf[PrivateChannel]))) { - DPUtils.reply(message, channel, "this command can only be issued in a direct message with the bot.").subscribe + DPUtils.reply(message, channel, "this command can only be issued in a direct message with the bot.").subscribe() return true } val user: DiscordPlayer = ChromaGamerBase.getUser(author.getId.asString, classOf[DiscordPlayer]) @@ -30,7 +30,7 @@ class MCChatCommand(private val module: MinecraftChatModule) extends ICommand2DC MCChatPrivate.privateMCChat(channel, mcchat, author, user) DPUtils.reply(message, channel, "Minecraft chat " + (if (mcchat) "enabled. Use '" + DiscordPlugin.getPrefix + "mcchat' again to turn it off." - else "disabled.")).subscribe + else "disabled.")).subscribe() true // TODO: Pin channel switching to indicate the current channel } diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala index f493760..18fb427 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala @@ -2,6 +2,7 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.ComponentManager import buttondevteam.discordplugin.* +import buttondevteam.discordplugin.DPUtils.SpecExtensions import buttondevteam.discordplugin.listeners.CommandListener import buttondevteam.discordplugin.playerfaker.{VanillaCommandListener, VanillaCommandListener14, VanillaCommandListener15} import buttondevteam.lib.* @@ -12,7 +13,7 @@ import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} import discord4j.core.`object`.entity.{Member, Message, User} import discord4j.core.event.domain.message.MessageCreateEvent -import discord4j.core.spec.{EmbedCreateSpec, MessageEditSpec} +import discord4j.core.spec.legacy.{LegacyEmbedCreateSpec, LegacyMessageEditSpec} import discord4j.rest.util.Color import org.bukkit.Bukkit import org.bukkit.entity.Player @@ -26,7 +27,7 @@ import java.util import java.util.concurrent.{LinkedBlockingQueue, TimeoutException} import java.util.function.{Consumer, Predicate} import java.util.stream.Collectors -import scala.jdk.CollectionConverters.SetHasAsScala +import scala.jdk.CollectionConverters.{ListHasAsScala, SetHasAsScala} import scala.jdk.OptionConverters.RichOptional object MCChatListener { @@ -58,12 +59,12 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { @EventHandler // Minecraft def onMCChat(ev: TBMCChatEvent): Unit = { if (!(ComponentManager.isEnabled(classOf[MinecraftChatModule])) || ev.isCancelled) { //SafeMode: Needed so it doesn't restart after server shutdown - return + return () } sendevents.add(new util.AbstractMap.SimpleEntry[TBMCChatEvent, Instant](ev, Instant.now)) if (sendtask != null) { - return + return () } sendrunnable = () => { def foo(): Unit = { @@ -90,8 +91,8 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { (if ("Minecraft" == e.getOrigin) "" else "[" + e.getOrigin.charAt(0) + "]") + DPUtils.sanitizeStringNoEscape(ChromaUtils.getDisplayName(e.getSender)) val color: chat.Color = e.getChannel.Color.get - val embed: Consumer[EmbedCreateSpec] = (ecs: EmbedCreateSpec) => { - def foo(ecs: EmbedCreateSpec) = { + val embed: Consumer[LegacyEmbedCreateSpec] = (ecs: LegacyEmbedCreateSpec) => { + def foo(ecs: LegacyEmbedCreateSpec) = { ecs.setDescription(e.getMessage).setColor(Color.of(color.getRed, color.getGreen, color.getBlue)) val url: String = module.profileURL.get e.getSender match { @@ -113,7 +114,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { val nanoTime: Long = System.nanoTime val doit = (lastmsgdata: MCChatUtils.LastMsgData) => { if (lastmsgdata.message == null - || authorPlayer != lastmsgdata.message.getEmbeds.get(0).getAuthor.toScala.map(_.getName).orNull + || authorPlayer != lastmsgdata.message.getEmbeds.get(0).getAuthor.toScala.flatMap(_.getName.toScala).orNull || lastmsgdata.time / 1000000000f < nanoTime / 1000000000f - 120 || !(lastmsgdata.mcchannel.ID == e.getChannel.ID) || lastmsgdata.content.length + e.getMessage.length + 1 > 2048) { @@ -124,7 +125,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { } else { lastmsgdata.content = lastmsgdata.content + "\n" + e.getMessage // The message object doesn't get updated - lastmsgdata.message.edit((mes: MessageEditSpec) => mes.setEmbed(embed.andThen((ecs: EmbedCreateSpec) => ecs.setDescription(lastmsgdata.content)))).block + lastmsgdata.message.edit((mes: LegacyMessageEditSpec) => mes.setEmbed(embed.andThen((ecs: LegacyEmbedCreateSpec) => ecs.setDescription(lastmsgdata.content))).^^()).block } } // Checks if the given channel is different than where the message was sent from @@ -151,7 +152,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { true } else { - lmd.channel.createMessage("The user no longer has permission to view the channel, connection removed.").subscribe + lmd.channel.createMessage("The user no longer has permission to view the channel, connection removed.").subscribe() false //If the user no longer has permission, remove the connection } } @@ -175,7 +176,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { }) { val mid: Int = event.getMessage.indexOf('#', start + 1) if (mid == -1) { - return + return () } var end_ = event.getMessage.indexOf(' ', mid + 1) if (end_ == -1) { @@ -290,7 +291,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { catch { case _: InterruptedException => rectask.cancel() - return + return () } val sender: User = event.getMessage.getAuthor.orElse(null) var dmessage: String = event.getMessage.getContent @@ -299,7 +300,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { val user: DiscordPlayer = dsender.getChromaUser def replaceUserMentions(): Unit = { - for (u <- SFlux(event.getMessage.getUserMentions).toIterable()) { //TODO: Role mentions + for (u <- event.getMessage.getUserMentions.asScala) { //TODO: Role mentions dmessage = dmessage.replace(u.getMention, "@" + u.getUsername) // TODO: IG Formatting val m = u.asMember(DiscordPlugin.mainServer.getId).onErrorResume(_ => Mono.empty).blockOptional if (m.isPresent) { @@ -341,7 +342,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e, module) } MCChatUtils.lastmsgfromd.put(event.getMessage.getChannelId.asLong, event.getMessage) - event.getMessage.addReaction(DiscordPlugin.DELIVERED_REACTION).subscribe + event.getMessage.addReaction(DiscordPlugin.DELIVERED_REACTION).subscribe() } if (dmessage.startsWith("/")) // Ingame command @@ -412,7 +413,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { .map("/" + _).collect(Collectors.joining(", ")) if (!isPrivate) - event.getMessage.delete.subscribe + event.getMessage.delete.subscribe() val cmd = dmessage.substring(1) val cmdlowercased = cmd.toLowerCase if (dsender.isInstanceOf[DiscordSender] && notWhitelisted(cmdlowercased)) { // Command not whitelisted @@ -420,11 +421,11 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { (if (user.getConnectedID(classOf[TBMCPlayer]) == null) "\nTo access your commands, first please connect your accounts, using /connect in " + DPUtils.botmention + "\nThen y" else "\nY") + "ou can access all of your regular commands (even offline) in private chat: DM me `mcchat`!") - return + return () } module.log(dsender.getName + " ran from DC: /" + cmd) if (dsender.isInstanceOf[DiscordSender] && runCustomCommand(dsender, cmdlowercased)) { - return + return () } val channel = if (clmd == null) user.channel.get else clmd.mcchannel val ev = new TBMCCommandPreprocessEvent(dsender, channel, dmessage, if (clmd == null) dsender else clmd.dcp) diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala index 3f0438d..b76182f 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatUtils.scala @@ -3,6 +3,7 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.{ComponentManager, MainPlugin, component} import buttondevteam.discordplugin.* import buttondevteam.discordplugin.ChannelconBroadcast.ChannelconBroadcast +import buttondevteam.discordplugin.DPUtils.SpecExtensions import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule import buttondevteam.discordplugin.mcchat.MCChatCustom.CustomLMD import buttondevteam.lib.{TBMCCoreAPI, TBMCSystemChatEvent} @@ -10,7 +11,7 @@ import com.google.common.collect.Sets import discord4j.common.util.Snowflake import discord4j.core.`object`.entity.channel.{Channel, MessageChannel, PrivateChannel, TextChannel} import discord4j.core.`object`.entity.{Message, User} -import discord4j.core.spec.TextChannelEditSpec +import discord4j.core.spec.legacy.LegacyTextChannelEditSpec import io.netty.util.collection.LongObjectHashMap import org.bukkit.Bukkit import org.bukkit.command.CommandSender @@ -18,6 +19,7 @@ import org.bukkit.entity.Player import org.bukkit.event.Event import org.bukkit.event.player.{AsyncPlayerPreLoginEvent, PlayerJoinEvent, PlayerLoginEvent, PlayerQuitEvent} import org.bukkit.plugin.AuthorNagException +import reactor.core.publisher.{Flux as JFlux, Mono as JMono} import reactor.core.scala.publisher.SMono import java.net.InetAddress @@ -31,7 +33,7 @@ import javax.annotation.Nullable import scala.collection.concurrent import scala.collection.convert.ImplicitConversions.`map AsJavaMap` import scala.collection.mutable.ListBuffer -import scala.jdk.CollectionConverters.CollectionHasAsScala +import scala.jdk.CollectionConverters.{CollectionHasAsScala, SeqHasAsJava} import scala.jdk.javaapi.CollectionConverters.asScala object MCChatUtils { @@ -65,12 +67,12 @@ object MCChatUtils { private def updatePL(lmd: MCChatUtils.LastMsgData): Unit = { if (!lmd.channel.isInstanceOf[TextChannel]) { TBMCCoreAPI.SendException("Failed to update player list for channel " + lmd.channel.getId, new Exception("The channel isn't a (guild) text channel."), getModule) - return + return () } var topic = lmd.channel.asInstanceOf[TextChannel].getTopic.orElse("") if (topic.isEmpty) topic = ".\n----\nMinecraft chat\n----\n." val s = topic.split("\\n----\\n") - if (s.length < 3) return + if (s.length < 3) return () var gid: String = null lmd match { case clmd: CustomLMD => gid = clmd.groupID @@ -88,8 +90,8 @@ object MCChatUtils { .filter(_ => C.incrementAndGet > 0) //Always true .map((p) => DPUtils.sanitizeString(p.getDisplayName)).collect(Collectors.joining(", ")) s(0) = s"$C player${if (C.get != 1) "s" else ""} online" - lmd.channel.asInstanceOf[TextChannel].edit((tce: TextChannelEditSpec) => - tce.setTopic(String.join("\n----\n", s: _*)).setReason("Player list update")).subscribe //Don't wait + lmd.channel.asInstanceOf[TextChannel].edit((tce: LegacyTextChannelEditSpec) => + tce.setTopic(String.join("\n----\n", s: _*)).setReason("Player list update").^^()).subscribe //Don't wait } private[mcchat] def checkEssentials(p: Player): Boolean = { @@ -125,8 +127,7 @@ object MCChatUtils { if (notEnabled) return SMono.empty val list = MCChatPrivate.lastmsgPerUser.map(data => action(SMono.just(data.channel))) .prepend(action(module.chatChannelMono)) - // lastmsgCustom.forEach(cc -> action.accept(cc.channel)); - Only send relevant messages to custom chat - SMono.whenDelayError(list) + SMono(JMono.whenDelayError(list.asJava)) } /** @@ -143,7 +144,7 @@ object MCChatUtils { (if (toggle == null) MCChatCustom.lastmsgCustom else MCChatCustom.lastmsgCustom.filter(cc => (cc.toggles & (1 << toggle.id)) != 0)) .map(_.channel).map(SMono.just).map(action) - SMono.whenDelayError(list) + SMono(JMono.whenDelayError(list.asJava)) } /** @@ -223,13 +224,13 @@ object MCChatUtils { if (channel.getId.asLong == module.chatChannel.get.asLong) { if (lastmsgdata == null) lastmsgdata = new MCChatUtils.LastMsgData(module.chatChannelMono.block(), null) else lastmsgdata.message = null - return + return () } // Don't set the whole object to null, the player and channel information should be preserved for (data <- if (channel.isInstanceOf[PrivateChannel]) MCChatPrivate.lastmsgPerUser else MCChatCustom.lastmsgCustom) { if (data.channel.getId.asLong == channel.getId.asLong) { data.message = null - return + return () } } //If it gets here, it's sending a message to a non-chat channel @@ -240,7 +241,7 @@ object MCChatUtils { if (hs == null) Sets.newHashSet(plugin) else if (hs.add(plugin)) hs else hs) def callEventExcludingSome(event: Event): Unit = { - if (notEnabled) return + if (notEnabled) return () val second = staticExcludedPlugins.get(event.getClass) val first = module.excludedPlugins.get val both = if (second.isEmpty) first @@ -305,7 +306,7 @@ object MCChatUtils { callEventExcludingSome(event) if (event.getLoginResult ne AsyncPlayerPreLoginEvent.Result.ALLOWED) { loginFail(event.getKickMessage) - return + return () } Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => { def foo(): Unit = { @@ -313,7 +314,7 @@ object MCChatUtils { callEventExcludingSome(ev) if (ev.getResult ne PlayerLoginEvent.Result.ALLOWED) { loginFail(ev.getKickMessage) - return + return () } callEventExcludingSome(new PlayerJoinEvent(dcp, "")) dcp.setLoggedIn(true) @@ -334,7 +335,7 @@ object MCChatUtils { * @param needsSync Whether we're in an async thread */ def callLogoutEvent(dcp: DiscordConnectedPlayer, needsSync: Boolean): Unit = { - if (!dcp.isLoggedIn) return + if (!dcp.isLoggedIn) return () val event = new PlayerQuitEvent(dcp, "") if (needsSync) callEventSync(event) else callEventExcludingSome(event) diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MCListener.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCListener.scala index 21377c8..6e40149 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MCListener.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MCListener.scala @@ -1,7 +1,7 @@ package buttondevteam.discordplugin.mcchat +import buttondevteam.discordplugin.* import buttondevteam.discordplugin.DPUtils.{FluxExtensions, MonoExtensions} -import buttondevteam.discordplugin._ import buttondevteam.lib.TBMCSystemChatEvent import buttondevteam.lib.player.{TBMCPlayer, TBMCPlayerBase, TBMCYEEHAWEvent} import discord4j.common.util.Snowflake @@ -11,8 +11,8 @@ import net.ess3.api.events.{AfkStatusChangeEvent, MuteStatusChangeEvent, NickCha import org.bukkit.Bukkit import org.bukkit.entity.Player import org.bukkit.event.entity.PlayerDeathEvent +import org.bukkit.event.player.* import org.bukkit.event.player.PlayerLoginEvent.Result -import org.bukkit.event.player._ import org.bukkit.event.server.{BroadcastMessageEvent, TabCompleteEvent} import org.bukkit.event.{EventHandler, EventPriority, Listener} import reactor.core.scala.publisher.{SFlux, SMono} @@ -21,8 +21,8 @@ class MCListener(val module: MinecraftChatModule) extends Listener { final private val muteRole = DPUtils.roleData(module.getConfig, "muteRole", "Muted") @EventHandler(priority = EventPriority.HIGHEST) def onPlayerLogin(e: PlayerLoginEvent): Unit = { - if (e.getResult ne Result.ALLOWED) return - if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return + if (e.getResult ne Result.ALLOWED) return () + if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return () val dcp = MCChatUtils.LoggedInPlayers.get(e.getPlayer.getUniqueId) if (dcp.nonEmpty) MCChatUtils.callLogoutEvent(dcp.get, needsSync = false) } @@ -39,7 +39,7 @@ class MCListener(val module: MinecraftChatModule) extends Listener { MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, chan, p, module)) MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, cc, p, module)) //Stored per-channel SMono.empty - }))).subscribe + }))).subscribe() val message = e.getJoinMessage sendJoinLeaveMessage(message, e.getPlayer) ChromaBot.updatePlayerList() @@ -51,7 +51,7 @@ class MCListener(val module: MinecraftChatModule) extends Listener { private def sendJoinLeaveMessage(message: String, player: Player): Unit = if (message != null && message.trim.nonEmpty) - MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), player, ChannelconBroadcast.JOINLEAVE, hookmsg = true).subscribe + MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), player, ChannelconBroadcast.JOINLEAVE, hookmsg = true).subscribe() @EventHandler(priority = EventPriority.MONITOR) def onPlayerLeave(e: PlayerQuitEvent): Unit = { if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return // Only care about real users @@ -69,23 +69,23 @@ class MCListener(val module: MinecraftChatModule) extends Listener { } @EventHandler(priority = EventPriority.LOW) def onPlayerDeath(e: PlayerDeathEvent): Unit = - MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(e.getDeathMessage), e.getEntity, ChannelconBroadcast.DEATH, hookmsg = true).subscribe + MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(e.getDeathMessage), e.getEntity, ChannelconBroadcast.DEATH, hookmsg = true).subscribe() @EventHandler def onPlayerAFK(e: AfkStatusChangeEvent): Unit = { val base = e.getAffected.getBase - if (e.isCancelled || !base.isOnline) return + if (e.isCancelled || !base.isOnline) return () val msg = base.getDisplayName + " is " + (if (e.getValue) "now" else "no longer") + " AFK." - MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, hookmsg = false).subscribe + MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(msg), base, ChannelconBroadcast.AFK, hookmsg = false).subscribe() } @EventHandler def onPlayerMute(e: MuteStatusChangeEvent): Unit = { val role = muteRole.get - if (role == null) return + if (role == null) return () val source = e.getAffected.getSource - if (!source.isPlayer) return + if (!source.isPlayer) return () val p = TBMCPlayerBase.getPlayer(source.getPlayer.getUniqueId, classOf[TBMCPlayer]).getAs(classOf[DiscordPlayer]) - if (p == null) return + if (p == null) return () DPUtils.ignoreError(SMono(DiscordPlugin.dc.getUserById(Snowflake.of(p.getDiscordID))) .flatMap(user => SMono(user.asMember(DiscordPlugin.mainServer.getId))) .flatMap(user => role.flatMap((r: Role) => { @@ -101,14 +101,14 @@ class MCListener(val module: MinecraftChatModule) extends Listener { } foo(r) - }))).subscribe + }))).subscribe() } @EventHandler def onChatSystemMessage(event: TBMCSystemChatEvent): Unit = - MCChatUtils.forAllowedMCChat(MCChatUtils.send(event.getMessage), event).subscribe + MCChatUtils.forAllowedMCChat(MCChatUtils.send(event.getMessage), event).subscribe() @EventHandler def onBroadcastMessage(event: BroadcastMessageEvent): Unit = - MCChatUtils.forCustomAndAllMCChat(MCChatUtils.send(event.getMessage), ChannelconBroadcast.BROADCAST, hookmsg = false).subscribe + MCChatUtils.forCustomAndAllMCChat(MCChatUtils.send(event.getMessage), ChannelconBroadcast.BROADCAST, hookmsg = false).subscribe() @EventHandler def onYEEHAW(event: TBMCYEEHAWEvent): Unit = { //TODO: Inherit from the chat event base to have channel support val name = event.getSender match { @@ -119,7 +119,7 @@ class MCListener(val module: MinecraftChatModule) extends Listener { DiscordPlugin.mainServer.getEmojis.^^().filter(e => "YEEHAW" == e.getName).take(1).singleOrEmpty .map(Option.apply).defaultIfEmpty(Option.empty) .flatMap(yeehaw => MCChatUtils.forPublicPrivateChat(MCChatUtils.send(name + - yeehaw.map(guildEmoji => " <:YEEHAW:" + guildEmoji.getId.asString + ">s").getOrElse(" YEEHAWs")))).subscribe + yeehaw.map(guildEmoji => " <:YEEHAW:" + guildEmoji.getId.asString + ">s").getOrElse(" YEEHAWs")))).subscribe() } @EventHandler def onNickChange(event: NickChangeEvent): Unit = MCChatUtils.updatePlayerList() @@ -127,7 +127,7 @@ class MCListener(val module: MinecraftChatModule) extends Listener { @EventHandler def onTabComplete(event: TabCompleteEvent): Unit = { val i = event.getBuffer.lastIndexOf(' ') val t = event.getBuffer.substring(i + 1) //0 if not found - if (!t.startsWith("@")) return + if (!t.startsWith("@")) return () val token = t.substring(1) val x = DiscordPlugin.mainServer.getMembers.^^().flatMap(m => SFlux.just(m.getUsername, m.getNickname.orElse(""))) .filter(_.startsWith(token)).map("@" + _).doOnNext(event.getCompletions.add(_)).blockLast() @@ -136,7 +136,7 @@ class MCListener(val module: MinecraftChatModule) extends Listener { @EventHandler def onCommandSend(event: PlayerCommandSendEvent): Boolean = event.getCommands.add("g") @EventHandler def onVanish(event: VanishStatusChangeEvent): Unit = { - if (event.isCancelled) return + if (event.isCancelled) return () Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => MCChatUtils.updatePlayerList()) } } \ No newline at end of file diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala index fa2c467..c9e2f67 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MinecraftChatModule.scala @@ -220,7 +220,7 @@ class MinecraftChatModule extends Component[DiscordPlugin] { */ private def sendStateMessage(color: Color, message: String) = MCChatUtils.forCustomAndAllMCChat(_.flatMap( - _.createEmbed(_.setColor(color).setTitle(message)).^^() + _.createEmbed(_.setColor(color).setTitle(message).^^()).^^() .onErrorResume(_ => SMono.empty) ), ChannelconBroadcast.RESTART, hookmsg = false).block() diff --git a/src/main/scala/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala b/src/main/scala/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala index ea7749f..db4c38e 100644 --- a/src/main/scala/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala +++ b/src/main/scala/buttondevteam/discordplugin/mccommands/DiscordMCCommand.scala @@ -60,7 +60,7 @@ import java.lang.reflect.Method def foo(): Unit = { if (!DiscordPlugin.plugin.tryReloadConfig) { sender.sendMessage("§cFailed to reload config so not restarting. Check the console.") - return + return () } MinecraftChatModule.state = DPState.RESTARTING_PLUGIN //Reset in MinecraftChatModule sender.sendMessage("§bDisabling DiscordPlugin...") @@ -95,12 +95,12 @@ import java.lang.reflect.Method "Shows an invite link to the server" )) def invite(sender: CommandSender): Unit = { if (checkSafeMode(sender)) { - return + return () } val invi: String = DiscordPlugin.plugin.inviteLink.get if (invi.nonEmpty) { sender.sendMessage("§bInvite link: " + invi) - return + return () } DiscordPlugin.mainServer.getInvites.limitRequest(1) .switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("§cNo invites found for the server."))) diff --git a/src/main/scala/buttondevteam/discordplugin/role/GameRoleModule.scala b/src/main/scala/buttondevteam/discordplugin/role/GameRoleModule.scala index 25e7e61..ad8faa1 100644 --- a/src/main/scala/buttondevteam/discordplugin/role/GameRoleModule.scala +++ b/src/main/scala/buttondevteam/discordplugin/role/GameRoleModule.scala @@ -21,7 +21,7 @@ import scala.jdk.CollectionConverters.SeqHasAsJava @ComponentMetadata(enabledByDefault = false) object GameRoleModule { def handleRoleEvent(roleEvent: RoleEvent): Unit = { val grm = ComponentManager.getIfEnabled(classOf[GameRoleModule]) - if (grm == null) return + if (grm == null) return () val GameRoles = grm.GameRoles val logChannel = grm.logChannel.get val notMainServer = (_: Role).getGuildId.asLong != DiscordPlugin.mainServer.getId.asLong @@ -39,23 +39,23 @@ import scala.jdk.CollectionConverters.SeqHasAsJava else SMono.empty } - }).subscribe + }).subscribe() () } }, 100) case roleDeleteEvent: RoleDeleteEvent => val role = roleDeleteEvent.getRole.orElse(null) - if (role == null) return - if (notMainServer(role)) return + if (role == null) return () + if (notMainServer(role)) return () if (GameRoles.remove(role.getName) && logChannel != null) - logChannel.flatMap(_.createMessage("Removed " + role.getName + " as a game role.").^^()).subscribe + logChannel.flatMap(_.createMessage("Removed " + role.getName + " as a game role.").^^()).subscribe() case roleUpdateEvent: RoleUpdateEvent => if (!roleUpdateEvent.getOld.isPresent) { grm.logWarn("Old role not stored, cannot update game role!") - return + return () } val or = roleUpdateEvent.getOld.get - if (notMainServer(or)) return + if (notMainServer(or)) return () val cr = roleUpdateEvent.getCurrent grm.isGameRole(cr).flatMap(isGameRole => { if (!isGameRole) @@ -76,7 +76,7 @@ import scala.jdk.CollectionConverters.SeqHasAsJava else SMono.empty } - }).subscribe + }).subscribe() case _ => } } diff --git a/src/main/scala/buttondevteam/discordplugin/role/RoleCommand.scala b/src/main/scala/buttondevteam/discordplugin/role/RoleCommand.scala index e0a6bce..71711e9 100644 --- a/src/main/scala/buttondevteam/discordplugin/role/RoleCommand.scala +++ b/src/main/scala/buttondevteam/discordplugin/role/RoleCommand.scala @@ -14,7 +14,7 @@ import reactor.core.publisher.Mono )) def add(sender: Command2DCSender, @Command2.TextArg rolename: String): Boolean = { val role = checkAndGetRole(sender, rolename) if (role == null) return true - try sender.getMessage.getAuthorAsMember.flatMap(m => m.addRole(role.getId).switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("added role.")))).subscribe + try sender.getMessage.getAuthorAsMember.flatMap(m => m.addRole(role.getId).switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("added role.")))).subscribe() catch { case e: Exception => TBMCCoreAPI.SendException("Error while adding role!", e, grm) @@ -29,7 +29,7 @@ import reactor.core.publisher.Mono )) def remove(sender: Command2DCSender, @Command2.TextArg rolename: String): Boolean = { val role = checkAndGetRole(sender, rolename) if (role == null) return true - try sender.getMessage.getAuthorAsMember.flatMap(m => m.removeRole(role.getId).switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("removed role.")))).subscribe + try sender.getMessage.getAuthorAsMember.flatMap(m => m.removeRole(role.getId).switchIfEmpty(Mono.fromRunnable(() => sender.sendMessage("removed role.")))).subscribe() catch { case e: Exception => TBMCCoreAPI.SendException("Error while removing role!", e, grm) diff --git a/src/test/java/buttondevteam/DiscordPlugin/AppTest.java b/src/test/java/buttondevteam/DiscordPlugin/AppTest.java deleted file mode 100755 index 4b8bbb9..0000000 --- a/src/test/java/buttondevteam/DiscordPlugin/AppTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package buttondevteam.DiscordPlugin; - -import buttondevteam.discordplugin.DiscordConnectedPlayer; -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestSuite; -import org.bukkit.attribute.Attribute; -import org.bukkit.entity.Player; - -/** - * Unit test for simple App. - */ -public class AppTest extends TestCase { - /** - * Create the test case - * - * @param testName - * name of the test case - */ - public AppTest(String testName) { - super(testName); - } - - /** - * @return the suite of tests being tested - */ - public static Test suite() { - return new TestSuite(AppTest.class); - } - - /** - * Rigourous Test :-) - */ - public void testApp() { - Player dcp = DiscordConnectedPlayer.createTest(); - - double h = dcp.getAttribute(Attribute.GENERIC_MAX_HEALTH).getDefaultValue(); ; ; - System.out.println(h); - } -} From cafd8096fa7af175aa2a7a13c482119343c03803 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Fri, 31 Dec 2021 00:38:28 +0100 Subject: [PATCH 22/23] Fix even more returns and Discord->MC chat --- .../buttondevteam/discordplugin/DPUtils.scala | 18 ++---- .../discordplugin/DiscordPlugin.scala | 10 ++-- .../discordplugin/DiscordSender.scala | 4 +- .../discordplugin/DiscordSenderBase.scala | 11 +--- .../listeners/CommonListeners.scala | 60 +++++++++---------- .../discordplugin/mcchat/MCChatCommand.scala | 4 +- .../discordplugin/mcchat/MCListener.scala | 4 +- 7 files changed, 47 insertions(+), 64 deletions(-) diff --git a/src/main/scala/buttondevteam/discordplugin/DPUtils.scala b/src/main/scala/buttondevteam/discordplugin/DPUtils.scala index b024832..be512b0 100644 --- a/src/main/scala/buttondevteam/discordplugin/DPUtils.scala +++ b/src/main/scala/buttondevteam/discordplugin/DPUtils.scala @@ -53,17 +53,10 @@ object DPUtils { private def escape(message: String) = { //var ts = new TreeSet<>(); val ts = new util.TreeSet[Array[Int]](Comparator.comparingInt((a: Array[Int]) => a(0)): Comparator[Array[Int]]) //Compare the start, then check the end var matcher = URL_PATTERN.matcher(message) - while ( { - matcher.find - }) ts.add(Array[Int](matcher.start, matcher.end)) + while (matcher.find) ts.add(Array[Int](matcher.start, matcher.end)) matcher = FORMAT_PATTERN.matcher(message) - /*Function aFunctionalInterface = result -> - Optional.ofNullable(ts.floor(new int[]{result.start(), 0})).map(a -> a[1]).orElse(0) < result.start() - ? "\\\\" + result.group() : result.group(); - return matcher.replaceAll(aFunctionalInterface); //Find nearest URL match and if it's not reaching to the char then escape*/ val sb = new StringBuffer - while ( { - matcher.find - }) matcher.appendReplacement(sb, if (Option(ts.floor(Array[Int](matcher.start, 0))).map( //Find a URL start <= our start + val sb = new StringBuffer + while (matcher.find) matcher.appendReplacement(sb, if (Option(ts.floor(Array[Int](matcher.start, 0))).map( //Find a URL start <= our start (a: Array[Int]) => a(1)).getOrElse(-1) < matcher.start //Check if URL end < our start ) "\\\\" + matcher.group else matcher.group) matcher.appendTail(sb) @@ -71,13 +64,14 @@ object DPUtils { } def getLogger: Logger = { - if (DiscordPlugin.plugin == null || DiscordPlugin.plugin.getLogger == null) return Logger.getLogger("DiscordPlugin") - DiscordPlugin.plugin.getLogger + if (DiscordPlugin.plugin == null || DiscordPlugin.plugin.getLogger == null) Logger.getLogger("DiscordPlugin") + else DiscordPlugin.plugin.getLogger } def channelData(config: IHaveConfig, key: String): ReadOnlyConfigData[SMono[MessageChannel]] = config.getReadOnlyDataPrimDef(key, 0L, (id: Any) => getMessageChannel(key, Snowflake.of(id.asInstanceOf[Long])), (_: SMono[MessageChannel]) => 0L) //We can afford to search for the channel in the cache once (instead of using mainServer) + def roleData(config: IHaveConfig, key: String, defName: String): ReadOnlyConfigData[SMono[Role]] = roleData(config, key, defName, SMono.just(DiscordPlugin.mainServer)) diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala index 921a59b..8e03848 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala @@ -42,8 +42,8 @@ import java.util.Optional private[discordplugin] var SafeMode = true def getPrefix: Char = { - if (plugin == null) return '/' - plugin.prefix.get + if (plugin == null) '/' + else plugin.prefix.get } private[discordplugin] var mainServer: Guild = null @@ -174,7 +174,7 @@ import java.util.Optional getLogger.severe("Main server not found! Invite the bot and do /discord restart") DiscordPlugin.dc.getApplicationInfo.subscribe((info: ApplicationInfo) => getLogger.severe("Click here: https://discordapp.com/oauth2/authorize?client_id=" + info.getId.asString + "&scope=bot&permissions=268509264")) saveConfig() //Put default there - return //We should have all guilds by now, no need to retry + return () //We should have all guilds by now, no need to retry } DiscordPlugin.mainServer = event.get(0).getGuild getLogger.warning("Main server set to first one: " + DiscordPlugin.mainServer.getName) @@ -186,7 +186,7 @@ import java.util.Optional //Won't disable, just prints the warning here if (MinecraftChatModule.state eq DPState.STOPPING_SERVER) { stopStarting() - return //Reusing that field to check if stopping while still initializing + return () //Reusing that field to check if stopping while still initializing } CommonListeners.register(DiscordPlugin.dc.getEventDispatcher) TBMCCoreAPI.RegisterEventsForExceptions(new MCListener, this) @@ -236,7 +236,7 @@ import java.util.Optional e.printStackTrace() } } - if (!ChromaBot.enabled) return //Failed to load + if (!ChromaBot.enabled) return () //Failed to load val timings = new Timings timings.printElapsed("Disable start") timings.printElapsed("Updating player list") diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordSender.scala b/src/main/scala/buttondevteam/discordplugin/DiscordSender.scala index 0462990..a77979e 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordSender.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordSender.scala @@ -27,8 +27,8 @@ class DiscordSender(user: User, channel: MessageChannel, pname: String) extends override def isPermissionSet(perm: Permission): Boolean = this.perm.isPermissionSet(perm) override def hasPermission(name: String): Boolean = { - if (name.contains("essentials") && !(name == "essentials.list")) return false - perm.hasPermission(name) + if (name.contains("essentials") && !(name == "essentials.list")) false + else perm.hasPermission(name) } override def hasPermission(perm: Permission): Boolean = this.perm.hasPermission(perm) diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala b/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala index c5625ad..9d35368 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala @@ -47,14 +47,9 @@ abstract class DiscordSenderBase protected(var user: User, var channel: MessageC this synchronized { msgtosend += "\n" + sendmsg if (sendtask == null) sendtask = Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => { - def foo(): Unit = { - channel.createMessage((if (user != null) user.getMention + "\n" - else "") + msgtosend.trim).subscribe() - sendtask = null - msgtosend = "" - } - - foo() + channel.createMessage((if (user != null) user.getMention + "\n" else "") + msgtosend.trim).subscribe() + sendtask = null + msgtosend = "" }, 4) // Waits a 0.2 second to gather all/most of the different messages } } catch { diff --git a/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala b/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala index 467ee8b..c72be51 100644 --- a/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala +++ b/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala @@ -7,11 +7,13 @@ import buttondevteam.discordplugin.util.Timings import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} import buttondevteam.lib.TBMCCoreAPI import buttondevteam.lib.architecture.Component +import discord4j.core.`object`.entity.Message import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} import discord4j.core.event.EventDispatcher import discord4j.core.event.domain.PresenceUpdateEvent import discord4j.core.event.domain.message.MessageCreateEvent import discord4j.core.event.domain.role.{RoleCreateEvent, RoleDeleteEvent, RoleUpdateEvent} +import reactor.core.Disposable import reactor.core.scala.publisher.{SFlux, SMono} object CommonListeners { @@ -26,44 +28,20 @@ object CommonListeners { - Minecraft chat (is enabled in the channel and message isn't [/]mcchat) - CommandListener (with the correct prefix in #bot, or in private) */ - def register(dispatcher: EventDispatcher) = { + def register(dispatcher: EventDispatcher): Unit = { dispatcher.on(classOf[MessageCreateEvent]).flatMap((event: MessageCreateEvent) => { - def foo(event: MessageCreateEvent): SMono[Boolean] = { - timings.printElapsed("Message received") - val `def` = SMono.empty - if (DiscordPlugin.SafeMode) return `def` - val author = event.getMessage.getAuthor - if (!author.isPresent || author.get.isBot) return `def` - if (FunModule.executeMemes(event.getMessage)) return `def` - val commandChannel = DiscordPlugin.plugin.commandChannel.get - SMono(event.getMessage.getChannel).map((mch: MessageChannel) => (commandChannel != null && mch.getId.asLong == commandChannel.asLong) //If mentioned, that's higher than chat - || mch.isInstanceOf[PrivateChannel] || event.getMessage.getContent.contains("channelcon")).flatMap( - (shouldRun: Boolean) => { //Only 'channelcon' is allowed in other channels - def foo(shouldRun: Boolean): SMono[Boolean] = { //Only continue if this doesn't handle the event - if (!shouldRun) return SMono.just(true) //The condition is only for the first command execution, not mcchat - CommandListener.runCommand(event.getMessage, commandChannel, mentionedonly = true) //#bot is handled here - } - - foo(shouldRun) - }).filterWhen(_ => { - timings.printElapsed("mcchat") - val mcchat = Component.getComponents.get(classOf[MinecraftChatModule]) - if (mcchat != null && mcchat.isEnabled) { //ComponentManager.isEnabled() searches the component again - return mcchat.asInstanceOf[MinecraftChatModule].getListener.handleDiscord(event) //Also runs Discord commands in chat channels - } - SMono.just(true) //Wasn't handled, continue - }).filterWhen(_ => CommandListener.runCommand(event.getMessage, commandChannel, mentionedonly = false)) - } - - foo(event) - }).onErrorContinue((err: Throwable, _) => TBMCCoreAPI.SendException("An error occured while handling a message!", err, DiscordPlugin.plugin)).subscribe() + SMono.just(event.getMessage).filter(_ => !DiscordPlugin.SafeMode) + .filter(message => message.getAuthor.filter(!_.isBot).isPresent) + .filter(message => !FunModule.executeMemes(message)) + .flatMap(handleMessage(event)) + }).onErrorContinue((err, _) => TBMCCoreAPI.SendException("An error occured while handling a message!", err, DiscordPlugin.plugin)).subscribe() dispatcher.on(classOf[PresenceUpdateEvent]).subscribe((event: PresenceUpdateEvent) => { if (!DiscordPlugin.SafeMode) FunModule.handleFullHouse(event) }) - SFlux(dispatcher.on(classOf[RoleCreateEvent])).subscribe(GameRoleModule.handleRoleEvent _) - SFlux(dispatcher.on(classOf[RoleDeleteEvent])).subscribe(GameRoleModule.handleRoleEvent _) - SFlux(dispatcher.on(classOf[RoleUpdateEvent])).subscribe(GameRoleModule.handleRoleEvent _) + SFlux(dispatcher.on(classOf[RoleCreateEvent])).subscribe(GameRoleModule.handleRoleEvent) + SFlux(dispatcher.on(classOf[RoleDeleteEvent])).subscribe(GameRoleModule.handleRoleEvent) + SFlux(dispatcher.on(classOf[RoleUpdateEvent])).subscribe(GameRoleModule.handleRoleEvent) } var debug = false @@ -71,4 +49,20 @@ object CommonListeners { def debug(debug: String): Unit = if (CommonListeners.debug) { //Debug DPUtils.getLogger.info(debug) } + + private def handleMessage(event: MessageCreateEvent) = { + (message: Message) => { + val commandChannel = Option(DiscordPlugin.plugin.commandChannel.get) + SMono(message.getChannel).filter(mch => commandChannel.isDefined && mch.getId.asLong() == commandChannel.get.asLong() //If mentioned, that's higher than chat + || mch.isInstanceOf[PrivateChannel] || message.getContent.contains("channelcon")).flatMap(_ => { //Only 'channelcon' is allowed in other channels + //Only continue if this doesn't handle the event + CommandListener.runCommand(message, commandChannel.get, mentionedonly = true) //#bot is handled here + }).`then`(SMono.just(true)) //The condition is only for the first command execution, not mcchat + .filterWhen(_ => { + Option(Component.getComponents.get(classOf[MinecraftChatModule])).filter(_.isEnabled) + .map(_.asInstanceOf[MinecraftChatModule].getListener.handleDiscord(event)) //Also runs Discord commands in chat channels + .getOrElse(SMono.just(true)) //Wasn't handled, continue + }).filterWhen(_ => CommandListener.runCommand(event.getMessage, commandChannel.get, mentionedonly = false)) + } + } } \ No newline at end of file diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatCommand.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatCommand.scala index 9b84215..74c27f0 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatCommand.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatCommand.scala @@ -14,14 +14,14 @@ import discord4j.core.`object`.entity.channel.PrivateChannel )) class MCChatCommand(private val module: MinecraftChatModule) extends ICommand2DC { @Command2.Subcommand override def `def`(sender: Command2DCSender): Boolean = { - if (!(module.allowPrivateChat.get)) { + if (!module.allowPrivateChat.get) { sender.sendMessage("using the private chat is not allowed on this Minecraft server.") return true } val message = sender.getMessage val channel = message.getChannel.block @SuppressWarnings(Array("OptionalGetWithoutIsPresent")) val author = message.getAuthor.get - if (!((channel.isInstanceOf[PrivateChannel]))) { + if (!channel.isInstanceOf[PrivateChannel]) { DPUtils.reply(message, channel, "this command can only be issued in a direct message with the bot.").subscribe() return true } diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MCListener.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCListener.scala index 6e40149..5782f0e 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MCListener.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MCListener.scala @@ -28,7 +28,7 @@ class MCListener(val module: MinecraftChatModule) extends Listener { } @EventHandler(priority = EventPriority.MONITOR) def onPlayerJoin(e: PlayerJoinEvent): Unit = { - if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return // Don't show the joined message for the fake player + if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return () // Don't show the joined message for the fake player Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, () => { def foo(): Unit = { val p = e.getPlayer @@ -54,7 +54,7 @@ class MCListener(val module: MinecraftChatModule) extends Listener { MCChatUtils.forAllowedCustomAndAllMCChat(MCChatUtils.send(message), player, ChannelconBroadcast.JOINLEAVE, hookmsg = true).subscribe() @EventHandler(priority = EventPriority.MONITOR) def onPlayerLeave(e: PlayerQuitEvent): Unit = { - if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return // Only care about real users + if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return () // Only care about real users MCChatUtils.OnlineSenders.filterInPlace((_, userMap) => userMap.entrySet.stream.noneMatch(_.getValue.getUniqueId.equals(e.getPlayer.getUniqueId))) Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, () => MCChatUtils.LoggedInPlayers.get(e.getPlayer.getUniqueId).foreach(MCChatUtils.callLoginEvents)) Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => ChromaBot.updatePlayerList(), 5) From 2a985509fbfc2e3992bc36ab0b207d11cd5c5cd8 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Wed, 5 Jan 2022 01:31:24 +0100 Subject: [PATCH 23/23] Remove custom command handling and start slash commands They don't work yet, but show up on Discord so that's something --- .../discordplugin/DiscordPlugin.scala | 17 +-- .../discordplugin/DiscordSenderBase.scala | 4 +- .../discordplugin/commands/Command2DC.scala | 26 ++++- .../listeners/CommandListener.scala | 110 ------------------ .../listeners/CommonListeners.scala | 39 +++---- .../discordplugin/mcchat/MCChatListener.scala | 2 - 6 files changed, 49 insertions(+), 149 deletions(-) delete mode 100644 src/main/scala/buttondevteam/discordplugin/listeners/CommandListener.scala diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala index 8e03848..9521482 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordPlugin.scala @@ -1,5 +1,6 @@ package buttondevteam.discordplugin +import buttondevteam.discordplugin.DiscordPlugin.dc import buttondevteam.discordplugin.announcer.AnnouncerModule import buttondevteam.discordplugin.broadcaster.GeneralEventBroadcasterModule import buttondevteam.discordplugin.commands.* @@ -84,7 +85,7 @@ import java.util.Optional var commandChannel: ReadOnlyConfigData[Snowflake] = DPUtils.snowflakeData(getIConfig, "commandChannel", 0L) /** * The role that allows using mod-only Discord commands. - * If empty (''), then it will only allow for the owner. + * If empty (''), then it will only allow for the owner. */ var modRole: ReadOnlyConfigData[SMono[Role]] = null /** @@ -204,19 +205,19 @@ import java.util.Optional Component.registerComponent(this, new AnnouncerModule) Component.registerComponent(this, new FunModule) ChromaBot.updatePlayerList() //The MCChatModule is tested to be enabled - manager.registerCommand(new VersionCommand) - manager.registerCommand(new UserinfoCommand) - manager.registerCommand(new HelpCommand) - manager.registerCommand(new DebugCommand) - manager.registerCommand(new ConnectCommand) + val applicationId = dc.getRestClient.getApplicationId.block() + val guildId = Some(DiscordPlugin.mainServer.getId.asLong()) + manager.registerCommand(new VersionCommand, applicationId, guildId) + manager.registerCommand(new UserinfoCommand, applicationId, guildId) + manager.registerCommand(new HelpCommand, applicationId, guildId) + manager.registerCommand(new DebugCommand, applicationId, guildId) + manager.registerCommand(new ConnectCommand, applicationId, guildId) TBMCCoreAPI.SendUnsentExceptions() TBMCCoreAPI.SendUnsentDebugMessages() val blw = new BukkitLogWatcher blw.start() LogManager.getRootLogger.asInstanceOf[Logger].addAppender(blw) logWatcher = blw - Interactions.create().onCommand("teszt", Interactions.createHandler() - .guild(gi => gi.acknowledge().withFollowup(_.createFollowupMessage("Teszt"))).build()); if (!TBMCCoreAPI.IsTestServer) DiscordPlugin.dc.updatePresence(ClientPresence.online(ClientActivity.playing("Minecraft"))).subscribe() else DiscordPlugin.dc.updatePresence(ClientPresence.online(ClientActivity.playing("testing"))).subscribe() getLogger.info("Loaded!") diff --git a/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala b/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala index 9d35368..46a9e69 100644 --- a/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala +++ b/src/main/scala/buttondevteam/discordplugin/DiscordSenderBase.scala @@ -46,11 +46,11 @@ abstract class DiscordSenderBase protected(var user: User, var channel: MessageC val sendmsg = DPUtils.sanitizeString(message) this synchronized { msgtosend += "\n" + sendmsg - if (sendtask == null) sendtask = Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => { + if (sendtask == null) sendtask = Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, (() => { channel.createMessage((if (user != null) user.getMention + "\n" else "") + msgtosend.trim).subscribe() sendtask = null msgtosend = "" - }, 4) // Waits a 0.2 second to gather all/most of the different messages + }): Runnable, 4) // Waits a 0.2 second to gather all/most of the different messages } } catch { case e: Exception => diff --git a/src/main/scala/buttondevteam/discordplugin/commands/Command2DC.scala b/src/main/scala/buttondevteam/discordplugin/commands/Command2DC.scala index 690cb2d..7301784 100644 --- a/src/main/scala/buttondevteam/discordplugin/commands/Command2DC.scala +++ b/src/main/scala/buttondevteam/discordplugin/commands/Command2DC.scala @@ -2,12 +2,36 @@ package buttondevteam.discordplugin.commands import buttondevteam.discordplugin.DiscordPlugin import buttondevteam.lib.chat.Command2 +import discord4j.common.util.Snowflake +import discord4j.core.`object`.command.ApplicationCommandOption +import discord4j.discordjson.json.{ApplicationCommandOptionData, ApplicationCommandRequest} import java.lang.reflect.Method class Command2DC extends Command2[ICommand2DC, Command2DCSender] { - override def registerCommand(command: ICommand2DC): Unit = + override def registerCommand(command: ICommand2DC): Unit = { + registerCommand(command, DiscordPlugin.dc.getApplicationInfo.block().getId.asLong()) + } + + def registerCommand(command: ICommand2DC, appId: Long, guildId: Option[Long] = None): Unit = { super.registerCommand(command, DiscordPlugin.getPrefix) //Needs to be configurable for the helps + val greetCmdRequest = ApplicationCommandRequest.builder() + .name(command.getCommandPath) //TODO: Main path + .description("A ChromaBot command.") //TODO: Description + .addOption(ApplicationCommandOptionData.builder() + .name("name") + .description("Your name") + .`type`(ApplicationCommandOption.Type.STRING.getValue) + .required(true) + .build() + ).build() + val service = DiscordPlugin.dc.getRestClient.getApplicationService + guildId match { + case Some(id) => service.createGuildApplicationCommand(appId, id, greetCmdRequest).subscribe() + case None => service.createGlobalApplicationCommand(appId, greetCmdRequest).subscribe() + } + } + override def hasPermission(sender: Command2DCSender, command: ICommand2DC, method: Method): Boolean = { //return !command.isModOnly() || sender.getMessage().getAuthor().hasRole(DiscordPlugin.plugin.modRole().get()); //TODO: modRole may be null; more customisable way? true diff --git a/src/main/scala/buttondevteam/discordplugin/listeners/CommandListener.scala b/src/main/scala/buttondevteam/discordplugin/listeners/CommandListener.scala deleted file mode 100644 index 0ae0459..0000000 --- a/src/main/scala/buttondevteam/discordplugin/listeners/CommandListener.scala +++ /dev/null @@ -1,110 +0,0 @@ -package buttondevteam.discordplugin.listeners - -import buttondevteam.discordplugin.commands.Command2DCSender -import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} -import buttondevteam.lib.TBMCCoreAPI -import discord4j.common.util.Snowflake -import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} -import discord4j.core.`object`.entity.{Member, Message, Role, User} -import reactor.core.scala.publisher.{SFlux, SMono} - -import java.util.concurrent.atomic.AtomicBoolean - -object CommandListener { - /** - * Runs a ChromaBot command. If mentionedonly is false, it will only execute the command if it was in #bot with the correct prefix or in private. - * - * @param message The Discord message - * @param mentionedonly Only run the command if ChromaBot is mentioned at the start of the message - * @return Whether it did not run the command - */ - def runCommand(message: Message, commandChannelID: Snowflake, mentionedonly: Boolean): SMono[Boolean] = { - val timings = CommonListeners.timings - val ret = SMono.just(true) - if (message.getContent.isEmpty) return ret //Pin messages and such, let the mcchat listener deal with it - val content = message.getContent - timings.printElapsed("A") - SMono(message.getChannel).flatMap((channel: MessageChannel) => { - def foo(channel: MessageChannel): SMono[Boolean] = { - var tmp = ret - if (!mentionedonly) { //mentionedonly conditions are in CommonListeners - timings.printElapsed("B") - if (!channel.isInstanceOf[PrivateChannel] && !(content.charAt(0) == DiscordPlugin.getPrefix && channel.getId.asLong == commandChannelID.asLong)) { // - return ret - } - timings.printElapsed("C") - tmp = ret.`then`(SMono(channel.`type`)).`then`(ret) // Fun (this true is ignored - x) - } - val cmdwithargs = new StringBuilder(content) - val gotmention = new AtomicBoolean - timings.printElapsed("Before self") - tmp.flatMapMany((_: Boolean) => SMono(DiscordPlugin.dc.getSelf) - .flatMap((self: User) => SMono(self.asMember(DiscordPlugin.mainServer.getId))) - .flatMapMany((self: Member) => { - def foo(self: Member) = { - timings.printElapsed("D") - gotmention.set(checkanddeletemention(cmdwithargs, self.getMention, message)) - gotmention.set(checkanddeletemention(cmdwithargs, self.getNicknameMention, message) || gotmention.get) - val mentions = SFlux(message.getRoleMentions) - SFlux(self.getRoles).filterWhen((r: Role) => mentions.any((rr: Role) => rr.getName == r.getName)).map(_.getMention) - } - - foo(self) - }).map((mentionRole: String) => { - def foo(mentionRole: String): Boolean = { - timings.printElapsed("E") - gotmention.set(checkanddeletemention(cmdwithargs, mentionRole, message) || gotmention.get) // Delete all mentions - !mentionedonly || gotmention.get //Stops here if false - } - - foo(mentionRole) - }).switchIfEmpty(SMono.fromCallable(() => !mentionedonly || gotmention.get))).filter(b => b) - .last(Option(false)).filter(b => b) - .doOnNext(_ => channel.`type`.subscribe).flatMap(_ => { - def foo(): SMono[Boolean] = { - val cmdwithargsString = cmdwithargs.toString - try { - timings.printElapsed("F") - if (!DiscordPlugin.plugin.manager.handleCommand(new Command2DCSender(message), cmdwithargsString)) - return DPUtils.reply(message, channel, "unknown command. Do " + DiscordPlugin.getPrefix + "help for help.").map(_ => false) - } catch { - case e: Exception => - TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e, DiscordPlugin.plugin) - } - SMono.just(false) //If the command succeeded or there was an error, return false - } - - foo() - }).defaultIfEmpty(true) - } - - foo(channel) - }) - } - - private def checkanddeletemention(cmdwithargs: StringBuilder, mention: String, message: Message): Boolean = { - val prefix = DiscordPlugin.getPrefix - if (message.getContent.startsWith(mention)) { // TODO: Resolve mentions: Compound arguments, either a mention or text - if (cmdwithargs.length > mention.length + 1) { - var i = cmdwithargs.indexOf(" ", mention.length) - if (i == -1) i = mention.length - else { //noinspection StatementWithEmptyBody - while ( { - i < cmdwithargs.length && cmdwithargs.charAt(i) == ' ' - }) { //Removes any space before the command - i += 1 - } - } - cmdwithargs.delete(0, i) - cmdwithargs.insert(0, prefix) //Always use the prefix for processing - } - else cmdwithargs.replace(0, cmdwithargs.length, prefix + "help") - } - else { - if (cmdwithargs.isEmpty) cmdwithargs.replace(0, 0, prefix + "help") - else if (cmdwithargs.charAt(0) != prefix) cmdwithargs.insert(0, prefix) - return false //Don't treat / as mention, mentions can be used in public mcchat - } - true - } -} \ No newline at end of file diff --git a/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala b/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala index c72be51..3b7da35 100644 --- a/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala +++ b/src/main/scala/buttondevteam/discordplugin/listeners/CommonListeners.scala @@ -11,29 +11,26 @@ import discord4j.core.`object`.entity.Message import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} import discord4j.core.event.EventDispatcher import discord4j.core.event.domain.PresenceUpdateEvent +import discord4j.core.event.domain.interaction.{ChatInputInteractionEvent, MessageInteractionEvent} import discord4j.core.event.domain.message.MessageCreateEvent import discord4j.core.event.domain.role.{RoleCreateEvent, RoleDeleteEvent, RoleUpdateEvent} import reactor.core.Disposable +import reactor.core.publisher.Mono import reactor.core.scala.publisher.{SFlux, SMono} object CommonListeners { val timings = new Timings - /* - MentionEvent: - - CommandListener (starts with mention, only 'channelcon' and not in #bot) - - MessageReceivedEvent: - - v CommandListener (starts with mention, in #bot or a connected chat) - - Minecraft chat (is enabled in the channel and message isn't [/]mcchat) - - CommandListener (with the correct prefix in #bot, or in private) - */ def register(dispatcher: EventDispatcher): Unit = { dispatcher.on(classOf[MessageCreateEvent]).flatMap((event: MessageCreateEvent) => { SMono.just(event.getMessage).filter(_ => !DiscordPlugin.SafeMode) .filter(message => message.getAuthor.filter(!_.isBot).isPresent) .filter(message => !FunModule.executeMemes(message)) - .flatMap(handleMessage(event)) + .filterWhen(message => { + Option(Component.getComponents.get(classOf[MinecraftChatModule])).filter(_.isEnabled) + .map(_.asInstanceOf[MinecraftChatModule].getListener.handleDiscord(event)) + .getOrElse(SMono.just(true)) //Wasn't handled, continue + }) }).onErrorContinue((err, _) => TBMCCoreAPI.SendException("An error occured while handling a message!", err, DiscordPlugin.plugin)).subscribe() dispatcher.on(classOf[PresenceUpdateEvent]).subscribe((event: PresenceUpdateEvent) => { if (!DiscordPlugin.SafeMode) @@ -42,6 +39,12 @@ object CommonListeners { SFlux(dispatcher.on(classOf[RoleCreateEvent])).subscribe(GameRoleModule.handleRoleEvent) SFlux(dispatcher.on(classOf[RoleDeleteEvent])).subscribe(GameRoleModule.handleRoleEvent) SFlux(dispatcher.on(classOf[RoleUpdateEvent])).subscribe(GameRoleModule.handleRoleEvent) + SFlux(dispatcher.on(classOf[ChatInputInteractionEvent], event => { + if(event.getCommandName() eq "help") + event.reply("Hello there") + else + Mono.empty() + })).subscribe() } var debug = false @@ -49,20 +52,4 @@ object CommonListeners { def debug(debug: String): Unit = if (CommonListeners.debug) { //Debug DPUtils.getLogger.info(debug) } - - private def handleMessage(event: MessageCreateEvent) = { - (message: Message) => { - val commandChannel = Option(DiscordPlugin.plugin.commandChannel.get) - SMono(message.getChannel).filter(mch => commandChannel.isDefined && mch.getId.asLong() == commandChannel.get.asLong() //If mentioned, that's higher than chat - || mch.isInstanceOf[PrivateChannel] || message.getContent.contains("channelcon")).flatMap(_ => { //Only 'channelcon' is allowed in other channels - //Only continue if this doesn't handle the event - CommandListener.runCommand(message, commandChannel.get, mentionedonly = true) //#bot is handled here - }).`then`(SMono.just(true)) //The condition is only for the first command execution, not mcchat - .filterWhen(_ => { - Option(Component.getComponents.get(classOf[MinecraftChatModule])).filter(_.isEnabled) - .map(_.asInstanceOf[MinecraftChatModule].getListener.handleDiscord(event)) //Also runs Discord commands in chat channels - .getOrElse(SMono.just(true)) //Wasn't handled, continue - }).filterWhen(_ => CommandListener.runCommand(event.getMessage, commandChannel.get, mentionedonly = false)) - } - } } \ No newline at end of file diff --git a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala index 18fb427..c1c1af9 100644 --- a/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala +++ b/src/main/scala/buttondevteam/discordplugin/mcchat/MCChatListener.scala @@ -3,7 +3,6 @@ package buttondevteam.discordplugin.mcchat import buttondevteam.core.ComponentManager import buttondevteam.discordplugin.* import buttondevteam.discordplugin.DPUtils.SpecExtensions -import buttondevteam.discordplugin.listeners.CommandListener import buttondevteam.discordplugin.playerfaker.{VanillaCommandListener, VanillaCommandListener14, VanillaCommandListener15} import buttondevteam.lib.* import buttondevteam.lib.chat.{ChatMessage, TBMCChatAPI} @@ -251,7 +250,6 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener { SMono(ev.getMessage.getChannel) .filter(channel => isChatEnabled(channel, author, hasCustomChat)) .filter(channel => !isRunningMCChatCommand(channel, ev.getMessage.getContent, prefix)) - .filterWhen(_ => CommandListener.runCommand(ev.getMessage, DiscordPlugin.plugin.commandChannel.get, mentionedonly = true)) //Allow running commands in chat channels .filter(channel => { MCChatUtils.resetLastMessage(channel) recevents.add(ev)