Merge remote-tracking branch 'origin/master'

This commit is contained in:
Him188 2020-11-24 12:58:48 +08:00
commit 425b570683
43 changed files with 985 additions and 392 deletions

View File

@ -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:

63
.github/workflows/ReleasePublishing.yml vendored Normal file
View File

@ -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 }}

View File

@ -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")
}
}

View File

@ -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,10 +221,20 @@ 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()
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("查看所有权限列表")
@SubCommand("listPermissions", "lp")
@ -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("<none>")
} 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("]")
}
}
}

View File

@ -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(
/** 尝试执行的指令 */

View File

@ -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<Contact>?
@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 <R> 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 <R> 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(

View File

@ -71,7 +71,7 @@ public interface CommandArgumentContext {
*
* @see EmptyCommandArgumentContext
*/
@JvmStatic
@JvmField // public static final CommandArgumentContext EMPTY;
public val EMPTY: CommandArgumentContext = EmptyCommandArgumentContext
}

View File

@ -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<T> : CommandParameter<T> {
}
/**
* 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<T : Any?> : CommandParameter<T> {
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<T> : CommandValueParameter<T>,
public override val isVararg: Boolean,
public override val type: KType,
) : AbstractCommandValueParameter<T>() {
override fun toString(): String = super.toString()
init {
requireNotNull(type.classifierAsKClassOrNull()) {
"type.classifier must be KClass."
@ -237,7 +258,5 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
* Extended by [CommandValueArgumentParser]
*/
@ConsoleExperimentalApi
public abstract class Extended<T> : AbstractCommandValueParameter<T>() {
abstract override fun toString(): String
}
public abstract class Extended<T> : AbstractCommandValueParameter<T>() // For implementer: take care of toString()
}

View File

@ -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()})"
}
}
}

View File

