From a0a7f756c4f68fd53e0969481374a8563d90c28e Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Tue, 9 Mar 2021 02:47:11 +0100 Subject: [PATCH] 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