Merge branch 'master' into ansi

This commit is contained in:
Him188 2020-11-28 12:32:38 +08:00 committed by GitHub
commit 4fd28ef68f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
125 changed files with 2658 additions and 1256 deletions

64
.github/workflows/DevTagPublishing.yml vendored Normal file
View File

@ -0,0 +1,64 @@
# This is a basic workflow to help you get started with Actions
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
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- '*-dev*'
# 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 }}

30
.github/workflows/TagRelease.yml vendored Normal file
View File

@ -0,0 +1,30 @@
# This is a basic workflow to help you get started with Actions
name: TagRelease
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- '*-dev*'
jobs:
build:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body: ""
draft: false
prerelease: true

View File

@ -1,48 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: CuiCloud 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: Gradle :mirai-console:cuiCloudUpload
run: ./gradlew :mirai-console:cuiCloudUpload -Dcui_cloud_key=${{ secrets.CUI_CLOUD_KEY }} -Pcui_cloud_key=${{ secrets.CUI_CLOUD_KEY }} -Dcui_cloud_url=${{ secrets.CUI_CLOUD_URL }} -Pcui_cloud_url=${{ secrets.CUI_CLOUD_URL }}
- name: Gradle :mirai-console-graphical:cuiCloudUpload
run: ./gradlew :mirai-console-graphical:cuiCloudUpload -Dcui_cloud_key=${{ secrets.CUI_CLOUD_KEY }} -Pcui_cloud_key=${{ secrets.CUI_CLOUD_KEY }} -Dcui_cloud_url=${{ secrets.CUI_CLOUD_URL }} -Pcui_cloud_url=${{ secrets.CUI_CLOUD_URL }}
# - name: Upload artifact
# uses: actions/upload-artifact@v1.0.0
# with:
# # Artifact name
# name: mirai-core
# # Directory containing files to upload
# path: "mirai-core/build/libs/mirai-core-*-all.jar"
# - name: Upload artifact
# uses: actions/upload-artifact@v1.0.0
# with:
# # Artifact name
# name: mirai-core-qqandroid-all
# # Directory containing files to upload
# path: "mirai-core-qqandroid/build/libs/mirai-core-qqandroid-*-all.jar"

View File

@ -1,48 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: mirai-repo 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: Gradle :mirai-console:githubUpload
run: ./gradlew :mirai-console:githubUpload -Dgithub_token=${{ secrets.MAMOE_TOKEN }} -Pgithub_token=${{ secrets.MAMOE_TOKEN }}
- name: Gradle :mirai-console-terminal:githubUpload
run: ./gradlew :mirai-console-terminal:githubUpload -Dgithub_token=${{ secrets.MAMOE_TOKEN }} -Pgithub_token=${{ secrets.MAMOE_TOKEN }}
# - name: Upload artifact
# uses: actions/upload-artifact@v1.0.0
# with:
# # Artifact name
# name: mirai-core
# # Directory containing files to upload
# path: "mirai-core/build/libs/mirai-core-*-all.jar"
# - name: Upload artifact
# uses: actions/upload-artifact@v1.0.0
# with:
# # Artifact name
# name: mirai-core-qqandroid-all
# # Directory containing files to upload
# path: "mirai-core-qqandroid/build/libs/mirai-core-qqandroid-*-all.jar"

View File

