Implement channel handling for Chroma users

- This allows properly implementing Minecraft chat for Discord users instead of creating fake CommandSenders
- Channel access needs to be implemented on all platforms - this could allow automatically managing who has access to a Discord channel for example
- In the future custom commands could be supported without having to connect accounts
- Also any information can be retrieved about the user when they chat, not just their name
This commit is contained in:
Norbi Peti 2023-06-09 00:11:53 +02:00
parent 291fc068fd
commit cd108dc787
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
9 changed files with 80 additions and 52 deletions

View file

@ -57,6 +57,11 @@ class MainPlugin : ButtonPlugin() {
*/
val prioritizeCustomCommands = iConfig.getData("prioritizeCustomCommands", false)
/**
* The permission group to use for players who are not in the server.
*/ // TODO: Combine the channel access test with command permissions (with a generic implementation)
val externalPlayerPermissionGroup get() = iConfig.getData("externalPlayerPermissionGroup", "default")
public override fun pluginEnable() {
instance = this
val pdf = description

View file

@ -67,7 +67,7 @@ class PlayerListener(val plugin: MainPlugin) : Listener {
private fun handlePreprocess(sender: CommandSender, message: String, event: Cancellable) {
if (event.isCancelled) return
val cg = ChromaGamerBase.getFromSender(sender)
val ev = TBMCCommandPreprocessEvent(cg, cg.channel.get(), message, sender)
val ev = TBMCCommandPreprocessEvent(cg, cg.channel.get(), message, cg)
Bukkit.getPluginManager().callEvent(ev)
if (ev.isCancelled) event.isCancelled = true //Cancel the original event
}
@ -76,13 +76,7 @@ class PlayerListener(val plugin: MainPlugin) : Listener {
fun onTBMCPreprocess(event: TBMCCommandPreprocessEvent) {
if (event.isCancelled) return
try {
val mcuser = event.sender.getAs(TBMCPlayerBase::class.java)
if (mcuser == null) { // TODO: The chat should continue to support unconnected accounts.
event.sender.sendMessage("You need to have your Minecraft account connected to send commands.")
event.isCancelled = true
return
}
val sender = Command2MCSender(mcuser, event.channel, event.permCheck)
val sender = Command2MCSender(event.sender, event.channel, event.permCheck)
event.isCancelled = ButtonPlugin.command2MC.handleCommand(sender, event.message)
} catch (e: Exception) {
TBMCCoreAPI.SendException(

View file

@ -1,15 +1,12 @@
package buttondevteam.core.component.channel
import buttondevteam.core.ComponentManager.get
import buttondevteam.core.MainPlugin
import buttondevteam.lib.architecture.ConfigData
import buttondevteam.lib.architecture.IHaveConfig
import buttondevteam.lib.architecture.ListConfigData
import buttondevteam.lib.chat.Color
import buttondevteam.lib.player.ChromaGamerBase
import org.bukkit.Bukkit
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import java.util.*
import java.util.function.Function
import java.util.function.Predicate
@ -45,7 +42,7 @@ open class Channel
* Only those with access can see the messages.
* If null, everyone has access.
*/
private val filterAndErrorMSG: Function<CommandSender, RecipientTestResult>?
private val filterAndErrorMSG: Function<ChromaGamerBase, RecipientTestResult>?
) {
private val config: IHaveConfig? = null // TODO: Use this
@ -188,28 +185,27 @@ open class Channel
* @param permgroup The group that can access the channel or **null** to only allow OPs.
* @return If has access
*/
fun inGroupFilter(permgroup: String?): Function<CommandSender, RecipientTestResult> {
// TODO: This is Minecraft specific. Change all of this so it supports other ways of checking permissions.
// TODO: The commands have to be Minecraft specific, but the channels should be generic.
// TODO: Implement a way to check permissions for other platforms. Maybe specific strings, like "admin" or "mod"?
return noScoreResult(
{ s ->
s.isOp || s is Player && permgroup?.let { pg ->
MainPlugin.permission.playerInGroup(s, pg)
} ?: false
},
"You need to be a(n) " + (permgroup ?: "OP") + " to use this channel."
)
@JvmStatic
fun inGroupFilter(permgroup: String?): Function<ChromaGamerBase, RecipientTestResult> {
return Function { it.checkChannelInGroup(permgroup) }
}
@JvmStatic
fun noScoreResult(
filter: Predicate<CommandSender>,
filter: Predicate<ChromaGamerBase>,
errormsg: String?
): Function<CommandSender, RecipientTestResult> {
return Function { s ->
if (filter.test(s)) RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE)
else RecipientTestResult(errormsg)
}
): Function<ChromaGamerBase, RecipientTestResult> {
return Function { noScoreResult(filter, errormsg, it) }
}
@JvmStatic
fun noScoreResult(
filter: Predicate<ChromaGamerBase>,
errormsg: String?,
user: ChromaGamerBase
): RecipientTestResult {
return if (filter.test(user)) RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE)
else RecipientTestResult(errormsg)
}
lateinit var globalChat: Channel

View file

@ -2,7 +2,6 @@ package buttondevteam.lib
import buttondevteam.core.component.channel.Channel
import buttondevteam.lib.player.ChromaGamerBase
import org.bukkit.command.CommandSender
import org.bukkit.event.Cancellable
import org.bukkit.event.Event
import org.bukkit.event.HandlerList
@ -17,7 +16,7 @@ class TBMCCommandPreprocessEvent(
val sender: ChromaGamerBase,
val channel: Channel,
val message: String,
val permCheck: CommandSender
val permCheck: ChromaGamerBase
) : Event(), Cancellable {
private var cancelled = false
override fun getHandlers(): HandlerList {

View file

@ -59,7 +59,22 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
}
override fun hasPermission(sender: Command2MCSender, data: SubcommandData<ICommand2MC, Command2MCSender>): Boolean {
if (sender.sender.isConsole) return true //Always allow the console
val defWorld = Bukkit.getWorlds().first().name
val check = if (sender.permCheck !is TBMCPlayerBase) ({
MainPlugin.permission.groupHas(
defWorld,
MainPlugin.instance.externalPlayerPermissionGroup.get(),
it
)
})
else if (sender.permCheck.isConsole) ({ true }) //Always allow the console
else ({ perm: String ->
MainPlugin.permission.playerHas(
sender.permCheck.player?.location?.world?.name ?: defWorld,
sender.permCheck.offlinePlayer,
perm
)
})
var p = true
val cmdperm = "chroma.command.${data.fullPath.replace(' ', '.')}"
@ -71,11 +86,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
for (perm in perms) {
if (perm != null) {
if (p) { //Use OfflinePlayer to avoid fetching player data
p = MainPlugin.permission.playerHas(
sender.sender.player?.location?.world?.name,
sender.sender.offlinePlayer,
perm
)
p = check(perm)
} else break //If any of the permissions aren't granted then don't allow
}
}
@ -113,14 +124,18 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
if (original != null) {
return original
}
// Check Bukkit sender type - TODO: This is no longer the Bukkit sender type
if (senderType.isAssignableFrom(sender.sender.javaClass))
return sender.sender
val cg = sender.sender
if (senderType.isAssignableFrom(cg.javaClass))
return cg
// Check Bukkit sender type
if (cg is TBMCPlayerBase) {
if (senderType.isAssignableFrom(cg.offlinePlayer.javaClass)) return cg.offlinePlayer
if (cg.player?.javaClass?.let { senderType.isAssignableFrom(it) } == true) return cg.player
}
//The command expects a user of our system
if (ChromaGamerBase::class.java.isAssignableFrom(senderType)) {
val cg = sender.sender
if (cg.javaClass == senderType)
return cg
@Suppress("UNCHECKED_CAST")
return sender.sender.getAs(senderType as Class<out ChromaGamerBase>)
}
return null
}
@ -187,7 +202,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
val user = ChromaGamerBase.getFromSender(sender) // TODO: Senders should only be used for TBMCPlayerBase classes.
///trim(): remove space if there are no args
handleCommand(
Command2MCSender(user as TBMCPlayerBase, user.channel.get(), sender),
Command2MCSender(user as TBMCPlayerBase, user.channel.get(), user),
("/${command.name} ${args.joinToString(" ")}").trim { it <= ' ' }, false
)
return true

View file

@ -1,10 +1,9 @@
package buttondevteam.lib.chat
import buttondevteam.core.component.channel.Channel
import buttondevteam.lib.player.TBMCPlayerBase
import org.bukkit.command.CommandSender
import buttondevteam.lib.player.ChromaGamerBase
class Command2MCSender(val sender: TBMCPlayerBase, val channel: Channel, val permCheck: CommandSender) : Command2Sender {
class Command2MCSender(val sender: ChromaGamerBase, val channel: Channel, val permCheck: ChromaGamerBase) : Command2Sender {
// TODO: Remove this class and only use the user classes.
// TODO: The command context should be stored separately.
override fun sendMessage(message: String) {

View file

@ -10,9 +10,9 @@ import buttondevteam.lib.TBMCChatEvent
import buttondevteam.lib.TBMCChatPreprocessEvent
import buttondevteam.lib.TBMCSystemChatEvent
import buttondevteam.lib.TBMCSystemChatEvent.BroadcastTarget
import buttondevteam.lib.player.ChromaGamerBase
import org.bukkit.Bukkit
import org.bukkit.ChatColor
import org.bukkit.command.CommandSender
import java.util.function.Supplier
object TBMCChatAPI {
@ -38,7 +38,7 @@ object TBMCChatAPI {
val rtr = getScoreOrSendError(channel, cm.permCheck)
val score = rtr.score
if (score == Channel.SCORE_SEND_NOPE || rtr.groupID == null) return@Supplier true
val eventPre = TBMCChatPreprocessEvent(cm.sender, channel, cm.message)
val eventPre = TBMCChatPreprocessEvent(cm.user, channel, cm.message)
Bukkit.getPluginManager().callEvent(eventPre)
if (eventPre.isCancelled) return@Supplier true
cm.message = eventPre.message
@ -74,7 +74,7 @@ object TBMCChatAPI {
return callEventAsync(event)
}
private fun getScoreOrSendError(channel: Channel, sender: CommandSender): RecipientTestResult {
private fun getScoreOrSendError(channel: Channel, sender: ChromaGamerBase): RecipientTestResult {
val result = channel.getRTR(sender)
if (result.errormessage != null) sender.sendMessage("${ChatColor.RED}" + result.errormessage)
return result

View file

@ -167,6 +167,13 @@ abstract class ChromaGamerBase : Command2Sender {
.findAny().orElseThrow { RuntimeException("Channel $id not found!") }
}, { ch -> ch.identifier })
/**
* Check channel access by checking if the user is in the given group. If the group is null, check if the user is OP.
*
* Note that these groups originally come from Minecraft.
*/ // TODO: Allow if a connected account has access
abstract fun checkChannelInGroup(group: String?): Channel.RecipientTestResult
companion object {
private const val TBMC_PLAYERS_DIR = "TBMC/players/"
private val senderConverters = ArrayList<Function<CommandSender, out Optional<out ChromaGamerBase>>>()

View file

@ -1,5 +1,7 @@
package buttondevteam.lib.player
import buttondevteam.core.MainPlugin
import buttondevteam.core.component.channel.Channel
import buttondevteam.lib.architecture.IHaveConfig
import org.bukkit.Bukkit
import org.bukkit.entity.Player
@ -45,7 +47,6 @@ abstract class TBMCPlayerBase : ChromaGamerBase() {
}
override fun sendMessage(message: String) {
// TODO: Random senders (Discord) won't receive messages. Including when trying to chat.
player?.sendMessage(message)
}
@ -57,6 +58,18 @@ abstract class TBMCPlayerBase : ChromaGamerBase() {
return playerName.get()
}
override fun checkChannelInGroup(group: String?): Channel.RecipientTestResult {
return Channel.noScoreResult(
{ s ->
s is TBMCPlayerBase && (s.offlinePlayer.isOp || s.player != null && group?.let { pg ->
MainPlugin.permission.playerInGroup(s.player, pg)
} ?: false)
},
"You need to be a(n) ${group ?: "OP"} to use this channel.",
this
)
}
companion object {
/**
* Get player as a plugin player.