diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt index 48c49ff01..9207be054 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt @@ -17,7 +17,10 @@ import kotlinx.coroutines.Job import kotlinx.io.charsets.Charset import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole.INSTANCE +import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.ConsoleCommandSender +import net.mamoe.mirai.console.command.internal.InternalCommandManager +import net.mamoe.mirai.console.command.primaryName import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.center.CuiPluginCenter @@ -75,6 +78,11 @@ interface MiraiConsole : CoroutineScope { companion object INSTANCE : MiraiConsole by MiraiConsoleInternal } +/** + * 获取 [MiraiConsole] 的 [Job] + */ +val MiraiConsole.job: Job + get() = this.coroutineContext[Job] ?: error("Internal error: Job not found in MiraiConsole.coroutineContext") //// internal @@ -132,10 +140,14 @@ internal object MiraiConsoleInternal : CoroutineScope, IMiraiConsole, MiraiConso if (coroutineContext[Job] == null) { throw IllegalMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.") } - this.coroutineContext[Job]!!.invokeOnCompletion { + job.invokeOnCompletion { Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) } } + BuiltInCommands.registerAll() + mainLogger.info { "Preparing built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" } + InternalCommandManager.commandListener // start + mainLogger.info { "Loading plugins..." } PluginManager.loadEnablePlugins() mainLogger.info { "${PluginManager.plugins.size} plugin(s) loaded." } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt index 16df71fa8..f8ac425b1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt @@ -9,12 +9,114 @@ package net.mamoe.mirai.console.command -import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI +import kotlinx.coroutines.runBlocking +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 kotlin.concurrent.thread +import kotlin.system.exitProcess + +/** + * 添加一个 [Bot] 实例到全局 Bot 列表, 但不登录. + */ +fun MiraiConsole.addBot(id: Long, password: String): Bot { + return Bot(id, password) { + fileBasedDeviceInfo() + this.loginSolver = this@addBot.frontEnd.createLoginSolver() + redirectNetworkLogToDirectory() + redirectBotLogToDirectory() + } +} interface BuiltInCommand : Command -@ConsoleExperimentalAPI -object BuiltInCommands + +@Suppress("unused") +object BuiltInCommands { + + val all: Array by lazy { + this::class.nestedClasses.mapNotNull { it.objectInstance as? Command }.toTypedArray() + } + + internal fun registerAll() { + BuiltInCommands::class.nestedClasses.forEach { + (it.objectInstance as? Command)?.register() + } + } + + object Help : SimpleCommand( + ConsoleCommandOwner, "help", "?", + description = "Gets help about the console." + ) { + init { + Runtime.getRuntime().addShutdownHook(thread(false) { + runBlocking { Stop.execute(ConsoleCommandSender.instance) } + }) + } + + @Handler + suspend fun CommandSender.handle() { + sendMessage("现在有指令: ${allRegisteredCommands.joinToString { it.primaryName }}") + sendMessage("帮助还没写, 将就一下") + } + } + + object Stop : SimpleCommand( + ConsoleCommandOwner, "stop", "shutdown", "exit", + description = "Stop the whole world." + ) { + init { + Runtime.getRuntime().addShutdownHook(thread(false) { + runBlocking { Stop.execute(ConsoleCommandSender.instance) } + }) + } + + @Handler + suspend fun CommandSender.handle() { + sendMessage("Stopping mirai-console") + kotlin.runCatching { + MiraiConsole.job.cancel() + }.fold( + onSuccess = { sendMessage("mirai-console stopped successfully.") }, + onFailure = { + MiraiConsole.mainLogger.error(it) + sendMessage(it.localizedMessage ?: it.message ?: it.toString()) + } + ) + exitProcess(0) + } + } + + object Login : SimpleCommand( + ConsoleCommandOwner, "login", + description = "Log in a bot account." + ) { + @Handler + suspend fun CommandSender.handle(id: Long, password: String) { + sendMessage( + kotlin.runCatching { + MiraiConsole.addBot(id, password).alsoLogin() + }.fold( + onSuccess = { "${it.nick} ($id) Login succeed" }, + onFailure = { throwable -> + "Login failed: ${throwable.localizedMessage ?: throwable.message}" + + if (this is MessageEventContextAware<*>) { + this.fromEvent.selectMessagesUnit { + "stacktrace" reply { + throwable.stacktraceString + } + } + "test" + } else "" + } + ) + ) + } + } +} /* diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index 5717e20b8..e4fb89abd 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -53,6 +53,12 @@ object ConsoleCommandOwner : CommandOwner() */ val CommandOwner.registeredCommands: List get() = InternalCommandManager.registeredCommands.filter { it.owner == this } +/** + * 获取所有已经注册了指令列表. + * @see JCommandManager.getRegisteredCommands Java 方法 + */ +val allRegisteredCommands: List get() = InternalCommandManager.registeredCommands.toList() // copy + /** * 指令前缀, 如 '/' * @see JCommandManager.getCommandPrefix Java 方法 diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt index e0dc664fe..a172fb73e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt @@ -14,9 +14,10 @@ package net.mamoe.mirai.console.command import kotlinx.coroutines.runBlocking import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsoleInternal +import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI import net.mamoe.mirai.console.utils.JavaFriendlyAPI import net.mamoe.mirai.contact.* -import net.mamoe.mirai.message.MessageEvent +import net.mamoe.mirai.message.* import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.PlainText @@ -83,6 +84,12 @@ fun User.asCommandSender(): UserCommandSender { } } +/** + * 表示由 [MessageEvent] 触发的指令 + */ +interface MessageEventContextAware : MessageEventExtensions { + val fromEvent: E +} /** * 代表一个用户私聊机器人执行指令 @@ -99,7 +106,7 @@ sealed class UserCommandSender : CommandSender, BotAwareCommandSender { */ abstract val subject: Contact - final override val bot: Bot get() = user.bot + override val bot: Bot get() = user.bot final override suspend fun sendMessage(message: Message) { subject.sendMessage(message) @@ -110,15 +117,46 @@ sealed class UserCommandSender : CommandSender, BotAwareCommandSender { * 代表一个用户私聊机器人执行指令 * @see Friend.asCommandSender */ -class FriendCommandSender(override val user: Friend) : UserCommandSender() { +open class FriendCommandSender(final override val user: Friend) : UserCommandSender() { override val subject: Contact get() = user } +/** + * 代表一个用户私聊机器人执行指令 + * @see Friend.asCommandSender + */ +class FriendCommandSenderOnMessage(override val fromEvent: FriendMessageEvent) : FriendCommandSender(fromEvent.sender), + MessageEventContextAware, MessageEventExtensions by fromEvent { + override val subject: Contact get() = super.subject + override val bot: Bot get() = super.bot +} + +/** + * 代表一个群成员执行指令. + * @see Member.asCommandSender + */ +open class MemberCommandSender(final override val user: Member) : UserCommandSender() { + inline val group: Group get() = user.group + override val subject: Contact get() = group +} + /** * 代表一个群成员在群内执行指令. * @see Member.asCommandSender */ -class MemberCommandSender(override val user: Member) : UserCommandSender() { - inline val group: Group get() = user.group - override val subject: Contact get() = group +class MemberCommandSenderOnMessage(override val fromEvent: GroupMessageEvent) : MemberCommandSender(fromEvent.sender), + MessageEventContextAware, MessageEventExtensions by fromEvent { + override val subject: Contact get() = super.subject + override val bot: Bot get() = super.bot +} + +/** + * 代表一个群成员通过临时会话私聊机器人执行指令. + * @see Member.asCommandSender + */ +@ConsoleExperimentalAPI +class TempCommandSenderOnMessage(override val fromEvent: TempMessageEvent) : MemberCommandSender(fromEvent.sender), + MessageEventContextAware, MessageEventExtensions by fromEvent { + override val subject: Contact get() = super.subject + override val bot: Bot get() = super.bot } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal/internal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal/internal.kt index d62a7902a..4338a946e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal/internal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal/internal.kt @@ -9,9 +9,15 @@ package net.mamoe.mirai.console.command.internal +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.* +import net.mamoe.mirai.console.job import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.event.Listener +import net.mamoe.mirai.event.subscribeAlways +import net.mamoe.mirai.message.MessageEvent import java.util.concurrent.locks.ReentrantLock @@ -22,7 +28,7 @@ internal infix fun Array.matchesBeginning(list: List): Boolean { return true } -internal object InternalCommandManager { +internal object InternalCommandManager : CoroutineScope by CoroutineScope(MiraiConsole.job) { const val COMMAND_PREFIX = "/" @JvmField @@ -56,6 +62,18 @@ internal object InternalCommandManager { } return optionalPrefixCommandMap[rawCommand.toLowerCase()] } + + internal val commandListener: Listener by lazy { + @Suppress("RemoveExplicitTypeArguments") + subscribeAlways( + concurrency = Listener.ConcurrencyKind.CONCURRENT, + priority = Listener.EventPriority.HIGH + ) { + if (this.sender.asCommandSender().executeCommand(message) != null) { + intercept() + } + } + } } internal infix fun Array.intersectsIgnoringCase(other: Array): Boolean { diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 44432903a..30f83dd6b 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,7 +9,7 @@ object Versions { const val core = "1.1-EA" - const val console = "1.1-dev-1" + const val console = "1.0-dev-1" const val consoleGraphical = "0.0.7" const val consoleTerminal = "0.1.0" const val consolePure = "0.1.0"