From ae4d5074a5a3e78b0c7f98ab1d88bcfcd9d2296e Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 30 Aug 2020 17:31:52 +0800 Subject: [PATCH] Redesign CommandSender; Optimize frontend implementation APIs --- .../net/mamoe/mirai/console/MiraiConsole.kt | 2 +- .../MiraiConsoleFrontEndDescription.kt | 10 + .../console/MiraiConsoleImplementation.kt | 87 ++- .../mirai/console/command/BuiltInCommands.kt | 2 +- .../mirai/console/command/CommandSender.kt | 674 ++++++++++++++---- .../mirai/console/command/SimpleCommand.kt | 4 +- .../description/CommandArgParserBuiltins.kt | 2 +- .../MiraiConsoleImplementationBridge.kt | 14 +- .../internal/command/CommandManagerImpl.kt | 3 +- .../internal/data/valueFromKTypeImpl.kt | 9 + .../internal/plugin/JarPluginLoaderImpl.kt | 2 +- .../internal/plugin/JvmPluginInternal.kt | 2 +- .../internal/plugin/PluginManagerImpl.kt | 2 +- .../mamoe/mirai/console/TestMiraiConosle.kt | 22 +- .../mirai/console/command/TestCommand.kt | 2 +- .../mamoe/mirai/console/pure/ConsoleThread.kt | 3 +- .../pure/MiraiConsoleImplementationPure.kt | 9 +- .../console/pure/MiraiConsolePureLoader.kt | 12 +- 18 files changed, 667 insertions(+), 194 deletions(-) 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 28ff81c47..a525eb575 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 @@ -85,7 +85,7 @@ public interface MiraiConsole : CoroutineScope { * 创建一个 logger */ @ConsoleExperimentalAPI - public fun newLogger(identity: String?): MiraiLogger + public fun createLogger(identity: String?): MiraiLogger public companion object INSTANCE : MiraiConsole by MiraiConsoleImplementationBridge { /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEndDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEndDescription.kt index f4257320e..21c1f8103 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEndDescription.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEndDescription.kt @@ -30,8 +30,18 @@ public interface MiraiConsoleFrontEndDescription { */ public val version: Semver + /** + * 兼容的 [MiraiConsole] 后端版本号 + * + * 如 `Semver("[1.0.0, 2.0.0)", Semver.SemverType.IVY)` + * + * 返回 `null` 表示禁止 [MiraiConsole] 后端检查版本兼容性. + */ + public val compatibleBackendVersion: Semver? get() = null + /** * 返回显示在 [MiraiConsole] 启动时的信息 */ + @JvmDefault public fun render(): String = "Frontend ${name}: version ${version}, provided by $vendor" } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt index 1efa9ecac..42e03786f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt @@ -12,7 +12,7 @@ package net.mamoe.mirai.console import kotlinx.atomicfu.locks.withLock -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.* import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.command.ConsoleCommandSender @@ -20,19 +20,22 @@ import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader -import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleInput +import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.LoginSolver import net.mamoe.mirai.utils.MiraiLogger import java.nio.file.Path +import java.util.* import java.util.concurrent.locks.ReentrantLock import kotlin.annotation.AnnotationTarget.* import kotlin.coroutines.CoroutineContext /** - * 标记一个仅用于 [MiraiConsole] 前端实现的 API. 这些 API 只应由前端实现者使用, 而不应该被插件或其他调用者使用. + * 标记一个仅用于 [MiraiConsole] 前端实现的 API. + * + * 这些 API 只应由前端实现者使用, 而不应该被插件或其他调用者使用. */ @Retention(AnnotationRetention.SOURCE) @RequiresOptIn(level = RequiresOptIn.Level.WARNING) @@ -41,42 +44,81 @@ import kotlin.coroutines.CoroutineContext public annotation class ConsoleFrontEndImplementation /** - * 由前端实现这个接口 + * 实现 [MiraiConsole] 的接口 * - * @see MiraiConsoleImplementation.start + * **注意**: 随着 Console 的更新, 在版本号 `x.y.z` 的 `y` 修改时此接口可能就会变动. 意味着前端实现着需要跟随 Console 更新. + * + * @see MiraiConsoleImplementation.start 启动 */ @ConsoleFrontEndImplementation public interface MiraiConsoleImplementation : CoroutineScope { /** - * [MiraiConsole] 的 [CoroutineScope.coroutineContext] + * [MiraiConsole] 的 [CoroutineScope.coroutineContext], 必须拥有如下元素 + * + * - [Job]: 用于管理整个 [MiraiConsole] 的生命周期. 当此 [Job] 被 [Job.cancel] 后, [MiraiConsole] 就会结束. + * - [CoroutineExceptionHandler]: 用于处理 [MiraiConsole] 所有协程抛出的 **未被捕捉** 的异常. 不是所有异常都会被传递到这里. */ public override val coroutineContext: CoroutineContext /** - * Console 运行根目录 - * @see MiraiConsole.rootPath + * Console 运行根目录绝对路径 (否则可能会被一些 native 插件覆盖相对路径) + * @see MiraiConsole.rootPath 获取更多信息 */ public val rootPath: Path /** - * Console 前端接口 + * 本前端实现的描述信息 */ - @ConsoleExperimentalAPI public val frontEndDescription: MiraiConsoleFrontEndDescription - /** - * 与前端交互所使用的 Logger - */ - public val mainLogger: MiraiLogger - /** * 内建加载器列表, 一般需要包含 [JarPluginLoader]. * - * @return 不可变的 [List] + * @return 不可变的 [List], [Collections.unmodifiableList] */ public val builtInPluginLoaders: List> - public val consoleCommandSender: ConsoleCommandSender + /** + * 由 Kotlin 用户实现 + * + * @see [ConsoleCommandSender] + */ + @ConsoleFrontEndImplementation + public interface ConsoleCommandSenderImpl { + @JvmSynthetic + public suspend fun sendMessage(message: Message) + + @JvmSynthetic + public suspend fun sendMessage(message: String) + } + + /** + * 由 Java 用户实现 + * + * @see [ConsoleCommandSender] + */ + @Suppress("INAPPLICABLE_JVM_NAME") + @ConsoleFrontEndImplementation + public interface JConsoleCommandSenderImpl : ConsoleCommandSenderImpl { + @JvmName("sendMessage") + public fun sendMessageJ(message: Message) + + @JvmName("sendMessage") + public fun sendMessageJ(message: String) + + + @JvmSynthetic + @JvmDefault + public override suspend fun sendMessage(message: Message): Unit = + withContext(Dispatchers.IO) { sendMessageJ(message) } + + @JvmSynthetic + @JvmDefault + public override suspend fun sendMessage(message: String): Unit = + withContext(Dispatchers.IO) { sendMessageJ(message) } + } + + public val consoleCommandSender: ConsoleCommandSenderImpl public val dataStorageForJarPluginLoader: PluginDataStorage public val configStorageForJarPluginLoader: PluginDataStorage @@ -91,7 +133,7 @@ public interface MiraiConsoleImplementation : CoroutineScope { /** * 创建一个 [LoginSolver] * - * **调用备注**: 此函数通常在构造 [Bot] 实例时调用. + * **备注**: 此函数通常在构造 [Bot] 实例, 即 [MiraiConsole.addBot] 时调用. * * @param requesterBot 请求者 [Bot.id] * @param configuration 请求者 [Bot.configuration] @@ -101,15 +143,18 @@ public interface MiraiConsoleImplementation : CoroutineScope { public fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver /** - * 创建一个 logger + * 创建一个 [MiraiLogger]. + * + * **注意**: [MiraiConsole] 会将 [net.mamoe.mirai.utils.DefaultLogger] 设置为 `MiraiConsole::createLogger`. + * 因此不要在 [createLogger] 中调用 [net.mamoe.mirai.utils.DefaultLogger] */ - public fun newLogger(identity: String?): MiraiLogger + public fun createLogger(identity: String?): MiraiLogger public companion object { internal lateinit var instance: MiraiConsoleImplementation private val initLock = ReentrantLock() - /** 由前端调用, 初始化 [MiraiConsole] 实例, 并启动 */ + /** 由前端调用, 初始化 [MiraiConsole] 实例并启动 */ @JvmStatic @ConsoleFrontEndImplementation @Throws(MalformedMiraiConsoleImplementationError::class) 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 59ad0522c..b4549263e 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 @@ -153,7 +153,7 @@ public object BuiltInCommands { onFailure = { throwable -> sendMessage( "Login failed: ${throwable.localizedMessage ?: throwable.message ?: throwable.toString()}" + - if (this is MessageEventContextAware<*>) { + if (this is CommandSenderOnMessage<*>) { CommandManagerImpl.launch(CoroutineName("stacktrace delayer from Login")) { fromEvent.nextMessageOrNull(60.secondsToMillis) { it.message.contentEquals("stacktrace") } } 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 69cb2bd83..1521889da 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 @@ -7,90 +7,275 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("NOTHING_TO_INLINE", "INAPPLICABLE_JVM_NAME") +@file:Suppress( + "NOTHING_TO_INLINE", "INAPPLICABLE_JVM_NAME", "FunctionName", "SuspendFunctionOnCoroutineScope", + "unused", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER" +) package net.mamoe.mirai.console.command import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.mirai.Bot +import net.mamoe.mirai.console.ConsoleFrontEndImplementation +import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute +import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender +import net.mamoe.mirai.console.command.CommandSender.Companion.asMemberCommandSender +import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender +import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender +import net.mamoe.mirai.console.command.ConsoleCommandSender.INSTANCE import net.mamoe.mirai.console.command.description.CommandArgumentParserException import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip +import net.mamoe.mirai.console.internal.data.castOrNull +import net.mamoe.mirai.console.internal.plugin.rootCauseOrSelf import net.mamoe.mirai.console.util.ConsoleExperimentalAPI +import net.mamoe.mirai.console.util.childScope +import net.mamoe.mirai.console.util.childScopeContext import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.* import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.PlainText +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.coroutines.CoroutineContext +import kotlin.internal.LowPriorityInOverloadResolution /** * 指令发送者. * - * ### 获得指令发送者 + * 只有 [CommandSender] 才能 [执行指令][CommandManager.execute] + * + * ## 获得指令发送者 * - [MessageEvent.toCommandSender] * - [FriendMessageEvent.toCommandSender] * - [GroupMessageEvent.toCommandSender] * - [TempMessageEvent.toCommandSender] * * - [Member.asCommandSender] + * - [Member.asTempCommandSender]] + * - [Member.asMemberCommandSender]] * - [Friend.asCommandSender] * - [User.asCommandSender] * - * ### 子类型 + * ## 实现 [CommandSender] + * 除 Console 前端外, 在任何时候都不要实现 [CommandSender] (包括使用委托). 必须使用上述扩展获取 [CommandSender] 实例. * - * 当真实收到由用户执行的指令时: - * - 若用户在群内指令执行, 对应 [CommandSender] 为 [MemberCommandSenderOnMessage] - * - 若用户在私聊环境内指令执行, 对应 [CommandSender] 为 [FriendCommandSenderOnMessage] - * - 若用户在临时会话内指令执行, 对应 [CommandSender] 为 [TempCommandSenderOnMessage] + * Console 前端可实现 [ConsoleCommandSender] * - * 当指令由其他插件主动执行时, 插件应使用 [toCommandSender] 或 [asCommandSender], 因此 - * - 若用户在群内指令执行, 对应 [CommandSender] 为 [MemberCommandSender] - * - 若用户在私聊环境内指令执行, 对应 [CommandSender] 为 [FriendCommandSender] - * - 若用户在临时会话内指令执行, 对应 [CommandSender] 为 [TempCommandSender] + * ## 子类型 + * + * 所有 [CommandSender] 都应继承 [AbstractCommandSender]. + * + * [AbstractCommandSender] 是密封类, 一级子类为: + * - [AbstractUserCommandSender] 代表用户 + * - [ConsoleCommandSender] 代表控制台 + * + * 二级子类, 当指令由插件 [主动执行][CommandManager.execute] 时, 插件应使用 [toCommandSender] 或 [asCommandSender], 因此, + * - 若在群聊环境, 对应 [CommandSender] 为 [MemberCommandSender] + * - 若在私聊环境, 对应 [CommandSender] 为 [FriendCommandSender] + * - 若在临时会话环境, 对应 [CommandSender] 为 [TempCommandSender] + * + * 三级子类, 当真实收到由用户执行的指令时: + * - 若在群聊环境, 对应 [CommandSender] 为 [MemberCommandSenderOnMessage] + * - 若在私聊环境, 对应 [CommandSender] 为 [FriendCommandSenderOnMessage] + * - 若在临时会话环境, 对应 [CommandSender] 为 [TempCommandSenderOnMessage] + * + * 类型关系如图. 箭头指向的是父类. + * + * ``` + * CoroutineScope + * ↑ + * | + * CommandSender <---------+---------------+-------------------------------+ + * ↑ | | | + * | | | | + * | UserCommandSender GroupAwareCommandSender CommandSenderOnMessage + * | ↑ ↑ ↑ + * | | | | + * AbstractCommandSender | | | + * ↑ | | | + * | sealed | | | + * +-------------+-------------+ | | | + * | | | | | + * | | | | | } + * ConsoleCommandSender AbstractUserCommandSender | | } 一级子类 + * ↑ | | } + * | sealed | | + * | | | + * +----------------------+ | | + * | | | | + * | +------+------------+---------------+ | + * | | | | + * | | | | } + * FriendCommandSender MemberCommandSender TempCommandSender | } 二级子类 + * ↑ ↑ ↑ | } + * | | | | + * | | | | } + * FriendCommandSenderOnMessage MemberCommandSenderOnMessage TempCommandSenderOnMessage | } 三级子类 + * | | | | } + * | | | | + * +-----------------------------+----------------------------+---------------+ + * ``` * * @see ConsoleCommandSender 控制台 * @see UserCommandSender [User] ([群成员][Member], [好友][Friend]) * @see toCommandSender * @see asCommandSender */ -@Suppress("FunctionName") -public interface CommandSender { +public interface CommandSender : CoroutineScope { /** - * 与这个 [CommandSender] 相关的 [Bot]. 当通过控制台执行时为 null. + * 与这个 [CommandSender] 相关的 [Bot]. + * 当通过控制台执行时为 `null`. */ public val bot: Bot? /** - * 获取好友昵称, 群员昵称, 或 Bot 的昵称. 当控制台发送消息时返回 [ConsoleCommandSender.NAME] + * 与这个 [CommandSender] 相关的 [Contact]. + * + * - 当一个群员执行指令时, [subject] 为所在 [群][Group] + * - 当通过控制台执行时为 `null`. + */ + public val subject: Contact? + + /** + * 指令原始发送*人*. + * - 当通过控制台执行时为 `null`. + */ + public val user: User? + + /** + * [User.nameCardOrNick] 或 [ConsoleCommandSender.NAME] */ public val name: String /** - * 立刻发送一条消息. 对于 [Member.asCommandSender], 这个函数总是发送给所在群 + * 立刻发送一条消息. + * + * 对于 [MemberCommandSender], 这个函数总是发送给所在群 */ @JvmBlockingBridge - public suspend fun sendMessage(message: Message) + public suspend fun sendMessage(message: Message): MessageReceipt? /** - * 立刻发送一条消息. 对于 [Member.asCommandSender], 这个函数总是发送给所在群 + * 立刻发送一条消息. + * + * 对于 [MemberCommandSender], 这个函数总是发送给所在群 */ - @JvmDefault @JvmBlockingBridge - public suspend fun sendMessage(message: String): Unit = sendMessage(PlainText(message)) + public suspend fun sendMessage(message: String): MessageReceipt? - @ConsoleExperimentalAPI - public suspend fun catchExecutionException(e: Throwable) { + @ConsoleExperimentalAPI("This is unstable and might get changed") + public suspend fun catchExecutionException(e: Throwable) + + public companion object { + + /////////////////////////////////////////////////////////////////////////// + // Constructors + /////////////////////////////////////////////////////////////////////////// + + /** + * 构造 [FriendCommandSenderOnMessage] + */ + @JvmStatic + public fun FriendMessageEvent.toCommandSender(): FriendCommandSenderOnMessage = + FriendCommandSenderOnMessage(this) + + /** + * 构造 [MemberCommandSenderOnMessage] + */ + @JvmStatic + public fun GroupMessageEvent.toCommandSender(): MemberCommandSenderOnMessage = + MemberCommandSenderOnMessage(this) + + /** + * 构造 [TempCommandSenderOnMessage] + */ + @JvmStatic + public fun TempMessageEvent.toCommandSender(): TempCommandSenderOnMessage = TempCommandSenderOnMessage(this) + + /** + * 构造 [CommandSenderOnMessage] + */ + @JvmStatic + @Suppress("UNCHECKED_CAST") + public fun T.toCommandSender(): CommandSenderOnMessage = when (this) { + is FriendMessageEvent -> toCommandSender() + is GroupMessageEvent -> toCommandSender() + is TempMessageEvent -> toCommandSender() + else -> throw IllegalArgumentException("unsupported MessageEvent: ${this::class.qualifiedNameOrTip}") + } as CommandSenderOnMessage + + /** + * 得到 [TempCommandSender] + */ + @JvmStatic + public fun Member.asTempCommandSender(): TempCommandSender = TempCommandSender(this) + + /** + * 得到 [MemberCommandSender] + */ + @JvmStatic + public fun Member.asMemberCommandSender(): MemberCommandSender = MemberCommandSender(this) + + /** + * 得到 [MemberCommandSender] 或 [TempCommandSender] + * @see asTempCommandSender + * @see asMemberCommandSender + */ + @JvmStatic + public fun Member.asCommandSender(isTemp: Boolean): UserCommandSender { + return if (isTemp) asTempCommandSender() else asMemberCommandSender() + } + + /** + * 得到 [FriendCommandSender] + */ + @JvmStatic + public fun Friend.asCommandSender(): FriendCommandSender = FriendCommandSender(this) + + /** + * 得到 [UserCommandSender] + * + * @param isTemp + */ + @JvmStatic + public fun User.asCommandSender(isTemp: Boolean): UserCommandSender = when (this) { + is Friend -> this.asCommandSender() + is Member -> if (isTemp) TempCommandSender(this) else MemberCommandSender(this) + else -> error("stub") + } + } +} + +/** + * 所有 [CommandSender] 都必须继承自此对象. + * @see CommandSender 查看更多信息 + */ +public sealed class AbstractCommandSender : CommandSender, CoroutineScope { + public abstract override val bot: Bot? + public abstract override val subject: Contact? + public abstract override val user: User? + public abstract override suspend fun sendMessage(message: Message): MessageReceipt? + public abstract override fun toString(): String + public override suspend fun sendMessage(message: String): MessageReceipt? = sendMessage(PlainText(message)) + + @ConsoleExperimentalAPI("This is unstable and might get changed") + override suspend fun catchExecutionException(e: Throwable) { if (this is CommandSenderOnMessage<*>) { - // TODO: 2020/8/22 bad scope val cause = e.rootCauseOrSelf val message = cause .takeIf { it is CommandArgumentParserException }?.message ?: "${cause::class.simpleName.orEmpty()}: ${cause.message}" + // TODO: 2020/8/30 优化 net.mamoe.mirai.console.command.CommandSender.catchExecutionException + sendMessage(message) // \n\n60 秒内发送 stacktrace 查看堆栈信息 - bot.launch(CoroutineName("stacktrace delayer from command")) { + this@AbstractCommandSender.launch(CoroutineName("stacktrace delayer from command")) { if (fromEvent.nextMessageOrNull(60_000) { it.message.contentEquals("stacktrace") || it.message.contentEquals("stack") } != null) { @@ -103,20 +288,199 @@ public interface CommandSender { } } -internal val Throwable.rootCauseOrSelf: Throwable get() = generateSequence(this) { it.cause }.lastOrNull() ?: this - /** - * 可以知道其 [Bot] 的 [CommandSender] + * 当 [this] 为 [AbstractCommandSender] 时返回. + * + * 正常情况下, 所有 [CommandSender] 都应该继承 [AbstractCommandSender] + * + * 在需要类型智能转换等情况时可使用此函数. + * + * ### 契约 + * 本函数定义契约, + * - 若函数正常返回, Kotlin 编译器认为 [this] 是 [AbstractCommandSender] 实例并执行智能类型转换. + * + * @return `this` */ -public interface BotAwareCommandSender : CommandSender { - public override val bot: Bot +public fun CommandSender.checkIsAbstractCommandSender(): AbstractCommandSender { + contract { + returns() implies (this@checkIsAbstractCommandSender is AbstractCommandSender) + } + check(this is AbstractCommandSender) { "A CommandSender must extend AbstractCommandSender" } + return this } /** - * 可以知道其 [Group] 环境的 [CommandSender] + * 当 [this] 为 [AbstractUserCommandSender] 时返回. + * + * 正常情况下, 所有 [UserCommandSender] 都应该继承 [AbstractUserCommandSender] + * + * 在需要类型智能转换等情况时可使用此函数. + * + * ### 契约 + * 本函数定义契约, + * - 若函数正常返回, Kotlin 编译器认为 [this] 是 [AbstractUserCommandSender] 实例并执行智能类型转换. + * + * @return `this` */ -public interface GroupAwareCommandSender : CommandSender { - public val group: Group +public fun UserCommandSender.checkIsAbstractUserCommandSender(): AbstractUserCommandSender { + contract { + returns() implies (this@checkIsAbstractUserCommandSender is AbstractUserCommandSender) + } + check(this is AbstractUserCommandSender) { "A UserCommandSender must extend AbstractUserCommandSender" } + return this +} + +/** + * 当 [this] 为 [ConsoleCommandSender] 时返回 `true` + * + * ### 契约 + * 本函数定义契约, + * - 若返回 `true`, Kotlin 编译器认为 [this] 是 [ConsoleCommandSender] 实例并执行智能类型转换. + * - 若返回 `false`, Kotlin 编译器认为 [this] 是 [UserCommandSender] 实例并执行智能类型转换. + */ +public fun CommandSender.isConsole(): Boolean { + contract { + returns(true) implies (this@isConsole is ConsoleCommandSender) + returns(false) implies (this@isConsole is UserCommandSender) + } + this.checkIsAbstractCommandSender() + return this is ConsoleCommandSender +} + +/** + * 当 [this] 不为 [ConsoleCommandSender], 即为 [UserCommandSender] 时返回 `true`. + * + * ### 契约 + * 本函数定义契约, + * - 若返回 `true`, Kotlin 编译器认为 [this] 是 [UserCommandSender] 实例并执行智能类型转换. + * - 若返回 `false`, Kotlin 编译器认为 [this] 是 [ConsoleCommandSender] 实例并执行智能类型转换. + */ +public fun CommandSender.isNotConsole(): Boolean { + contract { + returns(true) implies (this@isNotConsole is UserCommandSender) + returns(false) implies (this@isNotConsole is ConsoleCommandSender) + } + this.checkIsAbstractCommandSender() + return this !is ConsoleCommandSender +} + +/** + * 当 [this] 为 [UserCommandSender] 时返回 `true` + * + * ### 契约 + * 本函数定义契约, + * - 若返回 `true`, Kotlin 编译器认为 [this] 是 [UserCommandSender] 实例并执行智能类型转换. + * - 若返回 `false`, Kotlin 编译器认为 [this] 是 [ConsoleCommandSender] 实例并执行智能类型转换. + */ +public fun CommandSender.isUser(): Boolean { + contract { + returns(true) implies (this@isUser is UserCommandSender) + returns(false) implies (this@isUser is ConsoleCommandSender) + } + this.checkIsAbstractCommandSender() + return this is UserCommandSender +} + +/** + * 当 [this] 不为 [UserCommandSender], 即为 [ConsoleCommandSender] 时返回 `true` + * + * ### 契约 + * 本函数定义契约, + * - 若返回 `true`, Kotlin 编译器认为 [this] 是 [ConsoleCommandSender] 实例并执行智能类型转换. + * - 若返回 `false`, Kotlin 编译器认为 [this] 是 [UserCommandSender] 实例并执行智能类型转换. + */ +public fun CommandSender.isNotUser(): Boolean { + contract { + returns(true) implies (this@isNotUser is ConsoleCommandSender) + returns(false) implies (this@isNotUser is UserCommandSender) + } + this.checkIsAbstractCommandSender() + return this !is UserCommandSender +} + +/** + * 折叠 [AbstractCommandSender] 的两种可能性. + * + * - 当 [this] 为 [ConsoleCommandSender] 时执行 [ifIsConsole] + * - 当 [this] 为 [UserCommandSender] 时执行 [ifIsUser] + * + * ### 示例 + * ``` + * // 当一个指令执行过程出错 + * val exception: Exception = ... + * + * sender.fold( + * ifIsConsole = { // this: ConsoleCommandSender + * sendMessage(exception.stackTraceToString()) // 展示整个 stacktrace + * }, + * ifIsUser = { // this: UserCommandSender + * sendMessage(exception.message.toString()) // 只展示 Exception.message + * } + * ) + * ``` + * + * @return [ifIsConsole] 或 [ifIsUser] 执行结果. + */ +@JvmSynthetic +@LowPriorityInOverloadResolution +public inline fun CommandSender.fold( + ifIsConsole: ConsoleCommandSender.() -> R, + ifIsUser: UserCommandSender.() -> R, +): R { + contract { + callsInPlace(ifIsConsole, InvocationKind.AT_MOST_ONCE) + callsInPlace(ifIsUser, InvocationKind.AT_MOST_ONCE) + } + return when (val sender = this.checkIsAbstractCommandSender()) { + is ConsoleCommandSender -> ifIsConsole(sender) + is AbstractUserCommandSender -> ifIsUser(sender) + } +} + +/** + * 折叠 [AbstractCommandSender] 的两种可能性, 即在群内发送或在私聊环境发送. + * + * - 当 [this] 为 [MemberCommandSender] 时执行 [inGroup] + * - 当 [this] 为 [TempCommandSender] 或 [FriendCommandSender] 时执行 [inPrivate] + * + * ### 实验性 API + * + * 这是预览版本 API. 如果你对 [UserCommandSender.fold] 有建议, 请在 [mamoe/mirai-console/issues](https://github.com/mamoe/mirai-console/issues/new) 提交. + * + * @return [inGroup] 或 [inPrivate] 执行结果. + */ +@JvmSynthetic +@ConsoleExperimentalAPI +public inline fun UserCommandSender.foldContext( + inGroup: MemberCommandSender.() -> R, + inPrivate: UserCommandSender.() -> R, +): R { + contract { + callsInPlace(inGroup, InvocationKind.AT_MOST_ONCE) + callsInPlace(inPrivate, InvocationKind.AT_MOST_ONCE) + } + return when (val sender = this.checkIsAbstractUserCommandSender()) { + is MemberCommandSender -> inGroup(sender) + else -> inPrivate(sender) + } +} + +/** + * 尝试获取 [Group]. + * + * 当 [GroupAwareCommandSender] 时返回 [GroupAwareCommandSender.group], 否则返回 `null` + * + * ### 契约 + * 本函数定义契约, + * - 若返回非 `null` 实例, Kotlin 编译器认为 [this] 是 [GroupAwareCommandSender] 实例并执行智能类型转换. + * - 若返回 `null`, Kotlin 编译器认为 [this] 是 [FriendCommandSender] 实例并执行智能类型转换. + */ +public fun CommandSender.getBotOrNull(): Bot? { + contract { + returns(null) implies (this@getBotOrNull is AbstractUserCommandSender) + returnsNotNull() implies (this@getBotOrNull is ConsoleCommandSender) + } + return this.castOrNull()?.bot } /** @@ -124,154 +488,190 @@ public interface GroupAwareCommandSender : CommandSender { * @see INSTANCE */ // 前端实现 -public abstract class ConsoleCommandSender internal constructor() : CommandSender { +public abstract class ConsoleCommandSender @ConsoleFrontEndImplementation constructor() : AbstractCommandSender() { public final override val bot: Nothing? get() = null - public override val name: String get() = NAME + public final override val subject: Nothing? get() = null + public final override val user: Nothing? get() = null + public final override val name: String get() = NAME + public final override fun toString(): String = NAME - public companion object { - public const val NAME: String = "CONSOLE" - - internal val instance get() = MiraiConsoleImplementationBridge.consoleCommandSender - } -} - -@ConsoleExperimentalAPI -public fun FriendMessageEvent.toCommandSender(): FriendCommandSenderOnMessage = FriendCommandSenderOnMessage(this) - -@ConsoleExperimentalAPI -public fun GroupMessageEvent.toCommandSender(): MemberCommandSenderOnMessage = MemberCommandSenderOnMessage(this) - -@ConsoleExperimentalAPI -public fun TempMessageEvent.toCommandSender(): TempCommandSenderOnMessage = TempCommandSenderOnMessage(this) - -@ConsoleExperimentalAPI -public fun MessageEvent.toCommandSender(): CommandSenderOnMessage<*> = when (this) { - is FriendMessageEvent -> toCommandSender() - is GroupMessageEvent -> toCommandSender() - is TempMessageEvent -> toCommandSender() - else -> throw IllegalArgumentException("unsupported MessageEvent: ${this::class.qualifiedNameOrTip}") -} - -@ConsoleExperimentalAPI -public fun Member.asCommandSender(): MemberCommandSender = MemberCommandSender(this) - -@ConsoleExperimentalAPI -public fun Friend.asCommandSender(): FriendCommandSender = FriendCommandSender(this) - -@ConsoleExperimentalAPI -public fun User.asCommandSender(): UserCommandSender { - return when (this) { - is Friend -> this.asCommandSender() - is Member -> this.asCommandSender() - else -> error("stub") + public companion object INSTANCE : ConsoleCommandSender(), CoroutineScope { + public const val NAME: String = "ConsoleCommandSender" + public override val coroutineContext: CoroutineContext by lazy { MiraiConsole.childScopeContext(NAME) } + public override suspend fun sendMessage(message: Message): Nothing? { + MiraiConsoleImplementationBridge.consoleCommandSender.sendMessage(message) + return null + } } } /** - * 表示由 [MessageEvent] 触发的指令 + * 知道 [Group] 环境的 [UserCommandSender] + * + * 可能的子类: + * + * - [MemberCommandSender] 代表一个 [群员][Member] 执行指令 + * - [TempCommandSender] 代表一个 [群员][Member] 通过临时会话执行指令 */ -@ConsoleExperimentalAPI -public interface MessageEventContextAware : MessageEventExtensions { - public val fromEvent: E +public interface GroupAwareCommandSender : UserCommandSender { + public val group: Group } /** - * 代表一个用户私聊机器人执行指令 - * @see User.asCommandSender + * 尝试获取 [Group]. + * + * 当 [GroupAwareCommandSender] 时返回 [GroupAwareCommandSender.group], 否则返回 `null` + * + * ### 契约 + * 本函数定义契约, + * - 若返回非 `null` 实例, Kotlin 编译器认为 [this] 是 [GroupAwareCommandSender] 实例并执行智能类型转换. + * - 若返回 `null`, Kotlin 编译器认为 [this] 是 [FriendCommandSender] 实例并执行智能类型转换. */ -@ConsoleExperimentalAPI -public sealed class UserCommandSender : CommandSender, BotAwareCommandSender { +public fun CommandSender.getGroupOrNull(): Group? { + contract { + returns(null) implies (this@getGroupOrNull is FriendCommandSender) + returnsNotNull() implies (this@getGroupOrNull is GroupAwareCommandSender) + } + return this.castOrNull()?.group +} + +/////////////////////////////////////////////////////////////////////////// +// UserCommandSender +/////////////////////////////////////////////////////////////////////////// + +/** + * 代表一个用户执行指令 + * + * @see MemberCommandSender 代表一个 [群员][Member] 执行指令 + * @see FriendCommandSender 代表一个 [好友][Friend] 执行指令 + * @see TempCommandSender 代表一个 [群员][Member] 通过临时会话执行指令 + * + * @see CommandSenderOnMessage + */ +public interface UserCommandSender : CommandSender { /** * @see MessageEvent.sender */ - public abstract val user: User + public override val user: User // override nullability /** * @see MessageEvent.subject */ - public abstract val subject: Contact - - public override val bot: Bot get() = user.bot - public override val name: String get() = user.nameCardOrNick - - public final override suspend fun sendMessage(message: Message) { - subject.sendMessage(message) - } + public override val subject: Contact // override nullability + public override val bot: Bot // override nullability } /** - * 代表一个用户私聊机器人执行指令 - * @see Friend.asCommandSender + * [UserCommandSender] 的实现 */ -@ConsoleExperimentalAPI -public open class FriendCommandSender( +public sealed class AbstractUserCommandSender : UserCommandSender, AbstractCommandSender() { + public override val bot: Bot get() = user.bot // don't final + public final override val name: String get() = user.nameCardOrNick + public override suspend fun sendMessage(message: String): MessageReceipt = sendMessage(PlainText(message)) + public override suspend fun sendMessage(message: Message): MessageReceipt = user.sendMessage(message) +} + +/** + * 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.execute]) + * @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令 + */ +public open class FriendCommandSender internal constructor( public final override val user: Friend -) : UserCommandSender() { +) : AbstractUserCommandSender(), CoroutineScope by user.childScope("FriendCommandSender") { public override val subject: Contact get() = user + public override fun toString(): String = "FriendCommandSender($user)" + public override suspend fun sendMessage(message: String): MessageReceipt = sendMessage(PlainText(message)) + public override suspend fun sendMessage(message: Message): MessageReceipt = user.sendMessage(message) } /** - * 代表一个用户私聊机器人执行指令 - * @see Friend.asCommandSender + * 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式, 也有可能是由插件在代码直接执行 ([CommandManager.execute]) + * @see MemberCommandSenderOnMessage 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令. */ -@ConsoleExperimentalAPI -public class FriendCommandSenderOnMessage( +public open class MemberCommandSender internal constructor( + public final override val user: Member +) : AbstractUserCommandSender(), + GroupAwareCommandSender, + CoroutineScope by user.childScope("MemberCommandSender") { + public override val group: Group get() = user.group + public override val subject: Contact get() = group + public override fun toString(): String = "MemberCommandSender($user)" + public override suspend fun sendMessage(message: String): MessageReceipt = sendMessage(PlainText(message)) + public override suspend fun sendMessage(message: Message): MessageReceipt = user.sendMessage(message) +} + +/** + * 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.execute]) + * @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令 + */ +public open class TempCommandSender internal constructor( + public final override val user: Member +) : AbstractUserCommandSender(), + GroupAwareCommandSender, + CoroutineScope by user.childScope("TempCommandSender") { + public override val group: Group get() = user.group + public override val subject: Contact get() = group + public override fun toString(): String = "TempCommandSender($user)" + public override suspend fun sendMessage(message: String): MessageReceipt = sendMessage(PlainText(message)) + public override suspend fun sendMessage(message: Message): MessageReceipt = user.sendMessage(message) +} + +/////////////////////////////////////////////////////////////////////////// +// CommandSenderOnMessage +/////////////////////////////////////////////////////////////////////////// + +/** + * 代表一个真实 [用户][User] 主动私聊机器人或在群内发送消息而执行指令 + * + * @see MemberCommandSenderOnMessage 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令. + * @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令 + * @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令 + */ +public interface CommandSenderOnMessage : + CommandSender, + MessageEventExtensions { + + /** + * 消息源 [MessageEvent] + */ + public val fromEvent: T +} + +/** + * 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令 + * @see FriendCommandSender 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式 + */ +public class FriendCommandSenderOnMessage internal constructor( public override val fromEvent: FriendMessageEvent ) : FriendCommandSender(fromEvent.sender), - CommandSenderOnMessage, MessageEventExtensions by fromEvent { - public override val subject: Contact get() = super.subject + CommandSenderOnMessage, + MessageEventExtensions by fromEvent { + public override val subject: Friend get() = fromEvent.subject public override val bot: Bot get() = super.bot } -@ConsoleExperimentalAPI -public interface CommandSenderOnMessage : MessageEventContextAware, CommandSender - /** - * 代表一个群成员执行指令. - * @see Member.asCommandSender + * 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令. + * @see MemberCommandSender 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式 */ -@ConsoleExperimentalAPI -public open class MemberCommandSender( - public final override val user: Member -) : UserCommandSender(), GroupAwareCommandSender { - public override val group: Group get() = user.group - public override val subject: Contact get() = group -} - -/** - * 代表一个群成员执行指令. - * @see Member.asCommandSender - */ -@ConsoleExperimentalAPI -public open class TempCommandSender( - public final override val user: Member -) : UserCommandSender(), GroupAwareCommandSender { - public override val group: Group get() = user.group - public override val subject: Contact get() = group -} - -/** - * 代表一个群成员在群内执行指令. - * @see Member.asCommandSender - */ -@ConsoleExperimentalAPI -public class MemberCommandSenderOnMessage( +public class MemberCommandSenderOnMessage internal constructor( public override val fromEvent: GroupMessageEvent ) : MemberCommandSender(fromEvent.sender), - CommandSenderOnMessage, MessageEventExtensions by fromEvent { - public override val subject: Contact get() = super.subject + CommandSenderOnMessage, + MessageEventExtensions by fromEvent { + public override val subject: Group get() = fromEvent.subject public override val bot: Bot get() = super.bot } /** - * 代表一个群成员通过临时会话私聊机器人执行指令. - * @see Member.asCommandSender + * 代表一个 [群员][Member] 主动在临时会话发送消息执行指令 + * @see TempCommandSender 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式 */ -@ConsoleExperimentalAPI -public class TempCommandSenderOnMessage( +public class TempCommandSenderOnMessage internal constructor( public override val fromEvent: TempMessageEvent ) : TempCommandSender(fromEvent.sender), - CommandSenderOnMessage, MessageEventExtensions by fromEvent { - public override val subject: Contact get() = super.subject + CommandSenderOnMessage, + MessageEventExtensions by fromEvent { + public override val subject: Member get() = fromEvent.subject public 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/SimpleCommand.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt index 24495bf04..2529b701c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt @@ -81,7 +81,9 @@ public abstract class SimpleCommand( } @Deprecated("prohibited", level = DeprecationLevel.HIDDEN) - internal override suspend fun CommandSender.onDefault(rawArgs: Array) = sendMessage(usage) + internal override suspend fun CommandSender.onDefault(rawArgs: Array) { + sendMessage(usage) + } internal final override val subCommandAnnotationResolver: SubCommandAnnotationResolver get() = SimpleCommandSubCommandAnnotationResolver diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgParserBuiltins.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgParserBuiltins.kt index b427f60a8..ac68fe1d2 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgParserBuiltins.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgParserBuiltins.kt @@ -308,7 +308,7 @@ internal interface InternalCommandArgumentParserExtensions : CommandArg } fun CommandSender.inferBotOrFail(): Bot = - (this as? BotAwareCommandSender)?.bot + (this as? UserCommandSender)?.bot ?: Bot.botInstancesSequence.singleOrNull() ?: illegalArgument("当前语境下无法推断目标 Bot, 因为目前有多个 Bot 在线.") diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt index 3b268afb9..08840d5bd 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt @@ -22,7 +22,6 @@ import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.Command.Companion.primaryName -import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.data.builtin.ConsoleDataScope @@ -33,7 +32,6 @@ import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.center.PluginCenter import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleInput -import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.utils.* import java.nio.file.Path import java.time.Instant @@ -54,11 +52,12 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI override val rootPath: Path by instance::rootPath override val frontEndDescription: MiraiConsoleFrontEndDescription by instance::frontEndDescription - @OptIn(ConsoleInternalAPI::class) - override val mainLogger: MiraiLogger by instance::mainLogger + override val mainLogger: MiraiLogger by lazy { + createLogger("main") + } override val coroutineContext: CoroutineContext by instance::coroutineContext override val builtInPluginLoaders: List> by instance::builtInPluginLoaders - override val consoleCommandSender: ConsoleCommandSender by instance::consoleCommandSender + override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl by instance::consoleCommandSender override val dataStorageForJarPluginLoader: PluginDataStorage by instance::dataStorageForJarPluginLoader override val configStorageForJarPluginLoader: PluginDataStorage by instance::configStorageForJarPluginLoader @@ -70,11 +69,10 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI instance.createLoginSolver(requesterBot, configuration) init { - DefaultLogger = this::newLogger + DefaultLogger = this::createLogger } - @ConsoleExperimentalAPI - override fun newLogger(identity: String?): MiraiLogger = instance.newLogger(identity) + override fun createLogger(identity: String?): MiraiLogger = instance.createLogger(identity) @OptIn(ConsoleExperimentalAPI::class) internal fun doStart() { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt index 5b2a78514..78b3820da 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CommandManagerImpl.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.Command.Companion.primaryName +import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.message.MessageEvent @@ -27,7 +28,7 @@ import java.util.concurrent.locks.ReentrantLock internal object CommandManagerImpl : CommandManager, CoroutineScope by CoroutineScope(MiraiConsole.job) { private val logger: MiraiLogger by lazy { - MiraiConsole.newLogger("command") + MiraiConsole.createLogger("command") } @JvmField diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt index c0673e76c..feec64e53 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/valueFromKTypeImpl.kt @@ -159,6 +159,15 @@ internal inline fun Any.cast(): R { return this as R } +@PublishedApi +@Suppress("UNCHECKED_CAST") +internal inline fun Any.castOrNull(): R? { + contract { + returnsNotNull() implies (this@castOrNull is R) + } + return this as? R +} + @Suppress("UNCHECKED_CAST") internal inline fun Any.castOrInternalError(): R { contract { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt index d979884c9..8d2dbac8e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt @@ -36,7 +36,7 @@ internal object JarPluginLoaderImpl : override val configStorage: PluginDataStorage get() = MiraiConsoleImplementationBridge.configStorageForJarPluginLoader - private val logger: MiraiLogger = MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!) + private val logger: MiraiLogger = MiraiConsole.createLogger(JarPluginLoader::class.simpleName!!) override val dataStorage: PluginDataStorage get() = MiraiConsoleImplementationBridge.dataStorageForJarPluginLoader diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt index 0e7c6dae9..b52fac667 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt @@ -43,7 +43,7 @@ internal abstract class JvmPluginInternal( // region JvmPlugin final override val logger: MiraiLogger by lazy { - MiraiConsole.newLogger( + MiraiConsole.createLogger( "Plugin ${this.description.name}" ) } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt index 3b0bb81ea..5aa1b3c8d 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt @@ -44,7 +44,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol @Suppress("ObjectPropertyName") private val _pluginLoaders: MutableList> = mutableListOf() private val loadersLock: ReentrantLock = ReentrantLock() - private val logger = MiraiConsole.newLogger("plugin") + private val logger = MiraiConsole.createLogger("plugin") @JvmField internal val resolvedPlugins: MutableList = mutableListOf() diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt index 8ab78e8a1..351d5b833 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.command.CommandManager -import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.data.MemoryPluginDataStorage import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.plugin.DeferredPluginLoader @@ -26,7 +25,10 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.message.data.Message -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.BotConfiguration +import net.mamoe.mirai.utils.LoginSolver +import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.PlatformLogger import java.nio.file.Path import kotlin.coroutines.Continuation import kotlin.coroutines.CoroutineContext @@ -49,11 +51,17 @@ fun initTestEnvironment() { get() = Semver("1.0.0") } - override val mainLogger: MiraiLogger = DefaultLogger("main") override val builtInPluginLoaders: List> = listOf(DeferredPluginLoader { JarPluginLoader }) - override val consoleCommandSender: ConsoleCommandSender = object : ConsoleCommandSender() { - override suspend fun sendMessage(message: Message) = println(message) - } + override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl = + object : MiraiConsoleImplementation.ConsoleCommandSenderImpl { + override suspend fun sendMessage(message: Message) { + println(message) + } + + override suspend fun sendMessage(message: String) { + println(message) + } + } override val dataStorageForJarPluginLoader: PluginDataStorage = MemoryPluginDataStorage() override val configStorageForJarPluginLoader: PluginDataStorage = MemoryPluginDataStorage() override val dataStorageForBuiltIns: PluginDataStorage = MemoryPluginDataStorage() @@ -69,7 +77,7 @@ fun initTestEnvironment() { override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver = LoginSolver.Default - override fun newLogger(identity: String?): MiraiLogger { + override fun createLogger(identity: String?): MiraiLogger { return PlatformLogger(identity) } diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index 12cd782c4..82ab87faf 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -53,7 +53,7 @@ object TestSimpleCommand : RawCommand(owner, "testSimple", "tsS") { } } -internal val sender by lazy { ConsoleCommandSender.instance } +internal val sender by lazy { ConsoleCommandSender } internal val owner by lazy { ConsoleCommandOwner } diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt index 8f6ecae2c..c2f285cde 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt @@ -18,6 +18,7 @@ import net.mamoe.mirai.console.command.Command.Companion.primaryName import net.mamoe.mirai.console.command.CommandExecuteStatus import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand +import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.requestInput import net.mamoe.mirai.utils.DefaultLogger @@ -41,7 +42,7 @@ internal fun startupConsoleThread() { continue } // consoleLogger.debug("INPUT> $next") - val result = ConsoleCommandSenderImpl.executeCommand(next) + val result = ConsoleCommandSender.executeCommand(next) when (result.status) { CommandExecuteStatus.SUCCESSFUL -> { } diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt index f19e1de90..19e5305f1 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt @@ -27,10 +27,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import net.mamoe.mirai.console.ConsoleFrontEndImplementation -import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription import net.mamoe.mirai.console.MiraiConsoleImplementation -import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.data.MultiFilePluginDataStorage import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.internal.plugin.NamedSupervisorJob @@ -69,22 +67,19 @@ class MiraiConsoleImplementationPure listOf(DeferredPluginLoader { JarPluginLoader }) ), override val frontEndDescription: MiraiConsoleFrontEndDescription = ConsoleFrontEndDescImpl, - override val consoleCommandSender: ConsoleCommandSender = ConsoleCommandSenderImpl, + override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl = ConsoleCommandSenderImplPure, override val dataStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")), override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")), override val configStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")), override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")) ) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(NamedSupervisorJob("MiraiConsoleImplementationPure")) { - override val mainLogger: MiraiLogger by lazy { - MiraiConsole.newLogger("main") - } override val consoleInput: ConsoleInput get() = ConsoleInputImpl override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver { return DefaultLoginSolver(input = { requestInput("LOGIN> ") }) } - override fun newLogger(identity: String?): MiraiLogger = LoggerCreator(identity) + override fun createLogger(identity: String?): MiraiLogger = LoggerCreator(identity) init { with(rootPath.toFile()) { diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt index afb5b9ef2..06465154f 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt @@ -23,8 +23,8 @@ package net.mamoe.mirai.console.pure import kotlinx.coroutines.CancellationException import kotlinx.coroutines.runBlocking import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start -import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.message.data.Message @@ -74,12 +74,16 @@ internal fun overrideSTD() { } -internal object ConsoleCommandSenderImpl : ConsoleCommandSender() { - override suspend fun sendMessage(message: Message) { +internal object ConsoleCommandSenderImplPure : MiraiConsoleImplementation.ConsoleCommandSenderImpl { + override suspend fun sendMessage(message: String) { kotlin.runCatching { - lineReader.printAbove(message.contentToString()) + lineReader.printAbove(message) }.onFailure { consoleLogger.error(it) } } + + override suspend fun sendMessage(message: Message) { + return sendMessage(message.contentToString()) + } } \ No newline at end of file