diff --git a/README.md b/README.md
index 63922d3fd..57f99d123 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,19 @@
+
+
+
# mirai-console
高效率插件支持 QQ 机器人框架, 机器人核心来自 [mirai](https://github.com/mamoe/mirai)
diff --git a/mirai-console/build.gradle.kts b/mirai-console/build.gradle.kts
index 8a1eb1389..7ad3bc229 100644
--- a/mirai-console/build.gradle.kts
+++ b/mirai-console/build.gradle.kts
@@ -21,6 +21,9 @@ kotlin {
languageSettings.useExperimentalAnnotation("kotlin.OptIn")
languageSettings.progressiveMode = true
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
+ languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
+ languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
+ languageSettings.useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
}
}
}
diff --git a/mirai-console/src/main/java/net/mamoe/mirai/console/utils/CommandArgParser.kt b/mirai-console/src/main/java/net/mamoe/mirai/console/utils/CommandArgParser.kt
deleted file mode 100644
index 6365f68b0..000000000
--- a/mirai-console/src/main/java/net/mamoe/mirai/console/utils/CommandArgParser.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-@file:Suppress("NOTHING_TO_INLINE")
-
-package net.mamoe.mirai.console.utils
-
-import net.mamoe.mirai.Bot
-import net.mamoe.mirai.console.command.CommandSender
-import net.mamoe.mirai.console.command.GroupContactCommandSender
-import net.mamoe.mirai.contact.Group
-
-/**
- * this output type of that arg
- * input is always String
- */
-abstract class CommandArgParser {
- abstract fun parse(s: String, sender: CommandSender): T
- protected inline fun parseError(message: String, cause: Throwable? = null): Nothing {
- throw ParserException(message, cause)
- }
-}
-
-@Suppress("FunctionName")
-inline fun CommandArgParser(
- crossinline parser: CommandArgParser.(s: String, sender: CommandSender) -> T
-): CommandArgParser {
- return object : CommandArgParser() {
- override fun parse(s: String, sender: CommandSender): T = parser(s, sender)
- }
-}
-
-/**
- * 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范.
- */
-class ParserException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
-
-inline fun Int.Companion.parser(): CommandArgParser = IntArgParser
-inline fun Long.Companion.parser(): CommandArgParser = LongArgParser
-inline fun Byte.Companion.parser(): CommandArgParser = ByteArgParser
-inline fun Short.Companion.parser(): CommandArgParser = ShortArgParser
-inline fun Float.Companion.parser(): CommandArgParser = FloatArgParser
-inline fun Double.Companion.parser(): CommandArgParser = DoubleArgParser
-
-
-object IntArgParser : CommandArgParser() {
- override fun parse(s: String, sender: CommandSender): Int = s.toIntOrNull() ?: parseError("无法解析 $s 为整数")
-}
-
-object LongArgParser : CommandArgParser() {
- override fun parse(s: String, sender: CommandSender): Long = s.toLongOrNull() ?: parseError("无法解析 $s 为长整数")
-}
-
-object ShortArgParser : CommandArgParser() {
- override fun parse(s: String, sender: CommandSender): Short = s.toShortOrNull() ?: parseError("无法解析 $s 为短整数")
-}
-
-object ByteArgParser : CommandArgParser() {
- override fun parse(s: String, sender: CommandSender): Byte = s.toByteOrNull() ?: parseError("无法解析 $s 为字节")
-}
-
-object DoubleArgParser : CommandArgParser() {
- override fun parse(s: String, sender: CommandSender): Double =
- s.toDoubleOrNull() ?: parseError("无法解析 $s 为小数")
-}
-
-object FloatArgParser : CommandArgParser() {
- override fun parse(s: String, sender: CommandSender): Float =
- s.toFloatOrNull() ?: parseError("无法解析 $s 为小数")
-}
-
-object StringArgParser : CommandArgParser() {
- override fun parse(s: String, sender: CommandSender): String = s
-}
-
-/**
- * require a bot that already login in console
- * input: Bot UIN
- * output: Bot
- * errors: String->Int convert, Bot Not Exist
- */
-object ExistBotArgParser : CommandArgParser() {
- override fun parse(s: String, sender: CommandSender): Bot {
- val uin = s.toLongOrNull() ?: parseError("无法识别机器人账号 $s")
- return try {
- Bot.getInstance(uin)
- } catch (e: NoSuchElementException) {
- error("无法找到 Bot $uin")
- }
- }
-}
-
-
-object ExistGroupArgParser : CommandArgParser() {
- override fun parse(s: String, sender: CommandSender): Group {
- if ((s == "" || s == "~") && sender is GroupContactCommandSender) {
- return sender.contact as Group
- }
-
- val code = s.toLongOrNull() ?: parseError("无法识别群号码 $s")
- TODO()
- }
-}
diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandArg.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandArg.kt
deleted file mode 100644
index 6c1d4163a..000000000
--- a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandArg.kt
+++ /dev/null
@@ -1,272 +0,0 @@
-package net.mamoe.mirai.console.command
-
-import net.mamoe.mirai.Bot
-import net.mamoe.mirai.console.utils.fuzzySearchMember
-import net.mamoe.mirai.contact.*
-import net.mamoe.mirai.message.data.At
-import net.mamoe.mirai.message.data.SingleMessage
-import net.mamoe.mirai.message.data.content
-import java.lang.NumberFormatException
-
-/**
- * this output type of that arg
- * input is always String
- */
-interface CommandArg{
- operator fun invoke(s:String, commandSender: CommandSender):T = parse(s,commandSender)
-
- operator fun invoke(s:SingleMessage, commandSender: CommandSender):T = parse(s,commandSender)
-
- fun parse(s:String, commandSender: CommandSender):T
-
- fun parse(s:SingleMessage, commandSender: CommandSender):T
-}
-
-
-abstract class CommandArgImpl(): CommandArg {
- override fun parse(s: SingleMessage, commandSender: CommandSender): T = parse(s.content,commandSender)
-}
-
-class IntArg: CommandArgImpl(){
- override fun parse(s: String, commandSender: CommandSender): Int {
- return try{
- s.toInt()
- }catch (e:Exception){
- error("无法识别整数$s")
- }
- }
-}
-
-class LongArg: CommandArgImpl(){
- override fun parse(s: String, commandSender: CommandSender): Long {
- return try{
- s.toLong()
- }catch (e:Exception){
- error("无法识别长整数$s")
- }
- }
-}
-
-class DoubleArg: CommandArgImpl(){
- override fun parse(s: String, commandSender: CommandSender): Double {
- return try{
- s.toDouble()
- }catch (e:Exception){
- error("无法识别小数$s")
- }
- }
-}
-
-class FloatArg: CommandArgImpl(){
- override fun parse(s: String, commandSender: CommandSender): Float{
- return try{
- s.toFloat()
- }catch (e:Exception){
- error("无法识别小数$s")
- }
- }
-}
-
-class BooleanArg: CommandArgImpl(){
- override fun parse(s: String, commandSender: CommandSender): Boolean {
- return s.equals("true",true) || s.equals("yes",true)
- }
-}
-
-class StringArg: CommandArgImpl(){
- override fun parse(s: String, commandSender: CommandSender): String {
- return s
- }
-}
-
-/**
- * require a bot that already login in console
- * input: Bot UIN
- * output: Bot
- * errors: String->Int convert, Bot Not Exist
- */
-class ExistBotArg : CommandArgImpl() {
- override fun parse(s: String, commandSender: CommandSender): Bot {
- val uin = try {
- s.toLong()
- } catch (e: Exception) {
- error("无法识别QQ UIN$s")
- }
- return try {
- Bot.getInstance(uin)
- } catch (e: NoSuchElementException) {
- error("无法找到Bot $uin")
- }
- }
-}
-
-
-class ExistFriendArg: CommandArgImpl(){
- //Bot.friend
- //friend
- //~ = self
- override fun parse(s: String, commandSender: CommandSender): Friend {
- if(s == "~"){
- if(commandSender !is BotAware){
- error("无法解析~作为默认")
- }
- val targetID = when (commandSender) {
- is GroupContactCommandSender -> commandSender.realSender.id
- is ContactCommandSender -> commandSender.contact.id
- else -> error("无法解析~作为默认")
- }
- return try{
- commandSender.bot.friends[targetID]
- }catch (e:NoSuchElementException){
- error("无法解析~作为默认")
- }
- }
- if(commandSender is BotAware){
- return try{
- commandSender.bot.friends[s.toLong()]
- }catch (e:NoSuchElementException){
- error("无法找到" + s + "这个好友")
- }catch (e:NumberFormatException){
- error("无法解析$s")
- }
- }else{
- with(s.split(".")){
- if(this.size != 2){
- error("无法解析$s, 格式应为Bot.Friend")
- }
- return try{
- Bot.getInstance(this[0].toLong()).friends[this[1].toLong()]
- }catch (e:NoSuchElementException){
- error("无法找到好友或Bot")
- }catch (e:NumberFormatException){
- error("无法解析$s")
- }
- }
- }
- }
-
- override fun parse(s: SingleMessage, commandSender: CommandSender): Friend {
- return if(s is At){
- assert(commandSender is GroupContactCommandSender)
- return try {
- (commandSender as BotAware).bot.friends[s.target]
- }catch (e:NoSuchElementException){
- error("At的对象非Bot好友")
- }
- }else{
- error("无法识别Member" + s.content)
- }
- }
-}
-
-class ExistGroupArg: CommandArgImpl(){
- override fun parse(s: String, commandSender: CommandSender): Group {
- //by default
- if ((s == "" || s == "~") && commandSender is GroupContactCommandSender) {
- return commandSender.contact as Group
- }
- //from bot to group
- if (commandSender is BotAware) {
- val code = try {
- s.toLong()
- } catch (e: NoSuchElementException) {
- error("无法识别Group Code$s")
- }
- return try {
- commandSender.bot.getGroup(code)
- } catch (e: NoSuchElementException) {
- error("无法找到Group " + code + " from Bot " + commandSender.bot.id)
- }
- }
- //from console/other
- return with(s.split(".")) {
- if (this.size != 2) {
- error("请使用BotQQ号.群号 来表示Bot的一个群")
- }
- try {
- Bot.getInstance(this[0].toLong()).getGroup(this[1].toLong())
- }catch (e:NoSuchElementException){
- error("无法找到" + this[0] + "的" + this[1] + "群")
- }catch (e:NumberFormatException){
- error("无法识别群号或机器人UIN")
- }
- }
- }
-}
-
-class ExistMemberArg: CommandArgImpl(){
- //后台: Bot.Group.Member[QQ/名片]
- //私聊: Group.Member[QQ/名片]
- //群内: Q号
- //群内: 名片
- override fun parse(s: String, commandSender: CommandSender): Member {
- if(commandSender !is BotAware){
- with(s.split(".")){
- if(this.size < 3){
- error("无法识别Member, 请使用Bot.Group.Member[QQ/名片]的格式")
- }
- val bot = try {
- Bot.getInstance(this[0].toLong())
- }catch (e:NoSuchElementException){
- error("无法找到Bot")
- }catch (e:NumberFormatException){
- error("无法识别Bot")
- }
- val group = try{
- bot.getGroup(this[1].toLong())
- }catch (e:NoSuchElementException){
- error("无法找到Group")
- }catch (e:NumberFormatException){
- error("无法识别Group")
- }
-
- val memberIndex = this.subList(2,this.size).joinToString(".")
- return try{
- group.members[memberIndex.toLong()]
- }catch (ignored:Exception){
- group.fuzzySearchMember(memberIndex)?: error("无法找到成员$memberIndex")
- }
- }
- }else {
- val bot = commandSender.bot
- if(commandSender is GroupContactCommandSender){
- val group = commandSender.contact as Group
- return try {
- group.members[s.toLong()]
- } catch (ignored: Exception) {
- group.fuzzySearchMember(s) ?: error("无法找到成员$s")
- }
- }else {
- with(s.split(".")) {
- if (this.size < 2) {
- error("无法识别Member, 请使用Group.Member[QQ/名片]的格式")
- }
- val group = try {
- bot.getGroup(this[0].toLong())
- } catch (e: NoSuchElementException) {
- error("无法找到Group")
- } catch (e: NumberFormatException) {
- error("无法识别Group")
- }
-
- val memberIndex = this.subList(1, this.size).joinToString(".")
- return try {
- group.members[memberIndex.toLong()]
- } catch (ignored: Exception) {
- group.fuzzySearchMember(memberIndex) ?: error("无法找到成员$memberIndex")
- }
- }
- }
- }
- }
-
- override fun parse(s: SingleMessage, commandSender: CommandSender): Member {
- return if(s is At){
- assert(commandSender is GroupContactCommandSender)
- ((commandSender as GroupContactCommandSender).contact as Group).members[s.target]
- }else{
- error("无法识别Member" + s.content)
- }
- }
-}
-
diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandArgParser.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandArgParser.kt
new file mode 100644
index 000000000..28d45513d
--- /dev/null
+++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandArgParser.kt
@@ -0,0 +1,286 @@
+@file:Suppress("NOTHING_TO_INLINE")
+
+package net.mamoe.mirai.console.command
+
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.console.utils.fuzzySearchMember
+import net.mamoe.mirai.contact.Friend
+import net.mamoe.mirai.contact.Group
+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 {
+ abstract fun parse(s: String, sender: CommandSender): T
+ open fun parse(s: SingleMessage, sender: CommandSender): T = parse(s.content, sender)
+}
+
+@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 CommandArgParser(
+ crossinline parser: CommandArgParser.(s: String, sender: CommandSender) -> T
+): CommandArgParser = object : CommandArgParser() {
+ override fun parse(s: String, sender: CommandSender): T = parser(s, sender)
+}
+
+
+/**
+ * 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范.
+ */
+class ParserException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
+
+
+object IntArgParser : CommandArgParser() {
+ override fun parse(s: String, sender: CommandSender): Int =
+ s.toIntOrNull() ?: illegalArgument("无法解析 $s 为整数")
+}
+
+object LongArgParser : CommandArgParser() {
+ override fun parse(s: String, sender: CommandSender): Long =
+ s.toLongOrNull() ?: illegalArgument("无法解析 $s 为长整数")
+}
+
+object ShortArgParser : CommandArgParser() {
+ override fun parse(s: String, sender: CommandSender): Short =
+ s.toShortOrNull() ?: illegalArgument("无法解析 $s 为短整数")
+}
+
+object ByteArgParser : CommandArgParser() {
+ override fun parse(s: String, sender: CommandSender): Byte =
+ s.toByteOrNull() ?: illegalArgument("无法解析 $s 为字节")
+}
+
+object DoubleArgParser : CommandArgParser() {
+ override fun parse(s: String, sender: CommandSender): Double =
+ s.toDoubleOrNull() ?: illegalArgument("无法解析 $s 为小数")
+}
+
+object FloatArgParser : CommandArgParser() {
+ override fun parse(s: String, sender: CommandSender): Float =
+ s.toFloatOrNull() ?: illegalArgument("无法解析 $s 为小数")
+}
+
+object StringArgParser : CommandArgParser() {
+ override fun parse(s: String, sender: CommandSender): String = s
+}
+
+object BooleanArgParser : CommandArgParser() {
+ override fun parse(s: String, sender: CommandSender): Boolean = s.trim().let { str ->
+ str.equals("true", ignoreCase = true)
+ || str.equals("yes", ignoreCase = true)
+ || str.equals("enabled", ignoreCase = true)
+ }
+}
+
+/**
+ * require a bot that already login in console
+ * input: Bot UIN
+ * output: Bot
+ * errors: String->Int convert, Bot Not Exist
+ */
+object ExistBotArgParser : CommandArgParser() {
+ override fun parse(s: String, sender: CommandSender): Bot {
+ val uin = try {
+ s.toLong()
+ } catch (e: Exception) {
+ error("无法识别QQ UIN$s")
+ }
+ return try {
+ Bot.getInstance(uin)
+ } catch (e: NoSuchElementException) {
+ error("无法找到Bot $uin")
+ }
+ }
+}
+
+object ExistFriendArgParser : CommandArgParser() {
+ //Bot.friend
+ //friend
+ //~ = self
+ override fun parse(s: String, sender: CommandSender): Friend {
+ if (s == "~") {
+ if (sender !is BotAware) {
+ illegalArgument("无法解析~作为默认")
+ }
+ val targetID = when (sender) {
+ is GroupContactCommandSender -> sender.realSender.id
+ is ContactCommandSender -> sender.contact.id
+ else -> illegalArgument("无法解析~作为默认")
+ }
+ return try {
+ sender.bot.friends[targetID]
+ } catch (e: NoSuchElementException) {
+ illegalArgument("无法解析~作为默认")
+ }
+ }
+ if (sender is BotAware) {
+ return try {
+ sender.bot.friends[s.toLong()]
+ } catch (e: NoSuchElementException) {
+ error("无法找到" + s + "这个好友")
+ } catch (e: NumberFormatException) {
+ error("无法解析$s")
+ }
+ } else {
+ s.split(".").let { args ->
+ if (args.size != 2) {
+ illegalArgument("无法解析 $s, 格式应为 机器人账号.好友账号")
+ }
+ return try {
+ Bot.getInstance(args[0].toLong()).friends.getOrNull(
+ args[1].toLongOrNull() ?: illegalArgument("无法解析 $s 为好友")
+ ) ?: illegalArgument("无法找到好友 ${args[1]}")
+ } catch (e: NoSuchElementException) {
+ illegalArgument("无法找到机器人账号 ${args[0]}")
+ }
+ }
+ }
+ }
+
+ override fun parse(s: SingleMessage, sender: CommandSender): Friend {
+ if (s is At) {
+ assert(sender is GroupContactCommandSender)
+ return (sender as BotAware).bot.friends.getOrNull(s.target) ?: illegalArgument("At的对象非Bot好友")
+ } else {
+ error("无法解析 $s 为好友")
+ }
+ }
+}
+
+object ExistGroupArgParser : CommandArgParser() {
+ override fun parse(s: String, sender: CommandSender): Group {
+ //by default
+ if ((s == "" || s == "~") && sender is GroupContactCommandSender) {
+ return sender.contact as Group
+ }
+ //from bot to group
+ if (sender is BotAware) {
+ val code = try {
+ s.toLong()
+ } catch (e: NoSuchElementException) {
+ error("无法识别Group Code$s")
+ }
+ return try {
+ sender.bot.getGroup(code)
+ } catch (e: NoSuchElementException) {
+ error("无法找到Group " + code + " from Bot " + sender.bot.id)
+ }
+ }
+ //from console/other
+ return with(s.split(".")) {
+ if (this.size != 2) {
+ error("请使用BotQQ号.群号 来表示Bot的一个群")
+ }
+ try {
+ Bot.getInstance(this[0].toLong()).getGroup(this[1].toLong())
+ } catch (e: NoSuchElementException) {
+ error("无法找到" + this[0] + "的" + this[1] + "群")
+ } catch (e: NumberFormatException) {
+ error("无法识别群号或机器人UIN")
+ }
+ }
+ }
+}
+
+object ExistMemberArgParser : CommandArgParser() {
+ //后台: Bot.Group.Member[QQ/名片]
+ //私聊: Group.Member[QQ/名片]
+ //群内: Q号
+ //群内: 名片
+ override fun parse(s: String, sender: CommandSender): Member {
+ if (sender !is BotAware) {
+ with(s.split(".")) {
+ checkArgument(this.size >= 3) {
+ "无法识别Member, 请使用Bot.Group.Member[QQ/名片]的格式"
+ }
+
+ val bot = try {
+ Bot.getInstance(this[0].toLong())
+ } catch (e: NoSuchElementException) {
+ illegalArgument("无法找到Bot")
+ } catch (e: NumberFormatException) {
+ illegalArgument("无法识别Bot")
+ }
+
+ val group = try {
+ bot.getGroup(this[1].toLong())
+ } catch (e: NoSuchElementException) {
+ illegalArgument("无法找到Group")
+ } catch (e: NumberFormatException) {
+ illegalArgument("无法识别Group")
+ }
+
+ val memberIndex = this.subList(2, this.size).joinToString(".")
+ return group.members.getOrNull(memberIndex.toLong())
+ ?: group.fuzzySearchMember(memberIndex)
+ ?: error("无法找到成员$memberIndex")
+ }
+ } else {
+ val bot = sender.bot
+ if (sender is GroupContactCommandSender) {
+ val group = sender.contact as Group
+ return try {
+ group.members[s.toLong()]
+ } catch (ignored: Exception) {
+ group.fuzzySearchMember(s) ?: illegalArgument("无法找到成员$s")
+ }
+ } else {
+ with(s.split(".")) {
+ if (this.size < 2) {
+ illegalArgument("无法识别Member, 请使用Group.Member[QQ/名片]的格式")
+ }
+ val group = try {
+ bot.getGroup(this[0].toLong())
+ } catch (e: NoSuchElementException) {
+ illegalArgument("无法找到Group")
+ } catch (e: NumberFormatException) {
+ illegalArgument("无法识别Group")
+ }
+
+ val memberIndex = this.subList(1, this.size).joinToString(".")
+ return try {
+ group.members[memberIndex.toLong()]
+ } catch (ignored: Exception) {
+ group.fuzzySearchMember(memberIndex) ?: illegalArgument("无法找到成员$memberIndex")
+ }
+ }
+ }
+ }
+ }
+
+ override fun parse(s: SingleMessage, sender: CommandSender): Member {
+ return if (s is At) {
+ checkArgument(sender is GroupContactCommandSender)
+ (sender.contact as Group).members[s.target]
+ } else {
+ illegalArgument("无法识别Member" + s.content)
+ }
+ }
+}
+
diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandDescriptor.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandDescriptor.kt
new file mode 100644
index 000000000..28c5164d9
--- /dev/null
+++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandDescriptor.kt
@@ -0,0 +1,139 @@
+@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate")
+
+package net.mamoe.mirai.console.command
+
+import kotlin.reflect.KClass
+
+/**
+ * 指令描述. 包含名称, 权限要求, 参数解析器环境, 参数列表.
+ */
+class CommandDescriptor(
+ /**
+ * 包含子命令的全名. 如 "`group kick`", 其中 `kick` 为 `group` 的子命令
+ */
+ val fullName: String,
+ /**
+ * 指令参数解析器环境.
+ */
+ val context: CommandParserContext,
+ /**
+ * 指令参数列表, 有顺序.
+ */
+ val params: List>,
+ /**
+ * 指令权限
+ *
+ * @see CommandPermission.or 要求其中一个权限
+ * @see CommandPermission.and 同时要求两个权限
+ */
+ val permission: CommandPermission = CommandPermission.Default
+)
+
+/**
+ * 构建一个 [CommandDescriptor]
+ */
+@Suppress("FunctionName")
+inline fun CommandDescriptor(
+ fullName: String,
+ block: CommandDescriptorBuilder.() -> Unit
+): CommandDescriptor = CommandDescriptorBuilder(fullName).apply(block).build()
+
+class CommandDescriptorBuilder(
+ val fullName: String
+) {
+ @PublishedApi
+ internal var context: CommandParserContext = CommandParserContext.Builtins
+
+ @PublishedApi
+ internal var permission: CommandPermission = CommandPermission.Default
+
+ @PublishedApi
+ internal var params: MutableList> = mutableListOf()
+
+ /** 增加指令参数解析器列表 */
+ @JvmSynthetic
+ inline fun context(block: CommandParserContextBuilder.() -> Unit) {
+ this.context += CommandParserContext(block)
+ }
+
+ /** 增加指令参数解析器列表 */
+ @JvmSynthetic
+ inline fun context(context: CommandParserContext): CommandDescriptorBuilder = apply {
+ this.context += context
+ }
+
+ /** 设置权限要求 */
+ fun permission(permission: CommandPermission): CommandDescriptorBuilder = apply {
+ this.permission = permission
+ }
+
+ /** 设置权限要求 */
+ @JvmSynthetic
+ inline fun permission(crossinline block: CommandSender.() -> Boolean) {
+ this.permission = AnonymousCommandPermission(block)
+ }
+
+ fun param(vararg params: CommandParam<*>): CommandDescriptorBuilder = apply {
+ this.params.addAll(params)
+ }
+
+ @JvmSynthetic
+ fun param(
+ name: String?,
+ type: KClass,
+ overrideParser: CommandArgParser? = null
+ ): CommandDescriptorBuilder = apply {
+ this.params.add(CommandParam(name, type).apply { this.parser = overrideParser })
+ }
+
+ fun param(
+ name: String?,
+ type: Class,
+ overrideParser: CommandArgParser? = null
+ ): CommandDescriptorBuilder =
+ param(name, type, overrideParser)
+
+ inline fun param(
+ name: String? = null,
+ overrideParser: CommandArgParser? = null
+ ): CommandDescriptorBuilder =
+ param(name, T::class, overrideParser)
+
+ @JvmSynthetic
+ fun param(vararg pairs: Pair>): CommandDescriptorBuilder = apply {
+ for (pair in pairs) {
+ this.params.add(CommandParam(pair.first, pair.second))
+ }
+ }
+
+ @JvmSynthetic
+ fun params(block: ParamBlock.() -> Unit): CommandDescriptorBuilder = apply {
+ ParamBlock(params).apply(block)
+ }
+
+ @JvmSynthetic
+ fun param(type: KClass<*>): CommandDescriptorBuilder = apply {
+ this.params.add(CommandParam(null, type))
+ }
+
+ fun param(type: Class<*>): CommandDescriptorBuilder = apply {
+ this.params.add(CommandParam(null, type.kotlin))
+ }
+
+ fun build(): CommandDescriptor = CommandDescriptor(fullName, context, params, permission)
+}
+
+@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
+inline class ParamBlock internal constructor(@PublishedApi internal val list: MutableList>) {
+ /** 添加一个名称为 [this], 类型为 [klass] 的参数. 返回添加成功的对象 */
+ infix fun String.typed(klass: KClass): CommandParam =
+ CommandParam(this, klass).also { list.add(it) }
+
+ /** 指定 [CommandParam.overrideParser] */
+ infix fun CommandParam.using(parser: CommandArgParser): CommandParam =
+ this.apply { this.parser = parser }
+
+ /** 覆盖 [CommandArgParser] */
+ inline infix fun String.using(parser: CommandArgParser): CommandParam =
+ this typed T::class using parser
+}
\ No newline at end of file
diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParam.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParam.kt
new file mode 100644
index 000000000..edd85f319
--- /dev/null
+++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParam.kt
@@ -0,0 +1,36 @@
+@file:Suppress("unused")
+
+package net.mamoe.mirai.console.command
+
+import kotlin.reflect.KClass
+
+/**
+ * 指令形式参数.
+ */
+data class CommandParam(
+ /**
+ * 参数名, 为 `null` 时即为匿名参数.
+ * 参数名允许重复 (尽管并不建议这样做).
+ * 参数名仅提供给 [CommandArgParser] 以发送更好的错误信息.
+ */
+ val name: String?,
+ /**
+ * 参数类型. 将从 [CommandDescriptor.context] 中寻找 [CommandArgParser] 解析.
+ */
+ val type: KClass // exact type
+) {
+ constructor(name: String?, type: KClass, parser: CommandArgParser) : this(name, type) {
+ this.parser = parser
+ }
+
+ @JvmField
+ internal var parser: CommandArgParser? = null
+
+
+ /**
+ * 覆盖的 [CommandArgParser].
+ *
+ * 如果非 `null`, 将不会从 [CommandParserContext] 寻找 [CommandArgParser]
+ */
+ val overrideParser: CommandArgParser? get() = parser
+}
diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParserContext.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParserContext.kt
new file mode 100644
index 000000000..502628ef4
--- /dev/null
+++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandParserContext.kt
@@ -0,0 +1,130 @@
+/*
+ * 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", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate")
+
+package net.mamoe.mirai.console.command
+
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.console.command.AbstractCommandParserContext.Node
+import net.mamoe.mirai.contact.Group
+import net.mamoe.mirai.contact.Member
+import kotlin.internal.LowPriorityInOverloadResolution
+import kotlin.reflect.KClass
+
+
+/**
+ * [KClass] 到 [CommandArgParser] 的匹配
+ */
+interface CommandParserContext {
+ operator fun get(klass: KClass): CommandArgParser?
+
+ /**
+ * 内建的默认 [CommandArgParser]
+ */
+ object Builtins : CommandParserContext by (CommandParserContext {
+ Int::class with IntArgParser
+ Byte::class with ByteArgParser
+ Short::class with ShortArgParser
+ Boolean::class with BooleanArgParser
+ String::class with StringArgParser
+ Long::class with LongArgParser
+ Double::class with DoubleArgParser
+ Float::class with FloatArgParser
+
+ Member::class with ExistMemberArgParser
+ Group::class with ExistGroupArgParser
+ Bot::class with ExistBotArgParser
+ })
+}
+
+fun CommandParserContext.parserFor(param: CommandParam): CommandArgParser? = this[param.type]
+
+/**
+ * 合并两个 [CommandParserContext], [replacer] 将会替换 [this] 中重复的 parser.
+ */
+operator fun CommandParserContext.plus(replacer: CommandParserContext): CommandParserContext {
+ return object : CommandParserContext {
+ override fun get(klass: KClass): CommandArgParser? = replacer[klass] ?: this@plus[klass]
+ }
+}
+
+@Suppress("UNCHECKED_CAST")
+open class AbstractCommandParserContext(val list: List>) : CommandParserContext {
+ class Node(
+ val klass: KClass,
+ val parser: CommandArgParser
+ )
+
+ override fun get(klass: KClass): CommandArgParser? =
+ this.list.firstOrNull { it.klass == klass }?.parser as CommandArgParser?
+}
+
+/**
+ * 构建一个 [CommandParserContext].
+ *
+ * ```
+ * CommandParserContext {
+ * Int::class with IntArgParser
+ * Member::class with ExistMemberArgParser
+ * Group::class with { s: String, sender: CommandSender ->
+ * Bot.getInstance(s.toLong()).getGroup(s.toLong())
+ * }
+ * Bot::class with { s: String ->
+ * Bot.getInstance(s.toLong())
+ * }
+ * }
+ * ```
+ */
+@Suppress("FunctionName")
+@JvmSynthetic
+inline fun CommandParserContext(block: CommandParserContextBuilder.() -> Unit): CommandParserContext {
+ return AbstractCommandParserContext(
+ CommandParserContextBuilder().apply(block).distinctByReversed { it.klass })
+}
+
+/**
+ * @see CommandParserContext
+ */
+class CommandParserContextBuilder : MutableList> by mutableListOf() {
+ @JvmName("add")
+ inline infix fun KClass.with(parser: CommandArgParser): Node<*> =
+ Node(this, parser)
+
+ /**
+ * 添加一个指令解析器
+ */
+ @JvmSynthetic
+ @LowPriorityInOverloadResolution
+ inline infix fun KClass.with(
+ crossinline parser: CommandArgParser.(s: String, sender: CommandSender) -> T
+ ): Node<*> = Node(this, CommandArgParser(parser))
+
+ /**
+ * 添加一个指令解析器
+ */
+ @JvmSynthetic
+ inline infix fun KClass.with(
+ crossinline parser: CommandArgParser.(s: String) -> T
+ ): Node<*> = Node(this, CommandArgParser { s: String, _: CommandSender -> parser(s) })
+}
+
+
+@PublishedApi
+internal inline fun Iterable.distinctByReversed(selector: (T) -> K): List {
+ val set = HashSet()
+ val list = ArrayList()
+ for (i in list.indices.reversed()) {
+ val element = list[i]
+ if (set.add(element.let(selector))) {
+ list.add(element)
+ }
+ }
+ return list
+}
\ No newline at end of file
diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt
new file mode 100644
index 000000000..af45d41a6
--- /dev/null
+++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt
@@ -0,0 +1,204 @@
+/*
+ * 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("unused", "NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate")
+
+package net.mamoe.mirai.console.command
+
+import net.mamoe.mirai.Bot
+import net.mamoe.mirai.console.utils.isManager
+import net.mamoe.mirai.contact.isAdministrator
+import net.mamoe.mirai.contact.isOperator
+import net.mamoe.mirai.contact.isOwner
+
+/**
+ * 指令权限
+ *
+ * @see AnonymousCommandPermission
+ */
+abstract class CommandPermission {
+ /**
+ * 判断 [this] 是否拥有这个指令的权限
+ */
+ abstract fun CommandSender.hasPermission(): Boolean
+
+
+ /**
+ * 满足两个权限其中一个即可使用指令
+ */ // no extension for Java
+ infix fun or(another: CommandPermission): CommandPermission = OrCommandPermission(this, another)
+
+ /**
+ * 同时拥有两个权限才能使用指令
+ */ // no extension for Java
+ infix fun and(another: CommandPermission): CommandPermission = AndCommandPermission(this, another)
+
+
+ /**
+ * 任何人都可以使用这个指令
+ */
+ object Any : CommandPermission() {
+ override fun CommandSender.hasPermission(): Boolean = true
+ }
+
+ /**
+ * 任何人都不能使用这个指令. 指令只能通过代码在 [CommandManager] 使用
+ */
+ object None : CommandPermission() {
+ override fun CommandSender.hasPermission(): Boolean = false
+ }
+
+ /**
+ * 管理员或群主可以使用这个指令
+ */
+ class Operator(
+ /**
+ * 指定只有来自某个 [Bot] 的管理员或群主才可以使用这个指令
+ */
+ vararg val fromBot: Long
+ ) : CommandPermission() {
+ constructor(vararg fromBot: Bot) : this(*fromBot.map { it.id }.toLongArray())
+
+ override fun CommandSender.hasPermission(): Boolean {
+ return this is GroupContactCommandSender && this.bot.id in fromBot && this.realSender.isOperator()
+ }
+
+ /**
+ * 来自任何 [Bot] 的任何一个管理员或群主都可以使用这个指令
+ */
+ companion object Any : CommandPermission() {
+ override fun CommandSender.hasPermission(): Boolean {
+ return this is GroupContactCommandSender && this.realSender.isOperator()
+ }
+ }
+ }
+
+ /**
+ * 群主可以使用这个指令
+ */
+ class GroupOwner(
+ /**
+ * 指定只有来自某个 [Bot] 的群主才可以使用这个指令
+ */
+ vararg val fromBot: Long
+ ) : CommandPermission() {
+ constructor(vararg fromBot: Bot) : this(*fromBot.map { it.id }.toLongArray())
+
+ override fun CommandSender.hasPermission(): Boolean {
+ return this is GroupContactCommandSender && this.bot.id in fromBot && this.realSender.isOwner()
+ }
+
+ /**
+ * 来自任何 [Bot] 的任何一个群主都可以使用这个指令
+ */
+ companion object Any : CommandPermission() {
+ override fun CommandSender.hasPermission(): Boolean {
+ return this is GroupContactCommandSender && this.realSender.isOwner()
+ }
+ }
+ }
+
+ /**
+ * 管理员 (不包含群主) 可以使用这个指令
+ */
+ class Administrator(
+ /**
+ * 指定只有来自某个 [Bot] 的管理员 (不包含群主) 才可以使用这个指令
+ */
+ vararg val fromBot: Long
+ ) : CommandPermission() {
+ constructor(vararg fromBot: Bot) : this(*fromBot.map { it.id }.toLongArray())
+
+ override fun CommandSender.hasPermission(): Boolean {
+ return this is GroupContactCommandSender && this.bot.id in fromBot && this.realSender.isAdministrator()
+ }
+
+ /**
+ * 来自任何 [Bot] 的任何一个管理员 (不包含群主) 都可以使用这个指令
+ */
+ companion object Any : CommandPermission() {
+ override fun CommandSender.hasPermission(): Boolean {
+ return this is GroupContactCommandSender && this.realSender.isAdministrator()
+ }
+ }
+ }
+
+ /**
+ * console 管理员可以使用这个指令
+ */
+ class Manager(
+ /**
+ * 指定只有来自某个 [Bot] 的管理员或群主才可以使用这个指令
+ */
+ vararg val fromBot: Long
+ ) : CommandPermission() {
+ constructor(vararg fromBot: Bot) : this(*fromBot.map { it.id }.toLongArray())
+
+ override fun CommandSender.hasPermission(): Boolean {
+ return this is GroupContactCommandSender && this.bot.id in fromBot && this.realSender.isManager
+ }
+
+ /**
+ * 任何 [Bot] 的 manager 都可以使用这个指令
+ */
+ companion object Any : CommandPermission() {
+ override fun CommandSender.hasPermission(): Boolean {
+ return this is GroupContactCommandSender && this.realSender.isManager
+ }
+ }
+ }
+
+ /**
+ * 仅控制台能使用和这个指令
+ */
+ object Console : CommandPermission() {
+ override fun CommandSender.hasPermission(): Boolean = false
+ }
+
+ companion object {
+ @JvmStatic
+ val Default: CommandPermission = Manager or Console
+ }
+}
+
+/**
+ * 使用 [lambda][block] 快速构造 [CommandPermission]
+ */
+@JvmSynthetic
+@Suppress("FunctionName")
+inline fun AnonymousCommandPermission(crossinline block: CommandSender.() -> Boolean): CommandPermission {
+ return object : CommandPermission() {
+ override fun CommandSender.hasPermission(): Boolean = block()
+ }
+}
+
+inline fun CommandSender.hasPermission(permission: CommandPermission): Boolean =
+ permission.run { this@hasPermission.hasPermission() }
+
+
+inline fun CommandPermission.hasPermission(sender: CommandSender): Boolean = this.run { sender.hasPermission() }
+
+
+internal class OrCommandPermission(
+ private val first: CommandPermission,
+ private val second: CommandPermission
+) : CommandPermission() {
+ override fun CommandSender.hasPermission(): Boolean {
+ return this.hasPermission(first) || this.hasPermission(second)
+ }
+}
+
+internal class AndCommandPermission(
+ private val first: CommandPermission,
+ private val second: CommandPermission
+) : CommandPermission() {
+ override fun CommandSender.hasPermission(): Boolean {
+ return this.hasPermission(first) || this.hasPermission(second)
+ }
+}
\ No newline at end of file
diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotHelper.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotHelper.kt
index d4c2d7410..1c01500c2 100644
--- a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotHelper.kt
+++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/BotHelper.kt
@@ -13,8 +13,16 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugins.*
import net.mamoe.mirai.console.utils.BotManagers.BOT_MANAGERS
+import net.mamoe.mirai.contact.User
import java.io.File
+
+/**
+ * 判断此用户是否为 console 管理员
+ */
+val User.isManager: Boolean
+ get() = this.bot.managers.contains(this.id)
+
@OptIn(ToBeRemoved::class)
internal object BotManagers {
val config = File("${MiraiConsole.path}/bot.yml").loadAsConfig()
diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/Utils.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/Utils.kt
index 9a0d835db..5562465be 100644
--- a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/Utils.kt
+++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/utils/Utils.kt
@@ -69,16 +69,16 @@ internal fun Throwable.addSuppressedMirai(e: Throwable) {
* XXXXXYYYYY.fuzzyCompare(XXXXXYYYZZ) = 0.8
*/
-fun String.fuzzyCompare(target:String):Double{
+internal fun String.fuzzyCompare(target: String): Double {
var step = 0
- if(this == target){
+ if (this == target) {
return 1.0
}
- if(target.length > this.length){
+ if (target.length > this.length) {
return 0.0
}
- for(i in this.indices){
- if(target.length == i){
+ for (i in this.indices) {
+ if (target.length == i) {
step--
}else {
if (this[i] != target[i]) {
@@ -97,14 +97,14 @@ fun String.fuzzyCompare(target:String):Double{
/**
* 模糊搜索一个List中index最接近target的东西
*/
-inline fun Collection.fuzzySearch(
+internal inline fun Collection.fuzzySearch(
target: String,
index: (T) -> String
-):T?{
- if(this.isEmpty()){
+): T? {
+ if (this.isEmpty()) {
return null
}
- var potential:T? = null
+ var potential: T? = null
var rate = 0.0
this.forEach {
val thisIndex = index(it)
@@ -126,14 +126,14 @@ inline fun Collection.fuzzySearch(
* 并且确保target是唯一的
* 如搜索index为XXXXYY list中同时存在XXXXYYY XXXXYYYY 将返回null
*/
-inline fun Collection.fuzzySearchOnly(
+internal inline fun Collection.fuzzySearchOnly(
target: String,
index: (T) -> String
-):T?{
- if(this.isEmpty()){
+): T? {
+ if (this.isEmpty()) {
return null
}
- var potential:T? = null
+ var potential: T? = null
var rate = 0.0
var collide = 0
this.forEach {
@@ -154,8 +154,8 @@ inline fun Collection.fuzzySearchOnly(
}
-fun Group.fuzzySearchMember(nameCardTarget:String):Member?{
- return this.members.fuzzySearchOnly(nameCardTarget){
+internal fun Group.fuzzySearchMember(nameCardTarget: String): Member? {
+ return this.members.fuzzySearchOnly(nameCardTarget) {
it.nameCard
}
}
\ No newline at end of file