@ -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<T : Any> : CommandValue
}
}
/**
* @see CommandValueArgumentParser.map
*/
public class MappingCommandValueArgumentParser<T : Any, R : Any>(
private val original: CommandValueArgumentParser<T>,
private val mapper: MappingCommandValueArgumentParser<T, R>.(T) -> R,

View File

@ -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<out OutType> {
@ -31,17 +38,22 @@ public interface TypeVariant<out OutType> {
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 <reified OutType> invoke(crossinline block: (valueParameter: Message) -> OutType): TypeVariant<OutType> {
return object : TypeVariant<OutType> {
override val outType: KType = typeOf<OutType>()
override fun mapValue(valueParameter: Message): OutType = block(valueParameter)
override fun mapValue(valueArgument: Message): OutType = block(valueArgument)
}
}
}
@ -51,20 +63,20 @@ public interface TypeVariant<out OutType> {
public object MessageContentTypeVariant : TypeVariant<MessageContent> {
@OptIn(ExperimentalStdlibApi::class)
override val outType: KType = typeOf<MessageContent>()
override fun mapValue(valueParameter: Message): MessageContent =
valueParameter.castOrNull<MessageContent>() ?: error("Accepts MessageContent only but given ${valueParameter.kClassQualifiedName}")
override fun mapValue(valueArgument: Message): MessageContent =
valueArgument.castOrNull<MessageContent>() ?: error("Accepts MessageContent only but given ${valueArgument.kClassQualifiedName}")
}
@ExperimentalCommandDescriptors
public object MessageChainTypeVariant : TypeVariant<MessageChain> {
@OptIn(ExperimentalStdlibApi::class)
override val outType: KType = typeOf<MessageChain>()
override fun mapValue(valueParameter: Message): MessageChain = valueParameter.asMessageChain()
override fun mapValue(valueArgument: Message): MessageChain = valueArgument.asMessageChain()
}
@ExperimentalCommandDescriptors
public object ContentStringTypeVariant : TypeVariant<String> {
@OptIn(ExperimentalStdlibApi::class)
override val outType: KType = typeOf<String>()
override fun mapValue(valueParameter: Message): String = valueParameter.content
override fun mapValue(valueArgument: Message): String = valueArgument.content
}

View File

@ -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<CommandValueArgument>
// maybe add contextual arguments, i.e. from MessageMetadata
}
@ExperimentalCommandDescriptors

View File

@ -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<TypeVariant<*>>
}

View File

@ -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<CommandArgumentContextAware>()?.context

View File

@ -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<Message>? = null
/**
* 在指令[语法解析][CommandCallParser]后调用.
*
* @return `null` 表示未处理
*/
public fun interceptCall(
call: CommandCall,
): InterceptResult<CommandCall>? = null
/**
* 在指令[调用解析][CommandCallResolver]后调用.
*
* @return `null` 表示未处理
*/
public fun interceptResolvedCall(
call: ResolvedCommandCall,
): InterceptResult<ResolvedCommandCall>? = null
public companion object {
/**
* 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall].
* 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [Message]
*/
@JvmStatic
public fun Message.intercepted(caller: CommandSender): InterceptResult<Message> {
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<CommandCall> {
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<ResolvedCommandCall> {
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<T> 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 <T, R> InterceptResult<T>.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 <T : R, R> InterceptResult<T>.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

View File

@ -32,7 +32,12 @@ public class CommandResolveResult private constructor(
public val failure: CommandExecuteResult.Failure?
get() = value.safeCast()
public inline fun <R> fold(
public constructor(call: ResolvedCommandCall) : this(call as Any?)
public constructor(failure: CommandExecuteResult.Failure) : this(failure as Any)
}
@ExperimentalCommandDescriptors
public inline fun <R> CommandResolveResult.fold(
onSuccess: (ResolvedCommandCall?) -> R,
onFailure: (CommandExecuteResult.Failure) -> R,
): R {
@ -44,8 +49,16 @@ public class CommandResolveResult private constructor(
return call.let(onSuccess)
}
public constructor(call: ResolvedCommandCall?) : this(call as Any?)
public constructor(failure: CommandExecuteResult.Failure) : this(failure as Any)
@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!!
}
/**

View File

@ -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<ResolvedCommandValueArgument<*>>
@ -56,18 +63,30 @@ public interface ResolvedCommandCall {
public companion object
}
/**
* Resolved [CommandValueParameter] for [ResolvedCommandCall.resolvedValueArguments]
*/
@ExperimentalCommandDescriptors
public data class ResolvedCommandValueArgument<T>(
val parameter: CommandValueParameter<T>,
/**
* 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,

View File

@ -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,
)

View File

@ -23,7 +23,7 @@ package net.mamoe.mirai.console.data
* 将被保存为配置 (YAML 作为示例):
* ```yaml
* AccountPluginData:
* map:
* info:
* a: b
* ```
*

View File

@ -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)
}

View File

@ -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<CommandCallInterceptor> {
public companion object ExtensionPoint :
AbstractInstanceExtensionPoint<CommandCallInterceptorProvider, CommandCallInterceptor>(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)
}

View File

@ -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<PermissionService<*>> {
public companion object ExtensionPoint :
AbstractSingletonExtensionPoint<PermissionServiceProvider, PermissionService<*>>(PermissionServiceProvider::class, BuiltInPermissionService)
AbstractSingletonExtensionPoint<PermissionServiceProvider, PermissionService<*>>(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
}
}
}
}
/**

View File

@ -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)

View File

@ -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<UnsupportedOperationException> { 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()
}
}
}

View File

@ -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<MessageEvent> 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

View File

@ -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<Long, String> by value(mutableMapOf(123456654321L to "example"))
@ConsoleExperimentalApi
@ValueDescription("自动登录配置")
public object AutoLoginConfig : AutoSavePluginConfig("AutoLogin") {
@Serializable
public data class Account(
@Comment("账号, 现只支持 QQ 数字账号")
val account: String,
val password: Password,
@Comment("""
账号配置. 可用配置列表 (注意大小写):
"protocol": "ANDROID_PHONE" / "ANDROID_PAD" / "ANDROID_WATCH"
""")
val configuration: Map<ConfigurationKey, @Serializable(with = YamlDynamicSerializer::class) Any> = mapOf(),
) {
@Serializable
public data class Password(
@Comment("密码种类, 可选 PLAIN 或 MD5")
val kind: PasswordKind,
@Comment("密码内容, PLAIN 时为密码文本, MD5 时为 16 进制")
val value: String,
)
@ValueDescription(
"""
账号和 MD5 密码列表
"""
)
val md5Passwords: MutableMap<Long, String> by value(
mutableMapOf(
123456654321L to "example".toByteArray().md5().toUHexString()
)
)
@Suppress("EnumEntryName")
@Serializable
public enum class ConfigurationKey {
protocol,
;
public object Parser : CommandValueArgumentParser<ConfigurationKey>, InternalCommandValueArgumentParserExtensions<ConfigurationKey>() {
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<ConfigurationKey>, InternalCommandValueArgumentParserExtensions<ConfigurationKey>() {
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<Account> by value(mutableListOf(
Account("123456", Account.Password(Account.PasswordKind.PLAIN, "pwd"), mapOf(Account.ConfigurationKey.protocol to "ANDROID_PHONE"))
))
}

View File

@ -154,7 +154,7 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T, plugin: Plugin?) -> Unit): Unit =
withExtensions(block)
val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
private val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
override fun <T : Extension> contribute(
extensionPoint: ExtensionPoint<T>,
plugin: Plugin,

View File

@ -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 <reified E : Throwable, R> 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 : Any> 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 : Any> 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

View File

@ -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

View File

@ -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")
) : Exception("Conflicting Permission registry. new: $newInstance, existing: $existingInstance")

View File

@ -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)
}

View File

@ -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,

View File

@ -181,8 +181,8 @@ internal class TestCommand {
@Test
fun `composite command descriptors`() {
val overloads = TestCompositeCommand.overloads
assertEquals("CommandSignatureVariant(<mute>, seconds: Int = ...)", overloads[0].toString())
assertEquals("CommandSignatureVariant(<mute>, target: Long, seconds: Int)", overloads[1].toString())
assertEquals("CommandSignature(<mute>, seconds: Int = ...)", overloads[0].toString())
assertEquals("CommandSignature(<mute>, target: Long, seconds: Int)", overloads[1].toString())
}
@Test

View File

@ -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"
}

View File

@ -20,8 +20,8 @@ console 由后端和前端一起工作. 使用时必须选择一个前端.
| 版本类型 | 版本号 |
|:------:|:------------------------------:|
| 稳定 | - |
| 预览 | 1.0-RC-1 |
| 稳定 | 1.0.0 |
| 预览 | - |
| 开发 | [![Version]][Bintray Download] |
## 配置项目

View File

@ -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,11 +10,9 @@ Mirai Console 前端开发文档。
`build.gradle``build.gradle.kts` 添加:
```kotlin
kotlin {
sourceSets.all {
kotlin.sourceSets.all {
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
}
}
```
此后就可以使用 `net.mamoe.mirai.console.ConsoleFrontEndImplementation` 标记的所有 API。
@ -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)
[MiraiConsoleImplementation.kt: Line 161](../backend/mirai-console/src/MiraiConsoleImplementation.kt#L161)

View File

@ -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;
}

View File

@ -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

View File

@ -103,12 +103,11 @@ 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)
.name("Mirai Console")
.system(true)
.jansi(true)
.dumb(true)
.paused(true)
.build()
.let { terminal ->
@ -136,15 +135,6 @@ val terminal: Terminal = run {
terminal.resume()
terminal
}
}.recoverCatching {
TerminalBuilder.builder()
.jansi(true)
.build()
}.recoverCatching {
TerminalBuilder.builder()
.system(true)
.build()
}.getOrThrow()
}
private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription {

View File

@ -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
}

View File

@ -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)
)
}
}

View File

@ -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
@ -44,12 +41,3 @@ 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<KtClassOrObject>().firstOrNull() ?: return null
val kotlinPluginClass =
resolve().castSafelyTo<KtConstructor<*>>()?.parent?.castSafelyTo<KtClass>() ?: return null
if (kotlinPluginClass.allSuperNames.none { it == PLUGIN_FQ_NAME }) return null
return main
}

View File

@ -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")