Separated MC-specific command metadata
Implementing permission handling for MC
This commit is contained in:
parent
be5f9ded60
commit
af7d097f9b
7 changed files with 97 additions and 55 deletions
|
@ -5,7 +5,6 @@ import buttondevteam.core.ComponentManager
|
|||
import buttondevteam.lib.TBMCCoreAPI
|
||||
import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException
|
||||
import buttondevteam.lib.chat.ICommand2MC
|
||||
import lombok.Getter
|
||||
import org.bukkit.configuration.ConfigurationSection
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
|
@ -24,7 +23,6 @@ abstract class Component<TP : JavaPlugin?> {
|
|||
|
||||
val config = IHaveConfig(null)
|
||||
|
||||
@Getter
|
||||
private val data //TODO
|
||||
: IHaveConfig? = null
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package buttondevteam.lib.architecture
|
|||
|
||||
import buttondevteam.core.MainPlugin
|
||||
import buttondevteam.lib.ChromaUtils
|
||||
import lombok.*
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.configuration.Configuration
|
||||
import org.bukkit.scheduler.BukkitTask
|
||||
|
|
|
@ -12,7 +12,6 @@ import com.mojang.brigadier.context.CommandContext
|
|||
import com.mojang.brigadier.exceptions.CommandSyntaxException
|
||||
import com.mojang.brigadier.tree.CommandNode
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode
|
||||
import lombok.RequiredArgsConstructor
|
||||
import org.bukkit.Bukkit
|
||||
import java.lang.reflect.Method
|
||||
import java.util.function.Function
|
||||
|
@ -24,8 +23,17 @@ import java.util.stream.Collectors
|
|||
* The method name is the subcommand, use underlines (_) to add further subcommands.
|
||||
* The args may be null if the conversion failed and it's optional.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||
abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
|
||||
/**
|
||||
* The first character in the command line that shows that it's a command.
|
||||
*/
|
||||
private val commandChar: Char,
|
||||
|
||||
/**
|
||||
* Whether the command's actual code has to be run on the primary thread.
|
||||
*/
|
||||
private val runOnPrimaryThread: Boolean
|
||||
) {
|
||||
/**
|
||||
* Parameters annotated with this receive all the remaining arguments
|
||||
*/
|
||||
|
@ -44,16 +52,10 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
|||
*/
|
||||
val helpText: Array<String> = [],
|
||||
/**
|
||||
* The main permission which allows using this command (individual access can be still revoked with "chroma.command.X").
|
||||
* Used to be "tbmc.admin". The [.MOD_GROUP] is provided to use with this.
|
||||
* Aliases for the subcommand that can be used to invoke it in addition to the method name.
|
||||
*/
|
||||
val permGroup: String = "", val aliases: Array<String> = []) {
|
||||
companion object {
|
||||
/**
|
||||
* Allowed for OPs only by default
|
||||
*/
|
||||
const val MOD_GROUP = "mod"
|
||||
}
|
||||
val aliases: Array<String> = [] // TODO
|
||||
) {
|
||||
}
|
||||
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||
|
@ -66,16 +68,6 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
|||
private val commandHelp = ArrayList<String>() //Mainly needed by Discord
|
||||
private val dispatcher = CommandDispatcher<TP>()
|
||||
|
||||
/**
|
||||
* The first character in the command line that shows that it's a command.
|
||||
*/
|
||||
private val commandChar = 0.toChar()
|
||||
|
||||
/**
|
||||
* Whether the command's actual code has to be run on the primary thread.
|
||||
*/
|
||||
private val runOnPrimaryThread = false
|
||||
|
||||
/**
|
||||
* Adds a param converter that obtains a specific object from a string parameter.
|
||||
* The converter may return null.
|
||||
|
@ -96,13 +88,17 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
|||
return false // Unknown command
|
||||
}
|
||||
//Needed because permission checking may load the (perhaps offline) sender's file which is disallowed on the main thread
|
||||
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance) { _ ->
|
||||
try {
|
||||
dispatcher.execute(results)
|
||||
} catch (e: CommandSyntaxException) {
|
||||
sender.sendMessage(e.message)
|
||||
} catch (e: Exception) {
|
||||
TBMCCoreAPI.SendException("Command execution failed for sender " + sender.name + "(" + sender.javaClass.canonicalName + ") and message " + commandline, e, MainPlugin.Instance)
|
||||
TBMCCoreAPI.SendException(
|
||||
"Command execution failed for sender " + sender.name + "(" + sender.javaClass.canonicalName + ") and message " + commandline,
|
||||
e,
|
||||
MainPlugin.Instance
|
||||
)
|
||||
}
|
||||
}
|
||||
return true //We found a method
|
||||
|
@ -342,7 +338,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
|||
invokeCommand.run();*/return 0
|
||||
}
|
||||
|
||||
abstract fun hasPermission(sender: TP, command: TC, subcommand: Method?): Boolean
|
||||
abstract fun hasPermission(context: CommandContext<TP>): Boolean
|
||||
val commandsText: Array<String> get() = commandHelp.toTypedArray()
|
||||
|
||||
/**
|
||||
|
@ -393,4 +389,11 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
|||
root.children.removeIf { node -> node.coreExecutable<TP, TC>()?.let { condition.test(it) } ?: false }
|
||||
for (child in root.children) unregisterCommandIf(condition, child.core())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all subcommands of the specified command. Only returns executable nodes.
|
||||
*/
|
||||
fun getSubcommands(mainCommand: LiteralCommandNode<TP>): List<CoreCommandNode<TP, TC, SubcommandData<TC, TP>>> {
|
||||
return dispatcher.root.children.mapNotNull { it.coreExecutable<TP, TC>() } // TODO: Needs more depth
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@ import buttondevteam.lib.TBMCCoreAPI
|
|||
import buttondevteam.lib.architecture.ButtonPlugin
|
||||
import buttondevteam.lib.architecture.Component
|
||||
import buttondevteam.lib.chat.commands.CommandUtils
|
||||
import buttondevteam.lib.chat.commands.CommandUtils.subcommandPath
|
||||
import buttondevteam.lib.chat.commands.MCCommandSettings
|
||||
import buttondevteam.lib.chat.commands.SubcommandData
|
||||
import buttondevteam.lib.player.ChromaGamerBase
|
||||
import com.mojang.brigadier.arguments.StringArgumentType
|
||||
|
@ -57,44 +59,52 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
|||
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset
|
||||
Bukkit.getPluginManager().addPermission(Permission(perm,
|
||||
PermissionDefault.TRUE)) //Allow commands by default, it will check mod-only
|
||||
for (method in command.javaClass.methods) {
|
||||
if (!method.isAnnotationPresent(Subcommand::class.java)) continue
|
||||
val path = CommandUtils.getCommandPath(method.name, '.')
|
||||
for (node in getSubcommands(commandNode)) {
|
||||
if (path.length > 0) {
|
||||
val subperm = perm + path
|
||||
if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset
|
||||
Bukkit.getPluginManager().addPermission(Permission(subperm,
|
||||
PermissionDefault.TRUE)) //Allow commands by default, it will check mod-only
|
||||
Bukkit.getPluginManager().addPermission(
|
||||
Permission(
|
||||
subperm,
|
||||
PermissionDefault.TRUE
|
||||
)
|
||||
) //Allow commands by default, it will check mod-only
|
||||
}
|
||||
val pg = permGroup(command, method)
|
||||
if (pg.length == 0) continue
|
||||
val pg = permGroup(node.data)
|
||||
if (pg.isEmpty()) continue
|
||||
val permGroup = "chroma.$pg"
|
||||
if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
|
||||
Bukkit.getPluginManager().addPermission(Permission(permGroup,
|
||||
PermissionDefault.OP)) //Do not allow any commands that belong to a group
|
||||
Bukkit.getPluginManager().addPermission(
|
||||
Permission(
|
||||
permGroup,
|
||||
PermissionDefault.OP
|
||||
)
|
||||
) //Do not allow any commands that belong to a group
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasPermission(sender: Command2MCSender, command: ICommand2MC, method: Method): Boolean {
|
||||
return hasPermission(sender.sender, command, method)
|
||||
override fun hasPermission(context: CommandContext<Command2MCSender>): Boolean {
|
||||
return hasPermission(context.source.sender, context.subcommandPath)
|
||||
}
|
||||
|
||||
fun hasPermission(sender: CommandSender, command: ICommand2MC?, method: Method): Boolean {
|
||||
fun hasPermission(sender: CommandSender, path: String): Boolean {
|
||||
if (sender is ConsoleCommandSender) return true //Always allow the console
|
||||
if (command == null) return true //Allow viewing the command - it doesn't do anything anyway
|
||||
var pg: String
|
||||
var p = true
|
||||
val cmdperm = "chroma.command." + command.commandPath.replace(' ', '.')
|
||||
val path = CommandUtils.getCommandPath(method.name, '.')
|
||||
val cmdperm = "chroma.command.$path"
|
||||
// TODO: Register a permission for the main command as well - the previous implementation relied on the way the commands were defined
|
||||
val perms = arrayOf(
|
||||
if (path.length > 0) cmdperm + path else null,
|
||||
cmdperm,
|
||||
cmdperm + path,
|
||||
if (permGroup(command, method).also { pg = it }.length > 0) "chroma.$pg" else null
|
||||
)
|
||||
for (perm in perms) {
|
||||
if (perm != null) {
|
||||
if (p) { //Use OfflinePlayer to avoid fetching player data
|
||||
p = if (sender is OfflinePlayer) MainPlugin.permission.playerHas(if (sender is Player) sender.location.world.name else null, sender as OfflinePlayer, perm) else false //Use sender's method
|
||||
p = if (sender is OfflinePlayer) MainPlugin.permission.playerHas(
|
||||
if (sender is Player) sender.location.world.name else null,
|
||||
sender as OfflinePlayer,
|
||||
perm
|
||||
) else false //Use sender's method
|
||||
if (!p) p = sender.hasPermission(perm)
|
||||
} else break //If any of the permissions aren't granted then don't allow
|
||||
}
|
||||
|
@ -108,14 +118,11 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
|
|||
* @param method The subcommand to check
|
||||
* @return The permission group for the subcommand or empty string
|
||||
*/
|
||||
private fun permGroup(command: ICommand2MC, method: Method?): String {
|
||||
if (method != null) {
|
||||
val sc = method.getAnnotation(Subcommand::class.java)
|
||||
if (sc != null && sc.permGroup().length > 0) {
|
||||
return sc.permGroup()
|
||||
}
|
||||
}
|
||||
return if (getAnnForValue(command.javaClass, CommandClass::class.java, Function { obj: CommandClass -> obj.modOnly() }, false)) Subcommand.MOD_GROUP else getAnnForValue(command.javaClass, CommandClass::class.java, Function<CommandClass, String> { obj: CommandClass -> obj.permGroup() }, "")
|
||||
private fun permGroup(data: SubcommandData<ICommand2MC, Command2MCSender>): String {
|
||||
val group = data.annotations.filterIsInstance<MCCommandSettings>().map {
|
||||
if (it.permGroup.isEmpty() && it.modOnly) MCCommandSettings.MOD_GROUP else ""
|
||||
}.firstOrNull()
|
||||
return group ?: ""
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,6 +3,7 @@ package buttondevteam.lib.chat.commands
|
|||
import buttondevteam.lib.chat.Command2Sender
|
||||
import buttondevteam.lib.chat.CoreCommandNode
|
||||
import buttondevteam.lib.chat.ICommand2
|
||||
import com.mojang.brigadier.context.CommandContext
|
||||
import com.mojang.brigadier.tree.CommandNode
|
||||
import java.util.*
|
||||
|
||||
|
@ -34,4 +35,9 @@ object CommandUtils {
|
|||
val ret = core<TP, TC, NoOpSubcommandData>()
|
||||
return if (ret.data is SubcommandData<*, *>) ret.core() else null
|
||||
}
|
||||
|
||||
val <TP : Command2Sender> CommandContext<TP>.subcommandPath
|
||||
get(): String {
|
||||
TODO("Return command path")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package buttondevteam.lib.chat.commands
|
||||
|
||||
import java.lang.annotation.Inherited
|
||||
|
||||
@Inherited
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||
annotation class MCCommandSettings(
|
||||
/**
|
||||
* The main permission which allows using this command (individual access can be still revoked with "chroma.command.X").
|
||||
* Used to be "tbmc.admin". The [.MOD_GROUP] is provided to use with this.
|
||||
*/
|
||||
val permGroup: String = "",
|
||||
/**
|
||||
* Whether the (sub)command is mod only. This means it requires the chroma.mod permission.
|
||||
* This is just a shorthand for providing MOD_GROUP for permGroup.
|
||||
*/
|
||||
val modOnly: Boolean = false
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
* Allowed for OPs only by default
|
||||
*/
|
||||
const val MOD_GROUP = "mod"
|
||||
}
|
||||
}
|
|
@ -41,7 +41,11 @@ class SubcommandData<TC : ICommand2<*>, TP : Command2Sender>(
|
|||
/**
|
||||
* A function that determines whether the user has permission to run this subcommand.
|
||||
*/
|
||||
private val permissionCheck: (TP) -> Boolean
|
||||
private val permissionCheck: (TP) -> Boolean,
|
||||
/**
|
||||
* All annotations implemented by the method that executes the command. Can be used to add custom metadata when implementing a platform.
|
||||
*/
|
||||
val annotations: Array<Annotation>
|
||||
) : NoOpSubcommandData(helpTextGetter) {
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue