mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-13 06:30:13 +08:00
Merge remote-tracking branch 'origin/reborn' into reborn
# Conflicts: # backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt # backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt
This commit is contained in:
commit
80dd007c9b
@ -1,4 +1,4 @@
|
||||
package net.mamoe.mirai.console.events;
|
||||
package net.mamoe.mirai.console.event;
|
||||
|
||||
import net.mamoe.mirai.console.plugins.PluginBase;
|
||||
import net.mamoe.mirai.event.Event;
|
@ -7,7 +7,7 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.events;
|
||||
package net.mamoe.mirai.console.event;
|
||||
|
||||
import net.mamoe.mirai.event.Event;
|
||||
import org.jetbrains.annotations.NotNull;
|
@ -11,12 +11,17 @@
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandDescriptor.SubCommandDescriptor
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.console.command.description.CommandParam
|
||||
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.plugins.MyArg
|
||||
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.KProperty
|
||||
|
||||
internal const val FOR_BINARY_COMPATIBILITY = "for binary compatibility"
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
|
||||
/**
|
||||
* 指令
|
||||
@ -24,20 +29,15 @@ internal const val FOR_BINARY_COMPATIBILITY = "for binary compatibility"
|
||||
* @see register 注册这个指令
|
||||
*/
|
||||
interface Command {
|
||||
val descriptor: CommandDescriptor
|
||||
/**
|
||||
* Permission of the command
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Permission(val permission:KClass<*>)
|
||||
val names: Array<out String>
|
||||
val usage: String
|
||||
val description: String
|
||||
val permission: CommandPermission
|
||||
val prefixOptional: Boolean
|
||||
|
||||
/**
|
||||
* If a command is prefix optional
|
||||
* e.g
|
||||
* mute work as (/mute) if prefix optional or vise versa
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class PrefixOptional()
|
||||
val owner: CommandOwner
|
||||
|
||||
suspend fun onCommand(sender: CommandSender, args: Array<out Any>)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,120 +49,188 @@ interface Command {
|
||||
* /mute addandremove (sub is case insensitive, lowercase are recommend)
|
||||
* /mute add and remove('add and remove' consider as a sub)
|
||||
*/
|
||||
abstract class CompositeCommand(val name:String, val alias:Array<String> = arrayOf()):Command{
|
||||
/**
|
||||
* 你应当使用 @SubCommand 来注册 sub 指令
|
||||
*/
|
||||
abstract class CompositeCommand @JvmOverloads constructor(
|
||||
override val owner: CommandOwner,
|
||||
vararg names: String,
|
||||
override val description: String = "",
|
||||
override val permission: CommandPermission = CommandPermission.Default,
|
||||
override val prefixOptional: Boolean = false,
|
||||
overrideContext: CommandParserContext = EmptyCommandParserContext
|
||||
) : Command {
|
||||
override val names: Array<out String> =
|
||||
names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).toTypedArray()
|
||||
val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
|
||||
override val usage: String by lazy { TODO() }
|
||||
|
||||
/** 指定子指令要求的权限 */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class SubCommand(val name:String)
|
||||
annotation class Permission(val permission: KClass<out CommandPermission>)
|
||||
|
||||
|
||||
/**
|
||||
* Usage of the sub command
|
||||
* you should not include arg names, which will be insert automatically
|
||||
*/
|
||||
/** 标记一个函数为子指令 */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Usage(val usage:String)
|
||||
annotation class SubCommand(val name: String)
|
||||
|
||||
/**
|
||||
* name of the parameter
|
||||
*
|
||||
* by default available
|
||||
*
|
||||
*/
|
||||
/** 指令描述 */
|
||||
@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)
|
||||
annotation class Name(val name: String)
|
||||
|
||||
|
||||
override val descriptor: CommandDescriptor by lazy {
|
||||
CommandDescriptor()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract class SimpleCommand(val name: String, val alias: Array<String> = arrayOf()
|
||||
):Command{
|
||||
override val descriptor: CommandDescriptor by lazy {
|
||||
CommandDescriptor()
|
||||
final override suspend fun onCommand(sender: CommandSender, args: Array<out Any>) {
|
||||
matchSubCommand(args)?.parseAndExecute(sender, args) ?: kotlin.run {
|
||||
defaultSubCommand.onCommand(sender, args)
|
||||
}
|
||||
subCommands
|
||||
}
|
||||
|
||||
/**
|
||||
* 你必须实现onCommand方法
|
||||
*/
|
||||
}
|
||||
|
||||
abstract class RawCommand(name:String, alias: Array<String> = arrayOf()):Command{
|
||||
override val descriptor: CommandDescriptor by lazy {
|
||||
CommandDescriptor()
|
||||
internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy {
|
||||
DefaultSubCommandDescriptor(
|
||||
"",
|
||||
CommandPermission.Default,
|
||||
onCommand = block { sender: CommandSender, args: Array<out Any> ->
|
||||
println("default finally got args: ${args.joinToString()}")
|
||||
true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
internal val subCommands: Array<SubCommandDescriptor> by lazy {
|
||||
this::class.declaredFunctions.filter { it.hasAnnotation<SubCommand>() }.map { function ->
|
||||
SubCommandDescriptor(
|
||||
arrayOf(function.name),
|
||||
arrayOf(CommandParam("p", MyArg::class)),
|
||||
"",
|
||||
CommandPermission.Default,
|
||||
onCommand = block { sender: CommandSender, args: Array<out Any> ->
|
||||
println("subname finally gor args: ${args.joinToString()}")
|
||||
true
|
||||
}
|
||||
)
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
@Command.Permission(CommandPermission.Default::class)
|
||||
abstract fun onCommand(list: List<Message>)
|
||||
private fun block(block: suspend (CommandSender, Array<out Any>) -> Boolean): suspend (CommandSender, Array<out Any>) -> Boolean {
|
||||
return block
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
/**
|
||||
* 解析完成的指令实际参数列表. 参数顺序与 [Command.descriptor] 的 [CommandDescriptor.params] 相同.
|
||||
*/
|
||||
class CommandArgs private constructor(
|
||||
@JvmField
|
||||
internal val values: List<Any>,
|
||||
private val fromCommand: SubCommandDescriptor
|
||||
) : List<Any> by values {
|
||||
/**
|
||||
* 获取第一个类型为 [R] 的参数
|
||||
*/
|
||||
@JvmSynthetic
|
||||
inline fun <reified R> getReified(): R {
|
||||
for (value in this) {
|
||||
if (value is R) {
|
||||
return value
|
||||
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
|
||||
}
|
||||
}
|
||||
error("Cannot find argument typed ${R::class.qualifiedName}")
|
||||
map.toSortedMap(Comparator { o1, o2 -> o1!!.contentHashCode() - o2!!.contentHashCode() })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取名称为 [name] 的参数.
|
||||
*
|
||||
* 若 [name] 为 `null` 则获取第一个匿名参数
|
||||
* @throws NoSuchElementException 找不到这个名称的参数时抛出
|
||||
*/
|
||||
operator fun get(name: String?): Any {
|
||||
val index = fromCommand.params.indexOfFirst { it.name == name }
|
||||
if (index == -1) {
|
||||
throw NoSuchElementException("Cannot find argument named $name")
|
||||
}
|
||||
return values[index]
|
||||
}
|
||||
internal inner class DefaultSubCommandDescriptor(
|
||||
val description: String,
|
||||
val permission: CommandPermission,
|
||||
val onCommand: suspend (sender: CommandSender, rawArgs: Array<out Any>) -> Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
* 获取名称为 [name] 的参数. 并强转为 [R].
|
||||
*
|
||||
* 若 [name] 为 `null` 则获取第一个匿名参数
|
||||
* @throws IllegalStateException 无法强转时抛出
|
||||
*/
|
||||
fun <R> getAs(name: String?): R {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return this[name] as? R ?: error("Argument $name has a type $")
|
||||
}
|
||||
|
||||
/** 获取第一个类型为 [R] 的参数并提供委托 */
|
||||
inline operator fun <reified R : Any> getValue(thisRef: Any?, property: KProperty<*>): R = getReified()
|
||||
|
||||
companion object {
|
||||
fun parseFrom(command: SubCommandDescriptor, sender: CommandSender, rawArgs: List<Any>): CommandArgs {
|
||||
val params = command.params
|
||||
|
||||
require(rawArgs.size >= params.size) { "No enough rawArgs: required ${params.size}, found only ${rawArgs.size}" }
|
||||
|
||||
command.params.asSequence().zip(rawArgs.asSequence()).map { (commandParam, any) ->
|
||||
command.parserFor(commandParam)?.parse(any, sender)
|
||||
?: error("Could not find a parser for param named ${commandParam.name}, typed ${commandParam.type.qualifiedName}")
|
||||
}.toList().let { bakedArgs ->
|
||||
return CommandArgs(bakedArgs, command)
|
||||
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
|
||||
|
@ -1,97 +0,0 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
|
||||
/**
|
||||
* 指令描述. 包含名称, 权限要求, 参数解析器环境, 参数列表.
|
||||
*
|
||||
* 这是指令系统较低级的 API. 大部分情况下请使用 [Command]
|
||||
*/
|
||||
class CommandDescriptor(
|
||||
val names: Array<String> = arrayOf(),
|
||||
|
||||
val subCommands: List<SubCommandDescriptor>,
|
||||
|
||||
val prefixOptional: Boolean = false,
|
||||
/** 覆盖内建的指令参数解析器环境. */
|
||||
overrideContext: CommandParserContext = CommandParserContext.Empty
|
||||
) {
|
||||
/** 子指令描述 */
|
||||
inner class SubCommandDescriptor(
|
||||
/** 子指令名, 如 "/mute group add" 中的 "group add". 当表示默认指令时为父指令名. 包含别名*/
|
||||
val names: Array<String> = arrayOf(),
|
||||
/** 用法说明 */
|
||||
val usage: String,
|
||||
/** 指令参数列表, 有顺序. */
|
||||
val params: List<CommandParam<*>>,
|
||||
/** 指令说明 */
|
||||
val description: String,
|
||||
/**
|
||||
* 指令权限
|
||||
* @see CommandPermission.or 要求其中一个权限
|
||||
* @see CommandPermission.and 同时要求两个权限
|
||||
*/
|
||||
val permission: CommandPermission = CommandPermission.Default,
|
||||
/** 指令执行器 */
|
||||
val onCommand: suspend (sender: CommandSender, args: CommandArgs) -> Boolean
|
||||
) {
|
||||
init {
|
||||
names.forEach {subName ->
|
||||
require(!(subName.startsWith(' ') || subName.endsWith(' '))) { "subName mustn't start or end with a caret" }
|
||||
require(subName.isValidSubName()) { "subName mustn't contain any of $ILLEGAL_SUB_NAME_CHARS" }
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
|
||||
internal inline val parent: CommandDescriptor get() = this@CommandDescriptor
|
||||
}
|
||||
|
||||
/**
|
||||
* 指令参数解析器环境.
|
||||
*/
|
||||
val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
|
||||
}
|
||||
|
||||
internal val CommandDescriptor.base: CommandDescriptor.SubCommandDescriptor get() = subCommands[0]
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
/**
|
||||
* 检查指令参数数量是否足够, 类型是否匹配.
|
||||
* @throws IllegalArgumentException
|
||||
*/
|
||||
fun CommandDescriptor.SubCommandDescriptor.checkArgs(args: CommandArgs) {
|
||||
require(args.size >= this.params.size) { "No enough args. Required ${params.size}, but given ${args.size}" }
|
||||
params.forEachIndexed { index, commandParam ->
|
||||
require(commandParam.type.isInstance(args[index])) {
|
||||
"Illegal arg #$index, required ${commandParam.type.qualifiedName}, but given ${args[index]::class.qualifiedName}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Any.flattenCommandComponents(): List<String> {
|
||||
val list = ArrayList<String>()
|
||||
when (this) {
|
||||
is String -> list.addAll(split(' ').filterNot { it.isBlank() })
|
||||
is PlainText -> list.addAll(content.flattenCommandComponents())
|
||||
is SingleMessage -> list.add(this.toString())
|
||||
is MessageChain -> this.asSequence().forEach { list.addAll(it.flattenCommandComponents()) }
|
||||
else -> throw IllegalArgumentException("Illegal component: $this")
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
internal fun Any.checkFullName(errorHint: String): Array<String> {
|
||||
return flattenCommandComponents().toList().also {
|
||||
require(it.isNotEmpty()) { "$errorHint must not be empty" }
|
||||
}.toTypedArray()
|
||||
}
|
@ -7,21 +7,25 @@ import kotlinx.atomicfu.locks.withLock
|
||||
import net.mamoe.mirai.console.plugins.PluginBase
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.reflect.KClass
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
|
||||
sealed class CommandOwner
|
||||
|
||||
object TestCommandOwner : CommandOwner()
|
||||
|
||||
abstract class PluginCommandOwner(plugin: PluginBase) : CommandOwner()
|
||||
|
||||
// 由前端实现
|
||||
internal abstract class ConsoleCommandOwner : CommandOwner()
|
||||
|
||||
/**
|
||||
* 获取已经注册了的指令列表
|
||||
*/
|
||||
val CommandOwner.registeredCommands: List<Command> get() = InternalCommandManager.registeredCommands.filter { it.owner == this }
|
||||
|
||||
@get:JvmName("getCommandPrefix")
|
||||
val CommandPrefix: String get() = InternalCommandManager.COMMAND_PREFIX
|
||||
val CommandPrefix: String
|
||||
get() = InternalCommandManager.COMMAND_PREFIX
|
||||
|
||||
fun CommandOwner.unregisterAllCommands() {
|
||||
for (registeredCommand in registeredCommands) {
|
||||
@ -33,136 +37,65 @@ fun CommandOwner.unregisterAllCommands() {
|
||||
* 注册一个指令. 若此指令已经注册或有已经注册的指令与 [SubCommandDescriptor] 重名, 返回 `false`
|
||||
*/
|
||||
fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock {
|
||||
if (findDuplicate() != null) {
|
||||
return false
|
||||
}
|
||||
if (findDuplicate() != null) return false
|
||||
InternalCommandManager.registeredCommands.add(this@register)
|
||||
if (this.prefixOptional) {
|
||||
for (name in this.names) {
|
||||
InternalCommandManager.optionalPrefixCommandMap[name] = this
|
||||
}
|
||||
} else {
|
||||
for (name in this.names) {
|
||||
InternalCommandManager.requiredPrefixCommandMap[name] = this
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找是否有重名的指令. 返回重名的指令.
|
||||
*/
|
||||
fun Command.findDuplicate(): Command? {
|
||||
return InternalCommandManager.registeredCommands.firstOrNull {
|
||||
it.descriptor.base.bakedSubNames intersects this.descriptor.base.bakedSubNames
|
||||
}
|
||||
}
|
||||
|
||||
private infix fun <T> Array<T>.intersects(other: Array<T>): Boolean {
|
||||
val max = this.size.coerceAtMost(other.size)
|
||||
for (i in 0 until max) {
|
||||
if (this[i] == other[i]) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
fun Command.findDuplicate(): Command? =
|
||||
InternalCommandManager.registeredCommands.firstOrNull { it.names intersects this.names }
|
||||
|
||||
/**
|
||||
* 取消注册这个指令. 若指令未注册, 返回 `false`
|
||||
*/
|
||||
fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
|
||||
return InternalCommandManager.registeredCommands.remove(this)
|
||||
InternalCommandManager.registeredCommands.remove(this)
|
||||
}
|
||||
|
||||
//// executing
|
||||
|
||||
/**
|
||||
* 解析并执行一个指令
|
||||
* @param args 接受 [String] 或 [Message]
|
||||
*
|
||||
* @param messages 接受 [String] 或 [Message], 其他对象将会被 [Any.toString]
|
||||
* @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
suspend fun CommandSender.executeCommand(vararg args: Any): Boolean {
|
||||
val command = InternalCommandManager.matchCommand(args[0].toString()) ?: return false
|
||||
|
||||
TODO()
|
||||
//return args.flattenCommandComponents().executeCommand(this)
|
||||
suspend fun CommandSender.executeCommand(vararg messages: Any): Boolean {
|
||||
if (messages.isEmpty()) return false
|
||||
return executeCommandInternal(
|
||||
messages,
|
||||
messages[0].let { if (it is SingleMessage) it.toString() else it.toString().substringBefore(' ') })
|
||||
}
|
||||
|
||||
internal inline fun <reified T> List<T>.dropToTypedArray(n: Int): Array<T> = Array(size - n) { this[n + it] }
|
||||
|
||||
/**
|
||||
* 解析并执行一个指令
|
||||
* @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配
|
||||
*/
|
||||
suspend fun MessageChain.executeAsCommand(sender: CommandSender): Boolean {
|
||||
TODO()
|
||||
/// return this.flattenCommandComponents().executeCommand(sender)
|
||||
suspend fun CommandSender.executeCommand(message: MessageChain): Boolean {
|
||||
if (message.isEmpty()) return false
|
||||
return executeCommandInternal(message, message[0].toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指令参数并直接执行一个指令.
|
||||
*/
|
||||
suspend inline fun CommandSender.execute(command: CommandDescriptor.SubCommandDescriptor, args: CommandArgs): Boolean {
|
||||
command.checkArgs(args)
|
||||
return command.onCommand(this@execute, args)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指令参数并直接执行一个指令.
|
||||
*/
|
||||
suspend inline fun Command.execute(sender: CommandSender, args: CommandArgs): Boolean =
|
||||
TODO()
|
||||
//sender.execute(this, args)
|
||||
|
||||
/**
|
||||
* 核心执行指令
|
||||
*/
|
||||
internal suspend fun List<Any>.executeCommand(origin: String, sender: CommandSender): Boolean {
|
||||
if (this.isEmpty()) return false
|
||||
val command = InternalCommandManager.matchCommand(origin) ?: return false
|
||||
TODO()
|
||||
}
|
||||
|
||||
internal infix fun Array<String>.matchesBeginning(list: List<Any>): Boolean {
|
||||
this.forEachIndexed { index, any ->
|
||||
if (list[index] != any) return false
|
||||
}
|
||||
internal suspend inline fun CommandSender.executeCommandInternal(
|
||||
messages: Any,
|
||||
commandName: String
|
||||
): Boolean {
|
||||
val command = InternalCommandManager.matchCommand(commandName) ?: return false
|
||||
val rawInput = messages.flattenCommandComponents()
|
||||
command.onCommand(this, rawInput.dropToTypedArray(1))
|
||||
return true
|
||||
}
|
||||
|
||||
internal object InternalCommandManager {
|
||||
const val COMMAND_PREFIX = "/"
|
||||
|
||||
/**
|
||||
* 全部注册的指令
|
||||
* /mute -> MuteCommand
|
||||
* /jinyan -> MuteCommand
|
||||
*/
|
||||
@JvmField
|
||||
internal val registeredCommands: MutableMap<String, Command> = mutableMapOf()
|
||||
/**
|
||||
* Command name of commands that are prefix optional
|
||||
* mute -> MuteCommand
|
||||
*/
|
||||
private val quickMatchCommands: MutableMap<String, Command> = mutableMapOf()
|
||||
|
||||
@JvmField
|
||||
internal val modifyLock = ReentrantLock()
|
||||
|
||||
|
||||
/**
|
||||
* 从原始的command中解析出Command对象
|
||||
*/
|
||||
internal fun matchCommand(rawCommand: String): Command? {
|
||||
if(!rawCommand.startsWith('/')){
|
||||
return quickMatchCommands[rawCommand
|
||||
.substringBefore(' ')
|
||||
.trim()
|
||||
]
|
||||
}
|
||||
return registeredCommands[rawCommand
|
||||
.substringBefore(' ')
|
||||
.trim()
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 从解析好的第一个字节来获取Command对象
|
||||
*/
|
||||
internal fun findCommand(name: String): Command? {
|
||||
if(!name.startsWith('/')){
|
||||
return quickMatchCommands[name]
|
||||
}
|
||||
return registeredCommands[name]
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -28,6 +28,7 @@ interface CommandPermission {
|
||||
*/
|
||||
fun CommandSender.hasPermission(): Boolean
|
||||
|
||||
|
||||
/**
|
||||
* 满足两个权限其中一个即可使用指令
|
||||
*/ // no extension for Java
|
||||
@ -38,6 +39,7 @@ interface CommandPermission {
|
||||
*/ // no extension for Java
|
||||
infix fun and(another: CommandPermission): CommandPermission = AndCommandPermission(this, another)
|
||||
|
||||
|
||||
/**
|
||||
* 任何人都可以使用这个指令
|
||||
*/
|
||||
@ -53,7 +55,7 @@ interface CommandPermission {
|
||||
}
|
||||
|
||||
/**
|
||||
* 来自任何 [Bot] 的任何一个群管理员或群主都可以使用这个指令
|
||||
* 来自任何 [Bot] 的任何一个管理员或群主都可以使用这个指令
|
||||
*/
|
||||
object Operator : CommandPermission {
|
||||
override fun CommandSender.hasPermission(): Boolean {
|
||||
@ -95,10 +97,7 @@ interface CommandPermission {
|
||||
override fun CommandSender.hasPermission(): Boolean = false
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val Default: CommandPermission = Manager or Console
|
||||
}
|
||||
object Default : CommandPermission by (Manager or Console)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,7 +114,9 @@ inline fun AnonymousCommandPermission(crossinline block: CommandSender.() -> Boo
|
||||
inline fun CommandSender.hasPermission(permission: CommandPermission): Boolean =
|
||||
permission.run { this@hasPermission.hasPermission() }
|
||||
|
||||
inline fun CommandPermission.hasPermission(sender: CommandSender): Boolean = this.run { sender.hasPermission() }
|
||||
|
||||
inline fun CommandPermission.testPermission(sender: CommandSender): Boolean = this.run { sender.hasPermission() }
|
||||
|
||||
|
||||
internal class OrCommandPermission(
|
||||
private val first: CommandPermission,
|
||||
|
@ -0,0 +1,63 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.console.command.description
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
import net.mamoe.mirai.message.data.content
|
||||
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)
|
||||
}
|
||||
|
||||
fun <T : Any> CommandArgParser<T>.parse(raw: Any, sender: CommandSender): T {
|
||||
contract {
|
||||
returns() implies (raw is String || raw is SingleMessage)
|
||||
}
|
||||
|
||||
return when (raw) {
|
||||
is String -> parse(raw, sender)
|
||||
is SingleMessage -> parse(raw, sender)
|
||||
else -> throw IllegalArgumentException("Illegal raw argument type: ${raw::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@JvmSynthetic
|
||||
inline fun CommandArgParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing {
|
||||
throw ParserException(message, cause)
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
inline fun CommandArgParser<*>.checkArgument(
|
||||
condition: Boolean,
|
||||
crossinline message: () -> String = { "Check failed." }
|
||||
) {
|
||||
contract {
|
||||
returns() implies condition
|
||||
}
|
||||
if (!condition) illegalArgument(message())
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建匿名 [CommandArgParser]
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
@JvmSynthetic
|
||||
inline fun <T : Any> CommandArgParser(
|
||||
crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T
|
||||
): CommandArgParser<T> = object : CommandArgParser<T>() {
|
||||
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范.
|
||||
*/
|
||||
class ParserException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
|
@ -1,8 +1,19 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
package net.mamoe.mirai.console.command.description
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
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.utils.fuzzySearchMember
|
||||
import net.mamoe.mirai.contact.Friend
|
||||
import net.mamoe.mirai.contact.Group
|
||||
@ -10,62 +21,6 @@ import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.message.data.At
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
import net.mamoe.mirai.message.data.content
|
||||
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)
|
||||
}
|
||||
|
||||
fun <T : Any> CommandArgParser<T>.parse(raw: Any, sender: CommandSender): T {
|
||||
contract {
|
||||
returns() implies (raw is String || raw is SingleMessage)
|
||||
}
|
||||
|
||||
return when (raw) {
|
||||
is String -> parse(raw, sender)
|
||||
is SingleMessage -> parse(raw, sender)
|
||||
else -> throw IllegalArgumentException("Illegal raw argument type: ${raw::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@JvmSynthetic
|
||||
inline fun CommandArgParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing {
|
||||
throw ParserException(message, cause)
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
inline fun CommandArgParser<*>.checkArgument(
|
||||
condition: Boolean,
|
||||
crossinline message: () -> String = { "Check failed." }
|
||||
) {
|
||||
contract {
|
||||
returns() implies condition
|
||||
}
|
||||
if (!condition) illegalArgument(message())
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建匿名 [CommandArgParser]
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
@JvmSynthetic
|
||||
inline fun <T : Any> CommandArgParser(
|
||||
crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T
|
||||
): CommandArgParser<T> = object : CommandArgParser<T>() {
|
||||
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范.
|
||||
*/
|
||||
class ParserException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
|
||||
|
||||
|
||||
object IntArgParser : CommandArgParser<Int>() {
|
||||
@ -99,7 +54,10 @@ object FloatArgParser : CommandArgParser<Float>() {
|
||||
}
|
||||
|
||||
object StringArgParser : CommandArgParser<String>() {
|
||||
override fun parse(raw: String, sender: CommandSender): String = raw
|
||||
override fun parse(raw: String, sender: CommandSender): String {
|
||||
println("STRING PARSER! $raw")
|
||||
return raw
|
||||
}
|
||||
}
|
||||
|
||||
object BooleanArgParser : CommandArgParser<Boolean>() {
|
@ -9,13 +9,15 @@
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
package net.mamoe.mirai.console.command.description
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.command.CommandParserContext.ParserPair
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.description.CommandParserContext.ParserPair
|
||||
import net.mamoe.mirai.contact.Friend
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
@ -53,28 +55,16 @@ interface CommandParserContext {
|
||||
Bot::class with ExistBotArgParser
|
||||
Friend::class with ExistFriendArgParser
|
||||
})
|
||||
|
||||
object Empty : CommandParserContext by CustomCommandParserContext(listOf())
|
||||
}
|
||||
|
||||
fun <T : Any> CommandParserContext.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
|
||||
param.overrideParser ?: this[param.type]
|
||||
|
||||
fun <T : Any> CommandDescriptor.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
|
||||
this.context.parserFor(param)
|
||||
|
||||
fun <T : Any> CommandDescriptor.SubCommandDescriptor.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
|
||||
this.parent.parserFor(param)
|
||||
|
||||
fun <T : Any> Command.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
|
||||
this.descriptor.parserFor(param)
|
||||
object EmptyCommandParserContext : CommandParserContext by CustomCommandParserContext(listOf())
|
||||
|
||||
/**
|
||||
* 合并两个 [CommandParserContext], [replacer] 将会替换 [this] 中重复的 parser.
|
||||
*/
|
||||
operator fun CommandParserContext.plus(replacer: CommandParserContext): CommandParserContext {
|
||||
if (replacer == CommandParserContext.Empty) return this
|
||||
if (this == CommandParserContext.Empty) return replacer
|
||||
if (replacer == EmptyCommandParserContext) return this
|
||||
if (this == EmptyCommandParserContext) return replacer
|
||||
return object : CommandParserContext {
|
||||
override fun <T : Any> get(klass: KClass<out T>): CommandArgParser<T>? = replacer[klass] ?: this@plus[klass]
|
||||
override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList()
|
||||
@ -86,7 +76,7 @@ operator fun CommandParserContext.plus(replacer: CommandParserContext): CommandP
|
||||
*/
|
||||
operator fun CommandParserContext.plus(replacer: List<ParserPair<*>>): CommandParserContext {
|
||||
if (replacer.isEmpty()) return this
|
||||
if (this == CommandParserContext.Empty) return CustomCommandParserContext(replacer)
|
||||
if (this == EmptyCommandParserContext) return CustomCommandParserContext(replacer)
|
||||
return object : CommandParserContext {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : Any> get(klass: KClass<out T>): CommandArgParser<T>? =
|
||||
@ -137,6 +127,9 @@ class CommandParserContextBuilder : MutableList<ParserPair<*>> by mutableListOf(
|
||||
inline infix fun <T : Any> KClass<T>.with(parser: CommandArgParser<T>): ParserPair<*> =
|
||||
ParserPair(this, parser).also { add(it) }
|
||||
|
||||
inline infix fun <reified T : Any> auto(parser: CommandArgParser<T>): ParserPair<*> =
|
||||
ParserPair(T::class, parser).also { add(it) }
|
||||
|
||||
/**
|
||||
* 添加一个指令解析器
|
||||
*/
|
||||
@ -153,6 +146,25 @@ class CommandParserContextBuilder : MutableList<ParserPair<*>> by mutableListOf(
|
||||
inline infix fun <T : Any> KClass<T>.with(
|
||||
crossinline parser: CommandArgParser<T>.(s: String) -> T
|
||||
): ParserPair<*> = ParserPair(this, CommandArgParser { s: String, _: CommandSender -> parser(s) }).also { add(it) }
|
||||
|
||||
/**
|
||||
* 添加一个指令解析器
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@JvmSynthetic
|
||||
inline infix fun <reified T : Any> auto(
|
||||
crossinline parser: CommandArgParser<*>.(s: String) -> T
|
||||
): ParserPair<*> = T::class with CommandArgParser { s: String, _: CommandSender -> parser(s) }
|
||||
|
||||
/**
|
||||
* 添加一个指令解析器
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@JvmSynthetic
|
||||
@LowPriorityInOverloadResolution
|
||||
inline infix fun <reified T : Any> auto(
|
||||
crossinline parser: CommandArgParser<*>.(s: String, sender: CommandSender) -> T
|
||||
): ParserPair<*> = T::class with CommandArgParser(parser)
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,25 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
package net.mamoe.mirai.console.command.description
|
||||
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.CompositeCommand
|
||||
import java.lang.reflect.Parameter
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
internal fun Parameter.toCommandParam(): CommandParam<*> {
|
||||
val name = getAnnotation(CompositeCommand.Name::class.java)
|
||||
return CommandParam(
|
||||
name?.name ?: this.name ?: throw IllegalArgumentException("Cannot construct CommandParam from a unnamed param"),
|
||||
this.type.kotlin
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 指令形式参数.
|
||||
* @see toCommandParam
|
||||
*/
|
||||
data class CommandParam<T : Any>(
|
||||
internal data class CommandParam<T : Any>(
|
||||
/**
|
||||
* 参数名. 不允许重复.
|
||||
*/
|
||||
@ -36,12 +48,3 @@ data class CommandParam<T : Any>(
|
||||
val overrideParser: CommandArgParser<T>? get() = _overrideParser
|
||||
}
|
||||
|
||||
fun <T : Any> CommandParam<T>.parserFrom(command: Command): CommandArgParser<T>? = command.parserFor(this)
|
||||
|
||||
fun <T : Any> CommandParam<T>.parserFrom(descriptor: CommandDescriptor): CommandArgParser<T>? =
|
||||
descriptor.parserFor(this)
|
||||
|
||||
fun <T : Any> CommandParam<T>.parserFrom(descriptor: CommandDescriptor.SubCommandDescriptor): CommandArgParser<T>? =
|
||||
descriptor.parserFor(this)
|
||||
|
||||
fun <T : Any> CommandParam<T>.parserFrom(context: CommandParserContext): CommandArgParser<T>? = context.parserFor(this)
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
|
||||
internal infix fun Array<String>.matchesBeginning(list: List<Any>): Boolean {
|
||||
this.forEachIndexed { index, any ->
|
||||
if (list[index] != any) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
internal object InternalCommandManager {
|
||||
const val COMMAND_PREFIX = "/"
|
||||
|
||||
@JvmField
|
||||
internal val registeredCommands: MutableList<Command> = mutableListOf()
|
||||
|
||||
/**
|
||||
* 全部注册的指令
|
||||
* /mute -> MuteCommand
|
||||
* /jinyan -> MuteCommand
|
||||
*/
|
||||
@JvmField
|
||||
internal val requiredPrefixCommandMap: MutableMap<String, Command> = mutableMapOf()
|
||||
|
||||
/**
|
||||
* Command name of commands that are prefix optional
|
||||
* mute -> MuteCommand
|
||||
*/
|
||||
@JvmField
|
||||
internal val optionalPrefixCommandMap: MutableMap<String, Command> = mutableMapOf()
|
||||
|
||||
@JvmField
|
||||
internal val modifyLock = ReentrantLock()
|
||||
|
||||
|
||||
/**
|
||||
* 从原始的 command 中解析出 Command 对象
|
||||
*/
|
||||
internal fun matchCommand(rawCommand: String): Command? {
|
||||
if (rawCommand.startsWith(COMMAND_PREFIX)) {
|
||||
return requiredPrefixCommandMap[rawCommand.substringAfter(COMMAND_PREFIX)]
|
||||
}
|
||||
return optionalPrefixCommandMap[rawCommand]
|
||||
}
|
||||
}
|
||||
|
||||
internal infix fun <T> Array<out T>.intersects(other: Array<out T>): Boolean {
|
||||
val max = this.size.coerceAtMost(other.size)
|
||||
for (i in 0 until max) {
|
||||
if (this[i] == other[i]) return true
|
||||
}
|
||||
return false
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
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(
|
||||
val sender: CommandSender,
|
||||
val command: Command,
|
||||
val rawArgs: Array<Any>
|
||||
) : AbstractEvent(), CancellableEvent, ConsoleEvent {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as CommandExecutionEvent
|
||||
|
||||
if (sender != other.sender) return false
|
||||
if (command != other.command) return false
|
||||
if (!rawArgs.contentEquals(other.rawArgs)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = sender.hashCode()
|
||||
result = 31 * result + command.hashCode()
|
||||
result = 31 * result + rawArgs.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package net.mamoe.mirai.console.event
|
||||
|
||||
import net.mamoe.mirai.event.Event
|
||||
|
||||
/**
|
||||
* 表示来自 mirai-console 的事件
|
||||
*/
|
||||
interface ConsoleEvent : Event
|
@ -9,7 +9,7 @@
|
||||
|
||||
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
|
||||
package net.mamoe.mirai.console.events
|
||||
package net.mamoe.mirai.console.event
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
@ -14,7 +14,7 @@ package net.mamoe.mirai.console.plugins
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.events.EventListener
|
||||
import net.mamoe.mirai.console.event.EventListener
|
||||
import net.mamoe.mirai.console.scheduler.PluginScheduler
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.io.File
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
object TestCompositeCommand : CompositeCommand(
|
||||
TestCommandOwner,
|
||||
"name1", "name2",
|
||||
description = """
|
||||
desc
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
|
||||
internal class TestComposite {
|
||||
|
||||
@Test
|
||||
fun testRegister() {
|
||||
TestCompositeCommand.register()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user