Use Scala version of Reactor & data types

This commit is contained in:
Norbi Peti 2021-03-02 01:18:20 +01:00
parent c57ac26b2d
commit fce6b91b97
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
18 changed files with 282 additions and 314 deletions

15
pom.xml
View file

@ -78,21 +78,6 @@
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 --> </useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>pre-scala</id>
<configuration>
<compilerArgs>-proc:only</compilerArgs>
</configuration>
<phase>generate-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>net.alchim31.maven</groupId> <groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId> <artifactId>scala-maven-plugin</artifactId>

View file

@ -6,10 +6,10 @@ 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
import reactor.core.publisher.Mono import reactor.core.scala.publisher.SMono
import java.util import java.util
import java.util.{Comparator, Optional} import java.util.Comparator
import java.util.logging.Logger import java.util.logging.Logger
import java.util.regex.Pattern import java.util.regex.Pattern
import javax.annotation.Nullable import javax.annotation.Nullable
@ -62,8 +62,8 @@ object DPUtils {
return matcher.replaceAll(aFunctionalInterface); //Find nearest URL match and if it's not reaching to the char then escape*/ val sb = new StringBuffer return matcher.replaceAll(aFunctionalInterface); //Find nearest URL match and if it's not reaching to the char then escape*/ val sb = new StringBuffer
while ( { while ( {
matcher.find matcher.find
}) matcher.appendReplacement(sb, if (Optional.ofNullable(ts.floor(Array[Int](matcher.start, 0))).map( //Find a URL start <= our start }) matcher.appendReplacement(sb, if (Option(ts.floor(Array[Int](matcher.start, 0))).map( //Find a URL start <= our start
(a: Array[Int]) => a(1)).orElse(-1) < matcher.start //Check if URL end < our start (a: Array[Int]) => a(1)).getOrElse(-1) < matcher.start //Check if URL end < our start
) "\\\\" + matcher.group else matcher.group) ) "\\\\" + matcher.group else matcher.group)
matcher.appendTail(sb) matcher.appendTail(sb)
sb.toString sb.toString
@ -74,22 +74,22 @@ object DPUtils {
DiscordPlugin.plugin.getLogger DiscordPlugin.plugin.getLogger
} }
def channelData(config: IHaveConfig, key: String): ReadOnlyConfigData[Mono[MessageChannel]] = def channelData(config: IHaveConfig, key: String): ReadOnlyConfigData[SMono[MessageChannel]] =
config.getReadOnlyDataPrimDef(key, 0L, (id: Any) => config.getReadOnlyDataPrimDef(key, 0L, (id: Any) =>
getMessageChannel(key, Snowflake.of(id.asInstanceOf[Long])), (_: Mono[MessageChannel]) => 0L) //We can afford to search for the channel in the cache once (instead of using mainServer) getMessageChannel(key, Snowflake.of(id.asInstanceOf[Long])), (_: SMono[MessageChannel]) => 0L) //We can afford to search for the channel in the cache once (instead of using mainServer)
def roleData(config: IHaveConfig, key: String, defName: String): ReadOnlyConfigData[Mono[Role]] = def roleData(config: IHaveConfig, key: String, defName: String): ReadOnlyConfigData[SMono[Role]] =
roleData(config, key, defName, Mono.just(DiscordPlugin.mainServer)) roleData(config, key, defName, SMono.just(DiscordPlugin.mainServer))
/** /**
* Needs to be a {@link ConfigData} for checking if it's set * Needs to be a {@link ConfigData} for checking if it's set
*/ */
def roleData(config: IHaveConfig, key: String, defName: String, guild: Mono[Guild]): ReadOnlyConfigData[Mono[Role]] = config.getReadOnlyDataPrimDef(key, defName, (name: Any) => { def roleData(config: IHaveConfig, key: String, defName: String, guild: SMono[Guild]): ReadOnlyConfigData[SMono[Role]] = config.getReadOnlyDataPrimDef(key, defName, (name: Any) => {
def foo(name: Any): Mono[Role] = { def foo(name: Any): SMono[Role] = {
if (!name.isInstanceOf[String] || name.asInstanceOf[String].isEmpty) return Mono.empty[Role] if (!name.isInstanceOf[String] || name.asInstanceOf[String].isEmpty) return SMono.empty[Role]
guild.flatMapMany(_.getRoles).filter((r: Role) => r.getName == name).onErrorResume((e: Throwable) => { guild.flatMapMany(_.getRoles).filter((r: Role) => r.getName == name).onErrorResume((e: Throwable) => {
def foo(e: Throwable): Mono[Role] = { def foo(e: Throwable): SMono[Role] = {
getLogger.warning("Failed to get role data for " + key + "=" + name + " - " + e.getMessage) getLogger.warning("Failed to get role data for " + key + "=" + name + " - " + e.getMessage)
Mono.empty[Role] SMono.empty[Role]
} }
foo(e) foo(e)
@ -97,7 +97,7 @@ object DPUtils {
} }
foo(name) foo(name)
}, (_: Mono[Role]) => defName) }, (_: SMono[Role]) => defName)
def snowflakeData(config: IHaveConfig, key: String, defID: Long): ReadOnlyConfigData[Snowflake] = def snowflakeData(config: IHaveConfig, key: String, defID: Long): ReadOnlyConfigData[Snowflake] =
config.getReadOnlyDataPrimDef(key, defID, (id: Any) => Snowflake.of(id.asInstanceOf[Long]), _.asLong) config.getReadOnlyDataPrimDef(key, defID, (id: Any) => Snowflake.of(id.asInstanceOf[Long]), _.asLong)
@ -137,7 +137,7 @@ object DPUtils {
*/ */
def disableIfConfigErrorRes(@Nullable component: Component[DiscordPlugin], config: ConfigData[_], result: Any): Boolean = { def disableIfConfigErrorRes(@Nullable component: Component[DiscordPlugin], config: ConfigData[_], result: Any): Boolean = {
//noinspection ConstantConditions //noinspection ConstantConditions
if (result == null || (result.isInstanceOf[Mono[_]] && !result.asInstanceOf[Mono[_]].hasElement.block)) { if (result == null || (result.isInstanceOf[SMono[_]] && !result.asInstanceOf[SMono[_]].hasElement.block())) {
var path: String = null var path: String = null
try { try {
if (component != null) Component.setComponentEnabled(component, false) if (component != null) Component.setComponentEnabled(component, false)
@ -157,26 +157,26 @@ object DPUtils {
} }
/** /**
* Send a response in the form of "@User, message". Use Mono.empty() if you don't have a channel object. * Send a response in the form of "@User, message". Use SMono.empty() if you don't have a channel object.
* *
* @param original The original message to reply to * @param original The original message to reply to
* @param channel The channel to send the message in, defaults to the original * @param channel The channel to send the message in, defaults to the original
* @param message The message to send * @param message The message to send
* @return A mono to send the message * @return A mono to send the message
*/ */
def reply(original: Message, @Nullable channel: MessageChannel, message: String): Mono[Message] = { def reply(original: Message, @Nullable channel: MessageChannel, message: String): SMono[Message] = {
val ch = if (channel == null) original.getChannel val ch = if (channel == null) SMono(original.getChannel)
else Mono.just(channel) else SMono.just(channel)
reply(original, ch, message) reply(original, ch, message)
} }
/** /**
* @see #reply(Message, MessageChannel, String) * @see #reply(Message, MessageChannel, String)
*/ */
def reply(original: Message, ch: Mono[MessageChannel], message: String): Mono[Message] = def reply(original: Message, ch: SMono[MessageChannel], message: String): SMono[Message] =
ch.flatMap(_.createMessage((if (original.getAuthor.isPresent) ch.flatMap(channel => SMono(channel.createMessage((if (original.getAuthor.isPresent)
original.getAuthor.get.getMention + ", " original.getAuthor.get.getMention + ", "
else "") + message)) else "") + message)))
def nickMention(userId: Snowflake): String = "<@!" + userId.asString + ">" def nickMention(userId: Snowflake): String = "<@!" + userId.asString + ">"
@ -189,21 +189,21 @@ object DPUtils {
* @param id The channel ID * @param id The channel ID
* @return A message channel * @return A message channel
*/ */
def getMessageChannel(key: String, id: Snowflake): Mono[MessageChannel] = { def getMessageChannel(key: String, id: Snowflake): SMono[MessageChannel] = {
if (id.asLong == 0L) return Mono.empty[MessageChannel] if (id.asLong == 0L) return SMono.empty[MessageChannel]
DiscordPlugin.dc.getChannelById(id).onErrorResume(e => { SMono(DiscordPlugin.dc.getChannelById(id)).onErrorResume(e => {
def foo(e: Throwable) = { def foo(e: Throwable) = {
getLogger.warning("Failed to get channel data for " + key + "=" + id + " - " + e.getMessage) getLogger.warning("Failed to get channel data for " + key + "=" + id + " - " + e.getMessage)
Mono.empty SMono.empty
} }
foo(e) foo(e)
}).filter(ch => ch.isInstanceOf[MessageChannel]).cast(classOf[MessageChannel]) }).filter(ch => ch.isInstanceOf[MessageChannel]).cast[MessageChannel]
} }
def getMessageChannel(config: ConfigData[Snowflake]): Mono[MessageChannel] = def getMessageChannel(config: ConfigData[Snowflake]): SMono[MessageChannel] =
getMessageChannel(config.getPath, config.get) getMessageChannel(config.getPath, config.get)
def ignoreError[T](mono: Mono[T]): Mono[T] = mono.onErrorResume((_: Throwable) => Mono.empty) def ignoreError[T](mono: SMono[T]): SMono[T] = mono.onErrorResume((_: Throwable) => SMono.empty)
} }

View file

@ -15,8 +15,8 @@ import org.bukkit.inventory.{Inventory, PlayerInventory}
import org.bukkit.permissions.{PermissibleBase, Permission, PermissionAttachment, PermissionAttachmentInfo} import org.bukkit.permissions.{PermissibleBase, Permission, PermissionAttachment, PermissionAttachmentInfo}
import org.bukkit.plugin.Plugin import org.bukkit.plugin.Plugin
import org.mockito.Answers.RETURNS_DEFAULTS import org.mockito.Answers.RETURNS_DEFAULTS
import org.mockito.{MockSettings, Mockito}
import org.mockito.invocation.InvocationOnMock import org.mockito.invocation.InvocationOnMock
import org.mockito.{MockSettings, Mockito}
import java.lang.reflect.Modifier import java.lang.reflect.Modifier
import java.net.InetSocketAddress import java.net.InetSocketAddress
@ -52,7 +52,24 @@ object DiscordConnectedPlayer {
}).stubOnly }).stubOnly
} }
abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel) extends DiscordSenderBase(user, channel) with IMCPlayer[DiscordConnectedPlayer] { /**
* @constructor The parameters must match with {@link #create ( User, MessageChannel, UUID, String, MinecraftChatModule)}
* @param user May be null.
* @param channel May not be null.
* @param uniqueId The UUID of the player.
* @param name The Minecraft name of the player.
* @param module The MinecraftChatModule or null if testing.
*/
abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel, val uniqueId: UUID, val name: String, val module: MinecraftChatModule) extends DiscordSenderBase(user, channel) with IMCPlayer[DiscordConnectedPlayer] {
private var loggedIn = false
private var displayName: String = name
private var location: Location = if (module == null) null else Bukkit.getWorlds.get(0).getSpawnLocation
private val basePlayer: OfflinePlayer = if (module == null) null else Bukkit.getOfflinePlayer(uniqueId)
private var perm: PermissibleBase = if (module == null) null else new PermissibleBase(basePlayer)
private val origPerm: PermissibleBase = perm
private val vanillaCmdListener: VCMDWrapper = if (module == null) null else new VCMDWrapper(VCMDWrapper.createListener(this, module))
override def isPermissionSet(name: String): Boolean = this.origPerm.isPermissionSet(name) override def isPermissionSet(name: String): Boolean = this.origPerm.isPermissionSet(name)
override def isPermissionSet(perm: Permission): Boolean = this.origPerm.isPermissionSet(perm) override def isPermissionSet(perm: Permission): Boolean = this.origPerm.isPermissionSet(perm)
@ -98,40 +115,11 @@ abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel) exten
override def getDisplayName: String = this.displayName override def getDisplayName: String = this.displayName
private var vanillaCmdListener: VCMDWrapper = null
private var loggedIn = false
private var origPerm: PermissibleBase = null
private var name: String = null
private var basePlayer: OfflinePlayer = null
private var perm: PermissibleBase = null
private var location: Location = null
final private var module: MinecraftChatModule = null
final private var uniqueId: UUID = null
final private var displayName: String = null
/**
* The parameters must match with {@link #create ( User, MessageChannel, UUID, String, MinecraftChatModule)}
*/
def this(user: User, channel: MessageChannel, uuid: UUID, mcname: String, module: MinecraftChatModule) {
this(user, channel)
location = Bukkit.getWorlds.get(0).getSpawnLocation
perm = new PermissibleBase(basePlayer = Bukkit.getOfflinePlayer(uuid))
origPerm = perm
name = mcname
this.module = module
uniqueId = uuid
displayName = mcname
vanillaCmdListener = new VCMDWrapper(VCMDWrapper.createListener(this, module))
}
/** /**
* For testing * For testing
*/ */
def this(user: User, channel: MessageChannel) { def this(user: User, channel: MessageChannel) =
this(user, channel) this(user, channel, UUID.randomUUID(), "Test", null)
module = null
uniqueId = UUID.randomUUID
}
override def setOp(value: Boolean): Unit = { //CraftPlayer-compatible implementation override def setOp(value: Boolean): Unit = { //CraftPlayer-compatible implementation
this.origPerm.setOp(value) this.origPerm.setOp(value)
@ -214,7 +202,7 @@ abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel) exten
override def getGameMode = GameMode.SPECTATOR override def getGameMode = GameMode.SPECTATOR
//noinspection ScalaDeprecation //noinspection ScalaDeprecation
@SuppressWarnings(Array("deprecation")) final private val spigot: Spigot = new Spigot() { @SuppressWarnings(Array("deprecation")) override def spigot: Spigot = new Spigot() {
override def getRawAddress: InetSocketAddress = null override def getRawAddress: InetSocketAddress = null
override def playEffect(location: Location, effect: Effect, id: Int, data: Int, offsetX: Float, offsetY: Float, offsetZ: Float, speed: Float, particleCount: Int, radius: Int): Unit = { override def playEffect(location: Location, effect: Effect, id: Int, data: Int, offsetX: Float, offsetY: Float, offsetZ: Float, speed: Float, particleCount: Int, radius: Int): Unit = {
@ -241,11 +229,9 @@ abstract class DiscordConnectedPlayer(user: User, channel: MessageChannel) exten
override def sendMessage(position: ChatMessageType, component: BaseComponent): Unit = override def sendMessage(position: ChatMessageType, component: BaseComponent): Unit =
sendMessage(component) //Ignore position sendMessage(component) //Ignore position
override def sendMessage(position: ChatMessageType, components: BaseComponent*) = override def sendMessage(position: ChatMessageType, components: BaseComponent*): Unit =
sendMessage(components) sendMessage(components: _*)
override def isInvulnerable = true override def isInvulnerable = true
} }
override def spigot: Spigot = spigot
} }

View file

@ -15,12 +15,12 @@ import buttondevteam.lib.architecture._
import buttondevteam.lib.player.ChromaGamerBase import buttondevteam.lib.player.ChromaGamerBase
import com.google.common.io.Files import com.google.common.io.Files
import discord4j.common.util.Snowflake import discord4j.common.util.Snowflake
import discord4j.core.{DiscordClientBuilder, GatewayDiscordClient}
import discord4j.core.`object`.entity.{ApplicationInfo, Guild, Role} import discord4j.core.`object`.entity.{ApplicationInfo, Guild, Role}
import discord4j.core.`object`.presence.{Activity, Presence} import discord4j.core.`object`.presence.{Activity, Presence}
import discord4j.core.`object`.reaction.ReactionEmoji import discord4j.core.`object`.reaction.ReactionEmoji
import discord4j.core.event.domain.guild.GuildCreateEvent import discord4j.core.event.domain.guild.GuildCreateEvent
import discord4j.core.event.domain.lifecycle.ReadyEvent import discord4j.core.event.domain.lifecycle.ReadyEvent
import discord4j.core.{DiscordClientBuilder, GatewayDiscordClient}
import discord4j.gateway.ShardInfo import discord4j.gateway.ShardInfo
import discord4j.store.jdk.JdkStoreService import discord4j.store.jdk.JdkStoreService
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
@ -29,7 +29,7 @@ import org.bukkit.command.CommandSender
import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.configuration.file.YamlConfiguration
import org.mockito.internal.util.MockUtil import org.mockito.internal.util.MockUtil
import reactor.core.Disposable import reactor.core.Disposable
import reactor.core.publisher.Mono import reactor.core.scala.publisher.SMono
import java.io.File import java.io.File
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@ -65,13 +65,16 @@ import java.util.Optional
* The main server where the roles and other information is pulled from. It's automatically set to the first server the bot's invited to. * The main server where the roles and other information is pulled from. It's automatically set to the first server the bot's invited to.
*/ */
private def mainServer = getIConfig.getDataPrimDef("mainServer", 0L, (id: Any) => { private def mainServer = getIConfig.getDataPrimDef("mainServer", 0L, (id: Any) => {
def foo(id: Any) = { //It attempts to get the default as well def foo(id: Any): Option[Guild] = { //It attempts to get the default as well
if (id.asInstanceOf[Long] == 0L) Optional.empty //Hack? if (id.asInstanceOf[Long] == 0L) Option.empty
else DiscordPlugin.dc.getGuildById(Snowflake.of(id.asInstanceOf[Long])).onErrorResume((t: Throwable) => Mono.fromRunnable(() => getLogger.warning("Failed to get guild: " + t.getMessage))).blockOptional else SMono.fromPublisher(DiscordPlugin.dc.getGuildById(Snowflake.of(id.asInstanceOf[Long])))
.onErrorResume((t: Throwable) => {
getLogger.warning("Failed to get guild: " + t.getMessage); SMono.empty
}).blockOption()
} }
foo(id) foo(id)
}, (g: Optional[Guild]) => (g.map((gg: Guild) => gg.getId.asLong): Optional[Long]).orElse(0L)) }, (g: Option[Guild]) => (g.map(_.getId.asLong): Option[Long]).getOrElse(0L))
/** /**
* The (bot) channel to use for Discord commands like /role. * The (bot) channel to use for Discord commands like /role.
@ -81,7 +84,7 @@ import java.util.Optional
* The role that allows using mod-only Discord commands. * The role that allows using mod-only Discord commands.
* If empty (''), then it will only allow for the owner. * If empty (''), then it will only allow for the owner.
*/ */
var modRole: ReadOnlyConfigData[Mono[Role]] = null var modRole: ReadOnlyConfigData[SMono[Role]] = null
/** /**
* The invite link to show by /discord invite. If empty, it defaults to the first invite if the bot has access. * The invite link to show by /discord invite. If empty, it defaults to the first invite if the bot has access.
*/ */
@ -153,7 +156,9 @@ import java.util.Optional
} }
private def stopStarting(): Unit = { private def stopStarting(): Unit = {
this synchronized (starting = false) this synchronized {
starting = false
}
notifyAll() notifyAll()
} }
@ -164,7 +169,7 @@ import java.util.Optional
DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe //Update from the initial presence DiscordPlugin.dc.updatePresence(Presence.online(Activity.playing("Minecraft"))).subscribe //Update from the initial presence
return return
} }
DiscordPlugin.mainServer = mainServer.get.orElse(null) //Shouldn't change afterwards DiscordPlugin.mainServer = mainServer.get.orNull //Shouldn't change afterwards
if (DiscordPlugin.mainServer == null) { if (DiscordPlugin.mainServer == null) {
if (event.size == 0) { if (event.size == 0) {
getLogger.severe("Main server not found! Invite the bot and do /discord restart") getLogger.severe("Main server not found! Invite the bot and do /discord restart")
@ -174,7 +179,7 @@ import java.util.Optional
} }
DiscordPlugin.mainServer = event.get(0).getGuild DiscordPlugin.mainServer = event.get(0).getGuild
getLogger.warning("Main server set to first one: " + DiscordPlugin.mainServer.getName) getLogger.warning("Main server set to first one: " + DiscordPlugin.mainServer.getName)
mainServer.set(Optional.of(DiscordPlugin.mainServer)) //Save in config mainServer.set(Option(DiscordPlugin.mainServer)) //Save in config
} }
DiscordPlugin.SafeMode = false DiscordPlugin.SafeMode = false
setupConfig() setupConfig()

View file

@ -2,28 +2,24 @@ package buttondevteam.discordplugin
import discord4j.core.`object`.entity.User import discord4j.core.`object`.entity.User
import discord4j.core.`object`.entity.channel.MessageChannel import discord4j.core.`object`.entity.channel.MessageChannel
import org.bukkit.{Bukkit, Server}
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.permissions.{PermissibleBase, Permission, PermissionAttachment, PermissionAttachmentInfo} import org.bukkit.permissions.{PermissibleBase, Permission, PermissionAttachment, PermissionAttachmentInfo}
import org.bukkit.plugin.Plugin import org.bukkit.plugin.Plugin
import reactor.core.publisher.Mono import org.bukkit.{Bukkit, Server}
import reactor.core.scala.publisher.SMono
import java.util import java.util
class DiscordSender(user: User, channel: MessageChannel) extends DiscordSenderBase(user, channel) with CommandSender { class DiscordSender(user: User, channel: MessageChannel, pname: String) extends DiscordSenderBase(user, channel) with CommandSender {
private val perm = new PermissibleBase(this) private val perm = new PermissibleBase(this)
private var name: String = null private val name: String = Option(pname)
.orElse(Option(user).map(u => SMono(u.asMember(DiscordPlugin.mainServer.getId))
.onErrorResume(_ => SMono.empty).blockOption()
.map(u => u.getDisplayName)))
.getOrElse("Discord user")
def this(user: User, channel: MessageChannel) { def this(user: User, channel: MessageChannel) {
this(user, channel) this(user, channel, null)
val `def` = "Discord user"
name = if (user == null) `def`
else user.asMember(DiscordPlugin.mainServer.getId).onErrorResume((_: Throwable) => Mono.empty).blockOptional.map(_.getDisplayName).orElse(`def`)
}
def this(user: User, channel: MessageChannel, name: String) {
this(user, channel)
this.name = name
} }
override def isPermissionSet(name: String): Boolean = perm.isPermissionSet(name) override def isPermissionSet(name: String): Boolean = perm.isPermissionSet(name)

View file

@ -90,7 +90,8 @@ import reactor.core.publisher.Flux
} }
if (msgsb.length > 0) channel.get.flatMap((ch: MessageChannel) => ch.createMessage(msgsb.toString)).flatMap(Message.pin).subscribe if (msgsb.length > 0) channel.get.flatMap((ch: MessageChannel) => ch.createMessage(msgsb.toString)).flatMap(Message.pin).subscribe
if (modmsgsb.length > 0) modChannel.get.flatMap((ch: MessageChannel) => ch.createMessage(modmsgsb.toString)).flatMap(Message.pin).subscribe if (modmsgsb.length > 0) modChannel.get.flatMap((ch: MessageChannel) => ch.createMessage(modmsgsb.toString)).flatMap(Message.pin).subscribe
if (lastAnnouncementTime.get ne lastanntime) lastAnnouncementTime.set(lastanntime) // If sending succeeded} catch { if (lastAnnouncementTime.get ne lastanntime) lastAnnouncementTime.set(lastanntime) // If sending succeeded
} catch {
case e: Exception => case e: Exception =>
e.printStackTrace() e.printStackTrace()
} }

