diff --git a/.github/workflows/Publishing.yml b/.github/workflows/DevTagPublishing.yml similarity index 98% rename from .github/workflows/Publishing.yml rename to .github/workflows/DevTagPublishing.yml index dc365bd7f..403aa333d 100644 --- a/.github/workflows/Publishing.yml +++ b/.github/workflows/DevTagPublishing.yml @@ -1,6 +1,6 @@ # This is a basic workflow to help you get started with Actions -name: Bintray Publish +name: Dev Tag Publishing # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch @@ -9,9 +9,6 @@ on: # Sequence of patterns matched against refs/tags tags: - '*-dev*' - release: - types: - - created # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: diff --git a/.github/workflows/ReleasePublishing.yml b/.github/workflows/ReleasePublishing.yml new file mode 100644 index 000000000..12a8e5450 --- /dev/null +++ b/.github/workflows/ReleasePublishing.yml @@ -0,0 +1,63 @@ +# This is a basic workflow to help you get started with Actions + +name: Bintray Publish + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + release: + types: + - created + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Gradle clean + run: ./gradlew clean + - name: Gradle build + run: ./gradlew build # if test's failed, don't publish + - name: Check keys + run: ./gradlew + :mirai-console:ensureBintrayAvailable + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Gradle :mirai-console:fillBuildConstants + run: ./gradlew + :mirai-console:fillBuildConstants + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Gradle :mirai-console:bintrayUpload + run: ./gradlew + :mirai-console:bintrayUpload + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Gradle :mirai-console-terminal:bintrayUpload + run: ./gradlew + :mirai-console-terminal:bintrayUpload + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Gradle :mirai-console-compiler-common:bintrayUpload + run: ./gradlew + :mirai-console-compiler-common:bintrayUpload + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Gradle :mirai-console-intellij:bintrayUpload + run: ./gradlew + :mirai-console-intellij:bintrayUpload + -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} + -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} + - name: Publish Gradle plugin + run: ./gradlew + :mirai-console-gradle:publishPlugins + -Dgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Pgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} + -Dgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} diff --git a/backend/mirai-console/src/MiraiConsole.kt b/backend/mirai-console/src/MiraiConsole.kt index 2211eff30..9f26892c2 100644 --- a/backend/mirai-console/src/MiraiConsole.kt +++ b/backend/mirai-console/src/MiraiConsole.kt @@ -143,7 +143,7 @@ public interface MiraiConsole : CoroutineScope { return when (password) { is ByteArray -> Bot(id, password, config) is String -> Bot(id, password, config) - else -> null!! + else -> throw IllegalArgumentException("Bad password type: `${password.javaClass.name}`. Require ByteArray or String") } } diff --git a/backend/mirai-console/src/command/BuiltInCommands.kt b/backend/mirai-console/src/command/BuiltInCommands.kt index a5d978b53..9da243d3b 100644 --- a/backend/mirai-console/src/command/BuiltInCommands.kt +++ b/backend/mirai-console/src/command/BuiltInCommands.kt @@ -20,22 +20,38 @@ import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Com import net.mamoe.mirai.console.command.descriptor.PermissionIdValueArgumentParser import net.mamoe.mirai.console.command.descriptor.PermitteeIdValueArgumentParser import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext +import net.mamoe.mirai.console.extensions.PermissionServiceProvider +import net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants +import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands +import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig +import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.* +import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN +import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService +import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl import net.mamoe.mirai.console.internal.util.runIgnoreException import net.mamoe.mirai.console.permission.Permission +import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.permission.PermissionService.Companion.cancel import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermissionOrFail import net.mamoe.mirai.console.permission.PermissionService.Companion.getPermittedPermissions import net.mamoe.mirai.console.permission.PermissionService.Companion.permit import net.mamoe.mirai.console.permission.PermitteeId +import net.mamoe.mirai.console.plugin.name +import net.mamoe.mirai.console.plugin.version import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.message.nextMessageOrNull import net.mamoe.mirai.utils.secondsToMillis +import java.lang.management.ManagementFactory +import java.lang.management.MemoryUsage +import java.time.ZoneId +import java.time.format.DateTimeFormatter import kotlin.concurrent.thread +import kotlin.math.floor import kotlin.system.exitProcess @@ -205,9 +221,19 @@ public object BuiltInCommands { @SubCommand("permittedPermissions", "pp", "grantedPermissions", "gp") public suspend fun CommandSender.permittedPermissions( @Name("被许可人 ID") target: PermitteeId, + @Name("包括重复") all: Boolean = false, ) { - val grantedPermissions = target.getPermittedPermissions() - sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() }) + var grantedPermissions = target.getPermittedPermissions().toList() + if (!all) { + grantedPermissions = grantedPermissions.filter { thisPerm -> + grantedPermissions.none { other -> thisPerm.parentsWithSelf.drop(1).any { it == other } } + } + } + if (grantedPermissions.isEmpty()) { + sendMessage("${target.asString()} 未被授予任何权限. 使用 `${CommandManager.commandPrefix}permission grant` 给予权限.") + } else { + sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() }) + } } @Description("查看所有权限列表") @@ -216,4 +242,203 @@ public object BuiltInCommands { sendMessage(PermissionService.INSTANCE.getRegisteredPermissions().joinToString("\n") { it.id.toString() }) } } + + + public object AutoLoginCommand : CompositeCommand( + ConsoleCommandOwner, "autoLogin", "自动登录", + description = "自动登录设置", + overrideContext = buildCommandArgumentContext { + ConfigurationKey::class with ConfigurationKey.Parser + } + ), BuiltInCommandInternal { + @Description("查看自动登录账号列表") + @SubCommand + public suspend fun CommandSender.list() { + sendMessage(buildString { + for (account in AutoLoginConfig.accounts) { + if (account.account == "123456") continue + append("- ") + append("账号: ") + append(account.account) + appendLine() + append(" 密码: ") + append(account.password.value) + appendLine() + + if (account.configuration.isNotEmpty()) { + appendLine(" 配置:") + for ((key, value) in account.configuration) { + append(" $key = $value") + } + appendLine() + } + } + }) + } + + @Description("添加自动登录") + @SubCommand + public suspend fun CommandSender.add(account: Long, password: String, passwordKind: PasswordKind = PLAIN) { + val accountStr = account.toString() + if (AutoLoginConfig.accounts.any { it.account == accountStr }) { + sendMessage("已有相同账号在自动登录配置中. 请先删除该账号.") + return + } + AutoLoginConfig.accounts.add(AutoLoginConfig.Account(accountStr, Password(passwordKind, password))) + sendMessage("已成功添加 '$account'.") + } + + @Description("清除所有配置") + @SubCommand + public suspend fun CommandSender.clear() { + AutoLoginConfig.accounts.clear() + sendMessage("已清除所有自动登录配置.") + } + + @Description("删除一个账号") + @SubCommand + public suspend fun CommandSender.remove(account: Long) { + val accountStr = account.toString() + if (AutoLoginConfig.accounts.removeIf { it.account == accountStr }) { + sendMessage("已成功删除 '$account'.") + return + } + sendMessage("账号 '$account' 未配置自动登录.") + } + + @Description("设置一个账号的一个配置项") + @SubCommand + public suspend fun CommandSender.setConfig(account: Long, configKey: ConfigurationKey, value: String) { + val accountStr = account.toString() + + val oldAccount = AutoLoginConfig.accounts.find { it.account == accountStr } ?: kotlin.run { + sendMessage("未找到账号 $account.") + return + } + + if (value.isEmpty()) return removeConfig(account, configKey) + + val newAccount = oldAccount.copy(configuration = oldAccount.configuration.toMutableMap().apply { + put(configKey, value) + }) + + AutoLoginConfig.accounts.remove(oldAccount) + AutoLoginConfig.accounts.add(newAccount) + + sendMessage("成功修改 '$account' 的配置 '$configKey' 为 '$value'") + } + + @Description("删除一个账号的一个配置项") + @SubCommand + public suspend fun CommandSender.removeConfig(account: Long, configKey: ConfigurationKey) { + val accountStr = account.toString() + + val oldAccount = AutoLoginConfig.accounts.find { it.account == accountStr } ?: kotlin.run { + sendMessage("未找到账号 $account.") + return + } + + val newAccount = oldAccount.copy(configuration = oldAccount.configuration.toMutableMap().apply { + remove(configKey) + }) + + AutoLoginConfig.accounts.remove(oldAccount) + AutoLoginConfig.accounts.add(newAccount) + + sendMessage("成功删除 '$account' 的配置 '$configKey'.") + } + } + + public object StatusCommand : SimpleCommand( + ConsoleCommandOwner, "status", "states", "状态", + description = "获取 Mirai Console 运行状态" + ), BuiltInCommandInternal { + @Handler + public suspend fun CommandSender.handle() { + sendMessage(buildString { + val buildDateFormatted = + MiraiConsoleBuildConstants.buildDate.atZone(ZoneId.systemDefault()) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + + append("Running MiraiConsole v${MiraiConsoleBuildConstants.versionConst}, built on ").append(buildDateFormatted) + .append(".\n") + append(MiraiConsoleImplementationBridge.frontEndDescription.render()).append("\n\n") + append("Permission Service: ").append( + if (PermissionService.INSTANCE is BuiltInPermissionService) { + "Built In Permission Service" + } else { + val plugin = PermissionServiceProvider.providerPlugin + if (plugin == null) { + PermissionService.INSTANCE.toString() + } else { + "${plugin.name} v${plugin.version}" + } + } + ) + append("\n\n") + + append("Plugins: ") + if (PluginManagerImpl.resolvedPlugins.isEmpty()) { + append("") + } else { + PluginManagerImpl.resolvedPlugins.joinTo(this) { plugin -> + "${plugin.name} v${plugin.version}" + } + } + append("\n\n") + val memoryMXBean = ManagementFactory.getMemoryMXBean() + + append("Object Pending Finalization Count: ") + .append(memoryMXBean.objectPendingFinalizationCount) + .append("\n") + + append(" Heap Memory: ") + renderMemoryUsage(memoryMXBean.heapMemoryUsage) + append("\nNon-Heap Memory: ") + renderMemoryUsage(memoryMXBean.nonHeapMemoryUsage) + }) + } + + private const val MEM_B = 1024L + private const val MEM_KB = 1024L shl 10 + private const val MEM_MB = 1024L shl 20 + private const val MEM_GB = 1024L shl 30 + + @Suppress("NOTHING_TO_INLINE") + private inline fun StringBuilder.appendDouble(number: Double): StringBuilder = + append(floor(number * 100) / 100) + + private fun StringBuilder.renderMemoryUsageNumber(num: Long) { + when { + num == -1L -> { + append(num) + } + num < MEM_B -> { + append(num).append("B") + } + num < MEM_KB -> { + appendDouble(num / 1024.0).append("KB") + } + num < MEM_MB -> { + appendDouble((num ushr 10) / 1024.0).append("MB") + } + else -> { + appendDouble((num ushr 20) / 1024.0).append("GB") + } + } + } + + + private fun StringBuilder.renderMemoryUsage(usage: MemoryUsage) { + append("(committed / init / used / max) [") + renderMemoryUsageNumber(usage.committed) + append(", ") + renderMemoryUsageNumber(usage.init) + append(", ") + renderMemoryUsageNumber(usage.used) + append(", ") + renderMemoryUsageNumber(usage.max) + append("]") + } + } } \ No newline at end of file diff --git a/backend/mirai-console/src/command/CommandExecuteResult.kt b/backend/mirai-console/src/command/CommandExecuteResult.kt index c1961445c..1eb365c88 100644 --- a/backend/mirai-console/src/command/CommandExecuteResult.kt +++ b/backend/mirai-console/src/command/CommandExecuteResult.kt @@ -14,18 +14,14 @@ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument +import net.mamoe.mirai.console.command.resolve.InterceptedReason import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.contracts.contract /** * 指令的执行返回 - * - * 注意: 现阶段 - * - * @see CommandExecuteStatus */ -@ConsoleExperimentalApi("Not yet implemented") @ExperimentalCommandDescriptors public sealed class CommandExecuteResult { /** 指令执行时发生的错误 (如果有) */ @@ -97,6 +93,21 @@ public sealed class CommandExecuteResult { public override val resolvedCall: ResolvedCommandCall? get() = null } + /** 没有匹配的指令 */ + public class Intercepted( + /** 解析的 [CommandCall] (如果匹配到) */ + public override val call: CommandCall?, + /** 解析的 [ResolvedCommandCall] (如果匹配到) */ + public override val resolvedCall: ResolvedCommandCall?, + /** 尝试执行的指令 (如果匹配到) */ + public override val command: Command?, + /** 拦截原因 */ + public val reason: InterceptedReason, + ) : Failure() { + /** 指令执行时发生的错误, 总是 `null` */ + public override val exception: Nothing? get() = null + } + /** 权限不足 */ public class PermissionDenied( /** 尝试执行的指令 */ diff --git a/backend/mirai-console/src/command/CommandSender.kt b/backend/mirai-console/src/command/CommandSender.kt index 5f1abba24..526e6675f 100644 --- a/backend/mirai-console/src/command/CommandSender.kt +++ b/backend/mirai-console/src/command/CommandSender.kt @@ -14,9 +14,7 @@ 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.MiraiConsole @@ -24,11 +22,9 @@ 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.descriptor.CommandArgumentParserException import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.data.castOrNull import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip -import net.mamoe.mirai.console.internal.plugin.rootCauseOrSelf import net.mamoe.mirai.console.permission.AbstractPermitteeId import net.mamoe.mirai.console.permission.Permittee import net.mamoe.mirai.console.permission.PermitteeId @@ -47,7 +43,7 @@ import kotlin.coroutines.CoroutineContext /** * 指令发送者. * - * 只有 [CommandSender] 才能 [执行指令][CommandManager.execute] + * 只有 [CommandSender] 才能 [执行指令][CommandManager.executeCommand] * * ## 获得指令发送者 * - [MessageEvent.toCommandSender] @@ -74,7 +70,7 @@ import kotlin.coroutines.CoroutineContext * - [AbstractUserCommandSender] 代表用户 * - [ConsoleCommandSender] 代表控制台 * - * 二级子类, 当指令由插件 [主动执行][CommandManager.execute] 时, 插件应使用 [toCommandSender] 或 [asCommandSender], 因此, + * 二级子类, 当指令由插件 [主动执行][CommandManager.executeCommand] 时, 插件应使用 [toCommandSender] 或 [asCommandSender], 因此, * - 若在群聊环境, 对应 [CommandSender] 为 [MemberCommandSender] * - 若在私聊环境, 对应 [CommandSender] 为 [FriendCommandSender] * - 若在临时会话环境, 对应 [CommandSender] 为 [TempCommandSender] @@ -173,9 +169,6 @@ public interface CommandSender : CoroutineScope, Permittee { @JvmBlockingBridge public suspend fun sendMessage(message: String): MessageReceipt? - @ConsoleExperimentalApi("This is unstable and might get changed") - public suspend fun catchExecutionException(e: Throwable) - public companion object { /////////////////////////////////////////////////////////////////////////// @@ -274,154 +267,54 @@ public sealed class AbstractCommandSender : CommandSender, CoroutineScope { public abstract override val subject: Contact? public abstract override val user: User? public abstract override fun toString(): String - - @ConsoleExperimentalApi("This is unstable and might get changed") - override suspend fun catchExecutionException(e: Throwable) { - if (this is CommandSenderOnMessage<*>) { - val cause = e.rootCauseOrSelf - - // TODO: 2020/10/17 - // CommandArgumentParserException 作为 IllegalCommandArgumentException 不会再进入此函数 - // 已在 - // - [console] CommandManagerImpl.commandListener - // - [terminal] ConsoleThread.kt - // 处理 - - 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 查看堆栈信息 - this@AbstractCommandSender.launch(CoroutineName("stacktrace delayer from command")) { - if (fromEvent.nextMessageOrNull(60_000) { - it.message.contentEquals("stacktrace") || it.message.contentEquals("stack") - } != null) { - sendMessage(e.stackTraceToString()) - } - } - } else { - sendMessage(e.stackTraceToString()) - } - } -} - -/** - * 当 [this] 为 [AbstractCommandSender] 时返回. - * - * 正常情况下, 所有 [CommandSender] 都应该继承 [AbstractCommandSender] - * - * 在需要类型智能转换等情况时可使用此函数. - * - * ### 契约 - * 本函数定义契约, - * - 若函数正常返回, Kotlin 编译器认为 [this] 是 [AbstractCommandSender] 实例并执行智能类型转换. - * - * @return `this` - */ -public fun CommandSender.checkIsAbstractCommandSender(): AbstractCommandSender { - contract { - returns() implies (this@checkIsAbstractCommandSender is AbstractCommandSender) - } - check(this is AbstractCommandSender) { "A CommandSender must extend AbstractCommandSender" } - return this -} - -/** - * 当 [this] 为 [AbstractUserCommandSender] 时返回. - * - * 正常情况下, 所有 [UserCommandSender] 都应该继承 [AbstractUserCommandSender] - * - * 在需要类型智能转换等情况时可使用此函数. - * - * ### 契约 - * 本函数定义契约, - * - 若函数正常返回, Kotlin 编译器认为 [this] 是 [AbstractUserCommandSender] 实例并执行智能类型转换. - * - * @return `this` - */ -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] 实例并执行智能类型转换. + * 当 [this] 不为 [ConsoleCommandSender] 时返回 `true` */ public fun CommandSender.isNotConsole(): Boolean { contract { - returns(true) implies (this@isNotConsole is UserCommandSender) - returns(false) implies (this@isNotConsole is ConsoleCommandSender) + returns(true) 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] 的两种可能性. + * 折叠 [AbstractCommandSender] 的可能性. * * - 当 [this] 为 [ConsoleCommandSender] 时执行 [ifIsConsole] * - 当 [this] 为 [UserCommandSender] 时执行 [ifIsUser] + * - 否则执行 [otherwise] * * ### 示例 * ``` @@ -438,20 +331,23 @@ public fun CommandSender.isNotUser(): Boolean { * ) * ``` * - * @return [ifIsConsole] 或 [ifIsUser] 执行结果. + * @return [ifIsConsole], [ifIsUser] 或 [otherwise] 执行结果. */ @JvmSynthetic public inline fun CommandSender.fold( ifIsConsole: ConsoleCommandSender.() -> R, ifIsUser: UserCommandSender.() -> R, + otherwise: CommandSender.() -> R = { error("CommandSender ${this::class.qualifiedName} is not supported") }, ): R { contract { callsInPlace(ifIsConsole, InvocationKind.AT_MOST_ONCE) callsInPlace(ifIsUser, InvocationKind.AT_MOST_ONCE) + callsInPlace(otherwise, InvocationKind.AT_MOST_ONCE) } - return when (val sender = this.checkIsAbstractCommandSender()) { + return when (val sender = this) { is ConsoleCommandSender -> ifIsConsole(sender) - is AbstractUserCommandSender -> ifIsUser(sender) + is UserCommandSender -> ifIsUser(sender) + else -> otherwise(sender) } } @@ -477,7 +373,7 @@ public inline fun UserCommandSender.foldContext( callsInPlace(inGroup, InvocationKind.AT_MOST_ONCE) callsInPlace(inPrivate, InvocationKind.AT_MOST_ONCE) } - return when (val sender = this.checkIsAbstractUserCommandSender()) { + return when (val sender = this) { is MemberCommandSender -> inGroup(sender) else -> inPrivate(sender) } @@ -603,7 +499,7 @@ public sealed class AbstractUserCommandSender : UserCommandSender, AbstractComma } /** - * 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.execute]) + * 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand]) * @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令 */ public open class FriendCommandSender internal constructor( @@ -622,7 +518,7 @@ public open class FriendCommandSender internal constructor( } /** - * 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式, 也有可能是由插件在代码直接执行 ([CommandManager.execute]) + * 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand]) * @see MemberCommandSenderOnMessage 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令. */ public open class MemberCommandSender internal constructor( @@ -644,7 +540,7 @@ public open class MemberCommandSender internal constructor( } /** - * 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.execute]) + * 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand]) * @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令 */ public open class TempCommandSender internal constructor( diff --git a/backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt b/backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt index 0540532fc..e8c6a4113 100644 --- a/backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt +++ b/backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt @@ -71,7 +71,7 @@ public interface CommandArgumentContext { * * @see EmptyCommandArgumentContext */ - @JvmStatic + @JvmField // public static final CommandArgumentContext EMPTY; public val EMPTY: CommandArgumentContext = EmptyCommandArgumentContext } diff --git a/backend/mirai-console/src/command/descriptor/CommandParameter.kt b/backend/mirai-console/src/command/descriptor/CommandParameter.kt index 01f4b3533..54f9d0f3e 100644 --- a/backend/mirai-console/src/command/descriptor/CommandParameter.kt +++ b/backend/mirai-console/src/command/descriptor/CommandParameter.kt @@ -14,6 +14,7 @@ import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter. import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.UserDefinedType.Companion.createRequired import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable import net.mamoe.mirai.console.command.parse.CommandValueArgument +import net.mamoe.mirai.console.command.resolve.ResolvedCommandValueArgument import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull import net.mamoe.mirai.console.internal.data.typeOf0 @@ -52,13 +53,31 @@ public abstract class AbstractCommandParameter : CommandParameter { } /** - * Inherited instances must be [AbstractCommandValueParameter] + * Inherited instances must be [AbstractCommandValueParameter]. + * + * ### Implementation details + * + * [CommandValueParameter] should: + * - implement [equals], [hashCode] since used in [ResolvedCommandValueArgument]. + * - implement [toString] to produce user-friendly textual representation of this parameter with type info. + * */ @ExperimentalCommandDescriptors public interface CommandValueParameter : CommandParameter { public val isVararg: Boolean + /** + * Checks whether this [CommandValueParameter] accepts [argument]. + * + * An [argument] can be accepted if: + * - [CommandValueArgument.type] is subtype of, or equals to [CommandValueParameter.type] (nullability considered), or + * - [CommandValueArgument.typeVariants] produces + * + * @return `true` if [argument] may be accepted through any approach mentioned above. + * + * @see accepting + */ public fun accepts(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): Boolean = accepting(argument, commandArgumentContext).isAcceptable @@ -208,6 +227,8 @@ public sealed class AbstractCommandValueParameter : CommandValueParameter, public override val isVararg: Boolean, public override val type: KType, ) : AbstractCommandValueParameter() { + override fun toString(): String = super.toString() + init { requireNotNull(type.classifierAsKClassOrNull()) { "type.classifier must be KClass." @@ -237,7 +258,5 @@ public sealed class AbstractCommandValueParameter : CommandValueParameter, * Extended by [CommandValueArgumentParser] */ @ConsoleExperimentalApi - public abstract class Extended : AbstractCommandValueParameter() { - abstract override fun toString(): String - } + public abstract class Extended : AbstractCommandValueParameter() // For implementer: take care of toString() } \ No newline at end of file diff --git a/backend/mirai-console/src/command/descriptor/CommandSignature.kt b/backend/mirai-console/src/command/descriptor/CommandSignature.kt index efeb4618b..777a97c67 100644 --- a/backend/mirai-console/src/command/descriptor/CommandSignature.kt +++ b/backend/mirai-console/src/command/descriptor/CommandSignature.kt @@ -58,9 +58,9 @@ public abstract class AbstractCommandSignature : CommandSignature { override fun toString(): String { val receiverParameter = receiverParameter return if (receiverParameter == null) { - "CommandSignatureVariant(${valueParameters.joinToString()})" + "CommandSignature(${valueParameters.joinToString()})" } else { - "CommandSignatureVariant($receiverParameter, ${valueParameters.joinToString()})" + "CommandSignature($receiverParameter, ${valueParameters.joinToString()})" } } } diff --git a/backend/mirai-console/src/command/descriptor/CommandValueArgumentParser.kt b/backend/mirai-console/src/command/descriptor/CommandValueArgumentParser.kt index e8d4e67b3..c09f7d87a 100644 --- a/backend/mirai-console/src/command/descriptor/CommandValueArgumentParser.kt +++ b/backend/mirai-console/src/command/descriptor/CommandValueArgumentParser.kt @@ -16,7 +16,10 @@ import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.CompositeCommand import net.mamoe.mirai.console.command.SimpleCommand +import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.map import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.parse +import net.mamoe.mirai.console.permission.PermissionId +import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.* import kotlin.contracts.InvocationKind @@ -45,6 +48,9 @@ import kotlin.contracts.contract * - [User]: [ExistingUserValueArgumentParser] * - [Contact]: [ExistingContactValueArgumentParser] * + * - [PermitteeId]: [PermitteeIdValueArgumentParser] + * - [PermissionId]: [PermissionIdValueArgumentParser] + * * * @see SimpleCommand 简单指令 * @see CompositeCommand 复合指令 @@ -143,6 +149,9 @@ public abstract class AbstractCommandValueArgumentParser : CommandValue } } +/** + * @see CommandValueArgumentParser.map + */ public class MappingCommandValueArgumentParser( private val original: CommandValueArgumentParser, private val mapper: MappingCommandValueArgumentParser.(T) -> R, diff --git a/backend/mirai-console/src/command/descriptor/TypeVariant.kt b/backend/mirai-console/src/command/descriptor/TypeVariant.kt index 2a4d49a82..322c92d94 100644 --- a/backend/mirai-console/src/command/descriptor/TypeVariant.kt +++ b/backend/mirai-console/src/command/descriptor/TypeVariant.kt @@ -9,8 +9,6 @@ package net.mamoe.mirai.console.command.descriptor -import net.mamoe.mirai.console.command.parse.CommandCall -import net.mamoe.mirai.console.command.parse.CommandCallParser import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.internal.data.castOrNull import net.mamoe.mirai.console.internal.data.kClassQualifiedName @@ -19,9 +17,18 @@ import kotlin.reflect.KType import kotlin.reflect.typeOf /** - * Implicit type variant specified by [CommandCallParser]. + * Intrinsic variant of an [CommandValueArgument]. * - * [TypeVariant] is not necessary for all [CommandCall]s. + * The *intrinsic* reveals the independent conversion property of this type. + * Conversion with [TypeVariant] is out of any contextual resource, + * except the [output type][TypeVariant.outType] declared by the [TypeVariant] itself. + * + * + * [TypeVariant] is not necessary for all [CommandValueArgument]s. + * + * @param OutType the type this [TypeVariant] can map a argument [Message] to . + * + * @see CommandValueArgument.typeVariants */ @ExperimentalCommandDescriptors public interface TypeVariant { @@ -31,17 +38,22 @@ public interface TypeVariant { public val outType: KType /** + * Maps an [valueArgument] to [outType] + * * @see CommandValueArgument.value */ - public fun mapValue(valueParameter: Message): OutType + public fun mapValue(valueArgument: Message): OutType public companion object { + /** + * Creates a [TypeVariant] with reified [OutType]. + */ @OptIn(ExperimentalStdlibApi::class) @JvmSynthetic public inline operator fun invoke(crossinline block: (valueParameter: Message) -> OutType): TypeVariant { return object : TypeVariant { override val outType: KType = typeOf() - override fun mapValue(valueParameter: Message): OutType = block(valueParameter) + override fun mapValue(valueArgument: Message): OutType = block(valueArgument) } } } @@ -51,20 +63,20 @@ public interface TypeVariant { public object MessageContentTypeVariant : TypeVariant { @OptIn(ExperimentalStdlibApi::class) override val outType: KType = typeOf() - override fun mapValue(valueParameter: Message): MessageContent = - valueParameter.castOrNull() ?: error("Accepts MessageContent only but given ${valueParameter.kClassQualifiedName}") + override fun mapValue(valueArgument: Message): MessageContent = + valueArgument.castOrNull() ?: error("Accepts MessageContent only but given ${valueArgument.kClassQualifiedName}") } @ExperimentalCommandDescriptors public object MessageChainTypeVariant : TypeVariant { @OptIn(ExperimentalStdlibApi::class) override val outType: KType = typeOf() - override fun mapValue(valueParameter: Message): MessageChain = valueParameter.asMessageChain() + override fun mapValue(valueArgument: Message): MessageChain = valueArgument.asMessageChain() } @ExperimentalCommandDescriptors public object ContentStringTypeVariant : TypeVariant { @OptIn(ExperimentalStdlibApi::class) override val outType: KType = typeOf() - override fun mapValue(valueParameter: Message): String = valueParameter.content + override fun mapValue(valueArgument: Message): String = valueArgument.content } diff --git a/backend/mirai-console/src/command/parse/CommandCall.kt b/backend/mirai-console/src/command/parse/CommandCall.kt index fdc39e079..1ed8b6cef 100644 --- a/backend/mirai-console/src/command/parse/CommandCall.kt +++ b/backend/mirai-console/src/command/parse/CommandCall.kt @@ -12,29 +12,45 @@ package net.mamoe.mirai.console.command.parse 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.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.resolve.CommandCallResolver +import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall +import net.mamoe.mirai.message.data.MessageChain /** * Unresolved [CommandCall]. * + * ### Implementation details + * [CommandCall] should be _immutable_, + * meaning all of its properties must be *pure* and should be implemented as an immutable property, or delegated by a lazy initializer. + * * @see CommandCallParser * @see CommandCallResolver + * + * @see ResolvedCommandCall */ @ExperimentalCommandDescriptors public interface CommandCall { + /** + * The [CommandSender] responsible to this call. + */ public val caller: CommandSender /** - * One of callee [Command]'s [Command.allNames] + * One of callee [Command]'s [Command.allNames]. + * + * Generally [CommandCallResolver] use [calleeName] to find target [Command] registered in [CommandManager] */ public val calleeName: String /** - * Explicit value arguments + * Explicit value arguments parsed from raw [MessageChain] or implicit ones deduced by the [CommandCallResolver]. */ public val valueArguments: List + + // maybe add contextual arguments, i.e. from MessageMetadata } @ExperimentalCommandDescriptors diff --git a/backend/mirai-console/src/command/parse/CommandValueArgument.kt b/backend/mirai-console/src/command/parse/CommandValueArgument.kt index 03b2771d5..eb17fc552 100644 --- a/backend/mirai-console/src/command/parse/CommandValueArgument.kt +++ b/backend/mirai-console/src/command/parse/CommandValueArgument.kt @@ -42,6 +42,12 @@ public interface CommandValueArgument : CommandArgument { * [MessageChain] is vararg */ public val value: Message + + /** + * Intrinsic variants of this argument. + * + * @see TypeVariant + */ public val typeVariants: List> } diff --git a/backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt b/backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt index 95ab24b12..201985ffd 100644 --- a/backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt +++ b/backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt @@ -28,7 +28,7 @@ import net.mamoe.mirai.message.data.asMessageChain @ExperimentalCommandDescriptors public object BuiltInCommandCallResolver : CommandCallResolver { override fun resolve(call: CommandCall): CommandResolveResult { - val callee = CommandManager.matchCommand(call.calleeName) ?: return CommandResolveResult(null) + val callee = CommandManager.matchCommand(call.calleeName) ?: return CommandResolveResult(CommandExecuteResult.UnresolvedCommand(call)) val valueArguments = call.valueArguments val context = callee.safeCast()?.context diff --git a/backend/mirai-console/src/command/resolve/CommandCallInterceptor.kt b/backend/mirai-console/src/command/resolve/CommandCallInterceptor.kt new file mode 100644 index 000000000..bf7529bd3 --- /dev/null +++ b/backend/mirai-console/src/command/resolve/CommandCallInterceptor.kt @@ -0,0 +1,194 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("unused", "NOTHING_TO_INLINE") + +package net.mamoe.mirai.console.command.resolve + +import kotlinx.serialization.Serializable +import net.mamoe.mirai.console.command.CommandSender +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.parse.CommandCall +import net.mamoe.mirai.console.command.parse.CommandCallParser +import net.mamoe.mirai.console.extensions.CommandCallInterceptorProvider +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage +import net.mamoe.mirai.console.internal.util.UNREACHABLE_CLAUSE +import net.mamoe.mirai.console.util.safeCast +import net.mamoe.mirai.message.data.Message +import org.jetbrains.annotations.Contract +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + + +/** + * 指令解析和调用拦截器. 用于在指令各解析阶段拦截或转换调用. + */ +@ExperimentalCommandDescriptors +public interface CommandCallInterceptor { + /** + * 在指令[语法解析][CommandCallParser]前调用. + * + * @return `null` 表示未处理 + */ + public fun interceptBeforeCall( + message: Message, + caller: CommandSender, + ): InterceptResult? = null + + /** + * 在指令[语法解析][CommandCallParser]后调用. + * + * @return `null` 表示未处理 + */ + public fun interceptCall( + call: CommandCall, + ): InterceptResult? = null + + /** + * 在指令[调用解析][CommandCallResolver]后调用. + * + * @return `null` 表示未处理 + */ + public fun interceptResolvedCall( + call: ResolvedCommandCall, + ): InterceptResult? = null + + public companion object { + /** + * 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall]. + * 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [Message] + */ + @JvmStatic + public fun Message.intercepted(caller: CommandSender): InterceptResult { + GlobalComponentStorage.run { + return CommandCallInterceptorProvider.foldExtensions(this@intercepted) { acc, ext -> + val intercepted = ext.instance.interceptBeforeCall(acc, caller) + intercepted?.fold( + onIntercepted = { return intercepted }, + otherwise = { it } + ) ?: acc + }.let { InterceptResult(it) } + } + } + + /** + * 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall]. + * 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [CommandCall] + */ + @JvmStatic + public fun CommandCall.intercepted(): InterceptResult { + GlobalComponentStorage.run { + return CommandCallInterceptorProvider.foldExtensions(this@intercepted) { acc, ext -> + val intercepted = ext.instance.interceptCall(acc) + intercepted?.fold( + onIntercepted = { return intercepted }, + otherwise = { it } + ) ?: acc + }.let { InterceptResult(it) } + } + } + + /** + * 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall]. + * 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [ResolvedCommandCall] + */ + @JvmStatic + public fun ResolvedCommandCall.intercepted(): InterceptResult { + GlobalComponentStorage.run { + return CommandCallInterceptorProvider.foldExtensions(this@intercepted) { acc, ext -> + val intercepted = ext.instance.interceptResolvedCall(acc) + intercepted?.fold( + onIntercepted = { return intercepted }, + otherwise = { it } + ) ?: acc + }.let { InterceptResult(it) } + } + } + } +} + +/** + * [CommandCallInterceptor] 拦截结果 + */ +@ExperimentalCommandDescriptors +public class InterceptResult internal constructor( + private val _value: Any?, + @Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?, +) { + /** + * 构造一个 [InterceptResult], 以 [value] 继续处理后续指令执行. + */ + public constructor(value: T) : this(value as Any?, null) + + /** + * 构造一个 [InterceptResult], 以 [原因][reason] 中断指令执行. + */ + public constructor(reason: InterceptedReason) : this(reason as Any?, null) + + @get:Contract(pure = true) + public val value: T? + @Suppress("UNCHECKED_CAST") + get() { + val value = this._value + return if (value is InterceptedReason) null else value as T + } + + @get:Contract(pure = true) + public val reason: InterceptedReason? + get() = this._value.safeCast() +} + +@ExperimentalCommandDescriptors +public inline fun InterceptResult.fold( + onIntercepted: (reason: InterceptedReason) -> R, + otherwise: (call: T) -> R, +): R { + contract { + callsInPlace(onIntercepted, InvocationKind.AT_MOST_ONCE) + callsInPlace(otherwise, InvocationKind.AT_MOST_ONCE) + } + value?.let(otherwise) + reason?.let(onIntercepted) + UNREACHABLE_CLAUSE +} + +@ExperimentalCommandDescriptors +public inline fun InterceptResult.getOrElse(onIntercepted: (reason: InterceptedReason) -> R): R { + contract { callsInPlace(onIntercepted, InvocationKind.AT_MOST_ONCE) } + reason?.let(onIntercepted) + return value!! +} + +/** + * 创建一个 [InterceptedReason] + * + * @see InterceptedReason.create + */ +@ExperimentalCommandDescriptors +@JvmSynthetic +public inline fun InterceptedReason(message: String): InterceptedReason = InterceptedReason.create(message) + +/** + * 拦截原因 + */ +@ExperimentalCommandDescriptors +public interface InterceptedReason { + public val message: String + + public companion object { + /** + * 创建一个 [InterceptedReason] + */ + public fun create(message: String): InterceptedReason = InterceptedReasonData(message) + } +} + +@OptIn(ExperimentalCommandDescriptors::class) +@Serializable +private data class InterceptedReasonData(override val message: String) : InterceptedReason diff --git a/backend/mirai-console/src/command/resolve/CommandCallResolver.kt b/backend/mirai-console/src/command/resolve/CommandCallResolver.kt index d592654be..c8e69ba15 100644 --- a/backend/mirai-console/src/command/resolve/CommandCallResolver.kt +++ b/backend/mirai-console/src/command/resolve/CommandCallResolver.kt @@ -32,22 +32,35 @@ public class CommandResolveResult private constructor( public val failure: CommandExecuteResult.Failure? get() = value.safeCast() - public inline fun fold( - onSuccess: (ResolvedCommandCall?) -> R, - onFailure: (CommandExecuteResult.Failure) -> R, - ): R { - contract { - callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE) - callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE) - } - failure?.let(onFailure)?.let { return it } - return call.let(onSuccess) - } - - public constructor(call: ResolvedCommandCall?) : this(call as Any?) + public constructor(call: ResolvedCommandCall) : this(call as Any?) public constructor(failure: CommandExecuteResult.Failure) : this(failure as Any) } +@ExperimentalCommandDescriptors +public inline fun CommandResolveResult.fold( + onSuccess: (ResolvedCommandCall?) -> R, + onFailure: (CommandExecuteResult.Failure) -> R, +): R { + contract { + callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE) + callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE) + } + failure?.let(onFailure)?.let { return it } + return call.let(onSuccess) +} + + +@ExperimentalCommandDescriptors +public inline fun CommandResolveResult.getOrElse( + onFailure: (CommandExecuteResult.Failure) -> ResolvedCommandCall?, +): ResolvedCommandCall { + contract { + callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE) + } + failure?.let(onFailure)?.let { return it } + return call!! +} + /** * The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered [] * diff --git a/backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt b/backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt index 56008f5b5..4f10ab59a 100644 --- a/backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt +++ b/backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt @@ -24,10 +24,17 @@ import net.mamoe.mirai.console.util.cast /** * The resolved [CommandCall]. * + * ### Implementation details + * [ResolvedCommandCall] should be _immutable_, + * meaning all of its properties must be *pure* and should be implemented as an immutable property, or delegated by a lazy initializer. + * * @see ResolvedCommandCallImpl */ @ExperimentalCommandDescriptors public interface ResolvedCommandCall { + /** + * The [CommandSender] responsible to this call. + */ public val caller: CommandSender /** @@ -48,7 +55,7 @@ public interface ResolvedCommandCall { /** * Resolved value arguments arranged mapping the [CommandSignature.valueParameters] by index. * - * **Implementation details**: Lazy calculation. + * **Default implementation details**: Lazy calculation. */ @ConsoleExperimentalApi public val resolvedValueArguments: List> @@ -56,18 +63,30 @@ public interface ResolvedCommandCall { public companion object } +/** + * Resolved [CommandValueParameter] for [ResolvedCommandCall.resolvedValueArguments] + */ @ExperimentalCommandDescriptors public data class ResolvedCommandValueArgument( val parameter: CommandValueParameter, + /** + * Argument value expected by the [parameter] + */ val value: T, ) // Don't move into companion, compilation error +/** + * Invoke this resolved call. + */ @ExperimentalCommandDescriptors public suspend inline fun ResolvedCommandCall.call() { return this@call.calleeSignature.call(this@call) } +/** + * Default implementation. + */ @ExperimentalCommandDescriptors public class ResolvedCommandCallImpl( override val caller: CommandSender, diff --git a/backend/mirai-console/src/data/ValueDescription.kt b/backend/mirai-console/src/data/ValueDescription.kt index aeeaaf9c8..4960ff6d8 100644 --- a/backend/mirai-console/src/data/ValueDescription.kt +++ b/backend/mirai-console/src/data/ValueDescription.kt @@ -31,13 +31,15 @@ import kotlinx.serialization.SerialInfo * map: * a: b * ``` + * + * @see net.mamoe.yamlkt.Comment */ @SerialInfo @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) public annotation class ValueDescription( /** - * 将会被 [String.trimIndent] 处理. + * 将会被 [String.trimIndent] 处理 */ val value: String, ) \ No newline at end of file diff --git a/backend/mirai-console/src/data/ValueName.kt b/backend/mirai-console/src/data/ValueName.kt index 17f101429..00452736b 100644 --- a/backend/mirai-console/src/data/ValueName.kt +++ b/backend/mirai-console/src/data/ValueName.kt @@ -23,7 +23,7 @@ package net.mamoe.mirai.console.data * 将被保存为配置 (YAML 作为示例): * ```yaml * AccountPluginData: - * map: + * info: * a: b * ``` * diff --git a/backend/mirai-console/src/extension/PluginComponentStorage.kt b/backend/mirai-console/src/extension/PluginComponentStorage.kt index 4cb05874f..601a19dde 100644 --- a/backend/mirai-console/src/extension/PluginComponentStorage.kt +++ b/backend/mirai-console/src/extension/PluginComponentStorage.kt @@ -11,6 +11,7 @@ package net.mamoe.mirai.console.extension import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCallParser +import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor import net.mamoe.mirai.console.command.resolve.CommandCallResolver import net.mamoe.mirai.console.extensions.* import net.mamoe.mirai.console.internal.extension.AbstractConcurrentComponentStorage @@ -128,4 +129,19 @@ public class PluginComponentStorage( @OverloadResolutionByLambdaReturnType public fun contributeCommandCallParser(provider: CommandCallResolverProvider): Unit = contribute(CommandCallResolverProvider, plugin, provider) + + ///////////////////////////////////// + + /** 注册一个 [CommandCallInterceptorProvider] */ + @ExperimentalCommandDescriptors + @OverloadResolutionByLambdaReturnType + public fun contributeCommandCallInterceptor(lazyInstance: () -> CommandCallInterceptor): Unit = + contribute(CommandCallInterceptorProvider, plugin, CommandCallInterceptorProviderImplLazy(lazyInstance)) + + /** 注册一个 [CommandCallInterceptorProvider] */ + @ExperimentalCommandDescriptors + @JvmName("contributeCommandCallInterceptorProvider") + @OverloadResolutionByLambdaReturnType + public fun contributeCommandCallParser(provider: CommandCallInterceptorProvider): Unit = + contribute(CommandCallInterceptorProvider, plugin, provider) } \ No newline at end of file diff --git a/backend/mirai-console/src/extensions/CommandCallInterceptorProvider.kt b/backend/mirai-console/src/extensions/CommandCallInterceptorProvider.kt new file mode 100644 index 000000000..7159ec4c6 --- /dev/null +++ b/backend/mirai-console/src/extensions/CommandCallInterceptorProvider.kt @@ -0,0 +1,20 @@ +package net.mamoe.mirai.console.extensions + +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor +import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint +import net.mamoe.mirai.console.extension.InstanceExtension + +@ExperimentalCommandDescriptors +public interface CommandCallInterceptorProvider : InstanceExtension { + public companion object ExtensionPoint : + AbstractInstanceExtensionPoint(CommandCallInterceptorProvider::class) +} + +@ExperimentalCommandDescriptors +public class CommandCallInterceptorProviderImpl(override val instance: CommandCallInterceptor) : CommandCallInterceptorProvider + +@ExperimentalCommandDescriptors +public class CommandCallInterceptorProviderImplLazy(initializer: () -> CommandCallInterceptor) : CommandCallInterceptorProvider { + override val instance: CommandCallInterceptor by lazy(initializer) +} \ No newline at end of file diff --git a/backend/mirai-console/src/extensions/PermissionServiceProvider.kt b/backend/mirai-console/src/extensions/PermissionServiceProvider.kt index d47043993..6a8546ef8 100644 --- a/backend/mirai-console/src/extensions/PermissionServiceProvider.kt +++ b/backend/mirai-console/src/extensions/PermissionServiceProvider.kt @@ -11,8 +11,11 @@ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.extension.AbstractSingletonExtensionPoint import net.mamoe.mirai.console.extension.SingletonExtension +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService import net.mamoe.mirai.console.permission.PermissionService +import net.mamoe.mirai.console.plugin.Plugin +import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * [权限服务][PermissionService] 提供器. @@ -21,7 +24,16 @@ import net.mamoe.mirai.console.permission.PermissionService */ public interface PermissionServiceProvider : SingletonExtension> { public companion object ExtensionPoint : - AbstractSingletonExtensionPoint>(PermissionServiceProvider::class, BuiltInPermissionService) + AbstractSingletonExtensionPoint>(PermissionServiceProvider::class, BuiltInPermissionService) { + @ConsoleExperimentalApi + public val providerPlugin: Plugin? by lazy { + GlobalComponentStorage.run { + val instance = PermissionService.INSTANCE + if (instance is BuiltInPermissionService) return@lazy null + PermissionServiceProvider.getExtensions().find { it.extension.instance === instance }?.plugin + } + } + } } /** diff --git a/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt b/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt index 141cb973b..7d8d8614b 100644 --- a/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt +++ b/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt @@ -14,8 +14,8 @@ import java.time.Instant internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants) @JvmStatic - val buildDate: Instant = Instant.ofEpochSecond(1605147625) - const val versionConst: String = "1.0-RC2-dev-4" + val buildDate: Instant = Instant.ofEpochSecond(1606185513) + const val versionConst: String = "1.0.1-dev-1" @JvmStatic val version: SemVersion = SemVersion(versionConst) diff --git a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt b/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt index b86024a88..67b45b9b7 100644 --- a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt +++ b/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt @@ -29,8 +29,9 @@ import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.extensions.PostStartupExtension import net.mamoe.mirai.console.extensions.SingletonExtensionSelector import net.mamoe.mirai.console.internal.command.CommandConfig -import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig +import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.MD5 +import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig import net.mamoe.mirai.console.internal.data.castOrNull @@ -41,6 +42,7 @@ import net.mamoe.mirai.console.internal.logging.MiraiConsoleLogger import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl import net.mamoe.mirai.console.internal.util.autoHexToBytes +import net.mamoe.mirai.console.internal.util.runIgnoreException import net.mamoe.mirai.console.logging.LoggerController import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.permission.PermissionService.Companion.permit @@ -49,6 +51,7 @@ import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.center.PluginCenter import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin import net.mamoe.mirai.console.plugin.loader.PluginLoader +import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.SemVersion @@ -184,24 +187,24 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI phase `load PermissionService`@{ mainLogger.verbose { "Loading PermissionService..." } - PermissionServiceProvider.selectedInstance // init - PermissionService.INSTANCE.let { ps -> if (ps is BuiltInPermissionService) { ConsoleDataScope.addAndReloadConfig(ps.config) mainLogger.verbose { "Reloaded PermissionService settings." } + } else { + mainLogger.info { "Loaded PermissionService from plugin ${PermissionServiceProvider.providerPlugin?.name}" } } } - ConsoleCommandSender.permit(RootPermission) + runIgnoreException { ConsoleCommandSender.permit(RootPermission) } } phase `prepare commands`@{ mainLogger.verbose { "Loading built-in commands..." } BuiltInCommands.registerAll() - mainLogger.verbose { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" } + mainLogger.info { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" } CommandManager - CommandManagerImpl.commandListener // start + // CommandManagerImpl.commandListener // start } phase `enable plugins`@{ @@ -218,20 +221,44 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI phase `auto-login bots`@{ runBlocking { - for ((id, password) in AutoLoginConfig.plainPasswords.filterNot { it.key == 123456654321L }) { - mainLogger.info { "Auto-login $id" } - MiraiConsole.addBot(id, password).alsoLogin() + val accounts = AutoLoginConfig.accounts.toList() + for (account in accounts) { + val id = kotlin.runCatching { + account.account.toLong() + }.getOrElse { + error("Bad auto-login account: '${account.account}'") + } + if (id == 123456L) continue + fun BotConfiguration.configBot() { + mainLogger.info { "Auto-login ${account.account}" } + + account.configuration[AutoLoginConfig.Account.ConfigurationKey.protocol] + ?.let { protocol -> + this.protocol = runCatching { + BotConfiguration.MiraiProtocol.valueOf(protocol.toString()) + }.getOrElse { + throw IllegalArgumentException( + "Bad auto-login config value for `protocol` for account $id", + it + ) + } + } + } + when (account.password.kind) { + PLAIN -> { + MiraiConsole.addBot(id, account.password.value, BotConfiguration::configBot).alsoLogin() + } + MD5 -> { + val md5 = kotlin.runCatching { + account.password.value.autoHexToBytes() + }.getOrElse { + error("Bad auto-login md5: '${account.password.value}' for account $id") + } + MiraiConsole.addBot(id, md5, BotConfiguration::configBot).alsoLogin() + } + } } - for ((id, password) in AutoLoginConfig.md5Passwords.filterNot { it.key == 123456654321L }) { - mainLogger.info { "Auto-login $id" } - val x = runCatching { - password.autoHexToBytes() - }.getOrElse { - error("Bad auto-login md5: '$password'") - } - MiraiConsole.addBot(id, x).alsoLogin() - } } } diff --git a/backend/mirai-console/src/internal/command/CommandManagerImpl.kt b/backend/mirai-console/src/internal/command/CommandManagerImpl.kt index 4818c1c1e..9ffb713ae 100644 --- a/backend/mirai-console/src/internal/command/CommandManagerImpl.kt +++ b/backend/mirai-console/src/internal/command/CommandManagerImpl.kt @@ -10,28 +10,23 @@ package net.mamoe.mirai.console.internal.command import kotlinx.atomicfu.locks.withLock -import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.Command.Companion.allNames import net.mamoe.mirai.console.command.CommandManager.INSTANCE.findDuplicate -import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCallParser.Companion.parseCommandCall +import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor.Companion.intercepted import net.mamoe.mirai.console.command.resolve.CommandCallResolver.Companion.resolve -import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge +import net.mamoe.mirai.console.command.resolve.getOrElse +import net.mamoe.mirai.console.internal.util.ifNull import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope -import net.mamoe.mirai.event.Listener -import net.mamoe.mirai.event.subscribeAlways -import net.mamoe.mirai.message.MessageEvent import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.asMessageChain -import net.mamoe.mirai.message.data.content import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.SimpleLogger import java.util.concurrent.locks.ReentrantLock @OptIn(ExperimentalCommandDescriptors::class) @@ -63,47 +58,6 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiCons } return optionalPrefixCommandMap[commandName.toLowerCase()] } - - internal val commandListener: Listener by lazy { - subscribeAlways( - coroutineContext = CoroutineExceptionHandler { _, throwable -> - logger.error(throwable) - }, - concurrency = Listener.ConcurrencyKind.CONCURRENT, - priority = Listener.EventPriority.HIGH - ) { - val sender = this.toCommandSender() - - when (val result = executeCommand(sender, message)) { - is CommandExecuteResult.PermissionDenied -> { - if (!result.command.prefixOptional || message.content.startsWith(CommandManager.commandPrefix)) { - if (MiraiConsoleImplementationBridge.loggerController.shouldLog("console.debug", SimpleLogger.LogPriority.DEBUG)) { - sender.sendMessage("权限不足") - } - intercept() - } - } - is CommandExecuteResult.IllegalArgument -> { - result.exception.message?.let { sender.sendMessage(it) } - intercept() - } - is CommandExecuteResult.Success -> { - intercept() - } - is CommandExecuteResult.ExecutionFailed -> { - sender.catchExecutionException(result.exception) - intercept() - } - is CommandExecuteResult.UnmatchedSignature, - is CommandExecuteResult.UnresolvedCommand, - -> { - // noop - } - } - } - } - - ///// IMPL @@ -173,15 +127,29 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiCons // Don't move into CommandManager, compilation error / VerifyError @OptIn(ExperimentalCommandDescriptors::class) internal suspend fun executeCommandImpl( - message: Message, + message0: Message, caller: CommandSender, checkPermission: Boolean, ): CommandExecuteResult { - val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.UnresolvedCommand(null) - val resolved = call.resolve().fold( - onSuccess = { it }, - onFailure = { return it } - ) ?: return CommandExecuteResult.UnresolvedCommand(call) + val message = message0 + .intercepted(caller) + .getOrElse { return CommandExecuteResult.Intercepted(null, null, null, it) } + + val call = message.asMessageChain() + .parseCommandCall(caller) + .ifNull { return CommandExecuteResult.UnresolvedCommand(null) } + .let { raw -> + raw.intercepted() + .getOrElse { return CommandExecuteResult.Intercepted(raw, null, null, it) } + } + + val resolved = call + .resolve() + .getOrElse { return it } + .let { raw -> + raw.intercepted() + .getOrElse { return CommandExecuteResult.Intercepted(call, raw, null, it) } + } val command = resolved.callee diff --git a/backend/mirai-console/src/internal/data/builtins/AutoLoginConfig.kt b/backend/mirai-console/src/internal/data/builtins/AutoLoginConfig.kt index a871d754a..7e7924b6a 100644 --- a/backend/mirai-console/src/internal/data/builtins/AutoLoginConfig.kt +++ b/backend/mirai-console/src/internal/data/builtins/AutoLoginConfig.kt @@ -7,31 +7,76 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("EXPOSED_SUPER_CLASS") + package net.mamoe.mirai.console.internal.data.builtins +import kotlinx.serialization.Serializable +import net.mamoe.mirai.console.command.CommandSender +import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser +import net.mamoe.mirai.console.command.descriptor.InternalCommandValueArgumentParserExtensions import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.value -import net.mamoe.mirai.console.internal.util.md5 -import net.mamoe.mirai.console.internal.util.toUHexString +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.yamlkt.Comment +import net.mamoe.yamlkt.YamlDynamicSerializer -internal object AutoLoginConfig : AutoSavePluginConfig("AutoLogin") { - @ValueDescription( - """ - 账号和明文密码列表 - """ - ) - val plainPasswords: MutableMap by value(mutableMapOf(123456654321L to "example")) +@ConsoleExperimentalApi +@ValueDescription("自动登录配置") +public object AutoLoginConfig : AutoSavePluginConfig("AutoLogin") { - - @ValueDescription( - """ - 账号和 MD5 密码列表 - """ - ) - val md5Passwords: MutableMap by value( - mutableMapOf( - 123456654321L to "example".toByteArray().md5().toUHexString() + @Serializable + public data class Account( + @Comment("账号, 现只支持 QQ 数字账号") + val account: String, + val password: Password, + @Comment(""" + 账号配置. 可用配置列表 (注意大小写): + "protocol": "ANDROID_PHONE" / "ANDROID_PAD" / "ANDROID_WATCH" + """) + val configuration: Map = mapOf(), + ) { + @Serializable + public data class Password( + @Comment("密码种类, 可选 PLAIN 或 MD5") + val kind: PasswordKind, + @Comment("密码内容, PLAIN 时为密码文本, MD5 时为 16 进制") + val value: String, ) - ) + + @Suppress("EnumEntryName") + @Serializable + public enum class ConfigurationKey { + protocol, + + ; + + public object Parser : CommandValueArgumentParser, InternalCommandValueArgumentParserExtensions() { + override fun parse(raw: String, sender: CommandSender): ConfigurationKey { + val key = values().find { it.name.equals(raw, ignoreCase = true) } + if (key != null) return key + illegalArgument("未知配置项, 可选值: ${values().joinToString()}") + } + } + } + + @Serializable + public enum class PasswordKind { + PLAIN, + MD5; + + public object Parser : CommandValueArgumentParser, InternalCommandValueArgumentParserExtensions() { + override fun parse(raw: String, sender: CommandSender): ConfigurationKey { + val key = ConfigurationKey.values().find { it.name.equals(raw, ignoreCase = true) } + if (key != null) return key + illegalArgument("未知配置项, 可选值: ${ConfigurationKey.values().joinToString()}") + } + } + } + } + + public val accounts: MutableList by value(mutableListOf( + Account("123456", Account.Password(Account.PasswordKind.PLAIN, "pwd"), mapOf(Account.ConfigurationKey.protocol to "ANDROID_PHONE")) + )) } \ No newline at end of file diff --git a/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt b/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt index 6f4af7bd2..dedf18192 100644 --- a/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt +++ b/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt @@ -154,7 +154,7 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { internal inline fun ExtensionPoint.useExtensions(block: (extension: T, plugin: Plugin?) -> Unit): Unit = withExtensions(block) - val instances: MutableMap, MutableSet>> = ConcurrentHashMap() + private val instances: MutableMap, MutableSet>> = ConcurrentHashMap() override fun contribute( extensionPoint: ExtensionPoint, plugin: Plugin, diff --git a/backend/mirai-console/src/internal/util/CommonUtils.kt b/backend/mirai-console/src/internal/util/CommonUtils.kt index aca7b0d7d..46411e7dd 100644 --- a/backend/mirai-console/src/internal/util/CommonUtils.kt +++ b/backend/mirai-console/src/internal/util/CommonUtils.kt @@ -7,12 +7,14 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:JvmName("CommonUtils") +@file:JvmName("CommonUtils") // maintain binary compatibility package net.mamoe.mirai.console.internal.util import io.github.karlatemp.caller.StackFrame import net.mamoe.mirai.console.internal.plugin.BuiltInJvmPluginLoaderImpl +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract internal inline fun runIgnoreException(block: () -> R): R? { try { @@ -38,3 +40,35 @@ internal fun StackFrame.findLoader(): ClassLoader? { BuiltInJvmPluginLoaderImpl.classLoaders.firstOrNull { it.findClass(className, true) != null } }.getOrNull() } + +@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") +@kotlin.internal.LowPriorityInOverloadResolution +internal inline fun T?.ifNull(block: () -> T): T { + contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) } + return this ?: block() +} + +@Suppress("DeprecatedCallableAddReplaceWith") +@Deprecated("Useless ifNull on not null value.") +@JvmName("ifNull1") +internal inline fun T.ifNull(block: () -> T): T = this + +@PublishedApi +internal inline fun assertionError(message: () -> String = { "Reached an unexpected branch." }): Nothing { + contract { callsInPlace(message, InvocationKind.EXACTLY_ONCE) } + throw AssertionError(message()) +} + +@PublishedApi +internal inline fun assertUnreachable(message: () -> String = { "Reached an unexpected branch." }): Nothing { + contract { callsInPlace(message, InvocationKind.EXACTLY_ONCE) } + throw AssertionError(message()) +} + +@MarkerUnreachableClause +@PublishedApi +internal inline val UNREACHABLE_CLAUSE: Nothing + get() = assertUnreachable() + +@DslMarker +private annotation class MarkerUnreachableClause \ No newline at end of file diff --git a/backend/mirai-console/src/logging/AbstractLoggerController.kt b/backend/mirai-console/src/logging/AbstractLoggerController.kt index 3d4725990..a3105bcac 100644 --- a/backend/mirai-console/src/logging/AbstractLoggerController.kt +++ b/backend/mirai-console/src/logging/AbstractLoggerController.kt @@ -18,7 +18,7 @@ public abstract class AbstractLoggerController : LoggerController { protected open fun shouldLog( priority: LogPriority, - settings: LogPriority + settings: LogPriority, ): Boolean = settings <= priority protected abstract fun getPriority(identity: String?): LogPriority diff --git a/backend/mirai-console/src/permission/PermissionRegistryConflictException.kt b/backend/mirai-console/src/permission/PermissionRegistryConflictException.kt index 0378a924f..a288d9886 100644 --- a/backend/mirai-console/src/permission/PermissionRegistryConflictException.kt +++ b/backend/mirai-console/src/permission/PermissionRegistryConflictException.kt @@ -17,4 +17,4 @@ package net.mamoe.mirai.console.permission public class PermissionRegistryConflictException( public val newInstance: Permission, public val existingInstance: Permission, -) : Exception("Conflicted Permission registry. new: $newInstance, existing: $existingInstance") \ No newline at end of file +) : Exception("Conflicting Permission registry. new: $newInstance, existing: $existingInstance") \ No newline at end of file diff --git a/backend/mirai-console/src/plugin/description/PluginDependency.kt b/backend/mirai-console/src/plugin/description/PluginDependency.kt index ff53a02aa..a5645a63a 100644 --- a/backend/mirai-console/src/plugin/description/PluginDependency.kt +++ b/backend/mirai-console/src/plugin/description/PluginDependency.kt @@ -16,6 +16,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_ID +import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.VERSION_REQUIREMENT import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.util.SemVersion @@ -37,7 +38,7 @@ public data class PluginDependency @JvmOverloads constructor( * * @see SemVersion.Requirement */ - public val versionRequirement: String? = null, + @ResolveContext(VERSION_REQUIREMENT) public val versionRequirement: String? = null, /** * 若为 `false`, 插件在找不到此依赖时也能正常加载. */ @@ -46,6 +47,7 @@ public data class PluginDependency @JvmOverloads constructor( init { kotlin.runCatching { PluginDescription.checkPluginId(id) + if (versionRequirement != null) SemVersion.parseRangeRequirement(versionRequirement) }.getOrElse { throw IllegalArgumentException(it) } @@ -63,7 +65,10 @@ public data class PluginDependency @JvmOverloads constructor( public override fun toString(): String = buildString { append(id) - versionRequirement?.let(::append) + versionRequirement?.let { + append(':') + append(it) + } if (isOptional) { append('?') } @@ -78,8 +83,12 @@ public data class PluginDependency @JvmOverloads constructor( public fun parseFromString(string: String): PluginDependency { require(string.isNotEmpty()) { "string is empty." } val optional = string.endsWith('?') - val (id, version) = string.removeSuffix("?").let { - it.substringBeforeLast(':') to it.substringAfterLast(':', "") + val (id, version) = string.removeSuffix("?").let { rule -> + if (rule.contains(':')) { + rule.substringBeforeLast(':') to rule.substringAfterLast(':') + } else { + rule to null + } } return PluginDependency(id, version, optional) } diff --git a/backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt b/backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt index d20a8852e..a65d810bb 100644 --- a/backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt +++ b/backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt @@ -19,6 +19,7 @@ import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.* import net.mamoe.mirai.console.internal.util.findLoader import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription +import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.SemVersion import net.mamoe.yamlkt.Yaml @@ -34,35 +35,6 @@ import net.mamoe.yamlkt.Yaml */ public interface JvmPluginDescription : PluginDescription { public companion object { - @Suppress("UNUSED_PARAMETER") - @Deprecated( - "Use top-level function instead", - ReplaceWith("JvmPluginDescription(id, version, block)", "net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription"), - DeprecationLevel.ERROR - ) - @JvmName("create") - public inline fun invoke( - @ResolveContext(PLUGIN_ID) id: String, - @ResolveContext(SEMANTIC_VERSION) version: String, - @ResolveContext(PLUGIN_NAME) name: String = id, - block: JvmPluginDescriptionBuilder.() -> Unit = {}, - ): JvmPluginDescription = error("Shouldn't be called") - - @Suppress("UNUSED_PARAMETER") - @Deprecated( - "Use top-level function instead", - ReplaceWith("JvmPluginDescription(id, version, block)", "net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription"), - DeprecationLevel.ERROR - ) - @JvmName("create") - @JvmSynthetic - public inline fun invoke( - @ResolveContext(PLUGIN_ID) id: String, - version: SemVersion, - @ResolveContext(PLUGIN_NAME) name: String = id, - block: JvmPluginDescriptionBuilder.() -> Unit = {}, - ): JvmPluginDescription = error("Shouldn't be called") - /** * 从 [pluginClassloader] 读取资源文件 [filename] 并以 YAML 格式解析为 [SimpleJvmPluginDescription] * @@ -71,6 +43,7 @@ public interface JvmPluginDescription : PluginDescription { */ // @JvmOverloads // compiler error @JvmStatic + @ConsoleExperimentalApi public fun loadFromResource( filename: String = "plugin.yml", pluginClassloader: ClassLoader = CallerFinder.getCaller()?.findLoader() ?: error("Cannot find caller classloader, please specify manually."), @@ -245,7 +218,7 @@ public class JvmPluginDescriptionBuilder( * * @see JvmPluginDescription */ -@Serializable +@Serializable // Keep this file in public API files. Might turn to `public` in the future. internal data class SimpleJvmPluginDescription @JvmOverloads constructor( override val id: String, diff --git a/backend/mirai-console/test/command/TestCommand.kt b/backend/mirai-console/test/command/TestCommand.kt index 82d7e524a..7b93718b8 100644 --- a/backend/mirai-console/test/command/TestCommand.kt +++ b/backend/mirai-console/test/command/TestCommand.kt @@ -181,8 +181,8 @@ internal class TestCommand { @Test fun `composite command descriptors`() { val overloads = TestCompositeCommand.overloads - assertEquals("CommandSignatureVariant(, seconds: Int = ...)", overloads[0].toString()) - assertEquals("CommandSignatureVariant(, target: Long, seconds: Int)", overloads[1].toString()) + assertEquals("CommandSignature(, seconds: Int = ...)", overloads[0].toString()) + assertEquals("CommandSignature(, target: Long, seconds: Int)", overloads[1].toString()) } @Test diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 6957a14d2..81dc13016 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -11,12 +11,12 @@ object Versions { const val core = "1.3.3" - const val console = "1.0-RC2-dev-6" + const val console = "1.0.1-dev-1" const val consoleGraphical = "0.0.7" const val consoleTerminal = console - const val kotlinCompiler = "1.4.20-RC" - const val kotlinStdlib = "1.4.10" + const val kotlinCompiler = "1.4.20" + const val kotlinStdlib = "1.4.20" const val kotlinIntellijPlugin = "1.4.20-RC-IJ2020.2-1" // -release const val intellij = "2020.2.1" @@ -34,7 +34,7 @@ object Versions { const val blockingBridge = "1.4.1" @Suppress("SpellCheckingInspection") - const val yamlkt = "0.7.1" + const val yamlkt = "0.7.3" const val intellijGradlePlugin = "0.4.16" } diff --git a/docs/ConfiguringProjects.md b/docs/ConfiguringProjects.md index 5063a8857..9f876c25a 100644 --- a/docs/ConfiguringProjects.md +++ b/docs/ConfiguringProjects.md @@ -20,8 +20,8 @@ console 由后端和前端一起工作. 使用时必须选择一个前端. | 版本类型 | 版本号 | |:------:|:------------------------------:| -| 稳定 | - | -| 预览 | 1.0-RC-1 | +| 稳定 | 1.0.0 | +| 预览 | - | | 开发 | [![Version]][Bintray Download] | ## 配置项目 diff --git a/docs/FrontEnd.md b/docs/FrontEnd.md index fd06cbe11..03a764a49 100644 --- a/docs/FrontEnd.md +++ b/docs/FrontEnd.md @@ -2,7 +2,7 @@ Mirai Console 前端开发文档。 -[`MiraiConsole`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +[`MiraiConsole`]: ../backend/mirai-console/src/MiraiConsole.kt ## 实现前端 @@ -10,10 +10,8 @@ Mirai Console 前端开发文档。 在 `build.gradle` 或 `build.gradle.kts` 添加: ```kotlin -kotlin { - sourceSets.all { - languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation") - } +kotlin.sourceSets.all { + languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation") } ``` @@ -22,9 +20,9 @@ kotlin { ### 实现 Mirai Console -[`MiraiConsole`] 是后端的公开对象,由 [MiraiConsoleImplementationBridge](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt) 代理,与前端链接。 +[`MiraiConsole`] 是后端的公开对象,由 [MiraiConsoleImplementationBridge](../backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt) 代理,与前端链接。 -前端需要实现 [MiraiConsoleImplementation.kt](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt)。 +前端需要实现 [MiraiConsoleImplementation.kt](../backend/mirai-console/src/MiraiConsoleImplementation)。 由于实现前端需要一定的技术能力,相信实现者都能理解源码内注释。 @@ -32,4 +30,4 @@ kotlin { 通过 `public fun MiraiConsoleImplementation.start()`。 -[MiraiConsoleImplementation.kt: Line 161](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt#L161) \ No newline at end of file +[MiraiConsoleImplementation.kt: Line 161](../backend/mirai-console/src/MiraiConsoleImplementation.kt#L161) diff --git a/docs/Plugins.md b/docs/Plugins.md index e4645c177..4d92de7a0 100644 --- a/docs/Plugins.md +++ b/docs/Plugins.md @@ -194,7 +194,7 @@ public final class JExample extends JavaPlugin { 由 Console 初始化(仅在某些静态初始化不可用的情况下使用): ```java public final class JExample extends JavaPlugin { - private static final JExample instance; + private static JExample instance; public static JExample getInstance() { return instance; } diff --git a/docs/Run.md b/docs/Run.md index 2e794c54f..d7c7edbdd 100644 --- a/docs/Run.md +++ b/docs/Run.md @@ -22,12 +22,34 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。 只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 terminal 前端可用。 +#### 从JCenter下载模块 + +mirai 在版本发布时会将发布的构建存放与 [mirai-bintray-repo]。 + +- mirai-core 会提供 [mirai-core-all] +- mirai-console 与其各个模块都会提供 `-all` 的 Shadowed 构建 + +```shell script +# 注: 自行更换对应版本号 + +# Download Mirai Core All + +curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-core-all/1.3.3/mirai-core-all-1.3.3-all.jar -o mirai-core-all-1.3.3.jar + +# Download Mirai Console All + +curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-console/1.0.0/mirai-console-1.0.0-all.jar -o mirai-console-1.0.0.jar + +# Download Mirai Console Terminal + +curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-console-terminal/1.0.0/mirai-console-terminal-1.0.0-all.jar -o mirai-console-terminal-1.0.0.jar + +``` + ### 启动 mirai-console-terminal 前端 -mirai 在版本发布时会同时发布打包依赖的 Shadow JAR,存放在 [mirai-repo]。 - -1. 在 [mirai-repo] 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`): - - mirai-core-qqandroid +1. 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`)(详见 [下载模块](#从JCenter下载模块)): + - mirai-core-all - mirai-console - mirai-console-terminal @@ -51,6 +73,7 @@ pause Linux: ```shell script #!/usr/bin/env bash +echo -e '\033]2;Mirai Console\007' java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $* ``` @@ -59,11 +82,14 @@ java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader #### mirai-console-terminal 前端参数 使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数 -[mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow - ### 启动 mirai-console-pure 前端 与启动 `mirai-console-terminal` 前端大体相同 - 下载 `mirai-console-terminal` 改成下载 `mirai-console-pure` - 启动入口从 `net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader` 改成 `net.mamoe.mirai.console.pure.MiraiConsolePureLoader` + + +[mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow +[mirai-bintray-repo]: https://bintray.com/him188moe/mirai +[mirai-core-all]: https://bintray.com/him188moe/mirai/mirai-core-all diff --git a/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt b/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt index 9cd5a3ead..581728676 100644 --- a/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt +++ b/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt @@ -103,48 +103,38 @@ val lineReader: LineReader by lazy { val terminal: Terminal = run { if (ConsoleTerminalSettings.noConsole) return@run NoConsole - val dumb = System.getProperty("java.class.path") - .contains("idea_rt.jar") || System.getProperty("mirai.idea") !== null || System.getenv("mirai.idea") !== null - - runCatching { - TerminalBuilder.builder() - .dumb(dumb) - .paused(true) - .build() - .let { terminal -> - if (terminal is AbstractWindowsTerminal) { - val pumpField = runCatching { - AbstractWindowsTerminal::class.java.getDeclaredField("pump").also { - it.isAccessible = true - } - }.onFailure { err -> - err.printStackTrace() - return@let terminal.also { it.resume() } - }.getOrThrow() - var response = terminal - terminal.setOnClose { - response = NoConsole + TerminalBuilder.builder() + .name("Mirai Console") + .system(true) + .jansi(true) + .dumb(true) + .paused(true) + .build() + .let { terminal -> + if (terminal is AbstractWindowsTerminal) { + val pumpField = runCatching { + AbstractWindowsTerminal::class.java.getDeclaredField("pump").also { + it.isAccessible = true } - terminal.resume() - val pumpThread = pumpField[terminal] as? Thread ?: return@let NoConsole - @Suppress("ControlFlowWithEmptyBody") - while (pumpThread.state == Thread.State.NEW); - Thread.sleep(1000) - terminal.setOnClose(null) - return@let response + }.onFailure { err -> + err.printStackTrace() + return@let terminal.also { it.resume() } + }.getOrThrow() + var response = terminal + terminal.setOnClose { + response = NoConsole } terminal.resume() - terminal + val pumpThread = pumpField[terminal] as? Thread ?: return@let NoConsole + @Suppress("ControlFlowWithEmptyBody") + while (pumpThread.state == Thread.State.NEW); + Thread.sleep(1000) + terminal.setOnClose(null) + return@let response } - }.recoverCatching { - TerminalBuilder.builder() - .jansi(true) - .build() - }.recoverCatching { - TerminalBuilder.builder() - .system(true) - .build() - }.getOrThrow() + terminal.resume() + terminal + } } private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription { diff --git a/tools/gradle-plugin/src/VersionConstants.kt b/tools/gradle-plugin/src/VersionConstants.kt index 5b584aadf..919c42906 100644 --- a/tools/gradle-plugin/src/VersionConstants.kt +++ b/tools/gradle-plugin/src/VersionConstants.kt @@ -10,6 +10,6 @@ package net.mamoe.mirai.console.gradle internal object VersionConstants { - const val CONSOLE_VERSION = "1.0-RC2-dev-6" // value is written here automatically during build + const val CONSOLE_VERSION = "1.0.1-dev-1" // value is written here automatically during build const val CORE_VERSION = "1.3.3" // value is written here automatically during build } \ No newline at end of file diff --git a/tools/intellij-plugin/src/diagnostics/PluginMainServiceNotConfiguredInspection.kt b/tools/intellij-plugin/src/diagnostics/PluginMainServiceNotConfiguredInspection.kt index 4d9fd7e9d..3ee3aebd8 100644 --- a/tools/intellij-plugin/src/diagnostics/PluginMainServiceNotConfiguredInspection.kt +++ b/tools/intellij-plugin/src/diagnostics/PluginMainServiceNotConfiguredInspection.kt @@ -14,14 +14,17 @@ import com.intellij.codeInspection.ProblemsHolder import com.intellij.openapi.progress.impl.CancellationCheck.Companion.runWithCancellationCheck import com.intellij.psi.PsiElementVisitor import net.mamoe.mirai.console.compiler.common.resolve.AUTO_SERVICE +import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME import net.mamoe.mirai.console.intellij.diagnostics.fix.ConfigurePluginMainServiceFix +import net.mamoe.mirai.console.intellij.resolve.allSuperNames import net.mamoe.mirai.console.intellij.resolve.hasAnnotation import org.jetbrains.kotlin.idea.debugger.readAction import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection import org.jetbrains.kotlin.idea.util.module import org.jetbrains.kotlin.idea.util.rootManager import org.jetbrains.kotlin.psi.KtClassOrObject -import org.jetbrains.kotlin.psi.referenceExpressionVisitor +import org.jetbrains.kotlin.psi.KtObjectDeclaration +import org.jetbrains.kotlin.psi.classOrObjectVisitor import java.util.* /* @@ -39,19 +42,20 @@ class PluginMainServiceNotConfiguredInspection : AbstractKotlinInspection() { } override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { - return referenceExpressionVisitor visitor@{ referenceExpr -> - val ktClass = referenceExpr.resolveMiraiPluginDeclaration() ?: return@visitor - val fqName = ktClass.fqName?.asString() ?: return@visitor + return classOrObjectVisitor visitor@{ element -> + if (element !is KtObjectDeclaration) return@visitor + if (element.allSuperNames.none { it == PLUGIN_FQ_NAME }) return@visitor + val fqName = element.fqName?.asString() ?: return@visitor - val found = isServiceConfiguredWithAutoService(ktClass) - || isServiceConfiguredWithResource(ktClass, fqName) + val found = isServiceConfiguredWithAutoService(element) + || isServiceConfiguredWithResource(element, fqName) if (!found) { holder.registerProblem( - ktClass.nameIdentifier ?: ktClass.identifyingElement ?: ktClass, + element.nameIdentifier ?: element.identifyingElement ?: element, "插件主类服务未配置", ProblemHighlightType.WARNING, - ConfigurePluginMainServiceFix(ktClass) + ConfigurePluginMainServiceFix(element) ) } } diff --git a/tools/intellij-plugin/src/diagnostics/diagnosticsUtil.kt b/tools/intellij-plugin/src/diagnostics/diagnosticsUtil.kt index 77c5f2019..86481de29 100644 --- a/tools/intellij-plugin/src/diagnostics/diagnosticsUtil.kt +++ b/tools/intellij-plugin/src/diagnostics/diagnosticsUtil.kt @@ -9,19 +9,16 @@ package net.mamoe.mirai.console.intellij.diagnostics -import com.intellij.util.castSafelyTo import net.mamoe.mirai.console.compiler.common.castOrNull -import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME -import net.mamoe.mirai.console.compiler.common.resolve.parents -import net.mamoe.mirai.console.intellij.resolve.allSuperNames import net.mamoe.mirai.console.intellij.resolve.getResolvedCall import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.nj2k.postProcessing.resolve -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtTypeReference +import org.jetbrains.kotlin.psi.KtUserType import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext @@ -43,13 +40,4 @@ fun KtTypeReference.isReferencing(fqName: FqName): Boolean { val KtTypeReference.referencedUserType: KtUserType? get() = this.typeElement.castOrNull() -fun KtTypeReference.resolveReferencedType() = referencedUserType?.referenceExpression?.mainReference?.resolve() - -fun KtReferenceExpression.resolveMiraiPluginDeclaration(): KtClassOrObject? { - val main = - parents.filterIsInstance().firstOrNull() ?: return null - val kotlinPluginClass = - resolve().castSafelyTo>()?.parent?.castSafelyTo() ?: return null - if (kotlinPluginClass.allSuperNames.none { it == PLUGIN_FQ_NAME }) return null - return main -} +fun KtTypeReference.resolveReferencedType() = referencedUserType?.referenceExpression?.mainReference?.resolve() \ No newline at end of file diff --git a/tools/intellij-plugin/src/line/marker/PluginMainLineMarkerProvider.kt b/tools/intellij-plugin/src/line/marker/PluginMainLineMarkerProvider.kt index 86549b070..979822e46 100644 --- a/tools/intellij-plugin/src/line/marker/PluginMainLineMarkerProvider.kt +++ b/tools/intellij-plugin/src/line/marker/PluginMainLineMarkerProvider.kt @@ -15,16 +15,17 @@ import com.intellij.codeInsight.daemon.LineMarkerProvider import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.editor.markup.GutterIconRenderer import com.intellij.psi.PsiElement +import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME import net.mamoe.mirai.console.intellij.Icons -import net.mamoe.mirai.console.intellij.diagnostics.resolveMiraiPluginDeclaration +import net.mamoe.mirai.console.intellij.resolve.allSuperNames import net.mamoe.mirai.console.intellij.resolve.getElementForLineMark -import org.jetbrains.kotlin.psi.KtReferenceExpression +import org.jetbrains.kotlin.psi.KtObjectDeclaration class PluginMainLineMarkerProvider : LineMarkerProvider { override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { - if (element !is KtReferenceExpression) return null - val main = element.resolveMiraiPluginDeclaration() ?: return null - return Info(getElementForLineMark(main)) + if (element !is KtObjectDeclaration) return null + if (element.allSuperNames.any { it == PLUGIN_FQ_NAME }) return Info(getElementForLineMark(element)) + return null } @Suppress("DEPRECATION")