Fix builtin commands and frontend-pure command processing

This commit is contained in:
Him188 2020-06-29 22:52:15 +08:00
parent 58eac01cad
commit 56a01c23fe
6 changed files with 124 additions and 57 deletions

View File

@ -9,13 +9,20 @@
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.isActive
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.job import net.mamoe.mirai.console.job
import net.mamoe.mirai.console.stacktraceString import net.mamoe.mirai.console.stacktraceString
import net.mamoe.mirai.event.selectMessagesUnit import net.mamoe.mirai.event.selectMessagesUnit
import net.mamoe.mirai.utils.DirectoryLogger
import net.mamoe.mirai.utils.weeksToMillis
import java.io.File
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -24,15 +31,44 @@ import kotlin.system.exitProcess
*/ */
fun MiraiConsole.addBot(id: Long, password: String): Bot { fun MiraiConsole.addBot(id: Long, password: String): Bot {
return Bot(id, password) { return Bot(id, password) {
/**
* 重定向 [网络日志][networkLoggerSupplier] 到指定目录. 若目录不存在将会自动创建 ([File.mkdirs])
* @see DirectoryLogger
* @see redirectNetworkLogToDirectory
*/
fun redirectNetworkLogToDirectory(
dir: File = File("logs"),
retain: Long = 1.weeksToMillis,
identity: (bot: Bot) -> String = { "Net ${it.id}" }
) {
require(!dir.isFile) { "dir must not be a file" }
dir.mkdirs()
networkLoggerSupplier = { DirectoryLogger(identity(it), dir, retain) }
}
fun redirectBotLogToDirectory(
dir: File = File("logs"),
retain: Long = 1.weeksToMillis,
identity: (bot: Bot) -> String = { "Net ${it.id}" }
) {
require(!dir.isFile) { "dir must not be a file" }
dir.mkdirs()
botLoggerSupplier = { DirectoryLogger(identity(it), dir, retain) }
}
fileBasedDeviceInfo() fileBasedDeviceInfo()
this.loginSolver = this@addBot.frontEnd.createLoginSolver() this.loginSolver = this@addBot.frontEnd.createLoginSolver()
redirectNetworkLogToDirectory() redirectNetworkLogToDirectory()
redirectBotLogToDirectory() // redirectBotLogToDirectory()
} }
} }
interface BuiltInCommand : Command @Suppress("EXPOSED_SUPER_INTERFACE")
interface BuiltInCommand : Command, BuiltInCommandInternal
// for identification
internal interface BuiltInCommandInternal : Command
@Suppress("unused") @Suppress("unused")
object BuiltInCommands { object BuiltInCommands {
@ -50,7 +86,7 @@ object BuiltInCommands {
object Help : SimpleCommand( object Help : SimpleCommand(
ConsoleCommandOwner, "help", ConsoleCommandOwner, "help",
description = "Gets help about the console." description = "Gets help about the console."
) { ), BuiltInCommand {
init { init {
Runtime.getRuntime().addShutdownHook(thread(false) { Runtime.getRuntime().addShutdownHook(thread(false) {
runBlocking { Stop.execute(ConsoleCommandSender.instance) } runBlocking { Stop.execute(ConsoleCommandSender.instance) }
@ -67,18 +103,23 @@ object BuiltInCommands {
object Stop : SimpleCommand( object Stop : SimpleCommand(
ConsoleCommandOwner, "stop", "shutdown", "exit", ConsoleCommandOwner, "stop", "shutdown", "exit",
description = "Stop the whole world." description = "Stop the whole world."
) { ), BuiltInCommand {
init { init {
Runtime.getRuntime().addShutdownHook(thread(false) { Runtime.getRuntime().addShutdownHook(thread(false) {
if (!MiraiConsole.isActive) {
return@thread
}
runBlocking { Stop.execute(ConsoleCommandSender.instance) } runBlocking { Stop.execute(ConsoleCommandSender.instance) }
}) })
} }
private val closingLock = Mutex()
@Handler @Handler
suspend fun CommandSender.handle() { suspend fun CommandSender.handle(): Unit = closingLock.withLock {
sendMessage("Stopping mirai-console") sendMessage("Stopping mirai-console")
kotlin.runCatching { kotlin.runCatching {
MiraiConsole.job.cancel() MiraiConsole.job.cancelAndJoin()
}.fold( }.fold(
onSuccess = { sendMessage("mirai-console stopped successfully.") }, onSuccess = { sendMessage("mirai-console stopped successfully.") },
onFailure = { onFailure = {
@ -93,16 +134,16 @@ object BuiltInCommands {
object Login : SimpleCommand( object Login : SimpleCommand(
ConsoleCommandOwner, "login", ConsoleCommandOwner, "login",
description = "Log in a bot account." description = "Log in a bot account."
) { ), BuiltInCommand {
@Handler @Handler
suspend fun CommandSender.handle(id: Long, password: String) { suspend fun CommandSender.handle(id: Long, password: String) {
sendMessage(
kotlin.runCatching { kotlin.runCatching {
MiraiConsole.addBot(id, password).alsoLogin() MiraiConsole.addBot(id, password).alsoLogin()
}.fold( }.fold(
onSuccess = { "${it.nick} ($id) Login succeed" }, onSuccess = { sendMessage("${it.nick} ($id) Login succeed") },
onFailure = { throwable -> onFailure = { throwable ->
"Login failed: ${throwable.localizedMessage ?: throwable.message}" + sendMessage("Login failed: ${throwable.localizedMessage ?: throwable.message ?: throwable.toString()}" +
if (this is MessageEventContextAware<*>) { if (this is MessageEventContextAware<*>) {
this.fromEvent.selectMessagesUnit { this.fromEvent.selectMessagesUnit {
"stacktrace" reply { "stacktrace" reply {
@ -110,10 +151,11 @@ object BuiltInCommands {
} }
} }
"test" "test"
} else "" } else "")
throw throwable
} }
) )
)
} }
} }
} }

View File

@ -34,6 +34,9 @@ abstract class SimpleCommand @JvmOverloads constructor(
) : Command, AbstractReflectionCommand(owner, names, description, permission, prefixOptional), ) : Command, AbstractReflectionCommand(owner, names, description, permission, prefixOptional),
CommandParserContextAware { CommandParserContextAware {
override val usage: String
get() = super.usage
/** /**
* 标注指令处理器 * 标注指令处理器
*/ */
@ -47,7 +50,7 @@ abstract class SimpleCommand @JvmOverloads constructor(
} }
@Deprecated("prohibited", level = DeprecationLevel.HIDDEN) @Deprecated("prohibited", level = DeprecationLevel.HIDDEN)
final override suspend fun CommandSender.onDefault(rawArgs: Array<out Any>) = error("shouldn't be reached") override suspend fun CommandSender.onDefault(rawArgs: Array<out Any>) = sendMessage(usage)
final override suspend fun CommandSender.onCommand(args: Array<out Any>) { final override suspend fun CommandSender.onCommand(args: Array<out Any>) {
subCommands.single().parseAndExecute(this, args, false) subCommands.single().parseAndExecute(this, args, false)

View File

@ -54,7 +54,7 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
@Suppress("PropertyName") @Suppress("PropertyName")
internal var _usage: String = "<not yet initialized>" internal var _usage: String = "<not yet initialized>"
final override val usage: String // initialized by subCommand reflection override val usage: String // initialized by subCommand reflection
get() = _usage get() = _usage
abstract suspend fun CommandSender.onDefault(rawArgs: Array<out Any>) abstract suspend fun CommandSender.onDefault(rawArgs: Array<out Any>)
@ -125,9 +125,10 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
argsWithSubCommandNameNotRemoved: Array<out Any>, argsWithSubCommandNameNotRemoved: Array<out Any>,
removeSubName: Boolean removeSubName: Boolean
) { ) {
if (!onCommand( val args = parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) names.size else 0)
if (args == null || !onCommand(
sender, sender,
parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) names.size else 0) args
) )
) { ) {
sender.sendMessage(usage) sender.sendMessage(usage)
@ -136,8 +137,10 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
@JvmField @JvmField
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray() 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> { 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}" } if (rawArgs.size < offset + this.params.size)
return null
//require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" }
return Array(this.params.size) { index -> return Array(this.params.size) { index ->
val param = params[index] val param = params[index]

View File

@ -8,7 +8,7 @@
*/ */
object Versions { object Versions {
const val core = "1.1-EA" const val core = "1.0.3"
const val console = "1.0-dev-1" const val console = "1.0-dev-1"
const val consoleGraphical = "0.0.7" const val consoleGraphical = "0.0.7"
const val consoleTerminal = "0.1.0" const val consoleTerminal = "0.1.0"

View File

@ -32,6 +32,7 @@ dependencies {
compileAndRuntime("net.mamoe:mirai-core:${Versions.core}") compileAndRuntime("net.mamoe:mirai-core:${Versions.core}")
compileAndRuntime(kotlin("stdlib")) // embedded by core compileAndRuntime(kotlin("stdlib")) // embedded by core
runtimeOnly("net.mamoe:mirai-core-qqandroid:${Versions.core}")
testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}") testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}")
testApi(project(":mirai-console")) testApi(project(":mirai-console"))
} }

View File

@ -22,14 +22,12 @@ package net.mamoe.mirai.console.pure
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.CommandExecuteStatus import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.CommandPrefix
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.command.executeCommandDetailed
import net.mamoe.mirai.console.job import net.mamoe.mirai.console.job
import net.mamoe.mirai.console.pure.MiraiConsolePure.Companion.start import net.mamoe.mirai.console.pure.MiraiConsolePure.Companion.start
import net.mamoe.mirai.console.utils.ConsoleInternalAPI import net.mamoe.mirai.console.utils.ConsoleInternalAPI
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.content
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -50,14 +48,19 @@ internal fun startup() {
} }
internal fun startConsoleThread() { internal fun startConsoleThread() {
thread(name = "Console", isDaemon = false) { thread(name = "Console Input") {
val consoleLogger = DefaultLogger("Console") val consoleLogger = DefaultLogger("Console")
try {
kotlinx.coroutines.runBlocking { kotlinx.coroutines.runBlocking {
while (isActive) { while (isActive) {
val next = MiraiConsoleFrontEndPure.requestInput("").let { val next = MiraiConsoleFrontEndPure.requestInput("").let {
if (it.startsWith(CommandPrefix)) { when {
it.startsWith(CommandPrefix) -> {
it it
} else CommandPrefix + it }
it == "?" -> CommandPrefix + BuiltInCommands.Help.primaryName
else -> CommandPrefix + it
}
} }
if (next.isBlank()) { if (next.isBlank()) {
continue continue
@ -68,6 +71,7 @@ internal fun startConsoleThread() {
CommandExecuteStatus.SUCCESSFUL -> { CommandExecuteStatus.SUCCESSFUL -> {
} }
CommandExecuteStatus.EXECUTION_EXCEPTION -> { CommandExecuteStatus.EXECUTION_EXCEPTION -> {
result.exception?.printStackTrace()
} }
CommandExecuteStatus.COMMAND_NOT_FOUND -> { CommandExecuteStatus.COMMAND_NOT_FOUND -> {
consoleLogger.warning("Unknown command: ${result.commandName}") consoleLogger.warning("Unknown command: ${result.commandName}")
@ -75,18 +79,32 @@ internal fun startConsoleThread() {
CommandExecuteStatus.PERMISSION_DENIED -> { CommandExecuteStatus.PERMISSION_DENIED -> {
consoleLogger.warning("Permission denied.") consoleLogger.warning("Permission denied.")
} }
} }
} }
} }
} catch (e: InterruptedException) {
return@thread
}
}.let { thread -> }.let { thread ->
MiraiConsole.job.invokeOnCompletion { MiraiConsole.job.invokeOnCompletion {
runCatching {
thread.interrupt() thread.interrupt()
}.exceptionOrNull()?.printStackTrace()
runCatching {
ConsoleUtils.terminal.close()
}.exceptionOrNull()?.printStackTrace()
} }
} }
} }
internal object ConsoleCommandSenderImpl : ConsoleCommandSender() { internal object ConsoleCommandSenderImpl : ConsoleCommandSender() {
override suspend fun sendMessage(message: Message) { override suspend fun sendMessage(message: Message) {
kotlin.runCatching {
ConsoleUtils.lineReader.printAbove(message.contentToString()) ConsoleUtils.lineReader.printAbove(message.contentToString())
}.onFailure {
println(message.content)
it.printStackTrace()
}
} }
} }