mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-11 02:50:15 +08:00
Rearrange implementations
This commit is contained in:
parent
d2194c2c2b
commit
2ba18ab7e4
@ -64,7 +64,7 @@ interface MiraiConsole {
|
||||
@MiraiExperimentalAPI
|
||||
fun newLogger(identity: String?): MiraiLogger
|
||||
|
||||
companion object INSTANCE : MiraiConsole by MiraiConsoleImpl
|
||||
companion object INSTANCE : MiraiConsole by MiraiConsoleInternal
|
||||
}
|
||||
|
||||
|
||||
@ -77,7 +77,7 @@ internal object MiraiConsoleInitializer {
|
||||
/** 由前端调用 */
|
||||
internal fun init(instance: IMiraiConsole) {
|
||||
this.instance = instance
|
||||
MiraiConsoleImpl.initialize()
|
||||
MiraiConsoleInternal.initialize()
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mira
|
||||
/**
|
||||
* mirai 控制台实例.
|
||||
*/
|
||||
internal object MiraiConsoleImpl : CoroutineScope, IMiraiConsole, MiraiConsole {
|
||||
internal object MiraiConsoleInternal : CoroutineScope, IMiraiConsole, MiraiConsole {
|
||||
override val pluginCenter: PluginCenter get() = CuiPluginCenter
|
||||
|
||||
private val instance: IMiraiConsole
|
||||
|
@ -11,17 +11,15 @@
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.description.*
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
import kotlin.reflect.KAnnotatedElement
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.*
|
||||
|
||||
/**
|
||||
* 指令
|
||||
* 通常情况下, 你的指令应继承 @see CompositeCommand/SimpleCommand
|
||||
* @see register 注册这个指令
|
||||
*
|
||||
* @see SimpleCommand
|
||||
* @see CompositeCommand
|
||||
*/
|
||||
interface Command {
|
||||
/**
|
||||
@ -30,6 +28,7 @@ interface Command {
|
||||
val names: Array<out String>
|
||||
|
||||
val usage: String
|
||||
|
||||
val description: String
|
||||
|
||||
/**
|
||||
@ -47,289 +46,12 @@ interface Command {
|
||||
/**
|
||||
* @param args 指令参数. 可能是 [SingleMessage] 或 [String]. 且已经以 ' ' 分割.
|
||||
*/
|
||||
suspend fun onCommand(sender: CommandSender, args: Array<out Any>)
|
||||
suspend fun CommandSender.onCommand(args: Array<out Any>)
|
||||
}
|
||||
|
||||
suspend inline fun Command.onCommand(sender: CommandSender, args: Array<out Any>) = sender.run { onCommand(args) }
|
||||
|
||||
/**
|
||||
* 主要指令名. 为 [Command.names] 的第一个元素.
|
||||
*/
|
||||
val Command.primaryName: String get() = names[0]
|
||||
|
||||
/**
|
||||
* 功能最集中的Commend
|
||||
* 支持且只支持有sub的指令
|
||||
* 例:
|
||||
* /mute add
|
||||
* /mute remove
|
||||
* /mute addandremove (sub is case insensitive, lowercase are recommend)
|
||||
* /mute add and remove('add and remove' consider as a sub)
|
||||
*/
|
||||
abstract class CompositeCommand @JvmOverloads constructor(
|
||||
override val owner: CommandOwner,
|
||||
vararg names: String,
|
||||
description: String = "no description available",
|
||||
override val permission: CommandPermission = CommandPermission.Default,
|
||||
override val prefixOptional: Boolean = false,
|
||||
overrideContext: CommandParserContext = EmptyCommandParserContext
|
||||
) : Command {
|
||||
override val description = description.trimIndent()
|
||||
override val names: Array<out String> =
|
||||
names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).also { list ->
|
||||
list.firstOrNull { !it.isValidSubName() }?.let {
|
||||
error("Name is not valid: $it")
|
||||
}
|
||||
}.toTypedArray()
|
||||
|
||||
/**
|
||||
* [CommandArgParser] 的环境
|
||||
*/
|
||||
val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
|
||||
|
||||
override var usage: String = "<command build failed>" // initialized by subCommand reflection
|
||||
internal set
|
||||
|
||||
/** 指定子指令要求的权限 */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Permission(val permission: KClass<out CommandPermission>)
|
||||
|
||||
/** 标记一个函数为子指令 */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class SubCommand(vararg val name: String)
|
||||
|
||||
/** 指令描述 */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Description(val description: String)
|
||||
|
||||
/** 参数名, 将参与构成 [usage] */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||
annotation class Name(val name: String)
|
||||
|
||||
final override suspend fun onCommand(sender: CommandSender, args: Array<out Any>) {
|
||||
matchSubCommand(args)?.parseAndExecute(sender, args) ?: kotlin.run {
|
||||
defaultSubCommand.onCommand(sender, args)
|
||||
}
|
||||
subCommands
|
||||
}
|
||||
|
||||
internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy {
|
||||
DefaultSubCommandDescriptor(
|
||||
"",
|
||||
CommandPermission.Default,
|
||||
onCommand = block { sender: CommandSender, args: Array<out Any> ->
|
||||
false//not supported yet
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
class IllegalParameterException internal constructor(message: String) : Exception(message)
|
||||
|
||||
internal val subCommands: Array<SubCommandDescriptor> by lazy {
|
||||
|
||||
val buildUsage = StringBuilder(this.description).append(": \n")
|
||||
|
||||
this@CompositeCommand::class.declaredFunctions.filter { it.hasAnnotation<SubCommand>() }.map { function ->
|
||||
|
||||
val notStatic = function.findAnnotation<JvmStatic>()==null
|
||||
val overridePermission = function.findAnnotation<Permission>()//optional
|
||||
val subDescription = function.findAnnotation<Description>()?.description?:"no description available"
|
||||
|
||||
if ((function.returnType.classifier as? KClass<*>)?.isSubclassOf(Boolean::class) != true) {
|
||||
throw IllegalParameterException("Return Type of SubCommand must be Boolean")
|
||||
}
|
||||
|
||||
val parameter = function.parameters.toMutableList()
|
||||
if (parameter.isEmpty()){
|
||||
throw IllegalParameterException("First parameter (receiver for kotlin) for sub commend " + function.name + " from " + this.primaryName + " should be <out CommandSender>")
|
||||
}
|
||||
|
||||
if (notStatic) {
|
||||
parameter.removeAt(0)
|
||||
}
|
||||
|
||||
(parameter.removeAt(0)).let {receiver ->
|
||||
if (
|
||||
receiver.isVararg ||
|
||||
((receiver.type.classifier as? KClass<*>).also { print(it) }
|
||||
?.isSubclassOf(CommandSender::class) != true)
|
||||
) {
|
||||
throw IllegalParameterException("First parameter (receiver for kotlin) for sub commend " + function.name + " from " + this.primaryName + " should be <out CommandSender>")
|
||||
}
|
||||
}
|
||||
|
||||
val commandName = function.findAnnotation<SubCommand>()!!.name.map {
|
||||
if (!it.isValidSubName()) {
|
||||
error("SubName $it is not valid")
|
||||
}
|
||||
it
|
||||
}.toTypedArray()
|
||||
|
||||
//map parameter
|
||||
val parms = parameter.map {
|
||||
buildUsage.append("/$primaryName ")
|
||||
|
||||
if (it.isVararg) {
|
||||
throw IllegalParameterException("parameter for sub commend " + function.name + " from " + this.primaryName + " should not be var arg")
|
||||
}
|
||||
if (it.isOptional) {
|
||||
throw IllegalParameterException("parameter for sub commend " + function.name + " from " + this.primaryName + " should not be var optional")
|
||||
}
|
||||
|
||||
val argName = it.findAnnotation<Name>()?.name ?: it.name ?: "unknown"
|
||||
buildUsage.append("<").append(argName).append("> ").append(" ")
|
||||
CommandParam(
|
||||
argName,
|
||||
(it.type.classifier as? KClass<*>)
|
||||
?: throw IllegalParameterException("unsolved type reference from param " + it.name + " in " + function.name + " from " + this.primaryName)
|
||||
)
|
||||
}.toTypedArray()
|
||||
|
||||
buildUsage.append(subDescription).append("\n")
|
||||
|
||||
SubCommandDescriptor(
|
||||
commandName,
|
||||
parms,
|
||||
subDescription,
|
||||
overridePermission?.permission?.getInstance() ?: permission,
|
||||
onCommand = block { sender: CommandSender, args: Array<out Any> ->
|
||||
if (notStatic) {
|
||||
function.callSuspend(this, sender, *args) as Boolean
|
||||
} else {
|
||||
function.callSuspend(sender, *args) as Boolean
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}.toTypedArray().also {
|
||||
usage = buildUsage.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun block(block: suspend (CommandSender, Array<out Any>) -> Boolean): suspend (CommandSender, Array<out Any>) -> Boolean {
|
||||
return block
|
||||
}
|
||||
|
||||
@JvmField
|
||||
internal val bakedCommandNameToSubDescriptorArray: Map<Array<String>, SubCommandDescriptor> = kotlin.run {
|
||||
val map = LinkedHashMap<Array<String>, SubCommandDescriptor>(subCommands.size * 2)
|
||||
for (descriptor in subCommands) {
|
||||
for (name in descriptor.bakedSubNames) {
|
||||
map[name] = descriptor
|
||||
}
|
||||
}
|
||||
map.toSortedMap(Comparator { o1, o2 -> o1!!.contentHashCode() - o2!!.contentHashCode() })
|
||||
}
|
||||
|
||||
internal inner class DefaultSubCommandDescriptor(
|
||||
val description: String,
|
||||
val permission: CommandPermission,
|
||||
val onCommand: suspend (sender: CommandSender, rawArgs: Array<out Any>) -> Boolean
|
||||
)
|
||||
|
||||
internal inner class SubCommandDescriptor(
|
||||
val names: Array<String>,
|
||||
val params: Array<CommandParam<*>>,
|
||||
val description: String,
|
||||
val permission: CommandPermission,
|
||||
val onCommand: suspend (sender: CommandSender, parsedArgs: Array<out Any>) -> Boolean
|
||||
) {
|
||||
internal suspend inline fun parseAndExecute(
|
||||
sender: CommandSender,
|
||||
argsWithSubCommandNameNotRemoved: Array<out Any>
|
||||
) {
|
||||
if (!onCommand(sender, parseArgs(sender, argsWithSubCommandNameNotRemoved, names.size))) {
|
||||
sender.sendMessage(usage)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
|
||||
private fun parseArgs(sender: CommandSender, rawArgs: Array<out Any>, offset: Int): Array<out Any> {
|
||||
require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" }
|
||||
|
||||
return Array(this.params.size) { index ->
|
||||
val param = params[index]
|
||||
val rawArg = rawArgs[offset + index]
|
||||
when (rawArg) {
|
||||
is String -> context[param.type]?.parse(rawArg, sender)
|
||||
is SingleMessage -> context[param.type]?.parse(rawArg, sender)
|
||||
else -> throw IllegalArgumentException("Illegal argument type: ${rawArg::class.qualifiedName}")
|
||||
} ?: error("Cannot find a parser for $rawArg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rawArgs 元素类型必须为 [SingleMessage] 或 [String], 且已经经过扁平化处理. 否则抛出异常 [IllegalArgumentException]
|
||||
*/
|
||||
internal fun matchSubCommand(rawArgs: Array<out Any>): SubCommandDescriptor? {
|
||||
val maxCount = rawArgs.size - 1
|
||||
var cur = 0
|
||||
bakedCommandNameToSubDescriptorArray.forEach { (name, descriptor) ->
|
||||
if (name.size != cur) {
|
||||
if (cur++ == maxCount) return null
|
||||
}
|
||||
if (name.contentEqualsOffset(rawArgs, length = cur)) {
|
||||
return descriptor
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract class SimpleCommand(
|
||||
override val owner: CommandOwner,
|
||||
val name: String,
|
||||
val alias: Array<String> = arrayOf()
|
||||
) : Command {
|
||||
abstract suspend fun CommandSender.onCommand(args: List<Any>)
|
||||
}
|
||||
|
||||
abstract class RawCommand(
|
||||
final override val owner: CommandOwner,
|
||||
name: String,
|
||||
alias: Array<String> = arrayOf()
|
||||
) : Command {
|
||||
final override val names: Array<String> = arrayOf(name, *alias)
|
||||
}
|
||||
|
||||
|
||||
private fun <T> Array<T>.contentEqualsOffset(other: Array<out Any>, length: Int): Boolean {
|
||||
repeat(length) { index ->
|
||||
if (other[index].toString() != this[index]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toCharArray()
|
||||
internal fun String.isValidSubName(): Boolean = ILLEGAL_SUB_NAME_CHARS.none { it in this }
|
||||
internal fun String.bakeSubName(): Array<String> = split(' ').filterNot { it.isBlank() }.toTypedArray()
|
||||
|
||||
internal fun Any.flattenCommandComponents(): ArrayList<Any> {
|
||||
val list = ArrayList<Any>()
|
||||
when (this::class.java) { // faster than is
|
||||
String::class.java -> (this as String).splitToSequence(' ').filterNot { it.isBlank() }.forEach { list.add(it) }
|
||||
PlainText::class.java -> (this as PlainText).content.splitToSequence(' ').filterNot { it.isBlank() }
|
||||
.forEach { list.add(it) }
|
||||
SingleMessage::class.java -> list.add(this as SingleMessage)
|
||||
Array<Any>::class.java -> (this as Array<*>).forEach { if (it != null) list.addAll(it.flattenCommandComponents()) }
|
||||
Iterable::class.java -> (this as Iterable<*>).forEach { if (it != null) list.addAll(it.flattenCommandComponents()) }
|
||||
else -> list.add(this.toString())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
||||
findAnnotation<T>() != null
|
||||
|
||||
internal inline fun <T : Any> KClass<out T>.getInstance(): T {
|
||||
return this.objectInstance ?: this.createInstance()
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,8 @@ package net.mamoe.mirai.console.command
|
||||
import kotlinx.atomicfu.locks.withLock
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.console.MiraiConsoleInternal
|
||||
import net.mamoe.mirai.console.command.internal.*
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
@ -50,7 +52,11 @@ abstract class PluginCommandOwner(val plugin: Plugin) : CommandOwner() {
|
||||
*
|
||||
* 由前端实现
|
||||
*/
|
||||
internal abstract class ConsoleCommandOwner : CommandOwner()
|
||||
internal abstract class ConsoleCommandOwner : CommandOwner() {
|
||||
companion object {
|
||||
internal val instance get() = MiraiConsoleInternal.consoleCommandOwner
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已经注册了的属于这个 [CommandOwner] 的指令列表.
|
||||
|
@ -13,18 +13,17 @@ package net.mamoe.mirai.console.command
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.utils.JavaFriendlyAPI
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.MessageEvent
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import org.jetbrains.annotations.Contract
|
||||
|
||||
/**
|
||||
* 指令发送者
|
||||
*
|
||||
* @see AbstractCommandSender 请继承于该抽象类
|
||||
* @see ConsoleCommandSender
|
||||
* @see UserCommandSender
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
interface CommandSender {
|
||||
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.description.CommandArgParser
|
||||
import net.mamoe.mirai.console.command.description.CommandParserContext
|
||||
import net.mamoe.mirai.console.command.description.EmptyCommandParserContext
|
||||
import net.mamoe.mirai.console.command.description.plus
|
||||
import net.mamoe.mirai.console.command.internal.CompositeCommandImpl
|
||||
import net.mamoe.mirai.console.command.internal.isValidSubName
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
/**
|
||||
* 功能最集中的Commend
|
||||
* 支持且只支持有sub的指令
|
||||
* 例:
|
||||
* /mute add
|
||||
* /mute remove
|
||||
* /mute addandremove (sub is case insensitive, lowercase are recommend)
|
||||
* /mute add and remove('add and remove' consider as a sub)
|
||||
*/
|
||||
abstract class CompositeCommand @JvmOverloads constructor(
|
||||
final override val owner: CommandOwner,
|
||||
vararg names: String,
|
||||
description: String = "no description available",
|
||||
final override val permission: CommandPermission = CommandPermission.Default,
|
||||
final override val prefixOptional: Boolean = false,
|
||||
overrideContext: CommandParserContext = EmptyCommandParserContext
|
||||
) : Command, CompositeCommandImpl() {
|
||||
final override val description = description.trimIndent()
|
||||
final override val names: Array<out String> =
|
||||
names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).also { list ->
|
||||
list.firstOrNull { !it.isValidSubName() }?.let {
|
||||
error("Name is not valid: $it")
|
||||
}
|
||||
}.toTypedArray()
|
||||
|
||||
/**
|
||||
* [CommandArgParser] 的环境
|
||||
*/
|
||||
val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
|
||||
|
||||
final override val usage: String get() = super.usage
|
||||
|
||||
/** 指定子指令要求的权限 */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Permission(val permission: KClass<out CommandPermission>)
|
||||
|
||||
/** 标记一个函数为子指令 */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class SubCommand(vararg val name: String)
|
||||
|
||||
/** 指令描述 */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Description(val description: String)
|
||||
|
||||
/** 参数名, 将参与构成 [usage] */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||
annotation class Name(val name: String)
|
||||
|
||||
final override suspend fun onCommand(sender: CommandSender, args: Array<out Any>) {
|
||||
matchSubCommand(args)?.parseAndExecute(sender, args) ?: kotlin.run {
|
||||
defaultSubCommand.onCommand(sender, args)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
abstract class SimpleCommand(
|
||||
override val owner: CommandOwner,
|
||||
override vararg val names: String,
|
||||
override val usage: String,
|
||||
override val description: String,
|
||||
override val permission: CommandPermission = CommandPermission.Default,
|
||||
override val prefixOptional: Boolean = false
|
||||
) : Command {
|
||||
abstract override suspend fun CommandSender.onCommand(args: Array<out Any>)
|
||||
}
|
@ -20,9 +20,11 @@ import kotlin.contracts.contract
|
||||
* this output type of that arg
|
||||
* input is always String
|
||||
*/
|
||||
abstract class CommandArgParser<out T : Any> {
|
||||
abstract fun parse(raw: String, sender: CommandSender): T
|
||||
open fun parse(raw: SingleMessage, sender: CommandSender): T = parse(raw.content, sender)
|
||||
interface CommandArgParser<out T : Any> {
|
||||
fun parse(raw: String, sender: CommandSender): T
|
||||
|
||||
@JvmDefault
|
||||
fun parse(raw: SingleMessage, sender: CommandSender): T = parse(raw.content, sender)
|
||||
}
|
||||
|
||||
fun <T : Any> CommandArgParser<T>.parse(raw: Any, sender: CommandSender): T {
|
||||
@ -61,7 +63,7 @@ inline fun CommandArgParser<*>.checkArgument(
|
||||
@JvmSynthetic
|
||||
inline fun <T : Any> CommandArgParser(
|
||||
crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T
|
||||
): CommandArgParser<T> = object : CommandArgParser<T>() {
|
||||
): CommandArgParser<T> = object : CommandArgParser<T> {
|
||||
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,11 @@
|
||||
package net.mamoe.mirai.console.command.description
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.BotAwareCommandSender
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.MemberCommandSender
|
||||
import net.mamoe.mirai.console.command.UserCommandSender
|
||||
import net.mamoe.mirai.console.command.internal.fuzzySearchMember
|
||||
import net.mamoe.mirai.contact.Friend
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
|
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.console.command.internal
|
||||
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.description.CommandParam
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
import kotlin.reflect.KAnnotatedElement
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.*
|
||||
|
||||
internal abstract class CompositeCommandImpl : Command {
|
||||
@JvmField
|
||||
@Suppress("PropertyName")
|
||||
internal var _usage: String = "<command build failed>"
|
||||
|
||||
override val usage: String // initialized by subCommand reflection
|
||||
get() = _usage
|
||||
|
||||
internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy {
|
||||
DefaultSubCommandDescriptor(
|
||||
"",
|
||||
CommandPermission.Default,
|
||||
onCommand = block { sender: CommandSender, args: Array<out Any> ->
|
||||
false//not supported yet
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
internal val subCommands: Array<SubCommandDescriptor> by lazy {
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
this as CompositeCommand
|
||||
|
||||
val buildUsage = StringBuilder(this.description).append(": \n")
|
||||
|
||||
this::class.declaredFunctions.filter { it.hasAnnotation<CompositeCommand.SubCommand>() }.map { function ->
|
||||
val notStatic = !function.hasAnnotation<JvmStatic>()
|
||||
val overridePermission = function.findAnnotation<CompositeCommand.Permission>()//optional
|
||||
val subDescription =
|
||||
function.findAnnotation<CompositeCommand.Description>()?.description ?: "no description available"
|
||||
|
||||
if ((function.returnType.classifier as? KClass<*>)?.isSubclassOf(Boolean::class) != true) {
|
||||
error("Return Type of SubCommand must be Boolean")
|
||||
}
|
||||
|
||||
val parameters = function.parameters.toMutableList()
|
||||
check(parameters.isNotEmpty()) {
|
||||
"First parameter (receiver for kotlin) for sub commend " + function.name + " from " + this.primaryName + " should be <out CommandSender>"
|
||||
}
|
||||
|
||||
if (notStatic) parameters.removeAt(0) // instance
|
||||
|
||||
(parameters.removeAt(0)).let { receiver ->
|
||||
check(!receiver.isVararg && !((receiver.type.classifier as? KClass<*>).also { print(it) }
|
||||
?.isSubclassOf(CommandSender::class) != true)) {
|
||||
"First parameter (receiver for kotlin) for sub commend " + function.name + " from " + this.primaryName + " should be <out CommandSender>"
|
||||
}
|
||||
}
|
||||
|
||||
val commandName = function.findAnnotation<CompositeCommand.SubCommand>()!!.name.map {
|
||||
if (!it.isValidSubName()) {
|
||||
error("SubName $it is not valid")
|
||||
}
|
||||
it
|
||||
}.toTypedArray()
|
||||
|
||||
//map parameter
|
||||
val params = parameters.map { param ->
|
||||
buildUsage.append("/$primaryName ")
|
||||
|
||||
if (param.isVararg) error("parameter for sub commend " + function.name + " from " + this.primaryName + " should not be var arg")
|
||||
if (param.isOptional) error("parameter for sub commend " + function.name + " from " + this.primaryName + " should not be var optional")
|
||||
|
||||
val argName = param.findAnnotation<CompositeCommand.Name>()?.name ?: param.name ?: "unknown"
|
||||
buildUsage.append("<").append(argName).append("> ").append(" ")
|
||||
CommandParam(
|
||||
argName,
|
||||
(param.type.classifier as? KClass<*>)
|
||||
?: throw IllegalArgumentException("unsolved type reference from param " + param.name + " in " + function.name + " from " + this.primaryName)
|
||||
)
|
||||
}.toTypedArray()
|
||||
|
||||
buildUsage.append(subDescription).append("\n")
|
||||
|
||||
SubCommandDescriptor(
|
||||
commandName,
|
||||
params,
|
||||
subDescription,
|
||||
overridePermission?.permission?.getInstance() ?: permission,
|
||||
onCommand = block { sender: CommandSender, args: Array<out Any> ->
|
||||
if (notStatic) {
|
||||
function.callSuspend(this, sender, *args) as Boolean
|
||||
} else {
|
||||
function.callSuspend(sender, *args) as Boolean
|
||||
}
|
||||
}
|
||||
)
|
||||
}.toTypedArray().also {
|
||||
_usage = buildUsage.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun block(block: suspend (CommandSender, Array<out Any>) -> Boolean): suspend (CommandSender, Array<out Any>) -> Boolean {
|
||||
return block
|
||||
}
|
||||
|
||||
@JvmField
|
||||
internal val bakedCommandNameToSubDescriptorArray: Map<Array<String>, SubCommandDescriptor> = kotlin.run {
|
||||
val map = LinkedHashMap<Array<String>, SubCommandDescriptor>(subCommands.size * 2)
|
||||
for (descriptor in subCommands) {
|
||||
for (name in descriptor.bakedSubNames) {
|
||||
map[name] = descriptor
|
||||
}
|
||||
}
|
||||
map.toSortedMap(Comparator { o1, o2 -> o1!!.contentHashCode() - o2!!.contentHashCode() })
|
||||
}
|
||||
|
||||
internal class DefaultSubCommandDescriptor(
|
||||
val description: String,
|
||||
val permission: CommandPermission,
|
||||
val onCommand: suspend (sender: CommandSender, rawArgs: Array<out Any>) -> Boolean
|
||||
)
|
||||
|
||||
internal inner class SubCommandDescriptor(
|
||||
val names: Array<String>,
|
||||
val params: Array<CommandParam<*>>,
|
||||
val description: String,
|
||||
val permission: CommandPermission,
|
||||
val onCommand: suspend (sender: CommandSender, parsedArgs: Array<out Any>) -> Boolean
|
||||
) {
|
||||
internal suspend inline fun parseAndExecute(
|
||||
sender: CommandSender,
|
||||
argsWithSubCommandNameNotRemoved: Array<out Any>
|
||||
) {
|
||||
if (!onCommand(sender, parseArgs(sender, argsWithSubCommandNameNotRemoved, names.size))) {
|
||||
sender.sendMessage(usage)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
|
||||
private fun parseArgs(sender: CommandSender, rawArgs: Array<out Any>, offset: Int): Array<out Any> {
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
this as CompositeCommand
|
||||
require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" }
|
||||
|
||||
return Array(this.params.size) { index ->
|
||||
val param = params[index]
|
||||
val rawArg = rawArgs[offset + index]
|
||||
when (rawArg) {
|
||||
is String -> context[param.type]?.parse(rawArg, sender)
|
||||
is SingleMessage -> context[param.type]?.parse(rawArg, sender)
|
||||
else -> throw IllegalArgumentException("Illegal argument type: ${rawArg::class.qualifiedName}")
|
||||
} ?: error("Cannot find a parser for $rawArg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rawArgs 元素类型必须为 [SingleMessage] 或 [String], 且已经经过扁平化处理. 否则抛出异常 [IllegalArgumentException]
|
||||
*/
|
||||
internal fun matchSubCommand(rawArgs: Array<out Any>): SubCommandDescriptor? {
|
||||
val maxCount = rawArgs.size - 1
|
||||
var cur = 0
|
||||
bakedCommandNameToSubDescriptorArray.forEach { (name, descriptor) ->
|
||||
if (name.size != cur) {
|
||||
if (cur++ == maxCount) return null
|
||||
}
|
||||
if (name.contentEqualsOffset(rawArgs, length = cur)) {
|
||||
return descriptor
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
internal fun <T> Array<T>.contentEqualsOffset(other: Array<out Any>, length: Int): Boolean {
|
||||
repeat(length) { index ->
|
||||
if (other[index].toString() != this[index]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toCharArray()
|
||||
internal fun String.isValidSubName(): Boolean = ILLEGAL_SUB_NAME_CHARS.none { it in this }
|
||||
internal fun String.bakeSubName(): Array<String> = split(' ').filterNot { it.isBlank() }.toTypedArray()
|
||||
|
||||
internal fun Any.flattenCommandComponents(): ArrayList<Any> {
|
||||
val list = ArrayList<Any>()
|
||||
when (this::class.java) { // faster than is
|
||||
String::class.java -> (this as String).splitToSequence(' ').filterNot { it.isBlank() }.forEach { list.add(it) }
|
||||
PlainText::class.java -> (this as PlainText).content.splitToSequence(' ').filterNot { it.isBlank() }
|
||||
.forEach { list.add(it) }
|
||||
SingleMessage::class.java -> list.add(this as SingleMessage)
|
||||
Array<Any>::class.java -> (this as Array<*>).forEach { if (it != null) list.addAll(it.flattenCommandComponents()) }
|
||||
Iterable::class.java -> (this as Iterable<*>).forEach { if (it != null) list.addAll(it.flattenCommandComponents()) }
|
||||
else -> list.add(this.toString())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
||||
findAnnotation<T>() != null
|
||||
|
||||
internal inline fun <T : Any> KClass<out T>.getInstance(): T {
|
||||
return this.objectInstance ?: this.createInstance()
|
||||
}
|
@ -7,8 +7,9 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
package net.mamoe.mirai.console.command.internal
|
||||
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
@ -51,7 +52,9 @@ internal object InternalCommandManager {
|
||||
*/
|
||||
internal fun matchCommand(rawCommand: String): Command? {
|
||||
if (rawCommand.startsWith(COMMAND_PREFIX)) {
|
||||
return requiredPrefixCommandMap[rawCommand.substringAfter(COMMAND_PREFIX)]
|
||||
return requiredPrefixCommandMap[rawCommand.substringAfter(
|
||||
COMMAND_PREFIX
|
||||
)]
|
||||
}
|
||||
return optionalPrefixCommandMap[rawCommand]
|
||||
}
|
||||
@ -170,10 +173,16 @@ internal suspend inline fun CommandSender.executeCommandInternal(
|
||||
messages: Any,
|
||||
commandName: String
|
||||
): Command? {
|
||||
val command = InternalCommandManager.matchCommand(commandName) ?: return null
|
||||
val command = InternalCommandManager.matchCommand(
|
||||
commandName
|
||||
) ?: return null
|
||||
|
||||
if (!command.testPermission(this)) {
|
||||
throw CommandExecutionException(command, commandName, CommandPermissionDeniedException(command))
|
||||
throw CommandExecutionException(
|
||||
command,
|
||||
commandName,
|
||||
CommandPermissionDeniedException(command)
|
||||
)
|
||||
}
|
||||
|
||||
kotlin.runCatching {
|
@ -9,16 +9,12 @@
|
||||
|
||||
package net.mamoe.mirai.console.event
|
||||
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.event.AbstractEvent
|
||||
import net.mamoe.mirai.event.CancellableEvent
|
||||
|
||||
/*
|
||||
data class CommandExecutionEvent( // TODO: 2020/6/26 impl CommandExecutionEvent
|
||||
val sender: CommandSender,
|
||||
val command: Command,
|
||||
val rawArgs: Array<Any>
|
||||
) : AbstractEvent(), CancellableEvent, ConsoleEvent {
|
||||
) : CancellableEvent, ConsoleEvent, AbstractEvent() {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
@ -39,3 +35,4 @@ data class CommandExecutionEvent( // TODO: 2020/6/26 impl CommandExecutionEvent
|
||||
return result
|
||||
}
|
||||
}
|
||||
*/
|
@ -27,6 +27,9 @@ import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
internal val <T> T.job: Job where T : CoroutineScope, T : Plugin get() = this.coroutineContext[Job]!!
|
||||
|
||||
/**
|
||||
* Hides implementations from [JvmPlugin]
|
||||
*/
|
||||
@PublishedApi
|
||||
internal abstract class JvmPluginImpl(
|
||||
parentCoroutineContext: CoroutineContext
|
||||
|
@ -25,4 +25,6 @@ abstract class AbstractJvmPlugin @JvmOverloads constructor(
|
||||
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
|
||||
) : JvmPlugin, JvmPluginImpl(parentCoroutineContext) {
|
||||
// TODO: 2020/6/24 添加 PluginSetting 继承 Setting, 实现 onValueChanged 并绑定自动保存.
|
||||
|
||||
abstract class PluginSetting
|
||||
}
|
Loading…
Reference in New Issue
Block a user