Implicit classes for conversion, more fixing

Added 'extension methods' to convert to Scala-friendly formats easily
This commit is contained in:
Norbi Peti 2021-03-09 02:47:11 +01:00
parent 7296ebd2f8
commit a0a7f756c4
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
17 changed files with 286 additions and 260 deletions

View file

@ -4,7 +4,7 @@ import buttondevteam.discordplugin.ChannelconBroadcast.ChannelconBroadcast
import buttondevteam.discordplugin.mcchat.MCChatUtils import buttondevteam.discordplugin.mcchat.MCChatUtils
import discord4j.core.`object`.entity.Message import discord4j.core.`object`.entity.Message
import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.`object`.entity.channel.MessageChannel
import reactor.core.publisher.Mono import reactor.core.scala.publisher.SMono
import javax.annotation.Nullable import javax.annotation.Nullable
@ -20,8 +20,8 @@ object ChromaBot {
* *
* @param message The message to send, duh (use {@link MessageChannel# createMessage ( String )}) * @param message The message to send, duh (use {@link MessageChannel# createMessage ( String )})
*/ */
def sendMessage(message: java.util.function.Function[Mono[MessageChannel], Mono[Message]]): Unit = def sendMessage(message: SMono[MessageChannel] => SMono[Message]): Unit =
MCChatUtils.forPublicPrivateChat(message.apply(_)).subscribe MCChatUtils.forPublicPrivateChat(message).subscribe
/** /**
* Send a message to the chat channels, private chats and custom chats. * 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 message The message to send, duh
* @param toggle The toggle type for channelcon * @param toggle The toggle type for channelcon
*/ */
def sendMessageCustomAsWell(message: Function[Mono[MessageChannel], Mono[Message]], @Nullable toggle: ChannelconBroadcast): Unit = def sendMessageCustomAsWell(message: SMono[MessageChannel] => SMono[Message], @Nullable toggle: ChannelconBroadcast): Unit =
MCChatUtils.forCustomAndAllMCChat(message.apply, toggle, false).subscribe MCChatUtils.forCustomAndAllMCChat(message.apply, toggle, hookmsg = false).subscribe
def updatePlayerList(): Unit = def updatePlayerList(): Unit =
MCChatUtils.updatePlayerList() MCChatUtils.updatePlayerList()

View file

@ -5,8 +5,9 @@ import buttondevteam.lib.architecture.{Component, ConfigData, IHaveConfig, ReadO
import discord4j.common.util.Snowflake import discord4j.common.util.Snowflake
import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.`object`.entity.channel.MessageChannel
import discord4j.core.`object`.entity.{Guild, Message, Role} import discord4j.core.`object`.entity.{Guild, Message, Role}
import discord4j.core.spec.EmbedCreateSpec import discord4j.core.spec.{EmbedCreateSpec, Spec}
import reactor.core.scala.publisher.SMono import reactor.core.publisher.{Flux, Mono}
import reactor.core.scala.publisher.{SFlux, SMono}
import java.util import java.util
import java.util.Comparator import java.util.Comparator
@ -206,4 +207,17 @@ object DPUtils {
getMessageChannel(config.getPath, config.get) getMessageChannel(config.getPath, config.get)
def ignoreError[T](mono: SMono[T]): SMono[T] = mono.onErrorResume((_: Throwable) => SMono.empty) 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 = ()
}
} }

View file

@ -89,7 +89,7 @@ import scala.annotation.tailrec
if (msgsb.nonEmpty) sendMsg(channel.get(), msgsb.toString()) if (msgsb.nonEmpty) sendMsg(channel.get(), msgsb.toString())
if (modmsgsb.nonEmpty) sendMsg(modChannel.get(), modmsgsb.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 { } catch {
case e: Exception => case e: Exception =>
e.printStackTrace() e.printStackTrace()

View file

@ -17,7 +17,9 @@ import org.bukkit.entity.Player
var WaitingToConnect: HashBiMap[String, String] = HashBiMap.create 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 = { @Command2.Subcommand def `def`(sender: Command2DCSender, Minecraftname: String): Boolean = {
val message = sender.getMessage val message = sender.getMessage
val channel = message.getChannel.block val channel = message.getChannel.block

View file

@ -16,7 +16,7 @@ class DebugCommand extends ICommand2DC {
.map((u: User) => SMono(u.asMember(DiscordPlugin.mainServer.getId))).getOrElse(SMono.empty)) .map((u: User) => SMono(u.asMember(DiscordPlugin.mainServer.getId))).getOrElse(SMono.empty))
.flatMap((m: Member) => DiscordPlugin.plugin.modRole.get .flatMap((m: Member) => DiscordPlugin.plugin.modRole.get
.map(mr => m.getRoleIds.stream.anyMatch((r: Snowflake) => r == mr.getId)) .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 .onErrorResume(_ => SMono.just(false)) //Role not found
.subscribe(success => { .subscribe(success => {
if (success) { if (success) {

View file

@ -5,6 +5,7 @@ import buttondevteam.discordplugin.DiscordPlugin
import buttondevteam.lib.TBMCDebugMessageEvent import buttondevteam.lib.TBMCDebugMessageEvent
import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.`object`.entity.channel.MessageChannel
import org.bukkit.event.{EventHandler, Listener} import org.bukkit.event.{EventHandler, Listener}
import reactor.core.scala.publisher.SMono
object DebugMessageListener { object DebugMessageListener {
private def SendMessage(message: String): Unit = { private def SendMessage(message: String): Unit = {
@ -16,7 +17,7 @@ object DebugMessageListener {
sb.append("```").append("\n") sb.append("```").append("\n")
sb.append(if (message.length > 2000) message.substring(0, 2000) else message).append("\n") sb.append(if (message.length > 2000) message.substring(0, 2000) else message).append("\n")
sb.append("```") sb.append("```")
mc.flatMap((ch: MessageChannel) => ch.createMessage(sb.toString)).subscribe mc.flatMap((ch: MessageChannel) => SMono(ch.createMessage(sb.toString))).subscribe
} catch { } catch {
case ex: Exception => case ex: Exception =>
ex.printStackTrace() ex.printStackTrace()

View file

@ -107,7 +107,7 @@ class FunModule extends Component[DiscordPlugin] with Listener {
* Answers for a recognized question. Selected randomly. * Answers for a recognized question. Selected randomly.
*/ */
final private val serverReadyAnswers: ConfigData[util.ArrayList[String]] = 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 = private def createUsableServerReadyStrings(): Unit =
IntStream.range(0, serverReadyAnswers.get.size).forEach((i: Int) => FunModule.usableServerReadyStrings.add(i.toShort)) IntStream.range(0, serverReadyAnswers.get.size).forEach((i: Int) => FunModule.usableServerReadyStrings.add(i.toShort))

View file

@ -39,9 +39,10 @@ object MCChatCustom {
@Nullable def getCustomChat(channel: Snowflake): CustomLMD = @Nullable def getCustomChat(channel: Snowflake): CustomLMD =
lastmsgCustom.find(_.channel.getId.asLong == channel.asLong).orNull lastmsgCustom.find(_.channel.getId.asLong == channel.asLong).orNull
def removeCustomChat(channel: Snowflake): Unit = { def removeCustomChat(channel: Snowflake): Boolean = {
lastmsgCustom synchronized { lastmsgCustom synchronized {
MCChatUtils.lastmsgfromd.remove(channel.asLong) MCChatUtils.lastmsgfromd.remove(channel.asLong)
val count = lastmsgCustom.size
lastmsgCustom.filterInPlace(lmd => { lastmsgCustom.filterInPlace(lmd => {
if (lmd.channel.getId.asLong != channel.asLong) return true if (lmd.channel.getId.asLong != channel.asLong) return true
lmd.mcchannel match { lmd.mcchannel match {
@ -50,6 +51,7 @@ object MCChatCustom {
} }
false false
}) })
lastmsgCustom.size < count
} }
} }

View file

@ -1,7 +1,6 @@
package buttondevteam.discordplugin.mcchat package buttondevteam.discordplugin.mcchat
import buttondevteam.core.ComponentManager import buttondevteam.core.ComponentManager
import buttondevteam.core.component.channel.Channel
import buttondevteam.discordplugin._ import buttondevteam.discordplugin._
import buttondevteam.discordplugin.listeners.CommandListener import buttondevteam.discordplugin.listeners.CommandListener
import buttondevteam.discordplugin.playerfaker.{VanillaCommandListener, VanillaCommandListener14, VanillaCommandListener15} import buttondevteam.discordplugin.playerfaker.{VanillaCommandListener, VanillaCommandListener14, VanillaCommandListener15}
@ -24,10 +23,10 @@ import reactor.core.scala.publisher.{SFlux, SMono}
import java.time.Instant import java.time.Instant
import java.util import java.util
import java.util.Optional
import java.util.concurrent.{LinkedBlockingQueue, TimeoutException} 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 java.util.stream.Collectors
import scala.jdk.CollectionConverters.SetHasAsScala
import scala.jdk.OptionConverters.RichOptional import scala.jdk.OptionConverters.RichOptional
object MCChatListener { 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 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 (e.getChannel.isGlobal && (e.isFromCommand || isdifferentchannel.test(module.chatChannel.get))) {
if (MCChatUtils.lastmsgdata == null) 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) doit(MCChatUtils.lastmsgdata)
} }
@ -146,7 +145,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener {
MCChatCustom.lastmsgCustom.filterInPlace(lmd => { MCChatCustom.lastmsgCustom.filterInPlace(lmd => {
if ((e.isFromCommand || isdifferentchannel.test(lmd.channel.getId)) //Test if msg is from Discord 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.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 if (e.shouldSendTo(lmd.dcp)) { //Check original user's permissions
doit(lmd) doit(lmd)
} }
@ -254,18 +253,17 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener {
.filter(channel => { .filter(channel => {
MCChatUtils.resetLastMessage(channel) MCChatUtils.resetLastMessage(channel)
recevents.add(ev) recevents.add(ev)
if (rectask != null) { if (rectask == null) {
return true recrun = () => {
} recthread = Thread.currentThread
recrun = () => { processDiscordToMC()
recthread = Thread.currentThread if (DiscordPlugin.plugin.isEnabled && !(stop)) {
processDiscordToMC() rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Continue message processing
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 true
return true
}).map(_ => false).defaultIfEmpty(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 dsender: DiscordSenderBase = MCChatUtils.getSender(event.getMessage.getChannelId, sender)
val user: DiscordPlayer = dsender.getChromaUser val user: DiscordPlayer = dsender.getChromaUser
for (u <- SFlux(event.getMessage.getUserMentions).toIterable()) { //TODO: Role mentions def replaceUserMentions(): Unit = {
dmessage = dmessage.replace(u.getMention, "@" + u.getUsername) // TODO: IG Formatting for (u <- SFlux(event.getMessage.getUserMentions).toIterable()) { //TODO: Role mentions
val m: Optional[Member] = u.asMember(DiscordPlugin.mainServer.getId).onErrorResume((t: Throwable) => Mono.empty).blockOptional dmessage = dmessage.replace(u.getMention, "@" + u.getUsername) // TODO: IG Formatting
if (m.isPresent) { val m = u.asMember(DiscordPlugin.mainServer.getId).onErrorResume(_ => Mono.empty).blockOptional
val mm: Member = m.get if (m.isPresent) {
val nick: String = mm.getDisplayName val mm: Member = m.get
dmessage = dmessage.replace(mm.getNicknameMention, "@" + nick) val nick: String = mm.getDisplayName
dmessage = dmessage.replace(mm.getNicknameMention, "@" + nick)
}
} }
} }
for (ch <- SFlux(event.getGuild.flux).flatMap(_.getChannels).toIterable()) { replaceUserMentions()
dmessage = dmessage.replace(ch.getMention, "#" + ch.getName)
} def replaceChannelMentions(): Unit = {
dmessage = EmojiParser.parseToAliases(dmessage, EmojiParser.FitzpatrickAction.PARSE) //Converts emoji to text- TODO: Add option to disable (resource pack?) for (ch <- SFlux(event.getGuild.flux).flatMap(_.getChannels).toIterable()) {
dmessage = dmessage.replaceAll(":(\\S+)\\|type_(?:(\\d)|(1)_2):", ":$1::skin-tone-$2:") //Convert to Discord's format so it still shows up dmessage = dmessage.replace(ch.getMention, "#" + ch.getName)
dmessage = dmessage.replaceAll("<a?:(\\S+):(\\d+)>", ":$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) 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("<a?:(\\S+):(\\d+)>", ":$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 { try {
val lmfd: Message = MCChatUtils.lastmsgfromd.get(event.getMessage.getChannelId.asLong) val lmfd = MCChatUtils.lastmsgfromd.get(event.getMessage.getChannelId.asLong)
if (lmfd != null) { if (lmfd != null)
lmfd.removeSelfReaction(DiscordPlugin.DELIVERED_REACTION).subscribe // Remove it no matter what, we know it's there 99.99% of the time lmfd.removeSelfReaction(DiscordPlugin.DELIVERED_REACTION).subscribe // Remove it no matter what, we know it's there 99.99% of the time
}
} catch { } catch {
case e: Exception => case e: Exception =>
TBMCCoreAPI.SendException("An error occured while removing reactions from chat!", e, module) 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) 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
handleIngameCommand(event, dmessage, dsender, user, clmd, isPrivate)
else if (handleIngameMessage(event, dmessage, dsender, user, clmd, isPrivate)) // Not a command
addCheckmark()
} catch { } catch {
case e: Exception => case e: Exception =>
TBMCCoreAPI.SendException("An error occured while handling message \"" + dmessage + "\"!", e, module) 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 * Handles a message coming from Discord to Minecraft.
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) { * @param event The Discord event
clmd.mcchannel.getRTR(clmd.dcp) * @param dmessage The message itself
} * @param dsender The sender who sent it
else { * @param user The Chroma user of the sender
dsender.getChromaUser.channel.get.getRTR(dsender) * @param clmd Custom chat last message data (if in a custom chat)
} * @param isPrivate Whether the chat is private
TBMCChatAPI.SendSystemMessage(if (clmd != null) clmd.mcchannel else dsender.getChromaUser.channel.get, rtr, * @return Whether the bot should react with a checkmark
(dsender match { */
case player: Player => private def handleIngameMessage(event: MessageCreateEvent, dmessage: String, dsender: DiscordSenderBase, user: DiscordPlayer,
player.getDisplayName clmd: MCChatCustom.CustomLMD, isPrivate: Boolean): Boolean = {
case _ => def getAttachmentText = {
dsender.getName val att = event.getMessage.getAttachments.asScala
}) + " pinned a message on Discord.", TBMCSystemChatEvent.BroadcastTarget.ALL) 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 { else {
val cmb: ChatMessage.ChatMessageBuilder = ChatMessage.builder(dsender, user, getChatMessage.apply(dmessage)).fromCommand(false) val cmb = ChatMessage.builder(dsender, user, dmessage + getAttachmentText()).fromCommand(false)
if (clmd != null) { if (clmd != null)
TBMCChatAPI.SendChatMessage(cmb.permCheck(clmd.dcp).build, clmd.mcchannel) TBMCChatAPI.SendChatMessage(cmb.permCheck(clmd.dcp).build, clmd.mcchannel)
} else
else {
TBMCChatAPI.SendChatMessage(cmb.build) TBMCChatAPI.SendChatMessage(cmb.build)
} true
react = 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 event.getMessage.delete.subscribe
} val cmd = dmessage.substring(1)
val cmd: String = dmessage.substring(1) val cmdlowercased = cmd.toLowerCase
val cmdlowercased: String = cmd.toLowerCase if (dsender.isInstanceOf[DiscordSender] && notWhitelisted(cmdlowercased)) { // Command not whitelisted
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" + whitelistedCommands() +
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) { (if (user.getConnectedID(classOf[TBMCPlayer]) == null)
"\nTo access your commands, first please connect your accounts, using /connect in " + DPUtils.botmention + "\nThen y" "\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`!")
else { return
"\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) module.log(dsender.getName + " ran from DC: /" + cmd)
if (dsender.isInstanceOf[DiscordSender] && runCustomCommand(dsender, cmdlowercased)) { if (dsender.isInstanceOf[DiscordSender] && runCustomCommand(dsender, cmdlowercased)) {
return true return
} }
val channel: Channel = if (clmd == null) { val channel = if (clmd == null) user.channel.get else clmd.mcchannel
user.channel.get 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
else { Bukkit.getPluginManager.callEvent(ev)
clmd.mcchannel if (!ev.isCancelled)
} runMCCommand(dsender, cmd)
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 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 = { private def runCustomCommand(dsender: DiscordSenderBase, cmdlowercased: String): Boolean = {
if (cmdlowercased.startsWith("list")) { 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("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(", "))) dsender.sendMessage("Players: " + players.stream.filter(MCChatUtils.checkEssentials).map(_.getDisplayName).collect(Collectors.joining(", ")))
return true true
} }
return false else false
} }
} }

View file

@ -4,12 +4,12 @@ import buttondevteam.core.ComponentManager
import buttondevteam.discordplugin.mcchat.MCChatUtils.LastMsgData import buttondevteam.discordplugin.mcchat.MCChatUtils.LastMsgData
import buttondevteam.discordplugin.{DiscordConnectedPlayer, DiscordPlayer, DiscordPlugin, DiscordSenderBase} import buttondevteam.discordplugin.{DiscordConnectedPlayer, DiscordPlayer, DiscordPlugin, DiscordSenderBase}
import buttondevteam.lib.player.TBMCPlayer import buttondevteam.lib.player.TBMCPlayer
import discord4j.common.util.Snowflake
import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.User
import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel}
import org.bukkit.Bukkit import org.bukkit.Bukkit
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
import scala.jdk.javaapi.CollectionConverters.asScala
object MCChatPrivate { object MCChatPrivate {
/** /**
@ -39,7 +39,7 @@ object MCChatPrivate {
def foo(): Unit = { def foo(): Unit = {
if ((p == null || p.isInstanceOf[DiscordSenderBase]) // Player is offline - If the player is online, that takes precedence 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 && 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) MCChatUtils.LoggedInPlayers.remove(sender.getUniqueId)
@ -61,14 +61,13 @@ object MCChatPrivate {
def isMinecraftChatEnabled(dp: DiscordPlayer): Boolean = isMinecraftChatEnabled(dp.getDiscordID) def isMinecraftChatEnabled(dp: DiscordPlayer): Boolean = isMinecraftChatEnabled(dp.getDiscordID)
def isMinecraftChatEnabled(did: String): Boolean = { // Don't load the player data just for this def isMinecraftChatEnabled(did: String): Boolean = { // Don't load the player data just for this
lastmsgPerUser.stream.anyMatch((lmd: MCChatUtils.LastMsgData) => lastmsgPerUser.exists(_.channel.asInstanceOf[PrivateChannel].getRecipientIds.stream.anyMatch(u => u.asString == did))
lmd.channel.asInstanceOf[PrivateChannel].getRecipientIds.stream.anyMatch((u: Snowflake) => u.asString == did))
} }
def logoutAll(): Unit = { def logoutAll(): Unit = {
MCChatUtils.ConnectedSenders synchronized { MCChatUtils.ConnectedSenders synchronized {
for (entry <- asScala(MCChatUtils.ConnectedSenders.entrySet)) { for ((_, userMap) <- MCChatUtils.ConnectedSenders) {
for (valueEntry <- entry.getValue.entrySet) { 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 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.callLogoutEvent(valueEntry.getValue, !Bukkit.isPrimaryThread)
} }

View file

@ -18,7 +18,6 @@ import org.bukkit.entity.Player
import org.bukkit.event.Event import org.bukkit.event.Event
import org.bukkit.event.player.{AsyncPlayerPreLoginEvent, PlayerJoinEvent, PlayerLoginEvent, PlayerQuitEvent} import org.bukkit.event.player.{AsyncPlayerPreLoginEvent, PlayerJoinEvent, PlayerLoginEvent, PlayerQuitEvent}
import org.bukkit.plugin.AuthorNagException import org.bukkit.plugin.AuthorNagException
import org.reactivestreams.Publisher
import reactor.core.scala.publisher.SMono import reactor.core.scala.publisher.SMono
import java.net.InetAddress import java.net.InetAddress
@ -46,7 +45,7 @@ object MCChatUtils {
@Nullable private[mcchat] var lastmsgdata: MCChatUtils.LastMsgData = null @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[mcchat] val lastmsgfromd = new LongObjectHashMap[Message] // Last message sent by a Discord user, used for clearing checkmarks
private var module: MinecraftChatModule = null 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 = { def updatePlayerList(): Unit = {
val mod = getModule val mod = getModule
@ -102,22 +101,22 @@ object MCChatUtils {
addSender(senders, user.getId.asString, sender) addSender(senders, user.getId.asString, sender)
def addSender[T <: DiscordSenderBase](senders: concurrent.Map[String, ConcurrentHashMap[Snowflake, T]], did: String, sender: T): T = { def addSender[T <: DiscordSenderBase](senders: concurrent.Map[String, ConcurrentHashMap[Snowflake, T]], did: String, sender: T): T = {
val origMap = senders.get(did) val mapOpt = senders.get(did)
val map = if (origMap.isEmpty) new ConcurrentHashMap[Snowflake, T] else origMap.get val map = if (mapOpt.isEmpty) new ConcurrentHashMap[Snowflake, T] else mapOpt.get
map.put(sender.getChannel.getId, sender) map.put(sender.getChannel.getId, sender)
senders.put(did, map) senders.put(did, map)
sender sender
} }
def getSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = { def getSender[T <: DiscordSenderBase](senders: concurrent.Map[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = {
val map = senders.get(user.getId.asString) val mapOpt = senders.get(user.getId.asString)
if (map != null) map.get(channel) if (mapOpt.nonEmpty) mapOpt.get.get(channel)
else null.asInstanceOf else null.asInstanceOf
} }
def removeSender[T <: DiscordSenderBase](senders: ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = { def removeSender[T <: DiscordSenderBase](senders: concurrent.Map[String, ConcurrentHashMap[Snowflake, T]], channel: Snowflake, user: User): T = {
val map = senders.get(user.getId.asString) val mapOpt = senders.get(user.getId.asString)
if (map != null) map.remove(channel) if (mapOpt.nonEmpty) mapOpt.get.remove(channel)
else null.asInstanceOf else null.asInstanceOf
} }
@ -138,11 +137,12 @@ object MCChatUtils {
*/ */
def forCustomAndAllMCChat(action: SMono[MessageChannel] => SMono[_], @Nullable toggle: ChannelconBroadcast, hookmsg: Boolean): SMono[_] = { def forCustomAndAllMCChat(action: SMono[MessageChannel] => SMono[_], @Nullable toggle: ChannelconBroadcast, hookmsg: Boolean): SMono[_] = {
if (notEnabled) return SMono.empty if (notEnabled) return SMono.empty
val list = new ListBuffer[Publisher[_]] val list =
if (!GeneralEventBroadcasterModule.isHooked || !hookmsg) list.append(forPublicPrivateChat(action)) List(if (!GeneralEventBroadcasterModule.isHooked || !hookmsg)
val customLMDFunction = (cc: MCChatCustom.CustomLMD) => action(SMono.just(cc.channel)) forPublicPrivateChat(action) else SMono.empty) ++
if (toggle == null) MCChatCustom.lastmsgCustom.map(customLMDFunction).foreach(list.append(_)) (if (toggle == null) MCChatCustom.lastmsgCustom
else MCChatCustom.lastmsgCustom.filter((cc) => (cc.toggles & (1 << toggle.id)) ne 0).map(customLMDFunction).foreach(list.append(_)) else MCChatCustom.lastmsgCustom.filter(cc => (cc.toggles & (1 << toggle.id)) != 0))
.map(_.channel).map(SMono.just).map(action)
SMono.whenDelayError(list) SMono.whenDelayError(list)
} }
@ -156,7 +156,7 @@ object MCChatUtils {
def forAllowedCustomMCChat(action: SMono[MessageChannel] => SMono[_], @Nullable sender: CommandSender, @Nullable toggle: ChannelconBroadcast): SMono[_] = { def forAllowedCustomMCChat(action: SMono[MessageChannel] => SMono[_], @Nullable sender: CommandSender, @Nullable toggle: ChannelconBroadcast): SMono[_] = {
if (notEnabled) return SMono.empty 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 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 if (sender == null) true
else clmd.groupID.equals(clmd.mcchannel.getGroupID(sender)) else clmd.groupID.equals(clmd.mcchannel.getGroupID(sender))
}).map(cc => action.apply(SMono.just(cc.channel))) //TODO: Send error messages on channel connect }).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. * 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 private[mcchat] def getSender(channel: Snowflake, author: User): DiscordSenderBase = { //noinspection OptionalGetWithoutIsPresent
List[() => DiscordSenderBase]( // https://stackoverflow.com/a/28833677/2703239 Option(getSender(OnlineSenders, channel, author)) // Find first non-null
() => getSender[DiscordSenderBase](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
() => getSender[DiscordSenderBase](ConnectedSenders, channel, author), // This doesn't support the public chat, but it'll always return null for it .orElse(Option(getSender(UnconnectedSenders, channel, author))) //
() => getSender[DiscordSenderBase](UnconnectedSenders, channel, author), // .orElse(Option(addSender(UnconnectedSenders, author,
() => addSender[DiscordSenderBase](UnconnectedSenders, author, new DiscordSender(author, SMono(DiscordPlugin.dc.getChannelById(channel)).block().asInstanceOf[MessageChannel]))))
new DiscordSender(author, SMono(DiscordPlugin.dc.getChannelById(channel)).block().asInstanceOf[MessageChannel]))) .get
.map(_.apply()).find(sender => sender != null).get
} }
/** /**

View file

@ -1,22 +1,22 @@
package buttondevteam.discordplugin.mcchat package buttondevteam.discordplugin.mcchat
import buttondevteam.discordplugin.DPUtils.FluxExtensions
import buttondevteam.discordplugin._ import buttondevteam.discordplugin._
import buttondevteam.lib.TBMCSystemChatEvent import buttondevteam.lib.TBMCSystemChatEvent
import buttondevteam.lib.player.{TBMCPlayer, TBMCPlayerBase, TBMCYEEHAWEvent} import buttondevteam.lib.player.{TBMCPlayer, TBMCPlayerBase, TBMCYEEHAWEvent}
import discord4j.common.util.Snowflake import discord4j.common.util.Snowflake
import discord4j.core.`object`.entity.Role
import discord4j.core.`object`.entity.channel.MessageChannel 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 net.ess3.api.events.{AfkStatusChangeEvent, MuteStatusChangeEvent, NickChangeEvent, VanishStatusChangeEvent}
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.event.{EventHandler, EventPriority, Listener}
import org.bukkit.event.entity.PlayerDeathEvent import org.bukkit.event.entity.PlayerDeathEvent
import org.bukkit.event.player.PlayerLoginEvent.Result import org.bukkit.event.player.PlayerLoginEvent.Result
import org.bukkit.event.player._ import org.bukkit.event.player._
import org.bukkit.event.server.{BroadcastMessageEvent, TabCompleteEvent} import org.bukkit.event.server.{BroadcastMessageEvent, TabCompleteEvent}
import reactor.core.publisher.{Flux, Mono} import org.bukkit.event.{EventHandler, EventPriority, Listener}
import reactor.core.publisher.Flux
import java.util.Optional import reactor.core.scala.publisher.SMono
class MCListener(val module: MinecraftChatModule) extends Listener { class MCListener(val module: MinecraftChatModule) extends Listener {
final private val muteRole = DPUtils.roleData(module.getConfig, "muteRole", "Muted") 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.getResult ne Result.ALLOWED) return
if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return if (e.getPlayer.isInstanceOf[DiscordConnectedPlayer]) return
val dcp = MCChatUtils.LoggedInPlayers.get(e.getPlayer.getUniqueId) 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 = { @EventHandler(priority = EventPriority.MONITOR) def onPlayerJoin(e: PlayerJoinEvent): Unit = {
@ -34,15 +34,13 @@ class MCListener(val module: MinecraftChatModule) extends Listener {
def foo(): Unit = { def foo(): Unit = {
val p = e.getPlayer val p = e.getPlayer
val dp = TBMCPlayerBase.getPlayer(p.getUniqueId, classOf[TBMCPlayer]).getAs(classOf[DiscordPlayer]) 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) => { if (dp != null)
def foo(cc: MessageChannel) = { DiscordPlugin.dc.getUserById(Snowflake.of(dp.getDiscordID)).flatMap(user =>
MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, chan, p, module)) user.getPrivateChannel.flatMap(chan => module.chatChannelMono.flatMap(cc => {
MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, cc, p, module)) //Stored per-channel MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, chan, p, module))
Mono.empty MCChatUtils.addSender(MCChatUtils.OnlineSenders, dp.getDiscordID, DiscordPlayerSender.create(user, cc, p, module)) //Stored per-channel
} SMono.empty
}))).subscribe
foo(cc)
}))).subscribe
val message = e.getJoinMessage val message = e.getJoinMessage
sendJoinLeaveMessage(message, e.getPlayer) sendJoinLeaveMessage(message, e.getPlayer)
ChromaBot.updatePlayerList() ChromaBot.updatePlayerList()
@ -58,8 +56,8 @@ class MCListener(val module: MinecraftChatModule) extends Listener {
@EventHandler(priority = EventPriority.MONITOR) def onPlayerLeave(e: PlayerQuitEvent): Unit = { @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.entrySet.removeIf((entry) => entry.getValue.entrySet.stream.anyMatch((p) => p.getValue.getUniqueId.equals(e.getPlayer.getUniqueId))) MCChatUtils.OnlineSenders.filterInPlace((_, userMap) => userMap.entrySet.stream.noneMatch(_.getValue.getUniqueId.equals(e.getPlayer.getUniqueId)))
Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, () => Optional.ofNullable(MCChatUtils.LoggedInPlayers.get(e.getPlayer.getUniqueId)).ifPresent(MCChatUtils.callLoginEvents)) Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, () => MCChatUtils.LoggedInPlayers.get(e.getPlayer.getUniqueId).foreach(MCChatUtils.callLoginEvents))
Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => ChromaBot.updatePlayerList(), 5) Bukkit.getScheduler.runTaskLaterAsynchronously(DiscordPlugin.plugin, () => ChromaBot.updatePlayerList(), 5)
val message = e.getQuitMessage val message = e.getQuitMessage
sendJoinLeaveMessage(message, e.getPlayer) sendJoinLeaveMessage(message, e.getPlayer)
@ -89,20 +87,22 @@ class MCListener(val module: MinecraftChatModule) extends Listener {
if (!source.isPlayer) return if (!source.isPlayer) return
val p = TBMCPlayerBase.getPlayer(source.getPlayer.getUniqueId, classOf[TBMCPlayer]).getAs(classOf[DiscordPlayer]) val p = TBMCPlayerBase.getPlayer(source.getPlayer.getUniqueId, classOf[TBMCPlayer]).getAs(classOf[DiscordPlayer])
if (p == null) return 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) => { DPUtils.ignoreError(SMono(DiscordPlugin.dc.getUserById(Snowflake.of(p.getDiscordID)))
def foo(r: Role): Mono[_] = { .flatMap(user => SMono(user.asMember(DiscordPlugin.mainServer.getId)))
if (e.getValue) user.addRole(r.getId) .flatMap(user => role.flatMap((r: Role) => {
else user.removeRole(r.getId) def foo(r: Role): SMono[_] = {
val modlog = module.modlogChannel.get if (e.getValue) user.addRole(r.getId)
val msg = (if (e.getValue) "M" else user.removeRole(r.getId)
else "Unm") + "uted user: " + user.getUsername + "#" + user.getDiscriminator val modlog = module.modlogChannel.get
module.log(msg) val msg = (if (e.getValue) "M"
if (modlog != null) return modlog.flatMap((ch: MessageChannel) => ch.createMessage(msg)) else "Unm") + "uted user: " + user.getUsername + "#" + user.getDiscriminator
Mono.empty module.log(msg)
} if (modlog != null) return modlog.flatMap((ch: MessageChannel) => SMono(ch.createMessage(msg)))
SMono.empty
}
foo(r) foo(r)
}))).subscribe }))).subscribe
} }
@EventHandler def onChatSystemMessage(event: TBMCSystemChatEvent): Unit = @EventHandler def onChatSystemMessage(event: TBMCSystemChatEvent): Unit =
@ -117,10 +117,10 @@ class MCListener(val module: MinecraftChatModule) extends Listener {
case _ => event.getSender.getName case _ => event.getSender.getName
} }
//Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO //Channel channel = ChromaGamerBase.getFromSender(event.getSender()).channel().get(); - TODO
DiscordPlugin.mainServer.getEmojis.filter(e => "YEEHAW" == e.getName).take(1).singleOrEmpty DiscordPlugin.mainServer.getEmojis.^^().filter(e => "YEEHAW" == e.getName).take(1).singleOrEmpty
.map(Optional.of(_)).defaultIfEmpty(Optional.empty) .map(Option.apply).defaultIfEmpty(Option.empty)
.flatMap((yeehaw) => MCChatUtils.forPublicPrivateChat(MCChatUtils.send(name + .flatMap(yeehaw => MCChatUtils.forPublicPrivateChat(MCChatUtils.send(name +
yeehaw.map((guildEmoji) => " <:YEEHAW:" + guildEmoji.getId.asString + ">s").orElse(" YEEHAWs")))).subscribe yeehaw.map(guildEmoji => " <:YEEHAW:" + guildEmoji.getId.asString + ">s").getOrElse(" YEEHAWs")))).subscribe
} }
@EventHandler def onNickChange(event: NickChangeEvent): Unit = MCChatUtils.updatePlayerList() @EventHandler def onNickChange(event: NickChangeEvent): Unit = MCChatUtils.updatePlayerList()

View file

@ -1,6 +1,7 @@
package buttondevteam.discordplugin.mcchat package buttondevteam.discordplugin.mcchat
import buttondevteam.core.component.channel.Channel import buttondevteam.core.component.channel.Channel
import buttondevteam.discordplugin.DPUtils.{MonoExtensions, SpecExtensions}
import buttondevteam.discordplugin.playerfaker.ServerWatcher import buttondevteam.discordplugin.playerfaker.ServerWatcher
import buttondevteam.discordplugin.playerfaker.perm.LPInjector import buttondevteam.discordplugin.playerfaker.perm.LPInjector
import buttondevteam.discordplugin.util.DPState import buttondevteam.discordplugin.util.DPState
@ -12,12 +13,12 @@ import discord4j.common.util.Snowflake
import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.`object`.entity.channel.MessageChannel
import discord4j.rest.util.Color import discord4j.rest.util.Color
import org.bukkit.Bukkit import org.bukkit.Bukkit
import reactor.core.publisher.Mono
import reactor.core.scala.publisher.SMono import reactor.core.scala.publisher.SMono
import java.util import java.util
import java.util.stream.Collectors import java.util.stream.Collectors
import java.util.{Objects, UUID} 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. * 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 * 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 * 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) { if (chcons != null) {
val chconkeys = chcons.getKeys(false) val chconkeys = chcons.getKeys(false)
for (chconkey <- chconkeys) { for (chconkey <- chconkeys.asScala) {
val chcon = chcons.getConfigurationSection(chconkey) val chcon = chcons.getConfigurationSection(chconkey)
val mcch = Channel.getChannels.filter((ch: Channel) => ch.ID == chcon.getString("mcchid")).findAny 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 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 groupid = chcon.getString("groupid")
val toggles = chcon.getInt("toggles") val toggles = chcon.getInt("toggles")
val brtoggles = chcon.getStringList("brtoggles") val brtoggles = chcon.getStringList("brtoggles")
if (!mcch.isPresent || ch == null || user == null || groupid == null) continue //todo: continue is not supported if (mcch.isPresent && ch != null && user != null && groupid != null) {
Bukkit.getScheduler.runTask(getPlugin, () => { Bukkit.getScheduler.runTask(getPlugin, () => { //<-- Needed because of occasional ConcurrentModificationExceptions when creating the player (PermissibleBase)
def foo() = { //<-- Needed because of occasional ConcurrentModificationExceptions when creating the player (PermissibleBase) val dcp = DiscordConnectedPlayer.create(user, ch.asInstanceOf[MessageChannel],
val dcp = DiscordConnectedPlayer.create(user, ch.asInstanceOf[MessageChannel], UUID.fromString(chcon.getString("mcuid")), chcon.getString("mcname"), this) 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)) MCChatCustom.addCustomChat(ch.asInstanceOf[MessageChannel], groupid, mcch.get, user, dcp, toggles,
} brtoggles.asScala.map(TBMCSystemChatEvent.BroadcastTarget.get).filter(Objects.nonNull).toSet)
})
foo() }
})
} }
} }
try if (lpInjector == null) lpInjector = new LPInjector(DiscordPlugin.plugin) 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("mcname", chcon.dcp.getName)
chconc.set("groupid", chcon.groupID) chconc.set("groupid", chcon.groupID)
chconc.set("toggles", chcon.toggles) 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 if (listener != null) { //Can be null if disabled because of a config error
listener.stop(true) listener.stop(true)
@ -219,10 +219,10 @@ class MinecraftChatModule extends Component[DiscordPlugin] {
* It will block to make sure all messages are sent * It will block to make sure all messages are sent
*/ */
private def sendStateMessage(color: Color, message: String) = private def sendStateMessage(color: Color, message: String) =
MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(_.setColor(color).setTitle(message))), MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(spec => spec.setColor(color).setTitle(message)).^^()),
ChannelconBroadcast.RESTART, hookmsg = false).block ChannelconBroadcast.RESTART, hookmsg = false).block()
private def sendStateMessage(color: Color, message: String, extra: String) = private def sendStateMessage(color: Color, message: String, extra: String) =
MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(_.setColor(color).setTitle(message).setDescription(extra)) MCChatUtils.forCustomAndAllMCChat(_.flatMap(_.createEmbed(_.setColor(color).setTitle(message).setDescription(extra).^^()).^^()
.onErrorResume((_: Throwable) => Mono.empty)), ChannelconBroadcast.RESTART, hookmsg = false).block .onErrorResume(_ => SMono.empty)), ChannelconBroadcast.RESTART, hookmsg = false).block()
} }

View file

@ -1,9 +1,9 @@
package buttondevteam.discordplugin.mccommands package buttondevteam.discordplugin.mccommands
import buttondevteam.discordplugin.{DPUtils, DiscordPlayer, DiscordPlugin, DiscordSenderBase}
import buttondevteam.discordplugin.commands.{ConnectCommand, VersionCommand} import buttondevteam.discordplugin.commands.{ConnectCommand, VersionCommand}
import buttondevteam.discordplugin.mcchat.{MCChatUtils, MinecraftChatModule} import buttondevteam.discordplugin.mcchat.{MCChatUtils, MinecraftChatModule}
import buttondevteam.discordplugin.util.DPState import buttondevteam.discordplugin.util.DPState
import buttondevteam.discordplugin.{DPUtils, DiscordPlayer, DiscordPlugin, DiscordSenderBase}
import buttondevteam.lib.chat.{Command2, CommandClass, ICommand2MC} import buttondevteam.lib.chat.{Command2, CommandClass, ICommand2MC}
import buttondevteam.lib.player.{ChromaGamerBase, TBMCPlayer, TBMCPlayerBase} import buttondevteam.lib.player.{ChromaGamerBase, TBMCPlayer, TBMCPlayerBase}
import discord4j.core.`object`.ExtendedInvite import discord4j.core.`object`.ExtendedInvite
@ -14,10 +14,10 @@ import reactor.core.publisher.Mono
import java.lang.reflect.Method import java.lang.reflect.Method
@CommandClass(path = "discord", helpText = Array(Array( @CommandClass(path = "discord", helpText = Array(
"Discord", "Discord",
"This command allows performing Discord-related actions." "This command allows performing Discord-related actions."
))) class DiscordMCCommand extends ICommand2MC { )) class DiscordMCCommand extends ICommand2MC {
@Command2.Subcommand def accept(player: Player): Boolean = { @Command2.Subcommand def accept(player: Player): Boolean = {
if (checkSafeMode(player)) return true if (checkSafeMode(player)) return true
val did = ConnectCommand.WaitingToConnect.get(player.getName) val did = ConnectCommand.WaitingToConnect.get(player.getName)
@ -45,17 +45,17 @@ import java.lang.reflect.Method
true true
} }
@Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = Array(Array( @Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP, helpText = Array(
"Reload Discord plugin", "Reload Discord plugin",
"Reloads the config. To apply some changes, you may need to also run /discord restart." "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.") if (DiscordPlugin.plugin.tryReloadConfig) sender.sendMessage("§bConfig reloaded.")
else sender.sendMessage("§cFailed to reload config.") 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", // "Restart the plugin", //
"This command disables and then enables the plugin." // "This command disables and then enables the plugin." //
))) def restart(sender: CommandSender): Unit = { )) def restart(sender: CommandSender): Unit = {
val task: Runnable = () => { val task: Runnable = () => {
def foo(): Unit = { def foo(): Unit = {
if (!DiscordPlugin.plugin.tryReloadConfig) { if (!DiscordPlugin.plugin.tryReloadConfig) {
@ -84,16 +84,16 @@ import java.lang.reflect.Method
} }
} }
@Command2.Subcommand(helpText = Array(Array( @Command2.Subcommand(helpText = Array(
"Version command", "Version command",
"Prints the plugin version"))) def version(sender: CommandSender): Unit = { "Prints the plugin version")) def version(sender: CommandSender): Unit = {
sender.sendMessage(VersionCommand.getVersion) sender.sendMessage(VersionCommand.getVersion)
} }
@Command2.Subcommand(helpText = Array(Array( @Command2.Subcommand(helpText = Array(
"Invite", "Invite",
"Shows an invite link to the server" "Shows an invite link to the server"
))) def invite(sender: CommandSender): Unit = { )) def invite(sender: CommandSender): Unit = {
if (checkSafeMode(sender)) { if (checkSafeMode(sender)) {
return return
} }

View file

@ -36,8 +36,10 @@ class DelegatingMockMaker() extends MockMaker {
override def createStaticMock[T](`type`: Class[T], settings: MockCreationSettings[T], handler: MockHandler[_]): MockMaker.StaticMockControl[T] = override def createStaticMock[T](`type`: Class[T], settings: MockCreationSettings[T], handler: MockHandler[_]): MockMaker.StaticMockControl[T] =
this.mockMaker.createStaticMock(`type`, settings, handler) 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] = override def createConstructionMock[T](`type`: Class[T], settingsFactory: java.util.function.Function[MockedConstruction.Context,
this.mockMaker.createConstructionMock[T](`type`, settingsFactory: Function[MockedConstruction.Context, MockCreationSettings[T]], handlerFactory, mockInitializer) 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 = { def setMockMaker(mockMaker: MockMaker): Unit = {
this.mockMaker = mockMaker this.mockMaker = mockMaker

View file

@ -4,8 +4,8 @@ import buttondevteam.discordplugin.DiscordConnectedPlayer
import buttondevteam.discordplugin.mcchat.MCChatUtils import buttondevteam.discordplugin.mcchat.MCChatUtils
import com.destroystokyo.paper.profile.CraftPlayerProfile import com.destroystokyo.paper.profile.CraftPlayerProfile
import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding import net.bytebuddy.implementation.bind.annotation.IgnoreForBinding
import org.bukkit.{Bukkit, Server}
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.{Bukkit, Server}
import org.mockito.Mockito import org.mockito.Mockito
import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker import org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker
import org.mockito.invocation.InvocationOnMock import org.mockito.invocation.InvocationOnMock
@ -46,14 +46,15 @@ class ServerWatcher {
def foo(invocation: InvocationOnMock): AnyRef = { def foo(invocation: InvocationOnMock): AnyRef = {
val method = invocation.getMethod val method = invocation.getMethod
val pc = method.getParameterCount val pc = method.getParameterCount
var player: DiscordConnectedPlayer = null var player = Option.empty[DiscordConnectedPlayer]
method.getName match { method.getName match {
case "getPlayer" => 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" => case "getPlayerExact" =>
if (pc == 1) { if (pc == 1) {
val argument = invocation.getArgument(0) 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": /*case "getOnlinePlayers":
@ -65,13 +66,14 @@ class ServerWatcher {
if (pc == 2) { if (pc == 2) {
val uuid = invocation.getArgument(0) val uuid = invocation.getArgument(0)
val name = invocation.getArgument(1) val name = invocation.getArgument(1)
player = if (uuid != null) MCChatUtils.LoggedInPlayers.get(uuid) player = if (uuid != null) MCChatUtils.LoggedInPlayers.get(uuid) else Option.empty
else null if (player.isEmpty && name != null)
if (player == null && name != null) player = MCChatUtils.LoggedInPlayers.values.stream.filter((dcp) => dcp.getName.equalsIgnoreCase(name)).findAny.orElse(null) player = MCChatUtils.LoggedInPlayers.values.find(_.getName.equalsIgnoreCase(name))
if (player != null) return new CraftPlayerProfile(player.getUniqueId, player.getName) 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) method.invoke(origServer, invocation.getArguments)
} }

View file

@ -8,10 +8,10 @@ import discord4j.core.`object`.entity.Role
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
@CommandClass class RoleCommand private[role](var grm: GameRoleModule) extends ICommand2DC { @CommandClass class RoleCommand private[role](var grm: GameRoleModule) extends ICommand2DC {
@Command2.Subcommand(helpText = Array(Array( @Command2.Subcommand(helpText = Array(
"Add role", "Add role",
"This command adds a role to your account." "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) val role = checkAndGetRole(sender, rolename)
if (role == null) return true 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
@ -23,10 +23,10 @@ import reactor.core.publisher.Mono
true true
} }
@Command2.Subcommand(helpText = Array(Array( @Command2.Subcommand(helpText = Array(
"Remove role", "Remove role",
"This command removes a role from your account." "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) val role = checkAndGetRole(sender, rolename)
if (role == null) return true 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