View file

@ -7,8 +7,7 @@ import buttondevteam.lib.chat.{Command2, CommandClass}
object VersionCommand { object VersionCommand {
def getVersion: Array[String] = { def getVersion: Array[String] = {
val desc = DiscordPlugin.plugin.getDescription val desc = DiscordPlugin.plugin.getDescription
Array[String]( // Array[String](desc.getFullName, desc.getWebsite)
desc.getFullName, desc.getWebsite //)
} }
} }

View file

@ -1,7 +1,6 @@
package buttondevteam.discordplugin.exceptions package buttondevteam.discordplugin.exceptions
import buttondevteam.core.ComponentManager import buttondevteam.core.ComponentManager
import buttondevteam.discordplugin.exceptions.ExceptionListenerModule.SendException
import buttondevteam.discordplugin.{DPUtils, DiscordPlugin} import buttondevteam.discordplugin.{DPUtils, DiscordPlugin}
import buttondevteam.lib.architecture.Component import buttondevteam.lib.architecture.Component
import buttondevteam.lib.{TBMCCoreAPI, TBMCExceptionEvent} import buttondevteam.lib.{TBMCCoreAPI, TBMCExceptionEvent}
@ -12,6 +11,7 @@ import org.bukkit.Bukkit
import org.bukkit.event.{EventHandler, Listener} import org.bukkit.event.{EventHandler, Listener}
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import java.util
import java.util.stream.Collectors import java.util.stream.Collectors
/** /**
@ -65,7 +65,10 @@ class ExceptionListenerModule extends Component[DiscordPlugin] with Listener {
@EventHandler def onException(e: TBMCExceptionEvent): Unit = { @EventHandler def onException(e: TBMCExceptionEvent): Unit = {
if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(getClass)) return if (DiscordPlugin.SafeMode || !ComponentManager.isEnabled(getClass)) return
if (lastthrown.stream.anyMatch((ex: Throwable) => util.Arrays.equals(e.getException.getStackTrace, ex.getStackTrace) && (if (e.getException.getMessage == null) ex.getMessage == null if (lastthrown.stream.anyMatch((ex: Throwable) => util.Arrays.equals(e.getException.getStackTrace, ex.getStackTrace) && (if (e.getException.getMessage == null) ex.getMessage == null
else e.getException.getMessage == ex.getMessage)) // e.Exception.Message==ex.Message && lastsourcemsg.contains(e.getSourceMessage)) { return } else e.getException.getMessage == ex.getMessage)) // e.Exception.Message==ex.Message
&& lastsourcemsg.contains(e.getSourceMessage)) {
return
}
ExceptionListenerModule ExceptionListenerModule
.SendException(e.getException, e.getSourceMessage) .SendException(e.getException, e.getSourceMessage)
if (lastthrown.size >= 10) lastthrown.remove(0) if (lastthrown.size >= 10) lastthrown.remove(0)

View file

@ -59,7 +59,7 @@ object FunModule {
return true //Handled return true //Handled
} }
lastlistp = Bukkit.getOnlinePlayers.size.toShort //Didn't handle lastlistp = Bukkit.getOnlinePlayers.size.toShort //Didn't handle
if (!TBMCCoreAPI.IsTestServer && util.Arrays.stream(fm.serverReady.get.anyMatch(msglowercased.contains)) { if (!TBMCCoreAPI.IsTestServer && util.Arrays.stream(fm.serverReady).get.anyMatch(msglowercased.contains _)) {
var next = 0 var next = 0
if (usableServerReadyStrings.size == 0) fm.createUsableServerReadyStrings() if (usableServerReadyStrings.size == 0) fm.createUsableServerReadyStrings()
next = usableServerReadyStrings.remove(serverReadyRandom.nextInt(usableServerReadyStrings.size)) next = usableServerReadyStrings.remove(serverReadyRandom.nextInt(usableServerReadyStrings.size))

View file

@ -6,7 +6,7 @@ import buttondevteam.lib.TBMCCoreAPI
import discord4j.common.util.Snowflake import discord4j.common.util.Snowflake
import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel} import discord4j.core.`object`.entity.channel.{MessageChannel, PrivateChannel}
import discord4j.core.`object`.entity.{Member, Message, Role, User} import discord4j.core.`object`.entity.{Member, Message, Role, User}
import reactor.core.publisher.{Flux, Mono} import reactor.core.scala.publisher.{SFlux, SMono}
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -18,14 +18,14 @@ object CommandListener {
* @param mentionedonly Only run the command if ChromaBot is mentioned at the start of the message * @param mentionedonly Only run the command if ChromaBot is mentioned at the start of the message
* @return Whether it <b>did not run</b> the command * @return Whether it <b>did not run</b> the command
*/ */
def runCommand(message: Message, commandChannelID: Snowflake, mentionedonly: Boolean): Mono[Boolean] = { def runCommand(message: Message, commandChannelID: Snowflake, mentionedonly: Boolean): SMono[Boolean] = {
val timings = CommonListeners.timings val timings = CommonListeners.timings
val ret = Mono.just(true) val ret = SMono.just(true)
if (message.getContent.isEmpty) return ret //Pin messages and such, let the mcchat listener deal with it if (message.getContent.isEmpty) return ret //Pin messages and such, let the mcchat listener deal with it
val content = message.getContent val content = message.getContent
timings.printElapsed("A") timings.printElapsed("A")
message.getChannel.flatMap((channel: MessageChannel) => { SMono(message.getChannel).flatMap((channel: MessageChannel) => {
def foo(channel: MessageChannel): Mono[Boolean] = { def foo(channel: MessageChannel): SMono[Boolean] = {
var tmp = ret var tmp = ret
if (!mentionedonly) { //mentionedonly conditions are in CommonListeners if (!mentionedonly) { //mentionedonly conditions are in CommonListeners
timings.printElapsed("B") timings.printElapsed("B")
@ -33,18 +33,20 @@ object CommandListener {
return ret return ret
} }
timings.printElapsed("C") timings.printElapsed("C")
tmp = ret.`then`(channel.`type`).thenReturn(true) // Fun (this true is ignored - x) tmp = ret.`then`(SMono(channel.`type`)).`then`(ret) // Fun (this true is ignored - x)
} }
val cmdwithargs = new StringBuilder(content) val cmdwithargs = new StringBuilder(content)
val gotmention = new AtomicBoolean val gotmention = new AtomicBoolean
timings.printElapsed("Before self") timings.printElapsed("Before self")
tmp.flatMapMany((x: Boolean) => DiscordPlugin.dc.getSelf.flatMap((self: User) => self.asMember(DiscordPlugin.mainServer.getId)).flatMapMany((self: Member) => { tmp.flatMapMany((_: Boolean) => SMono(DiscordPlugin.dc.getSelf)
def foo(self: Member): Flux[String] = { .flatMap((self: User) => SMono(self.asMember(DiscordPlugin.mainServer.getId)))
.flatMapMany((self: Member) => {
def foo(self: Member) = {
timings.printElapsed("D") timings.printElapsed("D")
gotmention.set(checkanddeletemention(cmdwithargs, self.getMention, message)) gotmention.set(checkanddeletemention(cmdwithargs, self.getMention, message))
gotmention.set(checkanddeletemention(cmdwithargs, self.getNicknameMention, message) || gotmention.get) gotmention.set(checkanddeletemention(cmdwithargs, self.getNicknameMention, message) || gotmention.get)
val mentions = message.getRoleMentions val mentions = SFlux(message.getRoleMentions)
self.getRoles.filterWhen((r: Role) => mentions.any((rr: Role) => rr.getName == r.getName)).map(_.getMention) SFlux(self.getRoles).filterWhen((r: Role) => mentions.any((rr: Role) => rr.getName == r.getName)).map(_.getMention)
} }
foo(self) foo(self)
@ -56,17 +58,20 @@ object CommandListener {
} }
foo(mentionRole) foo(mentionRole)
}: Boolean)[Mono[Boolean]].switchIfEmpty(Mono.fromSupplier[Boolean](() => !mentionedonly || gotmention.get)))[Mono[Boolean]].filter((b: Boolean) => b).last(false).filter((b: Boolean) => b).doOnNext((b: Boolean) => channel.`type`.subscribe).flatMap((b: Boolean) => { }).switchIfEmpty(SMono.fromCallable(() => !mentionedonly || gotmention.get))).filter(b => b)
def foo(): Mono[Boolean] = { .last(Option(false)).filter(b => b)
.doOnNext(_ => channel.`type`.subscribe).flatMap(_ => {
def foo(): SMono[Boolean] = {
val cmdwithargsString = cmdwithargs.toString val cmdwithargsString = cmdwithargs.toString
try { try {
timings.printElapsed("F") timings.printElapsed("F")
if (!DiscordPlugin.plugin.manager.handleCommand(new Command2DCSender(message), cmdwithargsString)) return DPUtils.reply(message, channel, "unknown command. Do " + DiscordPlugin.getPrefix + "help for help.").map((_: Message) => false) if (!DiscordPlugin.plugin.manager.handleCommand(new Command2DCSender(message), cmdwithargsString))
return DPUtils.reply(message, channel, "unknown command. Do " + DiscordPlugin.getPrefix + "help for help.").map(_ => false)
} catch { } catch {
case e: Exception => case e: Exception =>
TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e, DiscordPlugin.plugin) TBMCCoreAPI.SendException("Failed to process Discord command: " + cmdwithargsString, e, DiscordPlugin.plugin)
} }
Mono.just(false) //If the command succeeded or there was an error, return false SMono.just(false) //If the command succeeded or there was an error, return false
} }
foo() foo()

View file

@ -13,6 +13,7 @@ import discord4j.core.event.domain.PresenceUpdateEvent
import discord4j.core.event.domain.message.MessageCreateEvent import discord4j.core.event.domain.message.MessageCreateEvent
import discord4j.core.event.domain.role.{RoleCreateEvent, RoleDeleteEvent, RoleUpdateEvent} import discord4j.core.event.domain.role.{RoleCreateEvent, RoleDeleteEvent, RoleUpdateEvent}
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.core.scala.publisher.SMono
object CommonListeners { object CommonListeners {
val timings = new Timings val timings = new Timings
@ -36,24 +37,24 @@ object CommonListeners {
if (!author.isPresent || author.get.isBot) return `def` if (!author.isPresent || author.get.isBot) return `def`
if (FunModule.executeMemes(event.getMessage)) return `def` if (FunModule.executeMemes(event.getMessage)) return `def`
val commandChannel = DiscordPlugin.plugin.commandChannel.get val commandChannel = DiscordPlugin.plugin.commandChannel.get
event.getMessage.getChannel.map((mch: MessageChannel) => (commandChannel != null && mch.getId.asLong == commandChannel.asLong) //If mentioned, that's higher than chat SMono(event.getMessage.getChannel).map((mch: MessageChannel) => (commandChannel != null && mch.getId.asLong == commandChannel.asLong) //If mentioned, that's higher than chat
|| mch.isInstanceOf[PrivateChannel] || event.getMessage.getContent.contains("channelcon")).flatMap( || mch.isInstanceOf[PrivateChannel] || event.getMessage.getContent.contains("channelcon")).flatMap(
(shouldRun: Boolean) => { //Only 'channelcon' is allowed in other channels (shouldRun: Boolean) => { //Only 'channelcon' is allowed in other channels
def foo(shouldRun: Boolean): Mono[Boolean] = { //Only continue if this doesn't handle the event def foo(shouldRun: Boolean): SMono[Boolean] = { //Only continue if this doesn't handle the event
if (!shouldRun) return Mono.just(true) //The condition is only for the first command execution, not mcchat if (!shouldRun) return SMono.just(true) //The condition is only for the first command execution, not mcchat
timings.printElapsed("Run command 1") timings.printElapsed("Run command 1")
CommandListener.runCommand(event.getMessage, commandChannel, mentionedonly = true) //#bot is handled here CommandListener.runCommand(event.getMessage, commandChannel, mentionedonly = true) //#bot is handled here
} }
foo(shouldRun) foo(shouldRun)
}).filterWhen((ch: Any) => { }).filterWhen((ch: Any) => {
def foo(): Mono[Boolean] = { def foo() = {
timings.printElapsed("mcchat") timings.printElapsed("mcchat")
val mcchat = Component.getComponents.get(classOf[MinecraftChatModule]) val mcchat = Component.getComponents.get(classOf[MinecraftChatModule])
if (mcchat != null && mcchat.isEnabled) { //ComponentManager.isEnabled() searches the component again if (mcchat != null && mcchat.isEnabled) { //ComponentManager.isEnabled() searches the component again
return mcchat.asInstanceOf[MinecraftChatModule].getListener.handleDiscord(event) //Also runs Discord commands in chat channels return mcchat.asInstanceOf[MinecraftChatModule].getListener.handleDiscord(event) //Also runs Discord commands in chat channels
} }
Mono.just(true) //Wasn't handled, continue SMono.just(true) //Wasn't handled, continue
} }
foo() foo()

View file

@ -1,27 +1,24 @@
package buttondevteam.discordplugin.mcchat package buttondevteam.discordplugin.mcchat
import buttondevteam.core.component.channel.Channel import buttondevteam.core.component.channel.{Channel, ChatRoom}
import buttondevteam.core.component.channel.ChatRoom import buttondevteam.discordplugin.ChannelconBroadcast.ChannelconBroadcast
import buttondevteam.discordplugin._ import buttondevteam.discordplugin._
import buttondevteam.discordplugin.commands.{Command2DCSender, ICommand2DC} import buttondevteam.discordplugin.commands.{Command2DCSender, ICommand2DC}
import buttondevteam.lib.TBMCSystemChatEvent import buttondevteam.lib.TBMCSystemChatEvent
import buttondevteam.lib.chat.Command2 import buttondevteam.lib.chat.{Command2, CommandClass}
import buttondevteam.lib.chat.CommandClass import buttondevteam.lib.player.{ChromaGamerBase, TBMCPlayer}
import buttondevteam.lib.player.TBMCPlayer
import discord4j.core.`object`.entity.Message import discord4j.core.`object`.entity.Message
import discord4j.core.`object`.entity.channel.{GuildChannel, MessageChannel} import discord4j.core.`object`.entity.channel.{GuildChannel, MessageChannel}
import discord4j.rest.util.{Permission, PermissionSet} import discord4j.rest.util.{Permission, PermissionSet}
import lombok.RequiredArgsConstructor
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.command.CommandSender import reactor.core.scala.publisher.SMono
import reactor.core.publisher.Mono
import javax.annotation.Nullable
import java.lang.reflect.Method import java.lang.reflect.Method
import java.util import java.util
import java.util.{Objects, Optional}
import java.util.function.Supplier import java.util.function.Supplier
import java.util.stream.Collectors import java.util.stream.Collectors
import java.util.{Objects, Optional}
import javax.annotation.Nullable
@SuppressWarnings(Array("SimplifyOptionalCallChains")) //Java 11 @SuppressWarnings(Array("SimplifyOptionalCallChains")) //Java 11
@CommandClass(helpText = Array(Array("Channel connect", // @CommandClass(helpText = Array(Array("Channel connect", //
@ -37,11 +34,11 @@ import java.util.stream.Collectors
class ChannelconCommand(private val module: MinecraftChatModule) extends ICommand2DC { class ChannelconCommand(private val module: MinecraftChatModule) extends ICommand2DC {
@Command2.Subcommand def remove(sender: Command2DCSender): Boolean = { @Command2.Subcommand def remove(sender: Command2DCSender): Boolean = {
val message = sender.getMessage val message = sender.getMessage
if (checkPerms(message, null)) true if (checkPerms(message, null)) return true
else if (MCChatCustom.removeCustomChat(message.getChannelId)) else if (MCChatCustom.removeCustomChat(message.getChannelId))
DPUtils.reply(message, Mono.empty, "channel connection removed.").subscribe DPUtils.reply(message, SMono.empty, "channel connection removed.").subscribe
else else
DPUtils.reply(message, Mono.empty, "this channel isn't connected.").subscribe DPUtils.reply(message, SMono.empty, "this channel isn't connected.").subscribe
true true
} }
@ -54,31 +51,30 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman
if (cc == null) { if (cc == null) {
return respond(sender, "this channel isn't connected.") return respond(sender, "this channel isn't connected.")
} }
val togglesString: Supplier[String] = () => util.Arrays.stream(ChannelconBroadcast.values) val togglesString: Supplier[String] = () => ChannelconBroadcast.values
.map((t: ChannelconBroadcast) => .map(t => t.toString.toLowerCase + ": " + (if ((cc.toggles & (1 << t.id)) == 0) "disabled" else "enabled"))
t.toString.toLowerCase + ": " + (if ((cc.toggles & t.flag) == 0) "disabled" else "enabled")) .mkString("\n") + "\n\n" +
.collect(Collectors.joining("\n")) + "\n\n" +
TBMCSystemChatEvent.BroadcastTarget.stream.map((target: TBMCSystemChatEvent.BroadcastTarget) => TBMCSystemChatEvent.BroadcastTarget.stream.map((target: TBMCSystemChatEvent.BroadcastTarget) =>
target.getName + ": " + (if (cc.brtoggles.contains(target)) "enabled" else "disabled")) target.getName + ": " + (if (cc.brtoggles.contains(target)) "enabled" else "disabled"))
.collect(Collectors.joining("\n")) .collect(Collectors.joining("\n"))
if (toggle == null) { if (toggle == null) {
DPUtils.reply(message, Mono.empty, "toggles:\n" + togglesString.get).subscribe DPUtils.reply(message, SMono.empty, "toggles:\n" + togglesString.get).subscribe
return true return true
} }
val arg: String = toggle.toUpperCase val arg: String = toggle.toUpperCase
val b: Optional[ChannelconBroadcast] = util.Arrays.stream(ChannelconBroadcast.values).filter((t: ChannelconBroadcast) => t.toString == arg).findAny val b = ChannelconBroadcast.values.find((t: ChannelconBroadcast) => t.toString == arg)
if (!b.isPresent) { if (b.isEmpty) {
val bt: TBMCSystemChatEvent.BroadcastTarget = TBMCSystemChatEvent.BroadcastTarget.get(arg) val bt: TBMCSystemChatEvent.BroadcastTarget = TBMCSystemChatEvent.BroadcastTarget.get(arg)
if (bt == null) { if (bt == null) {
DPUtils.reply(message, Mono.empty, "cannot find toggle. Toggles:\n" + togglesString.get).subscribe DPUtils.reply(message, SMono.empty, "cannot find toggle. Toggles:\n" + togglesString.get).subscribe
return true return true
} }
val add: Boolean = !(cc.brtoggles.contains(bt)) val add: Boolean = !(cc.brtoggles.contains(bt))
if (add) { if (add) {
cc.brtoggles.add(bt) cc.brtoggles += bt
} }
else { else {
cc.brtoggles.remove(bt) cc.brtoggles -= bt
} }
return respond(sender, "'" + bt.getName + "' " + (if (add) "en" else "dis") + "abled") return respond(sender, "'" + bt.getName + "' " + (if (add) "en" else "dis") + "abled")
} }
@ -89,10 +85,10 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman
//1 0 | 1 //1 0 | 1
//1 1 | 0 //1 1 | 0
// XOR // XOR
cc.toggles ^= b.get.flag cc.toggles ^= (1 << b.get.id)
DPUtils.reply(message, Mono.empty, "'" + b.get.toString.toLowerCase + "' " DPUtils.reply(message, SMono.empty, "'" + b.get.toString.toLowerCase + "' "
+ (if ((cc.toggles & b.get.flag) == 0) "disabled" else "enabled")).subscribe + (if ((cc.toggles & (1 << b.get.id)) == 0) "disabled" else "enabled")).subscribe
return true true
} }
@Command2.Subcommand def `def`(sender: Command2DCSender, channelID: String): Boolean = { @Command2.Subcommand def `def`(sender: Command2DCSender, channelID: String): Boolean = {
@ -139,14 +135,14 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman
return true; return true;
}*/ }*/
//TODO: "Channel admins" that can connect channels? //TODO: "Channel admins" that can connect channels?
MCChatCustom.addCustomChat(channel, groupid, chan.get, author, dcp, 0, new util.HashSet[TBMCSystemChatEvent.BroadcastTarget]) MCChatCustom.addCustomChat(channel, groupid, chan.get, author, dcp, 0, Set())
if (chan.get.isInstanceOf[ChatRoom]) { if (chan.get.isInstanceOf[ChatRoom]) {
DPUtils.reply(message, channel, "alright, connection made to the room!").subscribe DPUtils.reply(message, channel, "alright, connection made to the room!").subscribe
} }
else { else {
DPUtils.reply(message, channel, "alright, connection made to group `" + groupid + "`!").subscribe DPUtils.reply(message, channel, "alright, connection made to group `" + groupid + "`!").subscribe
} }
return true true
} }
@SuppressWarnings(Array("ConstantConditions")) @SuppressWarnings(Array("ConstantConditions"))
@ -167,7 +163,7 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman
false false
} }
def getHelpText(method: Method, ann: Command2.Subcommand): Array[String] = override def getHelpText(method: Method, ann: Command2.Subcommand): Array[String] =
Array[String]( Array[String](
"Channel connect", "Channel connect",
"This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).", "This command allows you to connect a Minecraft channel to a Discord channel (just like how the global chat is connected to #minecraft-chat).",
@ -178,6 +174,6 @@ class ChannelconCommand(private val module: MinecraftChatModule) extends IComman
"To remove a connection use @ChromaBot channelcon remove in the channel.", "To remove a connection use @ChromaBot channelcon remove in the channel.",
"Mentioning the bot is needed in this case because the " + DiscordPlugin.getPrefix + " prefix only works in " + DPUtils.botmention + ".", "Mentioning the bot is needed in this case because the " + DiscordPlugin.getPrefix + " prefix only works in " + DPUtils.botmention + ".",
"Invite link: <https://discordapp.com/oauth2/authorize?client_id=" "Invite link: <https://discordapp.com/oauth2/authorize?client_id="
+ DiscordPlugin.dc.getApplicationInfo.map((info) => info.getId.asString).blockOptional.orElse("Unknown") + SMono(DiscordPlugin.dc.getApplicationInfo).map(info => info.getId.asString).blockOption().getOrElse("Unknown")
+ "&scope=bot&permissions=268509264>") + "&scope=bot&permissions=268509264>")
} }

View file

@ -18,7 +18,7 @@ object MCChatCustom {
*/ */
private[mcchat] val lastmsgCustom = new util.ArrayList[MCChatCustom.CustomLMD] private[mcchat] val lastmsgCustom = new util.ArrayList[MCChatCustom.CustomLMD]
def addCustomChat(channel: MessageChannel, groupid: String, mcchannel: Channel, user: User, dcp: DiscordConnectedPlayer, toggles: Int, brtoggles: util.Set[TBMCSystemChatEvent.BroadcastTarget]): Boolean = { def addCustomChat(channel: MessageChannel, groupid: String, mcchannel: Channel, user: User, dcp: DiscordConnectedPlayer, toggles: Int, brtoggles: Set[TBMCSystemChatEvent.BroadcastTarget]): Boolean = {
lastmsgCustom synchronized { lastmsgCustom synchronized {
var gid: String = null var gid: String = null
mcchannel match { mcchannel match {
@ -58,9 +58,9 @@ object MCChatCustom {
def getCustomChats: util.List[CustomLMD] = Collections.unmodifiableList(lastmsgCustom) def getCustomChats: util.List[CustomLMD] = Collections.unmodifiableList(lastmsgCustom)
class CustomLMD private(@NonNull channel: MessageChannel, @NonNull user: User, val groupID: String, class CustomLMD private[mcchat](@NonNull channel: MessageChannel, @NonNull user: User, val groupID: String,
@NonNull val mcchannel: Channel, val dcp: DiscordConnectedPlayer, var toggles: Int, @NonNull mcchannel: Channel, val dcp: DiscordConnectedPlayer, var toggles: Int,
var brtoggles: Set[TBMCSystemChatEvent.BroadcastTarget]) extends MCChatUtils.LastMsgData(channel, user) { var brtoggles: Set[TBMCSystemChatEvent.BroadcastTarget]) extends MCChatUtils.LastMsgData(channel, user, mcchannel) {
} }
} }

View file

@ -21,6 +21,7 @@ import org.bukkit.entity.Player
import org.bukkit.event.{EventHandler, Listener} import org.bukkit.event.{EventHandler, Listener}
import org.bukkit.scheduler.BukkitTask import org.bukkit.scheduler.BukkitTask
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.core.scala.publisher.{SFlux, SMono}
import java.time.Instant import java.time.Instant
import java.util import java.util
@ -244,30 +245,30 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener {
private var recthread: Thread = null private var recthread: Thread = null
// Discord // Discord
def handleDiscord(ev: MessageCreateEvent): Mono[Boolean] = { def handleDiscord(ev: MessageCreateEvent): SMono[Boolean] = {
val timings: Timings = CommonListeners.timings val timings: Timings = CommonListeners.timings
timings.printElapsed("Chat event") timings.printElapsed("Chat event")
val author: Optional[User] = ev.getMessage.getAuthor val author = Option(ev.getMessage.getAuthor.orElse(null))
val hasCustomChat: Boolean = MCChatCustom.hasCustomChat(ev.getMessage.getChannelId) val hasCustomChat = MCChatCustom.hasCustomChat(ev.getMessage.getChannelId)
val prefix: Char = DiscordPlugin.getPrefix val prefix = DiscordPlugin.getPrefix
return ev.getMessage.getChannel.filter((channel: MessageChannel) => { SMono(ev.getMessage.getChannel).filter(channel => {
def foo(channel: MessageChannel) = { def hasPrivateChat = channel.isInstanceOf[PrivateChannel] &&
author.exists((u: User) => MCChatPrivate.isMinecraftChatEnabled(u.getId.asString))
def hasPublicChat = ev.getMessage.getChannelId.asLong == module.chatChannel.get.asLong
timings.printElapsed("Filter 1") timings.printElapsed("Filter 1")
return !((ev.getMessage.getChannelId.asLong != module.chatChannel.get.asLong && !((channel.isInstanceOf[PrivateChannel] && author.map((u: User) => MCChatPrivate.isMinecraftChatEnabled(u.getId.asString)).orElse(false))) && !(hasCustomChat))) //Chat isn't enabled on this channel val chatEnabled = hasPublicChat || hasPrivateChat || hasCustomChat
} chatEnabled
}).filter(channel => {
foo(channel)
}).filter((channel: MessageChannel) => {
def foo(channel: MessageChannel) = {
timings.printElapsed("Filter 2") timings.printElapsed("Filter 2")
return !((channel.isInstanceOf[PrivateChannel] //Only in private chat && ev.getMessage.getContent.length < "/mcchat<>".length && ev.getMessage.getContent.replace(prefix + "", "").equalsIgnoreCase("mcchat")))//Either mcchat or /mcchat !(channel.isInstanceOf[PrivateChannel] //Only in private chat
&& ev.getMessage.getContent.length < "/mcchat<>".length
&& ev.getMessage.getContent.replace(prefix + "", "").equalsIgnoreCase("mcchat")) //Either mcchat or /mcchat
//Allow disabling the chat if needed //Allow disabling the chat if needed
} }).filterWhen(_ =>
CommandListener.runCommand(ev.getMessage, DiscordPlugin.plugin.commandChannel.get, mentionedonly = true)) //Allow running commands in chat channels
foo(channel) .filter(channel => {
}).filterWhen((channel: MessageChannel) => CommandListener.runCommand(ev.getMessage, DiscordPlugin.plugin.commandChannel.get, true)).filter //Allow running commands in chat channels
((channel: MessageChannel) => {
def foo(channel: MessageChannel) = {
MCChatUtils.resetLastMessage(channel) MCChatUtils.resetLastMessage(channel)
recevents.add(ev) recevents.add(ev)
timings.printElapsed("Message event added") timings.printElapsed("Message event added")
@ -275,22 +276,15 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener {
return true return true
} }
recrun = () => { recrun = () => {
def foo() = { //Don't return in a while loop next time
recthread = Thread.currentThread recthread = Thread.currentThread
processDiscordToMC() processDiscordToMC()
if (DiscordPlugin.plugin.isEnabled && !(stop)) { if (DiscordPlugin.plugin.isEnabled && !(stop)) {
rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Continue message processing rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Continue message processing
} }
} }
foo()
}
rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Start message processing rectask = Bukkit.getScheduler.runTaskAsynchronously(DiscordPlugin.plugin, recrun) //Start message processing
return true return true
} }).map(_ => false).defaultIfEmpty(true)
foo(channel)
}).map((b: MessageChannel) => false).defaultIfEmpty(true)
} }
private def processDiscordToMC(): Unit = { private def processDiscordToMC(): Unit = {
@ -307,7 +301,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener {
val dsender: DiscordSenderBase = MCChatUtils.getSender(event.getMessage.getChannelId, sender) val dsender: DiscordSenderBase = MCChatUtils.getSender(event.getMessage.getChannelId, sender)
val user: DiscordPlayer = dsender.getChromaUser val user: DiscordPlayer = dsender.getChromaUser
for (u <- event.getMessage.getUserMentions.toIterable) { //TODO: Role mentions for (u <- SFlux(event.getMessage.getUserMentions).toIterable()) { //TODO: Role mentions
dmessage = dmessage.replace(u.getMention, "@" + u.getUsername) // TODO: IG Formatting 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 val m: Optional[Member] = u.asMember(DiscordPlugin.mainServer.getId).onErrorResume((t: Throwable) => Mono.empty).blockOptional
if (m.isPresent) { if (m.isPresent) {
@ -317,7 +311,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener {
} }
} }
for (ch <- event.getGuild.flux.flatMap(_.getChannels).toIterable) { for (ch <- SFlux(event.getGuild.flux).flatMap(_.getChannels).toIterable()) {
dmessage = dmessage.replace(ch.getMention, "#" + ch.getName) dmessage = 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 = EmojiParser.parseToAliases(dmessage, EmojiParser.FitzpatrickAction.PARSE) //Converts emoji to text- TODO: Add option to disable (resource pack?)
@ -388,7 +382,7 @@ class MCChatListener(val module: MinecraftChatModule) extends Listener {
} }
react = true react = true
} }
return react react
} }
private def handleIngameCommand(event: MessageCreateEvent, dmessage: String, dsender: DiscordSenderBase, user: DiscordPlayer, clmd: MCChatCustom.CustomLMD, isPrivate: Boolean): Boolean = { private def handleIngameCommand(event: MessageCreateEvent, dmessage: String, dsender: DiscordSenderBase, user: DiscordPlayer, clmd: MCChatCustom.CustomLMD, isPrivate: Boolean): Boolean = {

View file

@ -1,6 +1,7 @@
package buttondevteam.discordplugin.mcchat package buttondevteam.discordplugin.mcchat
import buttondevteam.core.ComponentManager import buttondevteam.core.ComponentManager
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.common.util.Snowflake
@ -8,16 +9,16 @@ 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 java.util import scala.collection.mutable.ListBuffer
object MCChatPrivate { object MCChatPrivate {
/** /**
* Used for messages in PMs (mcchat). * Used for messages in PMs (mcchat).
*/ */
private[mcchat] val lastmsgPerUser = new util.ArrayList[MCChatUtils.LastMsgData] private[mcchat] var lastmsgPerUser: ListBuffer[LastMsgData] = ListBuffer()
def privateMCChat(channel: MessageChannel, start: Boolean, user: User, dp: DiscordPlayer): Unit = { def privateMCChat(channel: MessageChannel, start: Boolean, user: User, dp: DiscordPlayer): Unit = {
MCChatUtils.ConnectedSenders synchronized MCChatUtils.ConnectedSenders synchronized {
val mcp = dp.getAs(classOf[TBMCPlayer]) val mcp = dp.getAs(classOf[TBMCPlayer])
if (mcp != null) { // If the accounts aren't connected, can't make a connected sender if (mcp != null) { // If the accounts aren't connected, can't make a connected sender
val p = Bukkit.getPlayer(mcp.getUUID) val p = Bukkit.getPlayer(mcp.getUUID)
@ -52,8 +53,9 @@ object MCChatPrivate {
// ---- PermissionsEx warning is normal on logout ---- // ---- PermissionsEx warning is normal on logout ----
} }
if (!start) MCChatUtils.lastmsgfromd.remove(channel.getId.asLong) if (!start) MCChatUtils.lastmsgfromd.remove(channel.getId.asLong)
if (start) lastmsgPerUser.add(new MCChatUtils.LastMsgData(channel, user)) // Doesn't support group DMs if (start) lastmsgPerUser += new MCChatUtils.LastMsgData(channel, user) // Doesn't support group DMs
else lastmsgPerUser.removeIf((lmd: MCChatUtils.LastMsgData) => lmd.channel.getId.asLong == channel.getId.asLong) else lastmsgPerUser.filterInPlace(_.channel.getId.asLong != channel.getId.asLong) //Remove
}
} }
def isMinecraftChatEnabled(dp: DiscordPlayer): Boolean = isMinecraftChatEnabled(dp.getDiscordID) def isMinecraftChatEnabled(dp: DiscordPlayer): Boolean = isMinecraftChatEnabled(dp.getDiscordID)
@ -64,8 +66,8 @@ object MCChatPrivate {
} }
def logoutAll(): Unit = { def logoutAll(): Unit = {
MCChatUtils.ConnectedSenders synchronized MCChatUtils.ConnectedSenders synchronized {
for (entry <- MCChatUtils.ConnectedSenders.entrySet) { for (entry <- asScala(MCChatUtils.ConnectedSenders.entrySet)) {
for (valueEntry <- entry.getValue.entrySet) { for (valueEntry <- entry.getValue.entrySet) {
if (MCChatUtils.getSender(MCChatUtils.OnlineSenders, valueEntry.getKey, valueEntry.getValue.getUser) == null) { //If the player is online then the fake player was already logged out 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)
@ -73,4 +75,5 @@ object MCChatPrivate {
} }
} }
} }
}
} }

View file

@ -30,15 +30,16 @@ import java.util.function.Supplier
import java.util.logging.Level import java.util.logging.Level
import java.util.stream.{Collectors, Stream} import java.util.stream.{Collectors, Stream}
import javax.annotation.Nullable import javax.annotation.Nullable
import scala.jdk.javaapi.CollectionConverters.asScala
object MCChatUtils { object MCChatUtils {
/** /**
* May contain P&lt;DiscordID&gt; as key for public chat * May contain P&lt;DiscordID&gt; as key for public chat
*/ */
val UnconnectedSenders = new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordSender]] val UnconnectedSenders = asScala(new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordSender]])
val ConnectedSenders = new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordConnectedPlayer]] val ConnectedSenders = asScala(new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordConnectedPlayer]])
val OnlineSenders = new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordPlayerSender]] val OnlineSenders = asScala(new ConcurrentHashMap[String, ConcurrentHashMap[Snowflake, DiscordPlayerSender]])
val LoggedInPlayers = new ConcurrentHashMap[UUID, DiscordConnectedPlayer] val LoggedInPlayers = asScala(new ConcurrentHashMap[UUID, DiscordConnectedPlayer])
@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
@ -361,10 +362,15 @@ object MCChatUtils {
private[mcchat] def callEventSync(event: Event) = Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => callEventExcludingSome(event)) private[mcchat] def callEventSync(event: Event) = Bukkit.getScheduler.runTask(DiscordPlugin.plugin, () => callEventExcludingSome(event))
class LastMsgData(val channel: MessageChannel, val user: User) { class LastMsgData(val channel: MessageChannel, val user: User) {
var message: String = null var message: Message = null
var time = 0L var time = 0L
var content: String = null var content: String = null
var mcchannel: component.channel.Channel = null var mcchannel: component.channel.Channel = null
protected def this(channel: MessageChannel, user: User, mcchannel: component.channel.Channel) = {
this(channel, user)
this.mcchannel = mcchannel
}
} }
} }

View file

@ -1,66 +0,0 @@
package buttondevteam.discordplugin.playerfaker;
import buttondevteam.discordplugin.DiscordSenderBase;
import buttondevteam.discordplugin.IMCPlayer;
import buttondevteam.discordplugin.mcchat.MinecraftChatModule;
import buttondevteam.lib.TBMCCoreAPI;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import javax.annotation.Nullable;
@RequiredArgsConstructor
public class VCMDWrapper {
@Getter //Needed to mock the player
@Nullable
private final Object listener;
/**
* This constructor will only send raw vanilla messages to the sender in plain text.
*
* @param player The Discord sender player (the wrapper)
*/
public static <T extends DiscordSenderBase & IMCPlayer<T>> Object createListener(T player, MinecraftChatModule module) {
return createListener(player, null, module);
}
/**
* This constructor will send both raw vanilla messages to the sender in plain text and forward the raw message to the provided player.
*
* @param player The Discord sender player (the wrapper)
* @param bukkitplayer The Bukkit player to send the raw message to
* @param module The Minecraft chat module
*/
public static <T extends DiscordSenderBase & IMCPlayer<T>> Object createListener(T player, Player bukkitplayer, MinecraftChatModule module) {
try {
Object ret;
String mcpackage = Bukkit.getServer().getClass().getPackage().getName();
if (mcpackage.contains("1_12"))
ret = new VanillaCommandListener<>(player, bukkitplayer);
else if (mcpackage.contains("1_14"))
ret = new VanillaCommandListener14<>(player, bukkitplayer);
else if (mcpackage.contains("1_15") || mcpackage.contains("1_16"))
ret = VanillaCommandListener15.create(player, bukkitplayer); //bukkitplayer may be null but that's fine
else
ret = null;
if (ret == null)
compatWarning(module);
return ret;
} catch (NoClassDefFoundError | Exception e) {
compatWarning(module);
TBMCCoreAPI.SendException("Failed to create vanilla command listener", e, module);
return null;
}
}
private static void compatWarning(MinecraftChatModule module) {
module.logWarn("Vanilla commands won't be available from Discord due to a compatibility error. Disable vanilla command support to remove this message.");
}
static boolean compatResponse(DiscordSenderBase dsender) {
dsender.sendMessage("Vanilla commands are not supported on this Minecraft version.");
return true;
}
}

View file

@ -0,0 +1,54 @@
package buttondevteam.discordplugin.playerfaker
import buttondevteam.discordplugin.{DiscordSenderBase, IMCPlayer}
import buttondevteam.discordplugin.mcchat.MinecraftChatModule
import buttondevteam.lib.TBMCCoreAPI
import org.bukkit.Bukkit
import org.bukkit.entity.Player
object VCMDWrapper {
/**
* This constructor will only send raw vanilla messages to the sender in plain text.
*
* @param player The Discord sender player (the wrapper)
*/
def createListener[T <: DiscordSenderBase with IMCPlayer[T]](player: T, module: MinecraftChatModule): AnyRef =
createListener(player, null, module)
/**
* This constructor will send both raw vanilla messages to the sender in plain text and forward the raw message to the provided player.
*
* @param player The Discord sender player (the wrapper)
* @param bukkitplayer The Bukkit player to send the raw message to
* @param module The Minecraft chat module
*/
def createListener[T <: DiscordSenderBase with IMCPlayer[T]](player: T, bukkitplayer: Player, module: MinecraftChatModule): AnyRef = try {
var ret: AnyRef = null
val mcpackage = Bukkit.getServer.getClass.getPackage.getName
if (mcpackage.contains("1_12")) ret = new VanillaCommandListener[T](player, bukkitplayer)
else if (mcpackage.contains("1_14")) ret = new VanillaCommandListener14[T](player, bukkitplayer)
else if (mcpackage.contains("1_15") || mcpackage.contains("1_16")) ret = VanillaCommandListener15.create(player, bukkitplayer) //bukkitplayer may be null but that's fine
else ret = null
if (ret == null) compatWarning(module)
ret
} catch {
case e@(_: NoClassDefFoundError | _: Exception) =>
compatWarning(module)
TBMCCoreAPI.SendException("Failed to create vanilla command listener", e, module)
null
}
private def compatWarning(module: MinecraftChatModule): Unit =
module.logWarn("Vanilla commands won't be available from Discord due to a compatibility error. Disable vanilla command support to remove this message.")
private[playerfaker] def compatResponse(dsender: DiscordSenderBase) = {
dsender.sendMessage("Vanilla commands are not supported on this Minecraft version.")
true
}
}
class VCMDWrapper(private val listener: AnyRef) {
@javax.annotation.Nullable def getListener: AnyRef = listener
//Needed to mock the player @Nullable
}