@ -2,5 +2,5 @@
后端代码生成模块,用于最小化重复代码的人工成本。
- `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/main/kotlin/net/mamoe/mirai/console/codegen/MessageScopeCodegen.kt#L33)
- `Value``PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/main/kotlin/net/mamoe/mirai/console/codegen/ValuePluginDataCodegen.kt#L18)
- `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/MessageScopeCodegen.kt#L33)
- `Value``PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/ValuePluginDataCodegen.kt#L18)

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("FunctionName", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "PRE_RELEASE_CLASS")
@file:Suppress("FunctionName", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "PRE_RELEASE_CLASS", "unused")
package net.mamoe.mirai.console.codegen
@ -59,9 +59,9 @@ class CodegenScope : MutableList<Replacer> by mutableListOf() {
@CodegenDsl
operator fun Codegen.invoke(ktTypes: Collection<KtType>) {
add(Replacer {
it + buildString {
ktTypes.forEach { applyTo(this, it) }
add(Replacer { str ->
str + buildString {
ktTypes.forEach { ktType -> applyTo(this, ktType) }
}
})
}

View File

@ -26,10 +26,6 @@ internal fun <A> Array<A>.arrangements(): List<Pair<A, A>> {
return result
}
internal fun <A> Array<A>.distinctArrangements(): List<Pair<A, A>> {
return this.arrangements().distinctBy { it.first.toString().hashCode() + it.second.toString().hashCode() }
}
internal object MessageScopeCodegen {
object IterableMessageScopeBuildersCodegen : RegionCodegen("MessageScope.kt"), DefaultInvoke {
@JvmStatic
@ -181,7 +177,7 @@ internal object MessageScopeCodegen {
ReplaceWith(
"this.asMessageScope()(action)",
"net.mamoe.mirai.console.util.asMessageScope",
"net.mamoe.mirai.console.util.invoke"
"net.mamoe.mirai.console.util.invoke",
)
)
public inline fun <R> ${a}.scopeWith(action: MessageScope.() -> R): R = asMessageScope()(action)

View File

@ -20,7 +20,7 @@ open class JClazz(val primitiveName: String, val packageName: String) {
open val funName: String = "value"
}
class JListClazz(val item: JClazz) : JClazz("List<${item.packageName}>", "List<${item.packageName}>") {
class JListClazz(item: JClazz) : JClazz("List<${item.packageName}>", "List<${item.packageName}>") {
override val funName = item.primitiveName.toLowerCase() + "List"
}

View File

@ -57,6 +57,7 @@ import kotlin.internal.LowPriorityInOverloadResolution
""".trimIndent()
fun genAllValueUseSite(): String = buildString {
@Suppress("SpellCheckingInspection")
fun appendln(@Language("kt") code: String) {
this.appendLine(code.trimIndent())
}
@ -112,6 +113,7 @@ fun genAllValueUseSite(): String = buildString {
// SPECIAL
appendLine()
@Suppress("unused", "SpellCheckingInspection", "KDocUnresolvedReference")
appendln(
"""
fun <T : PluginData> PluginData.value(default: T): Value<T> {
@ -153,7 +155,7 @@ fun genAllValueUseSite(): String = buildString {
""${'"'}
这种只保存引用的 Value 可能会导致意料之外的结果, 在使用时须保持谨慎.
对值的改变不会触发自动保存, 也不会同步到 UI . UI 中只能编辑序列化之后的值.
""${'"'}, level = RequiresOptIn.Level.WARNING
""${'"'}, level = RequiresOptIn.Level.WARNING,
)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FUNCTION)

View File

@ -52,6 +52,7 @@ import kotlinx.serialization.builtins.*
import net.mamoe.mirai.console.data.*
""".trimIndent()
@Suppress("SpellCheckingInspection")
fun genAllValueImpl(): String = buildString {
fun appendln(@Language("kt") code: String) {
this.appendLine(code.trimIndent())
@ -118,6 +119,7 @@ fun genAllValueImpl(): String = buildString {
for (collectionName in listOf("List", "Set")) {
for (number in NUMBERS + OTHER_PRIMITIVES) {
@Suppress("unused")
appendln(
"""
@JvmName("valueImplMutable${number}${collectionName}")
@ -164,6 +166,7 @@ fun genAllValueImpl(): String = buildString {
appendLine()
@Suppress("unused")
appendln(
"""
internal fun <T : PluginData> PluginData.valueImpl(default: T): Value<T> {
@ -230,6 +233,7 @@ fun genPrimitiveValueImpl(
""".trimIndent() + "\n"
@Suppress("SpellCheckingInspection")
fun genCollectionValueImpl(
collectionName: String,
kotlinTypeName: String,

View File

@ -107,10 +107,8 @@ fun codegen(targetFile: String, block: CodegenScope.() -> Unit) {
println("Codegen target: ${it.absolutePath}")
}.apply {
writeText(
CodegenScope().apply(block).also { list ->
list.forEach {
CodegenScope().apply(block).onEach {
println("Applying replacement: $it")
}
}.applyTo(readText())
)
}

View File

@ -13,79 +13,36 @@ plugins {
}
version = Versions.console
description = "Console backend for mirai"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType(JavaCompile::class.java) {
options.encoding = "UTF8"
}
description = "Mirai Console Backend"
kotlin {
explicitApiWarning()
sourceSets.all {
target.compilations.all {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all"
//useIR = true
}
}
languageSettings.apply {
enableLanguageFeature("InlineClasses")
progressiveMode = true
useExperimentalAnnotation("kotlin.Experimental")
useExperimentalAnnotation("kotlin.RequiresOptIn")
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleExperimentalApi")
useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleInternalApi")
}
}
}
dependencies {
compileAndTestRuntime("net.mamoe:mirai-core:${Versions.core}")
compileAndTestRuntime(kotlin("stdlib", Versions.kotlinStdlib))
compileAndTestRuntime(kotlin("stdlib-jdk8", Versions.kotlinStdlib))
compileAndTestRuntime(`mirai-core`)
compileAndTestRuntime(`kotlin-stdlib`)
compileAndTestRuntime(`kotlin-stdlib-jdk8`)
compileAndTestRuntime("org.jetbrains.kotlinx:atomicfu:${Versions.atomicFU}")
compileAndTestRuntime(kotlinx("coroutines-core", Versions.coroutines))
compileAndTestRuntime(kotlinx("serialization-core", Versions.serialization))
compileAndTestRuntime(kotlin("reflect"))
compileAndTestRuntime(`kotlinx-atomicfu`)
compileAndTestRuntime(`kotlinx-coroutines-core`)
compileAndTestRuntime(`kotlinx-serialization-core`)
compileAndTestRuntime(`kotlinx-serialization-json`)
compileAndTestRuntime(`kotlin-reflect`)
smartImplementation("net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}")
smartImplementation("org.jetbrains:annotations:19.0.0")
smartApi(kotlinx("coroutines-jdk8", Versions.coroutines))
smartImplementation(yamlkt)
smartImplementation(`jetbrains-annotations`)
smartImplementation(`caller-finder`)
smartApi(`kotlinx-coroutines-jdk8`)
testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}")
testApi(kotlin("stdlib-jdk8"))
testApi(kotlin("test"))
testApi(kotlin("test-junit5"))
testApi("org.junit.jupiter:junit-jupiter-api:5.2.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0")
testApi(`mirai-core-qqandroid`)
testApi(`kotlin-stdlib-jdk8`)
}
tasks {
"test"(Test::class) {
useJUnitPlatform()
}
val compileKotlin by getting {}
val fillBuildConstants by registering {
register("fillBuildConstants") {
group = "mirai"
doLast {
(compileKotlin as KotlinCompile).source.filter { it.name == "MiraiConsoleBuildConstants.kt" }.single()
@ -108,8 +65,4 @@ tasks {
}
}
// region PUBLISHING
setupPublishing("mirai-console")
// endregion

View File

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

@ -7,9 +7,12 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console
import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.utils.MiraiExperimentalAPI
/**
@ -38,6 +41,7 @@ public interface MiraiConsoleFrontEndDescription {
*
* 返回 `null` 表示禁止 [MiraiConsole] 后端检查版本兼容性.
*/
@MiraiExperimentalAPI
public val compatibleBackendVersion: SemVersion? get() = null
/**

View File

@ -18,6 +18,8 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
import net.mamoe.mirai.console.logging.LoggerController
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.util.ConsoleInput
@ -177,6 +179,12 @@ public interface MiraiConsoleImplementation : CoroutineScope {
*/
public val isAnsiSupported: Boolean get() = false
/**
* 前端预先定义的 [LoggerController], 以允许前端使用自己的配置系统
*/
public val loggerController: LoggerController get() = LoggerControllerImpl
public companion object {
internal lateinit var instance: MiraiConsoleImplementation
private val initLock = ReentrantLock()

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
@ -102,17 +118,18 @@ public object BuiltInCommands {
@Handler
public suspend fun CommandSender.handle() {
GlobalScope.launch {
kotlin.runCatching {
closingLock.withLock {
if (!MiraiConsole.isActive) return@withLock
sendMessage("Stopping mirai-console")
kotlin.runCatching {
runIgnoreException<CancellationException> { MiraiConsole.job.cancelAndJoin() }
MiraiConsole.job.cancelAndJoin()
}.fold(
onSuccess = {
runIgnoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
},
onFailure = {
if (it is CancellationException) return@fold
@OptIn(ConsoleInternalApi::class)
MiraiConsole.mainLogger.error("Exception in stop", it)
runIgnoreException<EventCancelledException> {
@ -127,6 +144,7 @@ public object BuiltInCommands {
exitProcess(0)
}
}
}
public object LoginCommand : SimpleCommand(
ConsoleCommandOwner, "login", "登录",
@ -203,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")
@ -214,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

@ -11,37 +11,30 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.CommandExecuteResult.CommandExecuteStatus
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
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 net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import kotlin.contracts.contract
/**
* 指令的执行返回
*
* 注意: 现阶段
*
* @see CommandExecuteStatus
*/
@ConsoleExperimentalApi("Not yet implemented")
@ExperimentalCommandDescriptors
public sealed class CommandExecuteResult {
/** 指令最终执行状态 */
public abstract val status: CommandExecuteStatus
/** 指令执行时发生的错误 (如果有) */
public abstract val exception: Throwable?
/** 尝试执行的指令 (如果匹配到) */
public abstract val command: Command?
/** 尝试执行的指令名 (如果匹配到) */
public abstract val commandName: String?
/** 解析的 [CommandCall] (如果匹配到) */
public abstract val call: CommandCall?
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
public abstract val args: MessageChain?
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public abstract val resolvedCall: ResolvedCommandCall?
// abstract val to allow smart casting
@ -49,106 +42,140 @@ public sealed class CommandExecuteResult {
public class Success(
/** 尝试执行的指令 */
public override val command: Command,
/** 尝试执行的指令名 */
public override val commandName: String,
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
public override val args: MessageChain
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall,
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall,
) : CommandExecuteResult() {
/** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = null
/** 指令最终执行状态, 总是 [CommandExecuteStatus.SUCCESSFUL] */
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.SUCCESSFUL
}
/** 指令执行失败 */
public abstract class Failure : CommandExecuteResult()
/** 执行执行时发生了一个非法参数错误 */
public class IllegalArgument(
/** 指令执行时发生的错误 */
public override val exception: IllegalCommandArgumentException,
/** 尝试执行的指令 */
public override val command: Command,
/** 尝试执行的指令名 */
public override val commandName: String,
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
public override val args: MessageChain
) : CommandExecuteResult() {
/** 指令最终执行状态, 总是 [CommandExecuteStatus.EXECUTION_EXCEPTION] */
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.ILLEGAL_ARGUMENT
}
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall,
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall,
) : Failure()
/** 指令执行过程出现了错误 */
/** 指令方法调用过程出现了错误 */
public class ExecutionFailed(
/** 指令执行时发生的错误 */
public override val exception: Throwable,
/** 尝试执行的指令 */
public override val command: Command,
/** 尝试执行的指令名 */
public override val commandName: String,
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
public override val args: MessageChain
) : CommandExecuteResult() {
/** 指令最终执行状态, 总是 [CommandExecuteStatus.EXECUTION_EXCEPTION] */
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.EXECUTION_EXCEPTION
}
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall,
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall,
) : Failure()
/** 没有匹配的指令 */
public class UnresolvedCall(
/** 尝试执行的指令名 */
public override val commandName: String,
) : CommandExecuteResult() {
public class UnresolvedCommand(
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall?,
) : Failure() {
/** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = null
/** 尝试执行的指令, 总是 `null` */
public override val command: Nothing? get() = null
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
public override val args: Nothing? get() = null
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall? get() = null
}
/** 指令最终执行状态, 总是 [CommandExecuteStatus.COMMAND_NOT_FOUND] */
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.COMMAND_NOT_FOUND
/** 没有匹配的指令 */
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(
/** 尝试执行的指令 */
public override val command: Command,
/** 尝试执行的指令名 */
public override val commandName: String
) : CommandExecuteResult() {
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
public override val args: Nothing? get() = null
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall,
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall,
) : Failure() {
/** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = null
}
/** 没有匹配的指令 */
public class UnmatchedSignature(
/** 尝试执行的指令 */
public override val command: Command,
/** 解析的 [CommandCall] (如果匹配到) */
public override val call: CommandCall,
/** 尝试执行的指令 */
@ExperimentalCommandDescriptors
@ConsoleExperimentalApi
public val failureReasons: List<UnmatchedCommandSignature>,
) : Failure() {
/** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = null
/** 指令最终执行状态, 总是 [CommandExecuteStatus.PERMISSION_DENIED] */
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.PERMISSION_DENIED
}
/**
* 指令的执行状态
*/
public enum class CommandExecuteStatus {
/** 指令执行成功 */
SUCCESSFUL,
/** 指令执行过程出现了错误 */
EXECUTION_EXCEPTION,
/** 没有匹配的指令 */
COMMAND_NOT_FOUND,
/** 权限不足 */
PERMISSION_DENIED,
/** 非法参数 */
ILLEGAL_ARGUMENT,
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val resolvedCall: ResolvedCommandCall? get() = null
}
}
@ExperimentalCommandDescriptors
@Suppress("RemoveRedundantQualifierName")
public typealias CommandExecuteStatus = CommandExecuteResult.CommandExecuteStatus
@ConsoleExperimentalApi
public class UnmatchedCommandSignature(
public val signature: CommandSignature,
public val failureReason: FailureReason,
)
@ExperimentalCommandDescriptors
@ConsoleExperimentalApi
public sealed class FailureReason {
public class InapplicableReceiverArgument(
public override val parameter: CommandReceiverParameter<*>,
public val argument: CommandSender,
) : InapplicableArgument()
public class InapplicableValueArgument(
public override val parameter: CommandValueParameter<*>,
public val argument: CommandValueArgument,
) : InapplicableArgument()
public abstract class InapplicableArgument : FailureReason() {
public abstract val parameter: CommandParameter<*>
}
public abstract class ArgumentLengthMismatch : FailureReason()
public data class ResolutionAmbiguity(
/**
* Including [self][UnmatchedCommandSignature.signature].
*/
public val allCandidates: List<CommandSignature>,
) : FailureReason()
public object TooManyArguments : ArgumentLengthMismatch()
public object NotEnoughArguments : ArgumentLengthMismatch()
}
/**
* [this] [CommandExecuteResult.Success] 时返回 `true`
@ -164,59 +191,7 @@ public fun CommandExecuteResult.isSuccess(): Boolean {
}
/**
* [this] [CommandExecuteResult.IllegalArgument] 时返回 `true`
*/
@ExperimentalCommandDescriptors
@JvmSynthetic
public fun CommandExecuteResult.isIllegalArgument(): Boolean {
contract {
returns(true) implies (this@isIllegalArgument is CommandExecuteResult.IllegalArgument)
returns(false) implies (this@isIllegalArgument !is CommandExecuteResult.IllegalArgument)
}
return this is CommandExecuteResult.IllegalArgument
}
/**
* [this] [CommandExecuteResult.ExecutionFailed] 时返回 `true`
*/
@ExperimentalCommandDescriptors
@JvmSynthetic
public fun CommandExecuteResult.isExecutionException(): Boolean {
contract {
returns(true) implies (this@isExecutionException is CommandExecuteResult.ExecutionFailed)
returns(false) implies (this@isExecutionException !is CommandExecuteResult.ExecutionFailed)
}
return this is CommandExecuteResult.ExecutionFailed
}
/**
* [this] [CommandExecuteResult.PermissionDenied] 时返回 `true`
*/
@ExperimentalCommandDescriptors
@JvmSynthetic
public fun CommandExecuteResult.isPermissionDenied(): Boolean {
contract {
returns(true) implies (this@isPermissionDenied is CommandExecuteResult.PermissionDenied)
returns(false) implies (this@isPermissionDenied !is CommandExecuteResult.PermissionDenied)
}
return this is CommandExecuteResult.PermissionDenied
}
/**
* [this] [CommandExecuteResult.UnresolvedCall] 时返回 `true`
*/
@ExperimentalCommandDescriptors
@JvmSynthetic
public fun CommandExecuteResult.isCommandNotFound(): Boolean {
contract {
returns(true) implies (this@isCommandNotFound is CommandExecuteResult.UnresolvedCall)
returns(false) implies (this@isCommandNotFound !is CommandExecuteResult.UnresolvedCall)
}
return this is CommandExecuteResult.UnresolvedCall
}
/**
* [this] [CommandExecuteResult.ExecutionFailed], [CommandExecuteResult.IllegalArgument] [CommandExecuteResult.UnresolvedCall] 时返回 `true`
* [this] [CommandExecuteResult.ExecutionFailed], [CommandExecuteResult.IllegalArgument] , [CommandExecuteResult.UnmatchedSignature] [CommandExecuteResult.UnresolvedCommand] 时返回 `true`
*/
@ExperimentalCommandDescriptors
@JvmSynthetic

View File

@ -240,9 +240,9 @@ public suspend inline fun CommandSender.executeCommand(
@JvmSynthetic
public suspend inline fun Command.execute(
sender: CommandSender,
arguments: Message = EmptyMessageChain,
vararg arguments: Message = emptyArray(),
checkPermission: Boolean = true,
): CommandExecuteResult = CommandManager.executeCommand(sender, this, arguments, checkPermission)
): CommandExecuteResult = CommandManager.executeCommand(sender, this, arguments.asMessageChain(), checkPermission)
/**
* 执行一个确切的指令
@ -255,4 +255,4 @@ public suspend inline fun Command.execute(
sender: CommandSender,
arguments: String = "",
checkPermission: Boolean = true,
): CommandExecuteResult = execute(sender, PlainText(arguments), checkPermission)
): CommandExecuteResult = execute(sender, PlainText(arguments), checkPermission = checkPermission)

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

@ -18,11 +18,12 @@ import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
*
* [message] 将会发送给指令调用方.
*
* 如果指令调用方是 [ConsoleCommandSender],
* 还会将 [cause], [suppressedExceptions] 发送给 [ConsoleCommandSender] (如果存在)
*
* @see CommandArgumentParserException
*/
public open class IllegalCommandArgumentException : IllegalArgumentException {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}
public open class IllegalCommandArgumentException @JvmOverloads constructor(
message: String,
cause: Throwable? = null,
) : IllegalArgumentException(message, cause)

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

@ -339,7 +339,7 @@ public object ExistingMemberValueArgumentParser : InternalCommandValueArgumentPa
public object PermissionIdValueArgumentParser : InternalCommandValueArgumentParserExtensions<PermissionId>() {
override fun parse(raw: String, sender: CommandSender): PermissionId {
return kotlin.runCatching { PermissionId.parseFromString(raw) }.getOrElse {
illegalArgument("无法解析 $raw PermissionId")
illegalArgument("无法解析 $raw权限 ID.")
}
}
}
@ -348,7 +348,7 @@ public object PermitteeIdValueArgumentParser : InternalCommandValueArgumentParse
override fun parse(raw: String, sender: CommandSender): PermitteeId {
return if (raw == "~") sender.permitteeId
else kotlin.runCatching { AbstractPermitteeId.parseFromString(raw) }.getOrElse {
illegalArgument("无法解析 $raw PermissibleIdentifier")
illegalArgument("无法解析 $raw被许可人 ID.")
}
}

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
@ -75,15 +94,15 @@ public sealed class ArgumentAcceptance(
) {
public object Direct : ArgumentAcceptance(Int.MAX_VALUE)
public class WithTypeConversion(
public data class WithTypeConversion(
public val typeVariant: TypeVariant<*>,
) : ArgumentAcceptance(20)
public class WithContextualConversion(
public data class WithContextualConversion(
public val parser: CommandValueArgumentParser<*>,
) : ArgumentAcceptance(10)
public class ResolutionAmbiguity(
public data class ResolutionAmbiguity(
public val candidates: List<TypeVariant<*>>,
) : ArgumentAcceptance(0)
@ -101,7 +120,7 @@ public sealed class ArgumentAcceptance(
}
@ExperimentalCommandDescriptors
public class CommandReceiverParameter<T : CommandSender>(
public data class CommandReceiverParameter<T : CommandSender>(
override val isOptional: Boolean,
override val type: KType,
) : CommandParameter<T>, AbstractCommandParameter<T>() {
@ -165,10 +184,11 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
}
@ConsoleExperimentalApi
public class StringConstant(
public data class StringConstant(
@ConsoleExperimentalApi
public override val name: String?,
public val expectingValue: String,
public val ignoreCase: Boolean,
) : AbstractCommandValueParameter<String>() {
public override val type: KType get() = STRING_TYPE
public override val isOptional: Boolean get() = false
@ -186,7 +206,7 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
override fun toString(): String = "<$expectingValue>"
override fun acceptingImpl(expectingType: KType, argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance {
return if (argument.value.content == expectingValue) {
return if (argument.value.content.equals(expectingValue, ignoreCase)) {
ArgumentAcceptance.Direct
} else ArgumentAcceptance.Impossible
}
@ -201,12 +221,14 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
* @see createOptional
* @see createRequired
*/
public class UserDefinedType<T>(
public data class UserDefinedType<T>(
public override val name: String?,
public override val isOptional: Boolean,
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."
@ -236,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

@ -58,9 +58,7 @@ public open class CommandDeclarationException : RuntimeException {
* @see CommandValueArgumentParser
* @see AbstractCommandValueArgumentParser.illegalArgument
*/
public class CommandArgumentParserException : IllegalCommandArgumentException {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}
public class CommandArgumentParserException @JvmOverloads constructor(
message: String,
cause: Throwable? = null,
) : IllegalCommandArgumentException(message, cause)

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

@ -77,7 +77,7 @@ public abstract class JCompositeCommand
parentPermission: Permission = owner.parentPermission,
) : CompositeCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = parentPermission) {
/** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */
public final override var description: String = "<no descriptions available>"
public final override var description: String = super.description
protected set
public final override var permission: Permission = super.permission

View File

@ -12,25 +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

@ -9,16 +9,12 @@
package net.mamoe.mirai.console.command.resolve
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.*
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isNotAcceptable
import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.command.parse.DefaultCommandValueArgument
import net.mamoe.mirai.console.extensions.CommandCallResolverProvider
import net.mamoe.mirai.console.extensions.CommandCallResolverProviderImpl
import net.mamoe.mirai.console.internal.data.classifierAsKClass
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.safeCast
@ -31,21 +27,26 @@ import net.mamoe.mirai.message.data.asMessageChain
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public object BuiltInCommandCallResolver : CommandCallResolver {
public object Provider : CommandCallResolverProvider by CommandCallResolverProviderImpl(BuiltInCommandCallResolver)
override fun resolve(call: CommandCall): ResolvedCommandCall? {
val callee = CommandManager.matchCommand(call.calleeName) ?: return null
override fun resolve(call: CommandCall): CommandResolveResult {
val callee = CommandManager.matchCommand(call.calleeName) ?: return CommandResolveResult(CommandExecuteResult.UnresolvedCommand(call))
val valueArguments = call.valueArguments
val context = callee.safeCast<CommandArgumentContextAware>()?.context
val signature = resolveImpl(call.caller, callee, valueArguments, context) ?: return null
val errorSink = ErrorSink()
val signature = resolveImpl(call.caller, callee, valueArguments, context, errorSink) ?: kotlin.run {
return CommandResolveResult(errorSink.createFailure(call, callee))
}
return ResolvedCommandCallImpl(call.caller,
return CommandResolveResult(
ResolvedCommandCallImpl(
call.caller,
callee,
signature.signature,
signature.zippedArguments.map { it.second },
context ?: EmptyCommandArgumentContext)
context ?: EmptyCommandArgumentContext
)
)
}
private data class ResolveData(
@ -62,17 +63,41 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
val acceptance: ArgumentAcceptance,
)
private fun resolveImpl(
private class ErrorSink {
private val unmatchedCommandSignatures = mutableListOf<UnmatchedCommandSignature>()
private val resolutionAmbiguities = mutableListOf<CommandSignature>()
fun reportUnmatched(failure: UnmatchedCommandSignature) {
unmatchedCommandSignatures.add(failure)
}
fun reportAmbiguity(resolutionAmbiguity: CommandSignature) {
resolutionAmbiguities.add(resolutionAmbiguity)
}
fun createFailure(call: CommandCall, command: Command): CommandExecuteResult.Failure {
val failureReasons = unmatchedCommandSignatures.toMutableList()
val rA = FailureReason.ResolutionAmbiguity(resolutionAmbiguities)
failureReasons.addAll(resolutionAmbiguities.map { UnmatchedCommandSignature(it, rA) })
return CommandExecuteResult.UnmatchedSignature(command, call, unmatchedCommandSignatures)
}
}
private
fun CommandSignature.toResolveData(
caller: CommandSender,
callee: Command,
valueArguments: List<CommandValueArgument>,
context: CommandArgumentContext?,
errorSink: ErrorSink,
): ResolveData? {
callee.overloads
.mapNotNull l@{ signature ->
if (signature.receiverParameter?.type?.classifierAsKClass()?.isInstance(caller) == false) {
return@l null // not compatible receiver
val signature = this
val receiverParameter = signature.receiverParameter
if (receiverParameter?.type?.classifierAsKClass()?.isInstance(caller) == false) {
errorSink.reportUnmatched(
UnmatchedCommandSignature(signature, FailureReason.InapplicableReceiverArgument(receiverParameter, caller))
)// not compatible receiver
return null
}
val valueParameters = signature.valueParameters
@ -81,9 +106,12 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
val remainingParameters = valueParameters.drop(zipped.size).toMutableList()
if (remainingParameters.any { !it.isOptional && !it.isVararg }) return@l null // not enough args. // vararg can be empty.
if (remainingParameters.any { !it.isOptional && !it.isVararg }) {
errorSink.reportUnmatched(UnmatchedCommandSignature(signature, FailureReason.NotEnoughArguments))// not enough args. // vararg can be empty.
return null
}
if (zipped.isEmpty()) {
return if (zipped.isEmpty()) {
ResolveData(
signature = signature,
zippedArguments = emptyList(),
@ -112,7 +140,9 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) ->
val accepting = parameter.accepting(argument, context)
if (accepting.isNotAcceptable) {
return@l null // argument type not assignable
errorSink.reportUnmatched(UnmatchedCommandSignature(signature,
FailureReason.InapplicableValueArgument(parameter, argument)))// argument type not assignable
return null
}
ArgumentAcceptanceWithIndex(index, accepting)
},
@ -120,18 +150,34 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
)
}
}
.also { result -> result.singleOrNull()?.let { return it } }
private fun resolveImpl(
caller: CommandSender,
callee: Command,
valueArguments: List<CommandValueArgument>,
context: CommandArgumentContext?,
errorSink: ErrorSink,
): ResolveData? {
callee.overloads
.mapNotNull l@{ signature ->
signature.toResolveData(caller, valueArguments, context, errorSink)
}
.also { result -> result.takeSingleResolveData()?.let { return it } }
.takeLongestMatches()
.ifEmpty { return null }
.also { result -> result.singleOrNull()?.let { return it } }
.also { result -> result.takeSingleResolveData()?.let { return it } }
// take single ArgumentAcceptance.Direct
.also { list ->
val candidates = list
.asSequence().filterIsInstance<ResolveData>()
.flatMap { phase ->
phase.argumentAcceptances.filter { it.acceptance is ArgumentAcceptance.Direct }.map { phase to it }
}
}.toList()
candidates.singleOrNull()?.let { return it.first } // single Direct
if (candidates.distinctBy { it.second.index }.size != candidates.size) {
// Resolution ambiguity
/*
@ -145,13 +191,17 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
fun foo(a: AA, c: C) = 1
*/
// The call is foo(AA(), C()) or foo(A(), CC())
return null
candidates.forEach { candidate -> errorSink.reportAmbiguity(candidate.first.signature) }
}
}
return null
}
private fun Collection<Any>.takeSingleResolveData() = asSequence().filterIsInstance<ResolveData>().singleOrNull()
/*

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

@ -9,11 +9,57 @@
package net.mamoe.mirai.console.command.resolve
import net.mamoe.mirai.console.command.CommandExecuteResult
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.extensions.CommandCallResolverProvider
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.safeCast
import org.jetbrains.annotations.Contract
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@ExperimentalCommandDescriptors
public class CommandResolveResult private constructor(
internal val value: Any?,
) {
@get:Contract(pure = true)
public val call: ResolvedCommandCall?
get() = value.safeCast()
@get:Contract(pure = true)
public val failure: CommandExecuteResult.Failure?
get() = value.safeCast()
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 {
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 []
@ -23,19 +69,17 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi
*/
@ExperimentalCommandDescriptors
public interface CommandCallResolver {
public fun resolve(call: CommandCall): ResolvedCommandCall?
public fun resolve(call: CommandCall): CommandResolveResult
public companion object {
@JvmName("resolveCall")
@ConsoleExperimentalApi
@ExperimentalCommandDescriptors
public fun CommandCall.resolve(): ResolvedCommandCall? {
public fun CommandCall.resolve(): CommandResolveResult {
GlobalComponentStorage.run {
CommandCallResolverProvider.useExtensions { provider ->
provider.instance.resolve(this@resolve)?.let { return it }
}
}
return null
val instance = CommandCallResolverProvider.findSingletonInstance(CommandCallResolverProvider.builtinImplementation)
return instance.resolve(this@resolve)
}
}
}
}

View File

@ -20,15 +20,21 @@ import net.mamoe.mirai.console.command.parse.mapToTypeOrNull
import net.mamoe.mirai.console.internal.data.classifierAsKClass
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.cast
import kotlin.LazyThreadSafetyMode.PUBLICATION
/**
* 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
/**
@ -49,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<*>>
@ -57,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,
@ -77,7 +95,7 @@ public class ResolvedCommandCallImpl(
override val rawValueArguments: List<CommandValueArgument>,
private val context: CommandArgumentContext,
) : ResolvedCommandCall {
override val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> by lazy(PUBLICATION) {
override val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> by lazy {
calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) ->
val value = argument.mapToTypeOrNull(parameter.type) ?: context[parameter.type.classifierAsKClass()]?.parse(argument.value, caller)
?: throw NoValueArgumentMappingException(argument, parameter.type)

View File

@ -161,7 +161,7 @@ public fun <T> AbstractPluginData.findBackingFieldValue(property: KProperty<T>):
@ConsoleExperimentalApi
public fun <T> AbstractPluginData.findBackingFieldValue(propertyValueName: String): Value<out T>? {
@Suppress("UNCHECKED_CAST")
return this.valueNodes.find { it.valueName == propertyValueName }?.value as Value<out T>
return this.valueNodes.find { it.valueName == propertyValueName }?.value as Value<out T>?
}
/**

View File

@ -146,7 +146,7 @@ public open class AutoSavePluginData private constructor(
}
internal val debuggingLogger1 by lazy {
DefaultLogger("debug").withSwitch(false)
DefaultLogger("console.debug").withSwitch(false)
}
@Suppress("RESULT_CLASS_IN_RETURN_TYPE")
@ -170,4 +170,5 @@ internal inline fun <R> MiraiLogger.runCatchingLog(message: (Throwable) -> Strin
}.getOrNull()
}
@Suppress("SpellCheckingInspection")
private const val MAGIC_NUMBER_CFST_INIT: Long = Long.MAX_VALUE

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

@ -12,14 +12,13 @@ package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.resolve.BuiltInCommandCallResolver
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint
import net.mamoe.mirai.console.extension.InstanceExtension
import net.mamoe.mirai.console.extension.AbstractSingletonExtensionPoint
import net.mamoe.mirai.console.extension.SingletonExtension
@ExperimentalCommandDescriptors
public interface CommandCallResolverProvider : InstanceExtension<CommandCallResolver> {
public interface CommandCallResolverProvider : SingletonExtension<CommandCallResolver> {
public companion object ExtensionPoint :
AbstractInstanceExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(CommandCallResolverProvider::class,
BuiltInCommandCallResolver.Provider)
AbstractSingletonExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(CommandCallResolverProvider::class, BuiltInCommandCallResolver)
}
@ExperimentalCommandDescriptors

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(1604041264)
const val versionConst: String = "1.0-RC-1"
val buildDate: Instant = Instant.ofEpochSecond(1606348297)
const val versionConst: String = "1.0.1-dev-2"
@JvmStatic
val version: SemVersion = SemVersion(versionConst)

View File

@ -28,22 +28,28 @@ import net.mamoe.mirai.console.data.PluginDataStorage
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.CommandManagerImpl
import net.mamoe.mirai.console.internal.command.CommandConfig
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.castOrNull
import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig
import net.mamoe.mirai.console.internal.extension.BuiltInSingletonExtensionSelector
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
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
import net.mamoe.mirai.console.permission.RootPermission
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
@ -87,14 +93,29 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver =
instance.createLoginSolver(requesterBot, configuration)
override val loggerController: LoggerController by instance::loggerController
init {
DefaultLogger = this::createLogger
}
override fun createLogger(identity: String?): MiraiLogger = instance.createLogger(identity)
override fun createLogger(identity: String?): MiraiLogger {
val controller = loggerController
return MiraiConsoleLogger(controller, instance.createLogger(identity))
}
@Suppress("RemoveRedundantBackticks")
internal fun doStart() {
phase `setup logger controller`@{
if (loggerController === LoggerControllerImpl) {
// Reload LoggerConfig.
ConsoleDataScope.addAndReloadConfig(LoggerConfig)
LoggerControllerImpl.initialized = true
}
}
phase `greeting`@{
val buildDateFormatted =
buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
@ -123,6 +144,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
phase `load configurations`@{
mainLogger.verbose { "Loading configurations..." }
ConsoleDataScope.addAndReloadConfig(CommandConfig)
ConsoleDataScope.reloadAll()
}
@ -145,14 +167,6 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." }
}
phase `collect extensions`@{
for (resolvedPlugin in PluginManagerImpl.resolvedPlugins) {
resolvedPlugin.castOrNull<AbstractJvmPlugin>()?.let {
GlobalComponentStorage.mergeWith(it.componentStorage)
}
}
}
phase `load SingletonExtensionSelector`@{
SingletonExtensionSelector.init()
val instance = SingletonExtensionSelector.instance
@ -161,27 +175,28 @@ 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`@{
@ -198,20 +213,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,25 +10,22 @@
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.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.EmptyMessageChain
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 java.util.concurrent.locks.ReentrantLock
@ -61,49 +58,12 @@ 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)) {
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.UnresolvedCall -> {
// noop
}
}
}
}
///// IMPL
override fun getRegisteredCommands(owner: CommandOwner): List<Command> = _registeredCommands.filter { it.owner == owner }
override val allRegisteredCommands: List<Command> get() = _registeredCommands.toList() // copy
override val commandPrefix: String get() = "/"
override val commandPrefix: String get() = CommandConfig.commandPrefix
override fun unregisterAllCommands(owner: CommandOwner) {
for (registeredCommand in getRegisteredCommands(owner)) {
unregisterCommand(registeredCommand)
@ -167,24 +127,43 @@ 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.UnresolvedCall("")
val resolved = call.resolve() ?: return CommandExecuteResult.UnresolvedCall(call.calleeName)
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
if (checkPermission && !command.permission.testPermission(caller)) {
return CommandExecuteResult.PermissionDenied(command, call.calleeName)
return CommandExecuteResult.PermissionDenied(command, call, resolved)
}
return try {
resolved.calleeSignature.call(resolved)
CommandExecuteResult.Success(resolved.callee, call.calleeName, EmptyMessageChain)
CommandExecuteResult.Success(resolved.callee, call, resolved)
} catch (e: CommandArgumentParserException) {
CommandExecuteResult.IllegalArgument(e, resolved.callee, call, resolved)
} catch (e: Throwable) {
CommandExecuteResult.ExecutionFailed(e, resolved.callee, call.calleeName, EmptyMessageChain)
CommandExecuteResult.ExecutionFailed(e, resolved.callee, call, resolved)
}
}

View File

@ -144,6 +144,11 @@ internal class CommandReflector(
}
fun generateUsage(overloads: Iterable<CommandSignatureFromKFunction>): String {
return generateUsage(command, annotationResolver, overloads)
}
companion object {
fun generateUsage(command: Command, annotationResolver: SubCommandAnnotationResolver?, overloads: Iterable<CommandSignature>): String {
return overloads.joinToString("\n") { subcommand ->
buildString {
if (command.prefixOptional) {
@ -158,6 +163,7 @@ internal class CommandReflector(
append(" ")
//}
append(subcommand.valueParameters.joinToString(" ") { it.render() })
if (annotationResolver != null && subcommand is CommandSignatureFromKFunction) {
annotationResolver.getDescription(command, subcommand.originFunction)?.let { description ->
append(" # ")
append(description)
@ -165,16 +171,15 @@ internal class CommandReflector(
}
}
}
companion object {
}
private fun <T> AbstractCommandValueParameter<T>.render(): String {
return when (this) {
is AbstractCommandValueParameter.Extended,
is AbstractCommandValueParameter.UserDefinedType<*>,
-> {
"<${this.name ?: this.type.classifierAsKClass().simpleName}>"
val nameToRender = this.name ?: this.type.classifierAsKClass().simpleName
if (isOptional) "[$nameToRender]" else "<$nameToRender>"
}
is AbstractCommandValueParameter.StringConstant -> {
this.expectingValue
@ -236,7 +241,7 @@ internal class CommandReflector(
.map { (name, function) ->
val functionNameAsValueParameter =
name?.split(' ')?.mapIndexed { index, s -> createStringConstantParameter(index, s) }
name?.split(' ')?.mapIndexed { index, s -> createStringConstantParameterForName(index, s) }
.orEmpty()
val functionValueParameters =
@ -292,8 +297,8 @@ internal class CommandReflector(
return CommandReceiverParameter(this.type.isMarkedNullable, this.type)
}
private fun createStringConstantParameter(index: Int, expectingValue: String): AbstractCommandValueParameter.StringConstant {
return AbstractCommandValueParameter.StringConstant("#$index", expectingValue)
private fun createStringConstantParameterForName(index: Int, expectingValue: String): AbstractCommandValueParameter.StringConstant {
return AbstractCommandValueParameter.StringConstant("#$index", expectingValue, true)
}
private fun KParameter.toUserDefinedCommandParameter(): AbstractCommandValueParameter.UserDefinedType<*> {

View File

@ -0,0 +1,30 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.data.AutoSavePluginConfig
import net.mamoe.mirai.console.data.ValueDescription
import net.mamoe.mirai.console.data.ValueName
import net.mamoe.mirai.console.data.value
@ValueDescription("""
内置指令系统配置
""")
internal object CommandConfig : AutoSavePluginConfig("Command") {
override fun shouldPerformAutoSaveWheneverChanged(): Boolean {
return false
}
@ValueDescription("""
指令前缀, 默认 "/"
""")
@ValueName("commandPrefix")
val commandPrefix: String by value("/")
}

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

@ -11,10 +11,7 @@ package net.mamoe.mirai.console.internal.data.builtins
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder
import net.mamoe.mirai.console.data.PluginConfig
import net.mamoe.mirai.console.data.PluginData
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.data.*
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.utils.minutesToMillis

View File

@ -0,0 +1,33 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.internal.data.builtins
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.logging.AbstractLoggerController
internal object LoggerConfig : AutoSavePluginConfig("Logger") {
@ValueDescription("""
日志输出等级 可选值: ALL, VERBOSE, DEBUG, INFO, WARNING, ERROR, NONE
""")
val defaultPriority by value(AbstractLoggerController.LogPriority.INFO)
@ValueDescription("""
特定日志记录器输出等级
""")
val loggers: Map<String, AbstractLoggerController.LogPriority> by value(
mapOf(
"example.logger" to AbstractLoggerController.LogPriority.NONE,
"console.debug" to AbstractLoggerController.LogPriority.NONE,
)
)
}

View File

@ -11,11 +11,6 @@
package net.mamoe.mirai.console.internal.data
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.serializer
import net.mamoe.yamlkt.Yaml
import kotlin.reflect.KClass
// TODO: 2020/6/24 优化性能: 引入一个 comparator 之类来替代将 Int 包装为 Value<Int> 后进行 containsKey 比较的方法
@ -245,6 +240,8 @@ internal inline fun <E, R> MutableSet<E>.shadowMap(
}
}
/*
internal inline fun <T> dynamicList(crossinline supplier: () -> List<T>): List<T> {
return object : List<T> {
override val size: Int get() = supplier().size
@ -304,7 +301,6 @@ internal inline fun <T> dynamicMutableList(crossinline supplier: () -> MutableLi
}
}
internal inline fun <T> dynamicMutableSet(crossinline supplier: () -> MutableSet<T>): MutableSet<T> {
return object : MutableSet<T> {
override val size: Int get() = supplier().size
@ -322,7 +318,7 @@ internal inline fun <T> dynamicMutableSet(crossinline supplier: () -> MutableSet
override fun hashCode(): Int = supplier().hashCode()
}
}
*/
@Suppress("UNCHECKED_CAST", "USELESS_CAST") // type inference bug
internal inline fun <K, V> MutableMap<K, V>.observable(crossinline onChanged: () -> Unit): MutableMap<K, V> {
return object : MutableMap<K, V>, Map<K, V> by (this as Map<K, V>) {
@ -472,7 +468,7 @@ internal inline fun <T> MutableSet<T>.observable(crossinline onChanged: () -> Un
}
}
/*
@OptIn(InternalSerializationApi::class)
internal fun <R : Any> Any.smartCastPrimitive(clazz: KClass<R>): R {
kotlin.runCatching {
@ -482,3 +478,4 @@ internal fun <R : Any> Any.smartCastPrimitive(clazz: KClass<R>): R {
}
}
*/

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.internal.data
import net.mamoe.mirai.console.data.PluginData

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.internal.data
import kotlinx.serialization.ExperimentalSerializationApi

View File

@ -102,7 +102,7 @@ internal fun PluginData.valueFromKTypeImpl(type: KType): SerializerAwareValue<*>
}
}
internal fun KClass<*>.createInstanceSmart(): Any? {
internal fun KClass<*>.createInstanceSmart(): Any {
return when (this) {
Byte::class -> 0.toByte()
Short::class -> 0.toShort()

View File

@ -50,6 +50,8 @@ internal data class DataExtensionRegistry<out E : Extension>(
) : ExtensionRegistry<E>
internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
private val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
@Suppress("UNCHECKED_CAST")
internal fun <T : Extension> ExtensionPoint<out T>.getExtensions(): Set<ExtensionRegistry<T>> {
val userDefined = instances.getOrPut(this, ::CopyOnWriteArraySet) as Set<ExtensionRegistry<T>>
@ -61,6 +63,13 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
return builtins?.plus(userDefined) ?: userDefined
}
// unused for now
internal fun removeExtensionsRegisteredByPlugin(plugin: Plugin) {
instances.forEach { (_, u) ->
u.removeAll { it.plugin == plugin }
}
}
internal fun mergeWith(another: AbstractConcurrentComponentStorage) {
for ((ep, list) in another.instances) {
for (extensionRegistry in list) {
@ -154,7 +163,6 @@ 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()
override fun <T : Extension> contribute(
extensionPoint: ExtensionPoint<T>,
plugin: Plugin,

View File

@ -0,0 +1,30 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.internal.logging
import net.mamoe.mirai.console.ConsoleFrontEndImplementation
import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig
import net.mamoe.mirai.console.logging.AbstractLoggerController
import net.mamoe.mirai.console.util.ConsoleInternalApi
@ConsoleFrontEndImplementation
@ConsoleInternalApi
internal object LoggerControllerImpl : AbstractLoggerController() {
internal var initialized = false
override fun getPriority(identity: String?): LogPriority {
if (!initialized) return LogPriority.NONE
return if (identity == null) {
LoggerConfig.defaultPriority
} else {
LoggerConfig.loggers[identity] ?: LoggerConfig.defaultPriority
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.internal.logging
import net.mamoe.mirai.console.logging.LoggerController
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.MiraiLoggerPlatformBase
import net.mamoe.mirai.utils.SimpleLogger
internal class MiraiConsoleLogger(
private val controller: LoggerController,
val logger: MiraiLogger
) : MiraiLoggerPlatformBase() {
override val identity: String? get() = logger.identity
override val isEnabled: Boolean get() = logger.isEnabled
override fun info0(message: String?, e: Throwable?) {
if (controller.shouldLog(identity, SimpleLogger.LogPriority.INFO))
logger.info(message, e)
}
override fun warning0(message: String?, e: Throwable?) {
if (controller.shouldLog(identity, SimpleLogger.LogPriority.WARNING))
logger.warning(message, e)
}
override fun debug0(message: String?, e: Throwable?) {
if (controller.shouldLog(identity, SimpleLogger.LogPriority.DEBUG))
logger.debug(message, e)
}
override fun error0(message: String?, e: Throwable?) {
if (controller.shouldLog(identity, SimpleLogger.LogPriority.ERROR))
logger.error(message, e)
}
override fun verbose0(message: String?, e: Throwable?) {
if (controller.shouldLog(identity, SimpleLogger.LogPriority.VERBOSE))
logger.verbose(message, e)
}
}

View File

@ -39,7 +39,7 @@ internal fun parseFromStringImpl(string: String): AbstractPermitteeId {
val arg = str.substring(1)
if (arg == "*") return AnyContact
}
'm' -> kotlin.run {
'm' -> run {
val arg = str.substring(1)
if (arg == "*") return AnyMemberFromAnyGroup
else {
@ -56,7 +56,7 @@ internal fun parseFromStringImpl(string: String): AbstractPermitteeId {
}
}
}
't' -> kotlin.run {
't' -> run {
val arg = str.substring(1)
if (arg == "*") return AnyTempFromAnyGroup
else {

View File

@ -20,6 +20,7 @@ import net.mamoe.mirai.console.internal.util.PluginServiceHelper.loadAllServices
import net.mamoe.mirai.console.plugin.jvm.*
import net.mamoe.mirai.console.plugin.loader.AbstractFilePluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoadException
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.utils.MiraiLogger
import java.io.File
@ -70,7 +71,6 @@ internal object BuiltInJvmPluginLoaderImpl :
f to pluginClassLoader.findServices(
JvmPlugin::class,
KotlinPlugin::class,
AbstractJvmPlugin::class,
JavaPlugin::class
).loadAllServices()
}.flatMap { (f, list) ->
@ -94,20 +94,25 @@ internal object BuiltInJvmPluginLoaderImpl :
return filePlugins.toSet().map { it.value }
}
private val loadedPlugins = ConcurrentHashMap<JvmPlugin, Unit>()
@Throws(PluginLoadException::class)
override fun load(plugin: JvmPlugin) {
ensureActive()
if (loadedPlugins.put(plugin, Unit) != null) {
error("Plugin '${plugin.name}' is already loaded and cannot be reloaded.")
}
runCatching {
check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin" }
plugin.internalOnLoad(plugin.componentStorage)
check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin to be loaded by JvmPluginLoader.BuiltIn" }
plugin.internalOnLoad()
}.getOrElse {
throw PluginLoadException("Exception while loading ${plugin.description.name}", it)
}
}
override fun enable(plugin: JvmPlugin) {
if (plugin.isEnabled) return
if (plugin.isEnabled) error("Plugin '${plugin.name}' is already enabled and cannot be re-enabled.")
ensureActive()
runCatching {
if (plugin is JvmPluginInternal) {
@ -119,7 +124,7 @@ internal object BuiltInJvmPluginLoaderImpl :
}
override fun disable(plugin: JvmPlugin) {
if (!plugin.isEnabled) return
if (!plugin.isEnabled) error("Plugin '${plugin.name}' is not already disabled and cannot be re-disabled.")
if (MiraiConsole.isActive)
ensureActive()

View File

@ -16,6 +16,7 @@ import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.runCatchingLog
import net.mamoe.mirai.console.extension.PluginComponentStorage
import net.mamoe.mirai.console.internal.data.mkdir
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.plugin.Plugin
@ -43,9 +44,6 @@ internal abstract class JvmPluginInternal(
parentCoroutineContext: CoroutineContext,
) : JvmPlugin, CoroutineScope {
@Suppress("LeakingThis")
internal val componentStorage: PluginComponentStorage = PluginComponentStorage(this)
final override val parentPermission: Permission by lazy {
PermissionService.INSTANCE.register(
PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*"),
@ -54,6 +52,7 @@ internal abstract class JvmPluginInternal(
}
final override var isEnabled: Boolean = false
internal set
private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() }
final override fun getResourceAsStream(path: String): InputStream? =
@ -100,13 +99,16 @@ internal abstract class JvmPluginInternal(
}
@Throws(Throwable::class)
internal fun internalOnLoad(componentStorage: PluginComponentStorage) {
internal fun internalOnLoad() {
val componentStorage = PluginComponentStorage(this)
onLoad(componentStorage)
GlobalComponentStorage.mergeWith(componentStorage)
}
internal fun internalOnEnable(): Boolean {
parentPermission
if (!firstRun) refreshCoroutineContext()
kotlin.runCatching {
onEnable()
}.fold(

View File

@ -28,7 +28,7 @@ import net.mamoe.mirai.console.plugin.loader.PluginLoadException
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.console.util.SemVersion.Companion.contains
import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.utils.info
import java.io.File
import java.nio.file.Path
@ -248,8 +248,8 @@ internal fun List<PluginDescription>.findDependency(dependency: PluginDependency
}
internal fun PluginDescription.checkSatisfies(dependency: PluginDependency, plugin: PluginDescription) {
val requirement = dependency.versionRequirement
if (requirement != null && this.version !in requirement) {
val requirement = dependency.versionRequirement ?: return
if (SemVersion.parseRangeRequirement(requirement).test(this.version)) {
throw PluginLoadException("Plugin '${plugin.id}' ('${plugin.id}') requires '${dependency.id}' with version $requirement while the resolved is ${this.version}")
}
}

View File

@ -7,10 +7,15 @@
* 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 {
return block()
@ -28,3 +33,42 @@ internal inline fun <reified E : Throwable> runIgnoreException(block: () -> Unit
throw e
}
}
internal fun StackFrame.findLoader(): ClassLoader? {
classInstance?.let { return it.classLoader }
return runCatching {
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

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.internal.util
import net.mamoe.mirai.console.internal.data.cast
@ -18,7 +20,6 @@ import java.util.*
import kotlin.reflect.KClass
import java.lang.reflect.Member as JReflectionMember
@Suppress("unused")
internal class ServiceList<T>(
internal val classLoader: ClassLoader,
internal val delegate: List<String>
@ -38,8 +39,8 @@ internal object PluginServiceHelper {
return delegate.mapNotNull { classLoader.loadService<T>(it) }
}
fun <T : Any> ClassLoader.loadService(
classname: String
private fun <T : Any> ClassLoader.loadService(
classname: String,
): T? {
@Suppress("UNCHECKED_CAST")
return kotlin.runCatching {

View File

@ -189,7 +189,7 @@ internal object RangeTokenReader {
}
}
fun parse(source: String, token: Token): SemVersion.Requirement {
fun parse(source: String, token: Token): RequirementInternal {
return when (token) {
is Token.Group -> {
if (token.values.size == 1) {
@ -206,7 +206,7 @@ internal object RangeTokenReader {
}.map { parse(source, it) }.toList()
when (logic.first()) {
TokenType.OR -> {
return object : SemVersion.Requirement {
return object : RequirementInternal {
override fun test(version: SemVersion): Boolean {
rules.forEach { if (it.test(version)) return true }
return false
@ -214,7 +214,7 @@ internal object RangeTokenReader {
}
}
TokenType.AND -> {
return object : SemVersion.Requirement {
return object : RequirementInternal {
override fun test(version: SemVersion): Boolean {
rules.forEach { if (!it.test(version)) return false }
return true

View File

@ -0,0 +1,16 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.internal.util.semver
import net.mamoe.mirai.console.util.SemVersion
internal interface RequirementInternal {
fun test(version: SemVersion): Boolean
}

View File

@ -75,11 +75,11 @@ internal object SemVersionInternal {
}
@JvmStatic
internal fun parseRule(rule: String): SemVersion.Requirement {
internal fun parseRule(rule: String): RequirementInternal {
val trimmed = rule.trim()
if (directVersion.matches(trimmed)) {
val parsed = SemVersion.invoke(trimmed)
return object : SemVersion.Requirement {
return object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version.compareTo(parsed) == 0
}
}
@ -89,7 +89,7 @@ internal object SemVersionInternal {
.replace("x", ".+") +
"$"
).toRegex()
return object : SemVersion.Requirement {
return object : RequirementInternal {
override fun test(version: SemVersion): Boolean = regex.matches(version.toString())
}
}
@ -120,7 +120,7 @@ internal object SemVersionInternal {
'(', ')' -> ({ it < end })
else -> throw AssertionError()
}
return object : SemVersion.Requirement {
return object : RequirementInternal {
override fun test(version: SemVersion): Boolean = a(version) && b(version)
}
}
@ -129,32 +129,32 @@ internal object SemVersionInternal {
val version1 = SemVersion.invoke(result.groupValues[8])
return when (operator) {
">=" -> {
object : SemVersion.Requirement {
object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version >= version1
}
}
">" -> {
object : SemVersion.Requirement {
object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version > version1
}
}
"<=" -> {
object : SemVersion.Requirement {
object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version <= version1
}
}
"<" -> {
object : SemVersion.Requirement {
object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version < version1
}
}
"=" -> {
object : SemVersion.Requirement {
object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version.compareTo(version1) == 0
}
}
"!=" -> {
object : SemVersion.Requirement {
object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version.compareTo(version1) != 0
}
}
@ -164,15 +164,9 @@ internal object SemVersionInternal {
throw IllegalArgumentException("Cannot parse $rule")
}
private fun SemVersion.Requirement.withRule(rule: String): SemVersion.Requirement {
return object : SemVersion.Requirement {
override fun test(version: SemVersion): Boolean = this@withRule.test(version)
override fun toString(): String = rule
}
}
@JvmStatic
fun parseRangeRequirement(requirement: String): SemVersion.Requirement {
fun parseRangeRequirement(requirement: String): RequirementInternal {
if (requirement.isBlank()) {
throw IllegalArgumentException("Invalid requirement: Empty requirement rule.")
}
@ -180,7 +174,7 @@ internal object SemVersionInternal {
val collected = RangeTokenReader.collect(requirement, tokens.iterator(), true)
RangeTokenReader.check(requirement, collected.iterator(), null)
return kotlin.runCatching {
RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0)).withRule(requirement)
RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0))
}.onFailure { error ->
throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString {
collected.forEach { dump("", it) }

View File

@ -0,0 +1,62 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.logging
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.SimpleLogger
import java.util.*
@ConsoleExperimentalApi
public abstract class AbstractLoggerController : LoggerController {
protected open fun shouldLog(
priority: LogPriority,
settings: LogPriority,
): Boolean = settings <= priority
protected abstract fun getPriority(identity: String?): LogPriority
override fun shouldLog(identity: String?, priority: SimpleLogger.LogPriority): Boolean =
shouldLog(LogPriority.by(priority), getPriority(identity))
@Suppress("unused")
@ConsoleExperimentalApi
public enum class LogPriority {
ALL(null),
VERBOSE,
DEBUG,
INFO,
WARNING,
ERROR,
NONE(null);
private var mapped: SimpleLogger.LogPriority? = null
public companion object {
private val mapping = EnumMap<SimpleLogger.LogPriority, LogPriority>(SimpleLogger.LogPriority::class.java)
public fun by(priority: SimpleLogger.LogPriority): LogPriority = mapping[priority]!!
init {
values().forEach { priority ->
mapping[priority.mapped ?: return@forEach] = priority
}
}
}
@Suppress("UNUSED_PARAMETER")
constructor(void: Nothing?)
constructor() {
mapped = SimpleLogger.LogPriority.valueOf(name)
}
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.logging
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.utils.SimpleLogger
/**
* 日志控制系统
*
* @see [LoggerControllerImpl]
*/
@ConsoleExperimentalApi
public interface LoggerController {
/** 是否应该记录该等级的日志 */
public fun shouldLog(identity: String?, priority: SimpleLogger.LogPriority): Boolean
}

View File

@ -31,19 +31,8 @@ public data class PermissionId(
@ResolveContext(PERMISSION_NAME) public val name: String,
) {
init {
require(namespace.none { it.isWhitespace() }) {
"' ' is not allowed in namespace"
}
require(name.none { it.isWhitespace() }) {
"' ' is not allowed in name"
}
require(!namespace.contains(':')) {
"':' is not allowed in namespace"
}
require(!name.contains(':')) {
"':' is not allowed in name"
}
checkPermissionIdName(name)
checkPermissionIdName(namespace)
}
public object PermissionIdAsStringSerializer : KSerializer<PermissionId> by String.serializer().map(
@ -79,7 +68,7 @@ public data class PermissionId(
public fun checkPermissionIdName(@ResolveContext(PERMISSION_NAME) name: String) {
when {
name.isBlank() -> throw IllegalArgumentException("PermissionId.name should not be blank.")
name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.name.")
name.any(Char::isWhitespace) -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.name.")
name.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.name.")
}
}
@ -92,7 +81,7 @@ public data class PermissionId(
public fun checkPermissionIdNamespace(@ResolveContext(PERMISSION_NAME) namespace: String) {
when {
namespace.isBlank() -> throw IllegalArgumentException("PermissionId.namespace should not be blank.")
namespace.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.namespace.")
namespace.any(Char::isWhitespace) -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.namespace.")
namespace.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.namespace.")
}
}

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

@ -11,8 +11,13 @@
package net.mamoe.mirai.console.plugin.description
import kotlinx.serialization.KSerializer
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
/**
@ -20,6 +25,7 @@ import net.mamoe.mirai.console.util.SemVersion
*
* @see PluginDescription.dependencies
*/
@Serializable(with = PluginDependency.PluginDependencyAsStringSerializer::class)
public data class PluginDependency @JvmOverloads constructor(
/**
* 依赖插件 ID, [PluginDescription.id]
@ -32,7 +38,7 @@ public data class PluginDependency @JvmOverloads constructor(
*
* @see SemVersion.Requirement
*/
public val versionRequirement: SemVersion.Requirement? = null,
@ResolveContext(VERSION_REQUIREMENT) public val versionRequirement: String? = null,
/**
* 若为 `false`, 插件在找不到此依赖时也能正常加载.
*/
@ -41,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)
}
@ -55,4 +62,40 @@ public data class PluginDependency @JvmOverloads constructor(
) : this(
id, null, isOptional
)
public override fun toString(): String = buildString {
append(id)
versionRequirement?.let {
append(':')
append(it)
}
if (isOptional) {
append('?')
}
}
public companion object {
/**
* 解析 "$id:$versionRequirement?"
*/
@JvmStatic
@Throws(IllegalArgumentException::class)
public fun parseFromString(string: String): PluginDependency {
require(string.isNotEmpty()) { "string is empty." }
val optional = string.endsWith('?')
val (id, version) = string.removeSuffix("?").let { rule ->
if (rule.contains(':')) {
rule.substringBeforeLast(':') to rule.substringAfterLast(':')
} else {
rule to null
}
}
return PluginDependency(id, version, optional)
}
}
public object PluginDependencyAsStringSerializer : KSerializer<PluginDependency> by String.serializer().map(
serializer = { it.toString() },
deserializer = { parseFromString(it) }
)
}

View File

@ -76,6 +76,7 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi
* }
* ```
*
* @see StandardExportManagers
*/
@ConsoleExperimentalApi
public interface ExportManager {

View File

@ -11,11 +11,17 @@
package net.mamoe.mirai.console.plugin.jvm
import io.github.karlatemp.caller.CallerFinder
import io.github.karlatemp.caller.StackFrame
import kotlinx.serialization.Serializable
import net.mamoe.mirai.console.compiler.common.ResolveContext
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
/**
* JVM 插件的描述. 通常作为 `plugin.yml`
@ -29,13 +35,34 @@ import net.mamoe.mirai.console.util.SemVersion
*/
public interface JvmPluginDescription : PluginDescription {
public companion object {
/**
* [pluginClassloader] 读取资源文件 [filename] 并以 YAML 格式解析为 [SimpleJvmPluginDescription]
*
* @param filename [ClassLoader.getResourceAsStream] 的参数 `name`
* @param pluginClassloader 默认通过 [CallerFinder.getCaller] 获取调用方 [StackFrame] 然后获取其 [Class.getClassLoader].
*/
// @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."),
): JvmPluginDescription {
val stream = pluginClassloader.getResourceAsStream(filename) ?: error("Cannot find plugin description resource '$filename'")
val bytes = stream.use { it.readBytes() }
return Yaml.default.decodeFromString(SimpleJvmPluginDescription.serializer(), String(bytes))
}
}
}
/**
* 构建 [JvmPluginDescription]
* @see JvmPluginDescriptionBuilder
*/
@JvmName("create")
@JvmSynthetic
public inline operator fun invoke(
public inline fun JvmPluginDescription(
/**
* @see [PluginDescription.id]
*/
@ -55,9 +82,8 @@ public interface JvmPluginDescription : PluginDescription {
* 构建 [JvmPluginDescription]
* @see JvmPluginDescriptionBuilder
*/
@JvmName("create")
@JvmSynthetic
public inline operator fun invoke(
public inline fun JvmPluginDescription(
/**
* @see [PluginDescription.id]
*/
@ -72,8 +98,6 @@ public interface JvmPluginDescription : PluginDescription {
@ResolveContext(PLUGIN_NAME) name: String = id,
block: JvmPluginDescriptionBuilder.() -> Unit = {},
): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build()
}
}
/**
* [JvmPluginDescription] 构建器.
@ -94,7 +118,7 @@ public interface JvmPluginDescription : PluginDescription {
* .build();
* ```
*
* @see [JvmPluginDescription.invoke]
* @see [JvmPluginDescription]
*/
public class JvmPluginDescriptionBuilder(
@ResolveContext(PLUGIN_ID) private var id: String,
@ -148,20 +172,6 @@ public class JvmPluginDescriptionBuilder(
}
}
/**
* isOptional = false
*
* @see PluginDependency
*/
@ILoveKuriyamaMiraiForever
public fun dependsOn(
@ResolveContext(PLUGIN_ID) pluginId: String,
versionRequirement: SemVersion.Requirement,
isOptional: Boolean = false,
): JvmPluginDescriptionBuilder = apply {
this.dependencies.add(PluginDependency(pluginId, versionRequirement, isOptional))
}
/**
* @see PluginDependency
*/
@ -171,7 +181,7 @@ public class JvmPluginDescriptionBuilder(
@ResolveContext(VERSION_REQUIREMENT) versionRequirement: String,
isOptional: Boolean = false,
): JvmPluginDescriptionBuilder = apply {
this.dependencies.add(PluginDependency(pluginId, SemVersion.parseRangeRequirement(versionRequirement), isOptional))
this.dependencies.add(PluginDependency(pluginId, versionRequirement, isOptional))
}
/**
@ -192,7 +202,7 @@ public class JvmPluginDescriptionBuilder(
public fun build(): JvmPluginDescription =
@Suppress("DEPRECATION_ERROR")
SimpleJvmPluginDescription(name, version, id, author, info, dependencies)
SimpleJvmPluginDescription(id, name, version, author, info, dependencies)
/**
* 标注一个 [JvmPluginDescription] DSL
@ -208,11 +218,12 @@ public class JvmPluginDescriptionBuilder(
*
* @see JvmPluginDescription
*/
@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,
override val name: String,
override val version: SemVersion,
override val id: String = name,
override val author: String = "",
override val info: String = "",
override val dependencies: Set<PluginDependency> = setOf(),
@ -221,13 +232,13 @@ internal data class SimpleJvmPluginDescription
@Suppress("DEPRECATION_ERROR")
@JvmOverloads
constructor(
name: String,
id: String,
name: String = id,
version: String,
id: String = name,
author: String = "",
info: String = "",
dependencies: Set<PluginDependency> = setOf(),
) : this(name, SemVersion(version), id, author, info, dependencies)
) : this(id, name, SemVersion(version), author, info, dependencies)
init {
PluginDescription.checkPluginDescription(this)

View File

@ -76,7 +76,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
* @throws IllegalStateException 在插件已经被加载时抛出. 这属于意料之外的情况.
*/
@Throws(PluginLoadException::class)
@Throws(IllegalStateException::class, PluginLoadException::class)
public fun load(plugin: P)
/**

View File

@ -555,7 +555,7 @@ private class CombinedScope(
private class CommandSenderAsMessageScope(
private val sender: CommandSender,
) : MessageScope {
override val realTarget: Any?
override val realTarget: Any
get() = sender.user ?: sender // ConsoleCommandSender
override suspend fun sendMessage(message: Message) {
@ -570,7 +570,7 @@ private class CommandSenderAsMessageScope(
private class ContactAsMessageScope(
private val sender: Contact,
) : MessageScope {
override val realTarget: Any?
override val realTarget: Any
get() = sender
override suspend fun sendMessage(message: Message) {

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.util
import net.mamoe.mirai.message.data.MessageChain

View File

@ -26,12 +26,13 @@ import net.mamoe.mirai.console.internal.data.map
import net.mamoe.mirai.console.internal.util.semver.SemVersionInternal
import net.mamoe.mirai.console.util.SemVersion.Companion.equals
import net.mamoe.mirai.console.util.SemVersion.Requirement
import net.mamoe.mirai.console.util.SemVersion.SemVersionAsStringSerializer
import kotlin.LazyThreadSafetyMode.PUBLICATION
/**
* [语义化版本](https://semver.org/lang/zh-CN/) 支持
*
* 解析示例:
* ### 解析示例
*
* `1.0.0-M4+c25733b8` 将会解析出下面的内容,
* [major] (主本号), [minor] (次版本号), [patch] (修订号), [identifier] (先行版本号) [metadata] (元数据).
@ -48,10 +49,13 @@ import kotlin.LazyThreadSafetyMode.PUBLICATION
*
* 对于核心版本号, 此实现稍微比语义化版本规范宽松一些, 允许 x.y 的存在.
*
* ### 序列化
* 使用 [SemVersionAsStringSerializer], [SemVersion] 被序列化为 [toString] 的字符串.
*
* @see Requirement 版本号要修
* @see SemVersion.invoke 由字符串解析
*/
@Serializable(with = SemVersion.SemVersionAsStringSerializer::class)
@Serializable(with = SemVersionAsStringSerializer::class)
public data class SemVersion
/**
* @see SemVersion.invoke 字符串解析
@ -81,11 +85,47 @@ internal constructor(
* 一条依赖规则
* @see [parseRangeRequirement]
*/
public interface Requirement {
/** 在 [version] 满足此要求时返回 true */
public fun test(version: SemVersion): Boolean
@Serializable(Requirement.RequirementAsStringSerializer::class)
public data class Requirement internal constructor(
/**
* 规则的字符串表示方式
*
* @see [SemVersion.parseRangeRequirement]
*/
val rule: String,
) {
@Transient
internal val impl = kotlin.runCatching {
SemVersionInternal.parseRangeRequirement(rule)
}.getOrElse {
throw java.lang.IllegalArgumentException("Syntax error: $rule", it)
}
/** 在 [version] 满足此要求时返回 true */
public fun test(version: SemVersion): Boolean = impl.test(version)
/**
* 序列化为字符串, [rule]. 从字符串反序列化, [parseRangeRequirement].
*/
public object RequirementAsStringSerializer : KSerializer<Requirement> by String.serializer().map(
serializer = { it.rule },
deserializer = { parseRangeRequirement(it) }
)
public companion object {
/**
* @see parseRangeRequirement
*/
@JvmSynthetic
public operator fun invoke(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Requirement =
parseRangeRequirement(requirement)
}
}
/**
* 使用 [SemVersion.toString] 序列化, 使用 [SemVersion.invoke] 反序列化.
*/
public object SemVersionAsStringSerializer : KSerializer<SemVersion> by String.serializer().map(
serializer = { it.toString() },
deserializer = { SemVersion(it) }
@ -148,8 +188,8 @@ internal constructor(
*/
@JvmStatic
@Throws(IllegalArgumentException::class)
public fun parseRangeRequirement(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Requirement =
SemVersionInternal.parseRangeRequirement(requirement)
public fun parseRangeRequirement(@ResolveContext(VERSION_REQUIREMENT) rule: String): Requirement =
Requirement(rule)
/** @see [Requirement.test] */
@JvmStatic
@ -201,7 +241,11 @@ internal constructor(
public override fun toString(): String = toString
/**
* [SemVersion] 转为 Kotlin data class 风格的 [String]
* [SemVersion] 转为 Kotlin data class 风格的 [String].
*
* ```
* return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)"
* ```
*/
public fun toStructuredString(): String {
return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)"

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE", "unused")
@file:JvmMultifileClass
@file:JvmName("ConsoleUtils")

View File

@ -21,20 +21,18 @@ import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.console.util.ConsoleInternalApi
import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.PlatformLogger
import net.mamoe.mirai.utils.*
import java.nio.file.Path
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.io.path.createTempDirectory
import kotlin.test.assertNotNull
@OptIn(ConsoleInternalApi::class)
@OptIn(ConsoleInternalApi::class, kotlin.io.path.ExperimentalPathApi::class)
fun initTestEnvironment() {
object : MiraiConsoleImplementation {
override val rootPath: Path = createTempDir().toPath()
override val rootPath: Path = createTempDirectory()
@ConsoleExperimentalApi
override val frontEndDescription: MiraiConsoleFrontEndDescription

View File

@ -48,6 +48,15 @@ object TestCompositeCommand : CompositeCommand(
}
}
object TestRawCommand : RawCommand(
ConsoleCommandOwner,
"testRaw"
) {
override suspend fun CommandSender.onCommand(args: MessageChain) {
Testing.ok(args)
}
}
object TestSimpleCommand : RawCommand(owner, "testSimple", "tsS") {
override suspend fun CommandSender.onCommand(args: MessageChain) {
@ -104,6 +113,17 @@ internal class TestCommand {
}
}
@Test
fun `test raw command`() = runBlocking {
TestRawCommand.withRegistration {
val result = withTesting<MessageChain> {
assertSuccess(TestRawCommand.execute(sender, PlainText("a1"), PlainText("a2"), PlainText("a3")))
}
assertEquals(3, result.size)
assertEquals("a1, a2, a3", result.joinToString())
}
}
@Test
fun `test flattenCommandArgs`() {
val result = arrayOf("test", image).flattenCommandComponents().toTypedArray()
@ -161,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

@ -23,6 +23,7 @@ internal class PluginDataTest {
val map2: MutableMap<String, MutableMap<String, String>> by value()
}
@Suppress("unused")
private val jsonPrettyPrint = Json { prettyPrint = true }
private val json = Json {}

View File

@ -16,6 +16,7 @@ package net.mamoe.mirai.console.util
import net.mamoe.mirai.console.util.SemVersion.Companion.test
import org.junit.jupiter.api.Test
import kotlin.test.assertFails
internal class TestSemVersion {
@Test
@ -42,6 +43,16 @@ internal class TestSemVersion {
assert("1.0.0-rc.1".sem() < "1.0.0".sem())
}
@Test
internal fun testRequirementCopy() {
fun SemVersion.Requirement.check(a: SemVersion.Requirement.() -> SemVersion.Requirement) {
assert(a().impl !== this.impl)
}
SemVersion.parseRangeRequirement("1.0").check { copy() }
SemVersion.parseRangeRequirement("1.0").check { copy("2.0") }
SemVersion.parseRangeRequirement("1.0").check { copy("1.0") }
}
@Test
internal fun testRequirement() {
fun SemVersion.Requirement.assert(version: String): SemVersion.Requirement {
@ -50,9 +61,9 @@ internal class TestSemVersion {
}
fun assertInvalid(requirement: String) {
kotlin.runCatching {
assertFails(requirement) {
SemVersion.parseRangeRequirement(requirement)
}.onSuccess { assert(false) { requirement } }
}
}
fun SemVersion.Requirement.assertFalse(version: String): SemVersion.Requirement {

View File

@ -26,6 +26,7 @@ allprojects {
maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
jcenter()
mavenCentral()
maven(url = "https://dl.bintray.com/karlatemp/misc")
}
}
@ -43,14 +44,23 @@ subprojects {
}
val experimentalAnnotations = arrayOf(
"kotlin.Experimental",
"kotlin.RequiresOptIn",
"kotlin.ExperimentalUnsignedTypes",
// "kotlin.ExperimentalStdlibApi",
"kotlin.ExperimentalStdlibApi",
"kotlin.contracts.ExperimentalContracts",
"kotlin.time.ExperimentalTime",
"kotlin.experimental.ExperimentalTypeInference",
// "kotlinx.coroutines.ExperimentalCoroutinesApi",
"kotlinx.coroutines.ExperimentalCoroutinesApi",
"kotlinx.serialization.ExperimentalSerializationApi",
"kotlin.io.path.ExperimentalPathApi",
"io.ktor.util.KtorExperimentalAPI",
"kotlin.time.ExperimentalTime"
"net.mamoe.mirai.utils.MiraiInternalAPI",
"net.mamoe.mirai.utils.MiraiExperimentalAPI",
"net.mamoe.mirai.console.ConsoleFrontEndImplementation",
"net.mamoe.mirai.console.util.ConsoleExperimentalApi",
"net.mamoe.mirai.console.util.ConsoleInternalApi"
)
@ -139,10 +149,11 @@ fun Project.configureSourceSets() {
fun Project.configureKotlinExperimentalUsages() {
val sourceSets = kotlinSourceSets ?: return
for (target in sourceSets) {
for (target in sourceSets) target.languageSettings.run {
enableLanguageFeature("InlineClasses")
progressiveMode = true
experimentalAnnotations.forEach { a ->
target.languageSettings.useExperimentalAnnotation(a)
//target.languageSettings.enableLanguageFeature("InlineClasses")
useExperimentalAnnotation(a)
}
}
}

View File

@ -16,9 +16,6 @@ import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.bundling.Jar
import org.gradle.kotlin.dsl.*
import upload.Bintray
import java.io.InputStream
import java.io.OutputStream
import java.security.MessageDigest
import java.util.*
import kotlin.reflect.KProperty
@ -35,7 +32,7 @@ import kotlin.reflect.KProperty
* Configures the [bintray][com.jfrog.bintray.gradle.BintrayExtension] extension.
*/
@PublishedApi
internal fun org.gradle.api.Project.`bintray`(configure: com.jfrog.bintray.gradle.BintrayExtension.() -> Unit): Unit =
internal fun Project.`bintray`(configure: com.jfrog.bintray.gradle.BintrayExtension.() -> Unit): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("bintray", configure)
@PublishedApi
@ -47,7 +44,7 @@ internal operator fun <U : Task> RegisteringDomainObjectDelegateProviderWithType
)
@PublishedApi
internal val org.gradle.api.Project.`sourceSets`: org.gradle.api.tasks.SourceSetContainer
internal val Project.`sourceSets`: org.gradle.api.tasks.SourceSetContainer
get() =
(this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("sourceSets") as org.gradle.api.tasks.SourceSetContainer
@ -59,48 +56,10 @@ internal operator fun <T> ExistingDomainObjectDelegate<out T>.getValue(receiver:
* Configures the [publishing][org.gradle.api.publish.PublishingExtension] extension.
*/
@PublishedApi
internal fun org.gradle.api.Project.`publishing`(configure: org.gradle.api.publish.PublishingExtension.() -> Unit): Unit =
internal fun Project.`publishing`(configure: org.gradle.api.publish.PublishingExtension.() -> Unit): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("publishing", configure)
fun InputStream.md5(): ByteArray {
val digest = MessageDigest.getInstance("md5")
digest.reset()
use { input ->
object : OutputStream() {
override fun write(b: Int) {
digest.update(b.toByte())
}
}.use { output ->
input.copyTo(output)
}
}
return digest.digest()
}
@OptIn(ExperimentalUnsignedTypes::class)
@JvmOverloads
fun ByteArray.toUHexString(
separator: String = " ",
offset: Int = 0,
length: Int = this.size - offset
): String {
if (length == 0) {
return ""
}
val lastIndex = offset + length
return buildString(length * 2) {
this@toUHexString.forEachIndexed { index, it ->
if (index in offset until lastIndex) {
var ret = it.toUByte().toString(16).toUpperCase()
if (ret.length == 1) ret = "0$ret"
append(ret)
if (index < lastIndex - 1) append(separator)
}
}
}
}
inline fun Project.setupPublishing(
artifactId: String,
bintrayRepo: String = "mirai",
@ -125,6 +84,10 @@ inline fun Project.setupPublishing(
user = Bintray.getUser(project)
key = Bintray.getKey(project)
publish = true
override = true
setPublications("mavenJava")
setConfigurations("archives")

View File

@ -7,18 +7,23 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("MemberVisibilityCanBePrivate", "ObjectPropertyName", "unused")
object Versions {
const val core = "1.3.2"
const val console = "1.0-RC-1"
const val core = "1.3.3"
const val console = "1.0.1"
const val consoleGraphical = "0.0.7"
const val consoleTerminal = console
const val kotlinCompiler = "1.4.10"
const val kotlinStdlib = kotlinCompiler
const val kotlinCompiler = "1.4.20"
const val kotlinStdlib = "1.4.20"
const val coroutines = "1.3.9"
const val collectionsImmutable = "0.3.2"
const val serialization = "1.0.0-RC"
const val kotlinIntellijPlugin = "1.4.20-release-IJ2020.2-1" // keep to newest as kotlinCompiler
const val intellij = "2020.2.1" // don't update easily unless you want your disk space -= 500MB
const val coroutines = "1.4.0"
const val serialization = "1.0.1"
const val ktor = "1.4.1"
const val atomicFU = "0.14.4"
@ -26,6 +31,38 @@ object Versions {
const val bintray = "1.8.5"
const val blockingBridge = "1.1.0"
const val yamlkt = "0.5.3"
const val blockingBridge = "1.4.1"
@Suppress("SpellCheckingInspection")
const val yamlkt = "0.7.4"
const val intellijGradlePlugin = "0.4.16"
}
const val `kotlin-compiler` = "org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}"
const val `kotlin-stdlib` = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinStdlib}"
const val `kotlin-stdlib-jdk8` = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlinStdlib}"
const val `kotlin-reflect` = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinStdlib}"
const val `kotlin-test` = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlinStdlib}"
const val `kotlin-test-junit5` = "org.jetbrains.kotlin:kotlin-test-junit5:${Versions.kotlinStdlib}"
const val `kotlinx-coroutines-core` = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}"
const val `kotlinx-coroutines-jdk8` = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:${Versions.coroutines}"
const val `kotlinx-serialization-core` = "org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.serialization}"
const val `kotlinx-serialization-json` = "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.serialization}"
const val `kotlinx-serialization-protobuf` = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf:${Versions.serialization}"
const val `kotlinx-atomicfu` = "org.jetbrains.kotlinx:atomicfu:${Versions.atomicFU}"
const val `mirai-core` = "net.mamoe:mirai-core:${Versions.core}"
const val `mirai-core-qqandroid` = "net.mamoe:mirai-core-qqandroid:${Versions.core}"
const val `mirai-core-api` = "net.mamoe:mirai-core-api:${Versions.core}"
const val yamlkt = "net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}"
const val `jetbrains-annotations` = "org.jetbrains:annotations:19.0.0"
const val `caller-finder` = "io.github.karlatemp:caller:1.0.1"

View File

@ -21,7 +21,7 @@ fun DependencyHandlerScope.ktor(id: String, version: String = Versions.ktor) = "
@Suppress("unused")
fun DependencyHandler.compileAndTestRuntime(any: Any) {
add("compileOnly", any)
add("testRuntimeOnly", any)
add("testImplementation", any)
}
fun DependencyHandler.smartApi(

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -127,7 +127,7 @@ Mirai Console 内建 [`SimpleCommand`] 与 [`CompositeCommand`] 拥有 [`Command
object MySimpleCommand : SimpleCommand(
MyPluginMain, "tell", "私聊",
description = "Tell somebody privately",
usage = "/tell <target> <message>" // usage 如不设置则自动根据带有 @Handler 的方法生成
usage = "/tell <target> <message>", // usage 如不设置则自动根据带有 @Handler 的方法生成
) {
@Handler // 标记这是指令处理器 // 函数名随意
suspend fun CommandSender.handle(target: User, message: String) { // 这两个参数会被作为指令参数要求

View File

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

View File

@ -26,7 +26,35 @@ Mirai Console 项目由四个模块组成后端前端Gradle 插件In
### 构建
```shell script
gradlew build
./gradlew build
```
首次加载和构建 mirai-console 项目可能要花费数小时时间。
### 发布版本
(针对拥有 Mirai Console write 权限的项目成员)
若你要发布一个 Mirai Console dev release
1. 更新 buildSrc/Versions.kt 中 `project` 版本号为目标版本;
2. 本地执行 `./gradlew fillBuildConstants`
3. Push 第 12 步的修改为同一个 commitcommit 备注为版本号名称,如 `1.0.1-dev-1`
4. 添加 Git 版本号 tag格式为 `1.0.1-dev-1`(不带 `v`
5. `git push --tags` 推送 tag 更新GitHub Actions 将会检测到 tag 更新并执行 JCenter 发布。
若你要发布一个 Mirai Console 稳定版 release请按顺序进行如下检查
1. 在 GitHub [milestones](https://github.com/mamoe/mirai-console/milestones) 确认目标版本的工作已经处理完毕;
2. Close milestone
3. 更新 buildSrc/Versions.kt 中 `project` 版本号为目标版本;
4. 在 [ConfiguringProjects](ConfiguringProjects.md#选择版本) 更新稳定版本号;
5. 本地执行 `./gradlew fillBuildConstants`
6. Push 前几步的修改为同一个 commitcommit 备注为版本号名称,如 `1.1.0`
7. 在 GitHub release 根据 Git commit 记录编写更新记录:
- 描述所有来自社区的 PR 记录;
- 完整列举公开 API 的任何变动,简要描述或省略内部变动;
- 为更改按 “后端”“前端”“IDE 插件”“Gradle 插件” 分类;
8. 点击 `Publish`。GitHub Actions 将会检测到 tag 更新并执行 JCenter 发布。

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

@ -40,7 +40,7 @@ data class PermissionId(
[`PermissionId`] 是 [`Permission`] 的唯一标识符。知道 [`PermissionId`] 就可以获取到对应的 [`Permission`]。
字符串表示为 "$namespace:$name",如 "console:command.stop", "*:*"
字符串表示为 "$namespace:$name",如 "console:command.stop", "\*:\*"
#### 命名空间
@ -68,7 +68,7 @@ data class PermissionId(
#### 根权限
[`RootPermission`] 是所有权限的父权限。其 ID 为 "*:*"
[`RootPermission`] 是所有权限的父权限。其 ID 为 "\*:\*"
## 被许可人
@ -121,14 +121,14 @@ interface PermitteeId {
| 精确临时会话 | t123456.789 | 群 123456 内的成员 789. 必须通过临时会话 |
| 精确群成员 | m123456.789 | 群 123456 内的成员 789. 同时包含临时会话. |
| 精确用户 | u123456 | 同时包含群成员, 好友, 临时会话 |
| 任意群 | g* | |
| 任意群的任意群员 | m* | |
| 精确群的任意群员 | m123456.* | 群 123456 内的任意成员. 同时包含临时会话. |
| 任意群的任意临时会话 | t* | 必须通过临时会话 |
| 精确群的任意临时会话 | t123456.* | 群 123456 内的任意成员. 必须通过临时会话 |
| 任意好友 | f* | |
| 任意用户 | u* | 任何人在任何环境 |
| 任意对象 | * | 即任何人, 任何群, 控制台 |
| 任意群 | g\* | g 意为 group |
| 任意群的任意群员 | m\* | m 意为 member |
| 精确群的任意群员 | m123456.\* | 群 123456 内的任意成员. 同时包含临时会话. |
| 任意群的任意临时会话 | t\* | t 意为 temp. 必须通过临时会话 |
| 精确群的任意临时会话 | t123456.\* | 群 123456 内的任意成员. 必须通过临时会话 |
| 任意好友 | f\* | f 意为 friend |
| 任意用户 | u\* | u 意为 user. 任何人在任何环境 |
| 任意对象 | \* | 即任何人, 任何群, 控制台 |
### 获取被许可人
@ -164,8 +164,9 @@ fun Permission.testPermission(PermitteeId): Boolean
### 使用内置权限服务指令
**指令**: "/permission", "/perm", "/权限"
**指令**: "/permission", "/perm", "/权限"
使用指令而不带参数可以获取如下用法:
```
/permission cancel <被许可人 ID> <权限 ID> 取消授权一个权限
/permission cancelall <被许可人 ID> <权限 ID> 取消授权一个权限及其所有子权限

View File

@ -43,6 +43,9 @@
[为什么不支持热加载和卸载插件?]: QA.md#为什么不支持热加载和卸载插件
[使用 AutoService]: QA.md#使用-autoservice
[MCI]: ../tools/intellij-plugin/
[MiraiPixel]: ../tools/intellij-plugin/resources/icons/pluginMainDeclaration.png
Mirai Console 运行在 [JVM],支持使用 [Kotlin] 或 [Java] 语言编写的插件。
## 通用的插件接口 - [`Plugin`]
@ -63,23 +66,75 @@ interface Plugin : CommandOwner { // CommandOwner 是空的 interface
[`Plugin`] 接口拥有强扩展性,以支持 Mirai Console 统一管理使用其他编程语言编写的插件 (详见进阶章节 [扩展 - PluginLoader](Extensions.md))。
## 插件加载器 - [`PluginLoader`]
Mirai Console 使用不同的插件加载器来加载不同类型插件。
Mirai Console 内置 [`JvmPluginLoader`] 以加载 JVM 平台插件(见下文),并允许这些插件注册扩展的插件加载器(见章节 [扩展](Extensions.md))
## JVM 平台插件接口 - [`JvmPlugin`]
所有的 JVM 插件都必须实现 [`JvmPlugin`](否则不会被 [`JvmPluginLoader`] 加载)。
所有的 JVM 插件(特别地,`jar` 插件)都必须实现 [`JvmPlugin`](否则不会被 [`JvmPluginLoader`] 加载)。
Mirai Console 提供一些基础的实现,即 [`AbstractJvmPlugin`],并将 [`JvmPlugin`] 分为 [`KotlinPlugin`] 和 [`JavaPlugin`]。
### 主类和描述
### 主类
JVM 平台插件的主类应被实现为一个单例Kotlin `object`Java 静态初始化的类,详见下文示例)。
**Kotlin 使用者的插件主类应继承 [`KotlinPlugin`]。**
**其他 JVM 语言(如 Java使用者的插件主类应继承 [`JavaPlugin`]。**
#### 描述
插件描述需要在主类构造器传递给 `super`。因此插件不需要 `plugin.yml`, `plugin.xml` 等配置文件来指示信息。
#### 定义主类
Mirai Console 使用类似 `ServiceLoader` 的机制加载插件。
在 Kotlin[使用 AutoService])自动配置 service 信息。
在 Kotlin 或其他语言,可手动创建 service 文件: 在 `jar``META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin` 文件内存放插件主类全名(以纯文本 UTF-8 存储,文件内容只包含一行插件主类全名).
Mirai Console 使用类似 Java `ServiceLoader` 但更灵活的机制加载插件。
一个正确的主类定义可以是以下三种任一:
1. Kotlin (`public`) `object`
```kotlin
object A : KotlinPlugin( /* 描述 */ )
```
2. Java (`public`) 静态初始化单例 `class`
```java
public final class A extends JavaPlugin {
public static final A INSTANCE = new A(); // 必须 public static, 必须名为 INSTANCE
private A() {
super( /* 描述 */ );
}
}
```
3. Java (`public`) `class`
注意:这种由 Mirai Console 构造插件实例的方法是不推荐的。请首选上述静态初始化方法。
```java
public final class A extends JavaPlugin {
public A() { // 必须公开且无参
super( /* 描述 */ );
}
}
```
#### 确认主类正确定义
在 [Mirai Console IntelliJ 插件][MCI] 的帮助下,一个正确的插件主类定义的行号处会显示 Mirai 像素风格形象图:![MiraiPixel]
![PluginMainDeclaration](.images/PluginMainDeclaration.png)
#### 配置主类服务
[Mirai Console IntelliJ 插件][MCI] 会自动检查主类服务的配置。在没有正确配置时IDE 将会警告并为你自动配置:
![PluginMainServiceNotConfigured](.images/PluginMainServiceNotConfigured.png)
##### 手动配置主类服务
若无法使用 IntelliJ 插件,可在资源目录 `META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin` 文件内存放插件主类全名(以纯文本 UTF-8 存储,文件内容只包含一行插件主类全名)。
在 Kotlin也可[使用 AutoService])自动配置 service 信息。
### 描述
插件描述需要在主类构造器传递给 `super`。可以选择直接提供或从 JAR 资源文件读取。
有关插件版本号的限制:
- 插件自身的版本要求遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/) 规范, 合格的版本例如: `1.0.0`, `1.0`, `1.0-M1`, `1.0-pre-1`
@ -87,6 +142,10 @@ Mirai Console 使用类似 `ServiceLoader` 的机制加载插件。
有关描述的详细信息可在开发时查看源码内文档。
### 主类的完整示例
基于上文,你现在有以下三种主类定义方式。
#### 实现 Kotlin 插件主类
一个 Kotlin 插件的主类通常需:
@ -109,7 +168,7 @@ object SchedulePlugin : KotlinPlugin(
}
```
#### 实现 Java 插件
#### 实现 Java 插件主类
一个 Java 插件的主类通常需:
- 继承 [`JavaPlugin`]
@ -135,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

@ -43,7 +43,7 @@
- Java 中的「方法」在 Kotlin 中均被称为「函数」。
- Kotlin 默认的访问权限是 `public`。如 Kotlin `class Test` 相当于 Java 的 `public class Test {}`
- Kotlin 的函数定义 `fun test(int: Int): String` 相当于 Java 的方法定义 `public String test(int int)`
- Kotlin 的函数定义 `fun test(int: Int): String` 相当于 Java 的方法定义 `public String test(int integer)`
### 在 Java 使用 Mirai Console 中的 Kotlin `suspend` 函数

View File

@ -9,8 +9,10 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。
## 手动配置独立启动
强烈建议使用自动启动工具,若无法使用,可以参考如下手动启动方式。
### 环境
- JRE 11+ / JDK 11+
- JRE 8+ / JDK 8+
### 准备文件
@ -22,12 +24,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
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,19 +75,70 @@ pause
Linux:
```shell script
#!/usr/bin/env bash
echo -e '\033]2;Mirai Console\007'
java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $*
```
然后就可以开始使用 mirai-console 了
然后就可以开始使用 mirai-console 了
#### mirai-console-terminal 前端参数
使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数
使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数
[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
### 启动 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 Console 可以嵌入一个 JVM 应用启动。
### 环境
- JDK 1.8+ / Android SDK 26+ (Android 8+)
- Kotlin 1.4+
### 添加依赖
[选择版本](ConfiguringProjects.md#选择版本)
`build.gradle.kts`:
```kotlin
repositories {
jcenter()
}
dependencies {
implementation("net.mamoe:mirai-console:1.0.1")
implementation("net.mamoe:mirai-console-terminal:1.0.1")
implementation("net.mamoe:mirai-core:1.3.3")
}
```
### 启动 Terminal 前端
一行启动:
```kotlin
MiraiConsoleTerminalLoader.startAsDaemon()
```
注意, Mirai Console 将会以 '守护进程' 形式启动,不会阻止主线程退出。
### 从内存加载 JVM 插件(实验性)
在嵌入使用时,插件可以直接加载:
```kotlin
```kotlin
MiraiConsoleTerminalLoader.startAsDaemon()
// 先启动 Mirai Console
// Kotlin
Plugin.load() // 扩展函数
Plugin.enable() // 扩展函数
// Java
PluginManager.INSTANCE.loadPlugin(Plugin)
PluginManager.INSTANCE.enablePlugin(Plugin)
```
但注意:这种方法目前是实验性的——一些特定的功能如注册扩展可能不会正常工作。

View File

@ -7,21 +7,31 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:OptIn(ExperimentalCommandDescriptors::class)
package net.mamoe.mirai.console.terminal
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.CommandExecuteResult.*
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.StringConstant
import net.mamoe.mirai.console.command.descriptor.CommandReceiverParameter
import net.mamoe.mirai.console.command.descriptor.CommandValueParameter
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.terminal.noconsole.NoConsole
import net.mamoe.mirai.console.util.ConsoleInternalApi
import net.mamoe.mirai.console.util.cast
import net.mamoe.mirai.console.util.requestInput
import net.mamoe.mirai.console.util.safeCast
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.warning
import org.jline.reader.EndOfFileException
import org.jline.reader.UserInterruptException
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
val consoleLogger by lazy { DefaultLogger("console") }
@ -30,12 +40,13 @@ internal fun startupConsoleThread() {
if (terminal is NoConsole) return
MiraiConsole.launch(CoroutineName("Input Cancelling Daemon")) {
while (true) {
while (isActive) {
delay(2000)
}
}.invokeOnCompletion {
runCatching<Unit> {
terminal.close()
// 应该仅关闭用户输入
terminal.reader().shutdown()
ConsoleInputImpl.thread.shutdownNow()
runCatching {
ConsoleInputImpl.executingCoroutine?.cancel(EndOfFileException())
@ -57,21 +68,29 @@ internal fun startupConsoleThread() {
continue
}
// consoleLogger.debug("INPUT> $next")
val result = ConsoleCommandSender.executeCommand(next)
when (result.status) {
CommandExecuteStatus.SUCCESSFUL -> {
when (val result = ConsoleCommandSender.executeCommand(next)) {
is Success -> {
}
CommandExecuteStatus.ILLEGAL_ARGUMENT -> {
result.exception?.message?.let { consoleLogger.warning(it) }
is IllegalArgument -> { // user wouldn't want stacktrace for a parser error unless it is in debugging mode (to do).
val message = result.exception.message
if (message != null) {
consoleLogger.warning(message)
} else consoleLogger.warning(result.exception)
}
CommandExecuteStatus.EXECUTION_EXCEPTION -> {
result.exception?.let(consoleLogger::error)
is ExecutionFailed -> {
consoleLogger.error(result.exception)
}
CommandExecuteStatus.COMMAND_NOT_FOUND -> {
consoleLogger.warning("未知指令: ${result.commandName}, 输入 ? 获取帮助")
is UnresolvedCommand -> {
consoleLogger.warning { "未知指令: ${next}, 输入 ? 获取帮助" }
}
CommandExecuteStatus.PERMISSION_DENIED -> {
consoleLogger.warning("Permission denied.")
is PermissionDenied -> {
consoleLogger.warning { "权限不足." }
}
is UnmatchedSignature -> {
consoleLogger.warning { "参数不匹配, 你是否想执行: \n" + result.failureReasons.render(result.command, result.call) }
}
is Failure -> {
consoleLogger.warning { result.toString() }
}
}
} catch (e: InterruptedException) {
@ -90,3 +109,61 @@ internal fun startupConsoleThread() {
}
}
}
@OptIn(ExperimentalCommandDescriptors::class)
private fun List<UnmatchedCommandSignature>.render(command: Command, call: CommandCall): String {
val list =
this.filter lambda@{ signature ->
if (signature.failureReason.safeCast<FailureReason.InapplicableValueArgument>()?.parameter is StringConstant) return@lambda false
if (signature.signature.valueParameters.anyStringConstantUnmatched(call.valueArguments)) return@lambda false
true
}
if (list.isEmpty()) {
return command.usage
}
return list.joinToString("\n") { it.render(command) }
}
private fun List<CommandValueParameter<*>>.anyStringConstantUnmatched(arguments: List<CommandValueArgument>): Boolean {
return this.zip(arguments).any { (parameter, argument) ->
parameter is StringConstant && !parameter.accepts(argument, null)
}
}
@OptIn(ExperimentalCommandDescriptors::class)
internal fun UnmatchedCommandSignature.render(command: Command): String {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
val usage = net.mamoe.mirai.console.internal.command.CommandReflector.generateUsage(command, null, listOf(this.signature))
return usage.trim() + " (${failureReason.render()})"
}
@OptIn(ExperimentalCommandDescriptors::class)
internal fun FailureReason.render(): String {
return when (this) {
is FailureReason.InapplicableArgument -> "参数类型错误"
is FailureReason.InapplicableReceiverArgument -> "需要由 ${this.parameter.renderAsName()} 执行"
is FailureReason.TooManyArguments -> "参数过多"
is FailureReason.NotEnoughArguments -> "参数不足"
is FailureReason.ResolutionAmbiguity -> "调用歧义"
is FailureReason.ArgumentLengthMismatch -> {
// should not happen, render it anyway.
"参数长度不匹配"
}
}
}
@OptIn(ExperimentalCommandDescriptors::class)
internal fun CommandReceiverParameter<*>.renderAsName(): String {
val classifier = this.type.classifier.cast<KClass<out CommandSender>>()
return when {
classifier.isSubclassOf(ConsoleCommandSender::class) -> "控制台"
classifier.isSubclassOf(FriendCommandSenderOnMessage::class) -> "好友私聊"
classifier.isSubclassOf(FriendCommandSender::class) -> "好友"
classifier.isSubclassOf(MemberCommandSenderOnMessage::class) -> "群内发言"
classifier.isSubclassOf(MemberCommandSender::class) -> "群成员"
classifier.isSubclassOf(TempCommandSenderOnMessage::class) -> "临时会话"
classifier.isSubclassOf(TempCommandSender::class) -> "临时好友"
classifier.isSubclassOf(UserCommandSender::class) -> "用户"
else -> classifier.simpleName ?: classifier.toString()
}
}

View File

@ -104,12 +104,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 ->
@ -137,20 +136,12 @@ val terminal: Terminal = run {
terminal.resume()
terminal
}
}.recoverCatching {
TerminalBuilder.builder()
.jansi(true)
.build()
}.recoverCatching {
TerminalBuilder.builder()
.system(true)
.build()
}.getOrThrow()
}
private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription {
override val name: String get() = "Terminal"
override val vendor: String get() = "Mamoe Technologies"
// net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
// is console's version not frontend's version
override val version: SemVersion = SemVersion(net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.versionConst)

Some files were not shown because too many files have changed in this diff Show More