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
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.isActive
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.job
import net.mamoe.mirai.console.stacktraceString
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.system.exitProcess
@ -24,15 +31,44 @@ import kotlin.system.exitProcess
*/
fun MiraiConsole.addBot(id: Long, password: String): Bot {
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()
this.loginSolver = this@addBot.frontEnd.createLoginSolver()
redirectNetworkLogToDirectory()
redirectBotLogToDirectory()
// redirectBotLogToDirectory()
}
}
interface BuiltInCommand : Command
@Suppress("EXPOSED_SUPER_INTERFACE")
interface BuiltInCommand : Command, BuiltInCommandInternal
// for identification
internal interface BuiltInCommandInternal : Command
@Suppress("unused")
object BuiltInCommands {
@ -50,7 +86,7 @@ object BuiltInCommands {
object Help : SimpleCommand(
ConsoleCommandOwner, "help",
description = "Gets help about the console."
) {
), BuiltInCommand {
init {
Runtime.getRuntime().addShutdownHook(thread(false) {
runBlocking { Stop.execute(ConsoleCommandSender.instance) }
@ -67,18 +103,23 @@ object BuiltInCommands {
object Stop : SimpleCommand(
ConsoleCommandOwner, "stop", "shutdown", "exit",
description = "Stop the whole world."
) {
), BuiltInCommand {
init {
Runtime.getRuntime().addShutdownHook(thread(false) {
if (!MiraiConsole.isActive) {
return@thread
}
runBlocking { Stop.execute(ConsoleCommandSender.instance) }
})
}
private val closingLock = Mutex()
@Handler
suspend fun CommandSender.handle() {
suspend fun CommandSender.handle(): Unit = closingLock.withLock {
sendMessage("Stopping mirai-console")
kotlin.runCatching {
MiraiConsole.job.cancel()
MiraiConsole.job.cancelAndJoin()
}.fold(
onSuccess = { sendMessage("mirai-console stopped successfully.") },
onFailure = {
@ -93,16 +134,16 @@ object BuiltInCommands {
object Login : SimpleCommand(
ConsoleCommandOwner, "login",
description = "Log in a bot account."
) {
), BuiltInCommand {
@Handler
suspend fun CommandSender.handle(id: Long, password: String) {
sendMessage(
kotlin.runCatching {
MiraiConsole.addBot(id, password).alsoLogin()
}.fold(
onSuccess = { "${it.nick} ($id) Login succeed" },
onSuccess = { sendMessage("${it.nick} ($id) Login succeed") },
onFailure = { throwable ->
"Login failed: ${throwable.localizedMessage ?: throwable.message}" +
sendMessage("Login failed: ${throwable.localizedMessage ?: throwable.message ?: throwable.toString()}" +
if (this is MessageEventContextAware<*>) {
this.fromEvent.selectMessagesUnit {
"stacktrace" reply {
@ -110,10 +151,11 @@ object BuiltInCommands {
}
}
"test"
} else ""
} else "")
throw throwable
}
)
)
}
}
}

View File

@ -34,6 +34,9 @@ abstract class SimpleCommand @JvmOverloads constructor(
) : Command, AbstractReflectionCommand(owner, names, description, permission, prefixOptional),
CommandParserContextAware {
override val usage: String
get() = super.usage
/**
* 标注指令处理器
*/
@ -47,7 +50,7 @@ abstract class SimpleCommand @JvmOverloads constructor(
}
@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>) {
subCommands.single().parseAndExecute(this, args, false)

View File

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

View File

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

View File

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

View File

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