Command registering, matching, executing

This commit is contained in:
Him188 2020-05-14 15:33:10 +08:00
parent 41112affa8
commit ee8be9799f
19 changed files with 520 additions and 271 deletions

View File

@ -40,6 +40,8 @@ dependencies {
testApi("net.mamoe:mirai-core-qqandroid:${Versions.Mirai.core}") testApi("net.mamoe:mirai-core-qqandroid:${Versions.Mirai.core}")
testApi(kotlin("stdlib")) testApi(kotlin("stdlib"))
testApi(kotlin("test"))
testApi(kotlin("test-junit5"))
} }
version = Versions.Mirai.console version = Versions.Mirai.console

View File

@ -1,16 +0,0 @@
package net.mamoe.mirai.console.command;
// import jdk.jfr.Description;
public class JCommandManager {
private JCommandManager() {
}
public static CommandManager getInstance() {
return CommandManager.INSTANCE;
}
}

View File

@ -1,7 +1,6 @@
package net.mamoe.mirai.console.utils; package net.mamoe.mirai.console.utils;
import net.mamoe.mirai.Bot; import net.mamoe.mirai.Bot;
import net.mamoe.mirai.console.MiraiConsole;
import java.util.List; import java.util.List;
@ -12,7 +11,7 @@ import java.util.List;
public class BotManager { public class BotManager {
public static List<Long> getManagers(long botAccount) { public static List<Long> getManagers(long botAccount) {
Bot bot = MiraiConsole.INSTANCE.getBotOrThrow(botAccount); Bot bot = Bot.getInstance(botAccount);
return getManagers(bot); return getManagers(bot);
} }

View File

@ -13,9 +13,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.io.charsets.Charset import kotlinx.io.charsets.Charset
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandOwner
import net.mamoe.mirai.console.plugins.PluginManager
import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.PrintStream import java.io.PrintStream
@ -42,7 +43,7 @@ interface IMiraiConsole : CoroutineScope {
val mainLogger: MiraiLogger val mainLogger: MiraiLogger
} }
object MiraiConsole : CoroutineScope, IMiraiConsole { object MiraiConsole : CoroutineScope, IMiraiConsole, CommandOwner {
private lateinit var instance: IMiraiConsole private lateinit var instance: IMiraiConsole
/** 由前端调用 */ /** 由前端调用 */
@ -58,18 +59,20 @@ object MiraiConsole : CoroutineScope, IMiraiConsole {
override val coroutineContext: CoroutineContext get() = instance.coroutineContext override val coroutineContext: CoroutineContext get() = instance.coroutineContext
init { init {
DefaultLogger = {
this.newLogger(it)
}
this.coroutineContext[Job]!!.invokeOnCompletion { this.coroutineContext[Job]!!.invokeOnCompletion {
PluginManager.disablePlugins()
CommandManager.cancel()
Bot.botInstances.forEach { Bot.botInstances.forEach {
it.close() it.close()
} }
} }
} }
@MiraiExperimentalAPI
fun newLogger(identity: String?): MiraiLogger = frontEnd.loggerFor(identity)
} }
internal val Throwable.stacktraceString: String internal val Throwable.stacktraceString: String
get() = get() =
ByteArrayOutputStream().apply { ByteArrayOutputStream().apply {

View File

@ -7,15 +7,8 @@ import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO import io.ktor.client.engine.cio.CIO
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.util.KtorExperimentalAPI import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.mamoe.mirai.console.plugins.PluginManager
import net.mamoe.mirai.console.utils.retryCatching import net.mamoe.mirai.console.utils.retryCatching
import net.mamoe.mirai.console.utils.tryNTimes
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.net.HttpURLConnection
import java.net.URL
internal object CuiPluginCenter : PluginCenter { internal object CuiPluginCenter : PluginCenter {
@ -96,6 +89,8 @@ internal object CuiPluginCenter : PluginCenter {
} }
override suspend fun <T : Any> T.downloadPlugin(name: String, progressListener: T.(Float) -> Unit): File { override suspend fun <T : Any> T.downloadPlugin(name: String, progressListener: T.(Float) -> Unit): File {
TODO()
/*
val info = findPlugin(name) ?: error("Plugin Not Found") val info = findPlugin(name) ?: error("Plugin Not Found")
val targetFile = File(PluginManager.pluginsPath, "$name-" + info.version + ".jar") val targetFile = File(PluginManager.pluginsPath, "$name-" + info.version + ".jar")
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@ -116,6 +111,7 @@ internal object CuiPluginCenter : PluginCenter {
} }
} }
return targetFile return targetFile
*/
} }
override val name: String override val name: String

View File

@ -13,129 +13,134 @@ package net.mamoe.mirai.console.command
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugins.PluginBase import net.mamoe.mirai.console.plugins.PluginBase
import kotlin.reflect.KProperty
internal const val FOR_BINARY_COMPATIBILITY = "for binary compatibility"
/** /**
* 指令 * 指令
* *
* @see register 注册这个指令 * @see register 注册这个指令
* @see registerCommand 注册指令 DSL
*/ */
interface Command { interface Command {
/** val owner: CommandOwner
* 指令主名称 val descriptor: CommandDescriptor
*/
val name: String /*
@Deprecated(FOR_BINARY_COMPATIBILITY, level = DeprecationLevel.HIDDEN)
suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean {
return true
}*/
/** /**
* 别名 * 执行这个指令.
*/ */
val alias: List<String> suspend fun onCommand(sender: CommandSender, args: CommandArgs): Boolean
/**
* 描述, 将会显示在 "/help" 指令中
*/
val description: String
/**
* 用法说明
*/
val usage: String
suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean
}
abstract class AbstractCommand(
override val name: String,
override val alias: List<String>,
override val description: String,
override val usage: String
) : Command
/**
* 注册这个指令
*/
inline fun Command.register(commandOwner: CommandOwner) = CommandManager.register(commandOwner, this)
internal inline fun registerConsoleCommands(builder: CommandBuilder.() -> Unit): Command {
return CommandBuilder().apply(builder).register(ConsoleCommandOwner)
} }
/** /**
* 构造并注册一个指令 * 指令实际参数列表. 参数顺序与 [Command.descriptor] [CommandDescriptor.params] 相同.
*/ */
inline fun PluginBase.registerCommand(builder: CommandBuilder.() -> Unit): Command { class CommandArgs private constructor(
return CommandBuilder().apply(builder).register(this.asCommandOwner()) @JvmField
} internal val values: List<Any>,
private val fromCommand: Command
) : List<Any> by values {
// for java
@Suppress("unused")
abstract class BlockingCommand(
override val name: String,
override val alias: List<String> = listOf(),
override val description: String = "",
override val usage: String = ""
) : Command {
/** /**
* 最高优先级监听器. * 获取第一个类型为 [R] 的参数
*/
@JvmSynthetic
inline fun <reified R> getReified(): R {
for (value in this) {
if (value is R) {
return value
}
}
error("Cannot find argument typed ${R::class.qualifiedName}")
}
/**
* 获取名称为 [name] 的参数.
* *
* 指令调用将优先触发 [Command.onCommand], 若该函数返回 `false`, 则不会调用 [PluginBase.onCommand] * [name] `null` 则获取第一个匿名参数
* */ * @throws NoSuchElementException 找不到这个名称的参数时抛出
final override suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean { */
return withContext(Dispatchers.IO) { operator fun get(name: String?): Any {
onCommandBlocking(sender, args) val index = fromCommand.descriptor.params.indexOfFirst { it.name == name }
if (index == -1) {
throw NoSuchElementException("Cannot find argument named $name")
}
return values[index]
}
/**
* 获取名称为 [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: Command, sender: CommandSender, rawArgs: List<Any>): CommandArgs {
val params = command.descriptor.params
require(rawArgs.size >= params.size) { "No enough rawArgs: required ${params.size}, found only ${rawArgs.size}" }
command.descriptor.params.asSequence().zip(rawArgs.asSequence()).map { (commandParam, any) ->
command.parserFor(commandParam)?.parse(any, sender)
?: error("ICould not find a parser for param named ${commandParam.name}")
}.toList().let { bakedArgs ->
return CommandArgs(bakedArgs, command)
}
} }
} }
abstract fun onCommandBlocking(sender: CommandSender, args: List<String>): Boolean
} }
inline val Command.fullName get() = descriptor.fullName
inline val Command.usage get() = descriptor.usage
inline val Command.params get() = descriptor.params
inline val Command.description get() = descriptor.description
inline val Command.context get() = descriptor.context
inline val Command.aliases get() = descriptor.aliases
inline val Command.permission get() = descriptor.permission
inline val Command.allNames get() = descriptor.allNames
abstract class PluginCommand(
final override val owner: PluginBase,
descriptor: CommandDescriptor
) : AbstractCommand(descriptor)
internal abstract class ConsoleCommand(
descriptor: CommandDescriptor
) : AbstractCommand(descriptor) {
final override val owner: MiraiConsole get() = MiraiConsole
}
sealed class AbstractCommand(
final override val descriptor: CommandDescriptor
) : Command
/** /**
* @see registerCommand * For Java
*/ */
class CommandBuilder @PublishedApi internal constructor() { @Suppress("unused")
var name: String? = null abstract class BlockingCommand(
var alias: List<String>? = null owner: PluginBase,
var description: String = "" descriptor: CommandDescriptor
var usage: String = "use /help for help" ) : PluginCommand(owner, descriptor) {
final override suspend fun onCommand(sender: CommandSender, args: CommandArgs): Boolean {
internal var onCommand: (suspend CommandSender.(args: List<String>) -> Boolean)? = null return withContext(Dispatchers.IO) { onCommandBlocking(sender, args) }
fun onCommand(commandProcess: suspend CommandSender.(args: List<String>) -> Boolean) {
onCommand = commandProcess
} }
}
abstract fun onCommandBlocking(sender: CommandSender, args: CommandArgs): Boolean
// internal
internal class AnonymousCommand internal constructor(
override val name: String,
override val alias: List<String>,
override val description: String,
override val usage: String = "",
val onCommand: suspend CommandSender.(args: List<String>) -> Boolean
) : Command {
override suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean {
return onCommand.invoke(sender, args)
}
}
@PublishedApi
internal fun CommandBuilder.register(commandOwner: CommandOwner): AnonymousCommand {
if (name == null || onCommand == null) {
error("CommandBuilder not complete")
}
if (alias == null) {
alias = listOf()
}
return AnonymousCommand(
name!!,
alias!!,
description,
usage,
onCommand!!
).also { it.register(commandOwner) }
} }

View File

@ -17,8 +17,20 @@ import kotlin.contracts.contract
* input is always String * input is always String
*/ */
abstract class CommandArgParser<out T : Any> { abstract class CommandArgParser<out T : Any> {
abstract fun parse(s: String, sender: CommandSender): T abstract fun parse(raw: String, sender: CommandSender): T
open fun parse(s: SingleMessage, sender: CommandSender): T = parse(s.content, sender) 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") @Suppress("unused")
@ -46,7 +58,7 @@ inline fun CommandArgParser<*>.checkArgument(
inline fun <T : Any> CommandArgParser( inline fun <T : Any> CommandArgParser(
crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T
): CommandArgParser<T> = object : CommandArgParser<T>() { ): CommandArgParser<T> = object : CommandArgParser<T>() {
override fun parse(s: String, sender: CommandSender): T = parser(s, sender) override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
} }
@ -57,41 +69,41 @@ class ParserException(message: String, cause: Throwable? = null) : RuntimeExcept
object IntArgParser : CommandArgParser<Int>() { object IntArgParser : CommandArgParser<Int>() {
override fun parse(s: String, sender: CommandSender): Int = override fun parse(raw: String, sender: CommandSender): Int =
s.toIntOrNull() ?: illegalArgument("无法解析 $s 为整数") raw.toIntOrNull() ?: illegalArgument("无法解析 $raw 为整数")
} }
object LongArgParser : CommandArgParser<Long>() { object LongArgParser : CommandArgParser<Long>() {
override fun parse(s: String, sender: CommandSender): Long = override fun parse(raw: String, sender: CommandSender): Long =
s.toLongOrNull() ?: illegalArgument("无法解析 $s 为长整数") raw.toLongOrNull() ?: illegalArgument("无法解析 $raw 为长整数")
} }
object ShortArgParser : CommandArgParser<Short>() { object ShortArgParser : CommandArgParser<Short>() {
override fun parse(s: String, sender: CommandSender): Short = override fun parse(raw: String, sender: CommandSender): Short =
s.toShortOrNull() ?: illegalArgument("无法解析 $s 为短整数") raw.toShortOrNull() ?: illegalArgument("无法解析 $raw 为短整数")
} }
object ByteArgParser : CommandArgParser<Byte>() { object ByteArgParser : CommandArgParser<Byte>() {
override fun parse(s: String, sender: CommandSender): Byte = override fun parse(raw: String, sender: CommandSender): Byte =
s.toByteOrNull() ?: illegalArgument("无法解析 $s 为字节") raw.toByteOrNull() ?: illegalArgument("无法解析 $raw 为字节")
} }
object DoubleArgParser : CommandArgParser<Double>() { object DoubleArgParser : CommandArgParser<Double>() {
override fun parse(s: String, sender: CommandSender): Double = override fun parse(raw: String, sender: CommandSender): Double =
s.toDoubleOrNull() ?: illegalArgument("无法解析 $s 为小数") raw.toDoubleOrNull() ?: illegalArgument("无法解析 $raw 为小数")
} }
object FloatArgParser : CommandArgParser<Float>() { object FloatArgParser : CommandArgParser<Float>() {
override fun parse(s: String, sender: CommandSender): Float = override fun parse(raw: String, sender: CommandSender): Float =
s.toFloatOrNull() ?: illegalArgument("无法解析 $s 为小数") raw.toFloatOrNull() ?: illegalArgument("无法解析 $raw 为小数")
} }
object StringArgParser : CommandArgParser<String>() { object StringArgParser : CommandArgParser<String>() {
override fun parse(s: String, sender: CommandSender): String = s override fun parse(raw: String, sender: CommandSender): String = raw
} }
object BooleanArgParser : CommandArgParser<Boolean>() { object BooleanArgParser : CommandArgParser<Boolean>() {
override fun parse(s: String, sender: CommandSender): Boolean = s.trim().let { str -> override fun parse(raw: String, sender: CommandSender): Boolean = raw.trim().let { str ->
str.equals("true", ignoreCase = true) str.equals("true", ignoreCase = true)
|| str.equals("yes", ignoreCase = true) || str.equals("yes", ignoreCase = true)
|| str.equals("enabled", ignoreCase = true) || str.equals("enabled", ignoreCase = true)
@ -105,11 +117,11 @@ object BooleanArgParser : CommandArgParser<Boolean>() {
* errors: String->Int convert, Bot Not Exist * errors: String->Int convert, Bot Not Exist
*/ */
object ExistBotArgParser : CommandArgParser<Bot>() { object ExistBotArgParser : CommandArgParser<Bot>() {
override fun parse(s: String, sender: CommandSender): Bot { override fun parse(raw: String, sender: CommandSender): Bot {
val uin = try { val uin = try {
s.toLong() raw.toLong()
} catch (e: Exception) { } catch (e: Exception) {
error("无法识别QQ UIN$s") error("无法识别QQ UIN$raw")
} }
return try { return try {
Bot.getInstance(uin) Bot.getInstance(uin)
@ -123,8 +135,8 @@ object ExistFriendArgParser : CommandArgParser<Friend>() {
//Bot.friend //Bot.friend
//friend //friend
//~ = self //~ = self
override fun parse(s: String, sender: CommandSender): Friend { override fun parse(raw: String, sender: CommandSender): Friend {
if (s == "~") { if (raw == "~") {
if (sender !is BotAware) { if (sender !is BotAware) {
illegalArgument("无法解析~作为默认") illegalArgument("无法解析~作为默认")
} }
@ -141,20 +153,20 @@ object ExistFriendArgParser : CommandArgParser<Friend>() {
} }
if (sender is BotAware) { if (sender is BotAware) {
return try { return try {
sender.bot.friends[s.toLong()] sender.bot.friends[raw.toLong()]
} catch (e: NoSuchElementException) { } catch (e: NoSuchElementException) {
error("无法找到" + s + "这个好友") error("无法找到" + raw + "这个好友")
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
error("无法解析$s") error("无法解析$raw")
} }
} else { } else {
s.split(".").let { args -> raw.split(".").let { args ->
if (args.size != 2) { if (args.size != 2) {
illegalArgument("无法解析 $s, 格式应为 机器人账号.好友账号") illegalArgument("无法解析 $raw, 格式应为 机器人账号.好友账号")
} }
return try { return try {
Bot.getInstance(args[0].toLong()).friends.getOrNull( Bot.getInstance(args[0].toLong()).friends.getOrNull(
args[1].toLongOrNull() ?: illegalArgument("无法解析 $s 为好友") args[1].toLongOrNull() ?: illegalArgument("无法解析 $raw 为好友")
) ?: illegalArgument("无法找到好友 ${args[1]}") ) ?: illegalArgument("无法找到好友 ${args[1]}")
} catch (e: NoSuchElementException) { } catch (e: NoSuchElementException) {
illegalArgument("无法找到机器人账号 ${args[0]}") illegalArgument("无法找到机器人账号 ${args[0]}")
@ -163,28 +175,28 @@ object ExistFriendArgParser : CommandArgParser<Friend>() {
} }
} }
override fun parse(s: SingleMessage, sender: CommandSender): Friend { override fun parse(raw: SingleMessage, sender: CommandSender): Friend {
if (s is At) { if (raw is At) {
assert(sender is GroupContactCommandSender) assert(sender is GroupContactCommandSender)
return (sender as BotAware).bot.friends.getOrNull(s.target) ?: illegalArgument("At的对象非Bot好友") return (sender as BotAware).bot.friends.getOrNull(raw.target) ?: illegalArgument("At的对象非Bot好友")
} else { } else {
error("无法解析 $s 为好友") error("无法解析 $raw 为好友")
} }
} }
} }
object ExistGroupArgParser : CommandArgParser<Group>() { object ExistGroupArgParser : CommandArgParser<Group>() {
override fun parse(s: String, sender: CommandSender): Group { override fun parse(raw: String, sender: CommandSender): Group {
//by default //by default
if ((s == "" || s == "~") && sender is GroupContactCommandSender) { if ((raw == "" || raw == "~") && sender is GroupContactCommandSender) {
return sender.contact as Group return sender.contact as Group
} }
//from bot to group //from bot to group
if (sender is BotAware) { if (sender is BotAware) {
val code = try { val code = try {
s.toLong() raw.toLong()
} catch (e: NoSuchElementException) { } catch (e: NoSuchElementException) {
error("无法识别Group Code$s") error("无法识别Group Code$raw")
} }
return try { return try {
sender.bot.getGroup(code) sender.bot.getGroup(code)
@ -193,7 +205,7 @@ object ExistGroupArgParser : CommandArgParser<Group>() {
} }
} }
//from console/other //from console/other
return with(s.split(".")) { return with(raw.split(".")) {
if (this.size != 2) { if (this.size != 2) {
error("请使用BotQQ号.群号 来表示Bot的一个群") error("请使用BotQQ号.群号 来表示Bot的一个群")
} }
@ -213,9 +225,9 @@ object ExistMemberArgParser : CommandArgParser<Member>() {
//私聊: Group.Member[QQ/名片] //私聊: Group.Member[QQ/名片]
//群内: Q号 //群内: Q号
//群内: 名片 //群内: 名片
override fun parse(s: String, sender: CommandSender): Member { override fun parse(raw: String, sender: CommandSender): Member {
if (sender !is BotAware) { if (sender !is BotAware) {
with(s.split(".")) { with(raw.split(".")) {
checkArgument(this.size >= 3) { checkArgument(this.size >= 3) {
"无法识别Member, 请使用Bot.Group.Member[QQ/名片]的格式" "无法识别Member, 请使用Bot.Group.Member[QQ/名片]的格式"
} }
@ -246,12 +258,12 @@ object ExistMemberArgParser : CommandArgParser<Member>() {
if (sender is GroupContactCommandSender) { if (sender is GroupContactCommandSender) {
val group = sender.contact as Group val group = sender.contact as Group
return try { return try {
group.members[s.toLong()] group.members[raw.toLong()]
} catch (ignored: Exception) { } catch (ignored: Exception) {
group.fuzzySearchMember(s) ?: illegalArgument("无法找到成员$s") group.fuzzySearchMember(raw) ?: illegalArgument("无法找到成员$raw")
} }
} else { } else {
with(s.split(".")) { with(raw.split(".")) {
if (this.size < 2) { if (this.size < 2) {
illegalArgument("无法识别Member, 请使用Group.Member[QQ/名片]的格式") illegalArgument("无法识别Member, 请使用Group.Member[QQ/名片]的格式")
} }
@ -274,12 +286,12 @@ object ExistMemberArgParser : CommandArgParser<Member>() {
} }
} }
override fun parse(s: SingleMessage, sender: CommandSender): Member { override fun parse(raw: SingleMessage, sender: CommandSender): Member {
return if (s is At) { return if (raw is At) {
checkArgument(sender is GroupContactCommandSender) checkArgument(sender is GroupContactCommandSender)
(sender.contact as Group).members[s.target] (sender.contact as Group).members[raw.target]
} else { } else {
illegalArgument("无法识别Member" + s.content) illegalArgument("无法识别Member" + raw.content)
} }
} }
} }

View File

@ -2,6 +2,10 @@
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.SingleMessage
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
@ -11,15 +15,19 @@ class CommandDescriptor(
/** /**
* 包含子命令的全名. "`group kick`", 其中 `kick` `group` 的子命令 * 包含子命令的全名. "`group kick`", 其中 `kick` `group` 的子命令
*/ */
val fullName: String, fullName: CommandFullName,
/** /**
* 用法说明 * 用法说明
*/ */
val usage: String, usage: String,
/** /**
* 指令参数列表, 有顺序. * 指令参数列表, 有顺序.
*/ */
val params: List<CommandParam<*>>, val params: List<CommandParam<*>>,
/**
* 指令说明
*/
description: String = "",
/** /**
* 指令参数解析器环境. * 指令参数解析器环境.
*/ */
@ -27,7 +35,7 @@ class CommandDescriptor(
/** /**
* 指令别名 * 指令别名
*/ */
val aliases: Array<String> = arrayOf(), aliases: Array<CommandFullName> = arrayOf(),
/** /**
* 指令权限 * 指令权限
* *
@ -35,19 +43,101 @@ class CommandDescriptor(
* @see CommandPermission.and 同时要求两个权限 * @see CommandPermission.and 同时要求两个权限
*/ */
val permission: CommandPermission = CommandPermission.Default val permission: CommandPermission = CommandPermission.Default
) ) {
/**
* 指令别名
*/
val aliases: Array<CommandFullName> = aliases.map { it.checkFullName("alias") }.toTypedArray()
/**
* 指令说明
*/
val description: String = description.trim()
/**
* 用法说明
*/
val usage: String = usage.trim()
/**
* 包含子命令的全名. "`group kick`", 其中 `kick` `group` 的子命令
* 元素类型可以为 [Message] [String]
*/
val fullName: CommandFullName = fullName.checkFullName("fullName")
/**
* `fullName + aliases`
*/
val allNames = arrayOf(fullName, *aliases)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CommandDescriptor
if (!fullName.contentEquals(other.fullName)) return false
if (usage != other.usage) return false
if (params != other.params) return false
if (description != other.description) return false
if (context != other.context) return false
if (!aliases.contentEquals(other.aliases)) return false
if (permission != other.permission) return false
return true
}
override fun hashCode(): Int {
var result = fullName.hashCode()
result = 31 * result + usage.hashCode()
result = 31 * result + params.hashCode()
result = 31 * result + description.hashCode()
result = 31 * result + context.hashCode()
result = 31 * result + aliases.contentHashCode()
result = 31 * result + permission.hashCode()
return result
}
}
fun Command.checkArgs(args: CommandArgs) = this.descriptor.checkArgs(args)
fun CommandDescriptor.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(): Sequence<Any> = when (this) {
is Array<*> -> this.asSequence().flatMap {
it?.flattenCommandComponents() ?: throw java.lang.IllegalArgumentException("unexpected null value")
}
is String -> splitToSequence(' ').filterNot { it.isBlank() }
is PlainText -> content.flattenCommandComponents()
is SingleMessage -> sequenceOf(this)
is MessageChain -> this.asSequence().map { it.flattenCommandComponents() }
else -> throw IllegalArgumentException("Illegal component: $this")
}
internal fun CommandFullName.checkFullName(errorHint: String): CommandFullName {
return flattenCommandComponents().toList().also {
require(it.isNotEmpty()) { "$errorHint must not be empty" }
}.toTypedArray()
}
/** /**
* 构建一个 [CommandDescriptor] * 构建一个 [CommandDescriptor]
*/ */
@Suppress("FunctionName") @Suppress("FunctionName")
inline fun CommandDescriptor( inline fun CommandDescriptor(
fullName: String, vararg fullName: Any,
block: CommandDescriptorBuilder.() -> Unit block: CommandDescriptorBuilder.() -> Unit = {}
): CommandDescriptor = CommandDescriptorBuilder(fullName).apply(block).build() ): CommandDescriptor = CommandDescriptorBuilder(fullName).apply(block).build()
class CommandDescriptorBuilder( class CommandDescriptorBuilder(
val fullName: String vararg val fullName: Any
) { ) {
@PublishedApi @PublishedApi
internal var context: CommandParserContext = CommandParserContext.Builtins internal var context: CommandParserContext = CommandParserContext.Builtins
@ -62,7 +152,10 @@ class CommandDescriptorBuilder(
internal var usage: String = "<no usage>" internal var usage: String = "<no usage>"
@PublishedApi @PublishedApi
internal var aliases: MutableList<String> = mutableListOf() internal var aliases: MutableList<CommandFullName> = mutableListOf()
@PublishedApi
internal var description: String = ""
/** 增加指令参数解析器列表 */ /** 增加指令参数解析器列表 */
@JvmSynthetic @JvmSynthetic
@ -91,8 +184,15 @@ class CommandDescriptorBuilder(
usage = message usage = message
} }
fun alias(vararg name: String): CommandDescriptorBuilder = apply { fun description(description: String): CommandDescriptorBuilder = apply {
this.aliases.addAll(name) this.description = description
}
/**
* 添加一个别名
*/
fun alias(vararg fullName: Any): CommandDescriptorBuilder = apply {
this.aliases.add(fullName)
} }
fun param(vararg params: CommandParam<*>): CommandDescriptorBuilder = apply { fun param(vararg params: CommandParam<*>): CommandDescriptorBuilder = apply {
@ -105,7 +205,7 @@ class CommandDescriptorBuilder(
type: KClass<T>, type: KClass<T>,
overrideParser: CommandArgParser<T>? = null overrideParser: CommandArgParser<T>? = null
): CommandDescriptorBuilder = apply { ): CommandDescriptorBuilder = apply {
this.params.add(CommandParam(name, type).apply { this.parser = overrideParser }) this.params.add(CommandParam(name, type).apply { this._overrideParser = overrideParser })
} }
fun <T : Any> param( fun <T : Any> param(
@ -143,7 +243,7 @@ class CommandDescriptorBuilder(
} }
fun build(): CommandDescriptor = fun build(): CommandDescriptor =
CommandDescriptor(fullName, context, params, usage, aliases.toTypedArray(), permission) CommandDescriptor(fullName, usage, params, description, context, aliases.toTypedArray(), permission)
} }
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS") @Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
@ -154,7 +254,7 @@ inline class ParamBlock internal constructor(@PublishedApi internal val list: Mu
/** 指定 [CommandParam.overrideParser] */ /** 指定 [CommandParam.overrideParser] */
infix fun <T : Any> CommandParam<T>.using(parser: CommandArgParser<T>): CommandParam<T> = infix fun <T : Any> CommandParam<T>.using(parser: CommandArgParser<T>): CommandParam<T> =
this.apply { this.parser = parser } this.apply { this._overrideParser = parser }
/** 覆盖 [CommandArgParser] */ /** 覆盖 [CommandArgParser] */
inline infix fun <reified T : Any> String.using(parser: CommandArgParser<T>): CommandParam<T> = inline infix fun <reified T : Any> String.using(parser: CommandArgParser<T>): CommandParam<T> =

View File

@ -1,3 +1,121 @@
@file:Suppress("NOTHING_TO_INLINE")
@file:JvmName("CommandManager")
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
object CommandManager import kotlinx.atomicfu.locks.withLock
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import java.util.*
import java.util.concurrent.locks.ReentrantLock
typealias CommandFullName = Array<out Any>
interface CommandOwner
val CommandOwner.registeredCommands: List<Command> get() = InternalCommandManager.registeredCommands.filter { it.owner == this }
fun CommandOwner.unregisterAllCommands() {
for (registeredCommand in registeredCommands) {
registeredCommand.unregister()
}
}
/**
* 注册一个指令. 若此指令已经注册或有已经注册的指令与 [allNames] 重名, 返回 `false`
*/
fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock {
with(descriptor) {
if (findDuplicate() != null) {
return false
}
InternalCommandManager.registeredCommands.add(this@register)
for (name in this.allNames) {
InternalCommandManager.nameToCommandMap[name] = this@register
}
return true
}
}
/**
* 查找是否有重名的指令. 返回重名的指令.
*/
fun Command.findDuplicate(): Command? {
return InternalCommandManager.nameToCommandMap.entries.firstOrNull { (names, _) ->
this.allNames.any { it.contentEquals(names) }
}?.value
}
/**
* 取消注册这个指令. 若指令未注册, 返回 `false`
*/
fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
if (!InternalCommandManager.registeredCommands.contains(this)) {
return false
}
InternalCommandManager.registeredCommands.remove(this)
for (name in this.allNames) {
InternalCommandManager.nameToCommandMap.entries.removeIf {
it.key.contentEquals(this.fullName)
}
}
return true
}
/**
* 解析并执行一个指令
* @param args 接受 [String] [Message]
* @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配
*/
suspend fun CommandSender.executeCommand(vararg args: Any): Boolean {
return args.flattenCommandComponents().toList().executeCommand(this)
}
/**
* 解析并执行一个指令
* @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配
*/
suspend fun MessageChain.executeAsCommand(sender: CommandSender): Boolean {
return this.flattenCommandComponents().toList().executeCommand(sender)
}
suspend fun CommandSender.execute(command: Command, args: CommandArgs): Boolean = with(command) {
checkArgs(args)
return onCommand(this@execute, args)
}
suspend fun Command.execute(sender: CommandSender, args: CommandArgs): Boolean = sender.execute(this, args)
suspend fun Command.execute(sender: CommandSender, vararg args: Any): Boolean = sender.execute(this, args)
suspend fun CommandSender.execute(command: Command, vararg args: Any): Boolean = command.execute(this, args)
internal suspend fun List<Any>.executeCommand(sender: CommandSender): Boolean {
val command = InternalCommandManager.matchCommand(this) ?: return false
return command.onCommand(sender, CommandArgs.parseFrom(command, sender, this.drop(command.fullName.size)))
}
internal infix fun CommandFullName.matchesBeginning(list: List<Any>): Boolean {
this.forEachIndexed { index, any ->
if (list[index] != any) return false
}
return true
}
internal object InternalCommandManager {
@JvmField
internal val registeredCommands: MutableList<Command> = mutableListOf()
@JvmField
internal val nameToCommandMap: TreeMap<CommandFullName, Command> = TreeMap(Comparator.comparingInt { it.size })
@JvmField
internal val modifyLock = ReentrantLock()
internal fun matchCommand(splitted: List<Any>): Command? {
nameToCommandMap.entries.forEach {
if (it.key matchesBeginning splitted) return it.value
}
return null
}
}

View File

@ -20,17 +20,27 @@ data class CommandParam<T : Any>(
val type: KClass<T> // exact type val type: KClass<T> // exact type
) { ) {
constructor(name: String?, type: KClass<T>, parser: CommandArgParser<T>) : this(name, type) { constructor(name: String?, type: KClass<T>, parser: CommandArgParser<T>) : this(name, type) {
this.parser = parser this._overrideParser = parser
} }
@Suppress("PropertyName")
@JvmField @JvmField
internal var parser: CommandArgParser<T>? = null internal var _overrideParser: CommandArgParser<T>? = null
/** /**
* 覆盖的 [CommandArgParser]. * 覆盖的 [CommandArgParser].
* *
* 如果非 `null`, 将不会从 [CommandParserContext] 寻找 [CommandArgParser] * 如果非 `null`, 将不会从 [CommandParserContext] 寻找 [CommandArgParser]
*
* @see Command.parserFor
*/ */
val overrideParser: CommandArgParser<T>? get() = parser 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(context: CommandParserContext): CommandArgParser<T>? = context.parserFor(this)

View File

@ -12,7 +12,7 @@
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.AbstractCommandParserContext.Node import net.mamoe.mirai.console.command.AbstractCommandParserContext.ParserPair
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import kotlin.internal.LowPriorityInOverloadResolution import kotlin.internal.LowPriorityInOverloadResolution
@ -21,6 +21,7 @@ import kotlin.reflect.KClass
/** /**
* [KClass] [CommandArgParser] 的匹配 * [KClass] [CommandArgParser] 的匹配
* @see AbstractCommandParserContext
*/ */
interface CommandParserContext { interface CommandParserContext {
operator fun <T : Any> get(klass: KClass<T>): CommandArgParser<T>? operator fun <T : Any> get(klass: KClass<T>): CommandArgParser<T>?
@ -44,7 +45,14 @@ interface CommandParserContext {
}) })
} }
fun <T : Any> CommandParserContext.parserFor(param: CommandParam<T>): CommandArgParser<T>? = this[param.type] 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>? =
param.overrideParser ?: this.context.parserFor(param)
fun <T : Any> Command.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
param.overrideParser ?: this.descriptor.parserFor(param)
/** /**
* 合并两个 [CommandParserContext], [replacer] 将会替换 [this] 中重复的 parser. * 合并两个 [CommandParserContext], [replacer] 将会替换 [this] 中重复的 parser.
@ -56,8 +64,8 @@ operator fun CommandParserContext.plus(replacer: CommandParserContext): CommandP
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
open class AbstractCommandParserContext(val list: List<Node<*>>) : CommandParserContext { open class AbstractCommandParserContext(val list: List<ParserPair<*>>) : CommandParserContext {
class Node<T : Any>( class ParserPair<T : Any>(
val klass: KClass<T>, val klass: KClass<T>,
val parser: CommandArgParser<T> val parser: CommandArgParser<T>
) )
@ -92,10 +100,10 @@ inline fun CommandParserContext(block: CommandParserContextBuilder.() -> Unit):
/** /**
* @see CommandParserContext * @see CommandParserContext
*/ */
class CommandParserContextBuilder : MutableList<Node<*>> by mutableListOf() { class CommandParserContextBuilder : MutableList<ParserPair<*>> by mutableListOf() {
@JvmName("add") @JvmName("add")
inline infix fun <T : Any> KClass<T>.with(parser: CommandArgParser<T>): Node<*> = inline infix fun <T : Any> KClass<T>.with(parser: CommandArgParser<T>): ParserPair<*> =
Node(this, parser) ParserPair(this, parser)
/** /**
* 添加一个指令解析器 * 添加一个指令解析器
@ -104,7 +112,7 @@ class CommandParserContextBuilder : MutableList<Node<*>> by mutableListOf() {
@LowPriorityInOverloadResolution @LowPriorityInOverloadResolution
inline infix fun <T : Any> KClass<T>.with( inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T
): Node<*> = Node(this, CommandArgParser(parser)) ): ParserPair<*> = ParserPair(this, CommandArgParser(parser))
/** /**
* 添加一个指令解析器 * 添加一个指令解析器
@ -112,7 +120,7 @@ class CommandParserContextBuilder : MutableList<Node<*>> by mutableListOf() {
@JvmSynthetic @JvmSynthetic
inline infix fun <T : Any> KClass<T>.with( inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandArgParser<T>.(s: String) -> T crossinline parser: CommandArgParser<T>.(s: String) -> T
): Node<*> = Node(this, CommandArgParser { s: String, _: CommandSender -> parser(s) }) ): ParserPair<*> = ParserPair(this, CommandArgParser { s: String, _: CommandSender -> parser(s) })
} }

View File

@ -11,7 +11,6 @@ package net.mamoe.mirai.console.command
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
@ -58,11 +57,13 @@ abstract class AbstractCommandSender : CommandSender {
*/ */
object ConsoleCommandSender : AbstractCommandSender() { object ConsoleCommandSender : AbstractCommandSender() {
override suspend fun sendMessage(messageChain: Message) { override suspend fun sendMessage(messageChain: Message) {
MiraiConsole.logger("[Command]", 0, messageChain.toString()) TODO()
// MiraiConsole.logger("[Command]", 0, messageChain.toString())
} }
override suspend fun sendMessage(message: String) { override suspend fun sendMessage(message: String) {
MiraiConsole.logger("[Command]", 0, message) TODO()
// MiraiConsole.logger("[Command]", 0, message)
} }
override suspend fun flushMessage() { override suspend fun flushMessage() {

View File

@ -9,22 +9,26 @@
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
import kotlinx.coroutines.GlobalScope suspend fun main() {
import kotlinx.coroutines.launch ConsoleCommandSender.execute(DefaultCommands.Test, "test")
import net.mamoe.mirai.Bot }
import net.mamoe.mirai.Bot.Companion.botInstances
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugins.PluginManager
import net.mamoe.mirai.console.utils.addManager
import net.mamoe.mirai.console.utils.checkManager
import net.mamoe.mirai.console.utils.managers
import net.mamoe.mirai.console.utils.removeManager
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.message.GroupMessageEvent
import net.mamoe.mirai.utils.SimpleLogger
import java.util.*
internal object DefaultCommands {
object Test : ConsoleCommand(
CommandDescriptor("test") {
param<String>()
}
) {
override suspend fun onCommand(sender: CommandSender, args: CommandArgs): Boolean {
val s = args.getReified<String>()
sender.sendMessage(s)
return true
}
}
}
/*
/** /**
* Some defaults commands are recommend to be replaced by plugin provided commands * Some defaults commands are recommend to be replaced by plugin provided commands
@ -369,3 +373,5 @@ internal object DefaultCommands {
} }
} }
} }
*/

View File

@ -12,13 +12,12 @@
package net.mamoe.mirai.console.plugins package net.mamoe.mirai.console.plugins
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.Command import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandOwner
import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.events.EventListener import net.mamoe.mirai.console.events.EventListener
import net.mamoe.mirai.console.scheduler.PluginScheduler import net.mamoe.mirai.console.scheduler.PluginScheduler
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.SimpleLogger
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -28,15 +27,17 @@ import kotlin.coroutines.EmptyCoroutineContext
* 所有插件的基类 * 所有插件的基类
*/ */
abstract class PluginBase abstract class PluginBase
@JvmOverloads constructor(coroutineContext: CoroutineContext = EmptyCoroutineContext) : CoroutineScope { @JvmOverloads constructor(coroutineContext: CoroutineContext = EmptyCoroutineContext) : CoroutineScope, CommandOwner {
final override val coroutineContext: CoroutineContext = coroutineContext + SupervisorJob() final override val coroutineContext: CoroutineContext = coroutineContext + SupervisorJob()
/** /**
* 插件被分配的数据目录数据目录会与插件名称同名 * 插件被分配的数据目录数据目录会与插件名称同名
*/ */
val dataFolder: File by lazy { val dataFolder: File by lazy {
TODO()
/*
File(PluginManager.pluginsPath + "/" + PluginManager.lastPluginName) File(PluginManager.pluginsPath + "/" + PluginManager.lastPluginName)
.also { it.mkdir() } .also { it.mkdir() }*/
} }
/** /**
@ -82,13 +83,15 @@ abstract class PluginBase
* 插件的日志 * 插件的日志
*/ */
val logger: MiraiLogger by lazy { val logger: MiraiLogger by lazy {
TODO()
/*
SimpleLogger("Plugin $pluginName") { priority, message, e -> SimpleLogger("Plugin $pluginName") { priority, message, e ->
val identityString = "[${pluginName}]" val identityString = "[${pluginName}]"
MiraiConsole.logger(priority, identityString, 0, message) MiraiConsole.logger(priority, identityString, 0, message)
if (e != null) { if (e != null) {
MiraiConsole.logger(priority, identityString, 0, e) MiraiConsole.logger(priority, identityString, 0, e)
} }
} }*/
} }
/** /**
@ -98,10 +101,13 @@ abstract class PluginBase
return try { return try {
this.javaClass.classLoader.getResourceAsStream(fileName) this.javaClass.classLoader.getResourceAsStream(fileName)
} catch (e: Exception) { } catch (e: Exception) {
TODO()
/*
PluginManager.getFileInJarByName( PluginManager.getFileInJarByName(
this.pluginName, this.pluginName,
fileName fileName
) )*/
} }
} }

View File

@ -14,18 +14,18 @@ package net.mamoe.mirai.console.plugins
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.Command import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.description
import net.mamoe.mirai.console.command.unregisterAllCommands
import net.mamoe.mirai.console.encodeToString import net.mamoe.mirai.console.encodeToString
import net.mamoe.mirai.utils.LockFreeLinkedList import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.SimpleLogger
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.net.JarURLConnection import java.net.JarURLConnection
import java.net.URL import java.net.URL
import java.util.jar.JarFile import java.util.jar.JarFile
val PluginBase.description: PluginDescription get() = PluginManager.getPluginDescription(this) val PluginBase.description: PluginDescription get() = TODO()
object PluginManagerOld { object PluginManagerOld {
/** /**
@ -53,9 +53,9 @@ object PluginManagerOld {
*/ */
@JvmOverloads @JvmOverloads
fun disablePlugins(throwable: CancellationException? = null) { fun disablePlugins(throwable: CancellationException? = null) {
CommandManager.clearPluginsCommands() pluginsSequence.forEach { plugin ->
pluginsSequence.forEach { plugin.unregisterAllCommands()
it.disable(throwable) plugin.disable(throwable)
} }
nameToPluginBaseMap.clear() nameToPluginBaseMap.clear()
pluginDescriptions.clear() pluginDescriptions.clear()
@ -87,10 +87,7 @@ object PluginManagerOld {
File(it).mkdirs() File(it).mkdirs()
} }
private val logger = SimpleLogger("Plugin Manager") { p, message, e -> private val logger = MiraiConsole.newLogger("Plugin Manager")
MiraiConsole.logger(p, "[Plugin Manager]", 0, message)
MiraiConsole.logger(p, "[Plugin Manager]", 0, e)
}
/** /**
* 加载成功的插件, 名字->插件 * 加载成功的插件, 名字->插件
@ -351,7 +348,7 @@ object PluginManagerOld {
plugin: PluginBase, plugin: PluginBase,
exception: CancellationException? = null exception: CancellationException? = null
) { ) {
CommandManager.clearPluginCommands(plugin) plugin.unregisterAllCommands()
plugin.disable(exception) plugin.disable(exception)
nameToPluginBaseMap.remove(plugin.pluginName) nameToPluginBaseMap.remove(plugin.pluginName)
pluginDescriptions.remove(plugin.pluginName) pluginDescriptions.remove(plugin.pluginName)

View File

@ -10,7 +10,6 @@
package net.mamoe.mirai.console.plugins package net.mamoe.mirai.console.plugins
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.utils.SimpleLogger
import java.io.File import java.io.File
import java.net.URLClassLoader import java.net.URLClassLoader
@ -18,10 +17,7 @@ internal class PluginsLoader(private val parentClassLoader: ClassLoader) {
private val loggerName = "PluginsLoader" private val loggerName = "PluginsLoader"
private val pluginLoaders = linkedMapOf<String, PluginClassLoader>() private val pluginLoaders = linkedMapOf<String, PluginClassLoader>()
private val classesCache = mutableMapOf<String, Class<*>>() private val classesCache = mutableMapOf<String, Class<*>>()
private val logger = SimpleLogger(loggerName) { p, message, e -> private val logger = MiraiConsole.newLogger(loggerName)
MiraiConsole.logger(p, "[${loggerName}]", 0, message)
MiraiConsole.logger(p, "[${loggerName}]", 0, e)
}
/** /**
* 清除所有插件加载器 * 清除所有插件加载器

View File

@ -27,7 +27,7 @@ interface MiraiConsoleFrontEnd {
*/ */
val pluginCenter: PluginCenter get() = CuiPluginCenter val pluginCenter: PluginCenter get() = CuiPluginCenter
fun loggerFor(identity: Long): MiraiLogger fun loggerFor(identity: String?): MiraiLogger
/** /**
* UI 层准备接受新增的一个BOT * UI 层准备接受新增的一个BOT

View File

@ -8,6 +8,7 @@
*/ */
package net.mamoe.mirai.console.graphical package net.mamoe.mirai.console.graphical
import kotlinx.coroutines.cancel
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import tornadofx.launch import tornadofx.launch
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -24,7 +25,7 @@ class MiraiConsoleGraphicalLoader {
this.coreVersion = coreVersion this.coreVersion = coreVersion
this.consoleVersion = consoleVersion this.consoleVersion = consoleVersion
Runtime.getRuntime().addShutdownHook(thread(start = false) { Runtime.getRuntime().addShutdownHook(thread(start = false) {
MiraiConsole.stop() MiraiConsole.cancel()
}) })
launch<MiraiGraphicalUI>() launch<MiraiGraphicalUI>()
} }

View File

@ -23,41 +23,46 @@ pluginManagement {
rootProject.name = 'mirai-console' rootProject.name = 'mirai-console'
def onlyBackEnd = true
include(':mirai-console') include(':mirai-console')
project(':mirai-console').dir = file("backend/mirai-console") project(':mirai-console').dir = file("backend/mirai-console")
include(':mirai-console-pure') if (!onlyBackEnd) {
project(':mirai-console-pure').dir = file("frontend/mirai-console-pure")
include(':mirai-console-terminal') include(':mirai-console-pure')
project(':mirai-console-terminal').dir = file("frontend/mirai-console-terminal") project(':mirai-console-pure').dir = file("frontend/mirai-console-pure")
try{ include(':mirai-console-terminal')
def javaVersion = System.getProperty("java.version") project(':mirai-console-terminal').dir = file("frontend/mirai-console-terminal")
def versionPos = javaVersion.indexOf(".")
def javaVersionNum = javaVersion.substring(0, 1).toInteger()
if (javaVersion.startsWith("1.")) { try {
javaVersionNum = javaVersion.substring(2, 3).toInteger() def javaVersion = System.getProperty("java.version")
} else { def versionPos = javaVersion.indexOf(".")
if (versionPos==-1) versionPos = javaVersion.indexOf("-") def javaVersionNum = javaVersion.substring(0, 1).toInteger()
if (versionPos==-1){
println("jdk version unknown") if (javaVersion.startsWith("1.")) {
}else{ javaVersionNum = javaVersion.substring(2, 3).toInteger()
javaVersionNum = javaVersion.substring(0, versionPos).toInteger() } else {
if (versionPos == -1) versionPos = javaVersion.indexOf("-")
if (versionPos == -1) {
println("jdk version unknown")
} else {
javaVersionNum = javaVersion.substring(0, versionPos).toInteger()
}
}
if (javaVersionNum >= 9) {
include(':mirai-console-graphical')
project(':mirai-console-graphical').dir = file("frontend/mirai-console-graphical")
} else {
println("jdk版本为 " + javaVersionNum)
println("当前使用的 JDK 版本为 ${System.getProperty("java.version")}, 请使用JDK 9以上版本引入模块 `:mirai-console-graphical`\n")
} }
}
if (javaVersionNum >= 9) {
include(':mirai-console-graphical')
project(':mirai-console-graphical').dir = file("frontend/mirai-console-graphical")
} else {
println("jdk版本为 "+ javaVersionNum)
println("当前使用的 JDK 版本为 ${System.getProperty("java.version")}, 请使用JDK 9以上版本引入模块 `:mirai-console-graphical`\n")
}
}catch(Exception ignored){ } catch (Exception ignored) {
println("无法确定 JDK 版本, 将不会引入 `:mirai-console-graphical`") println("无法确定 JDK 版本, 将不会引入 `:mirai-console-graphical`")
}
} }