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) - `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/MessageScopeCodegen.kt#L33)
- `Value``PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/main/kotlin/net/mamoe/mirai/console/codegen/ValuePluginDataCodegen.kt#L18) - `Value``PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/ValuePluginDataCodegen.kt#L18)

View File

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

View File

@ -26,10 +26,6 @@ internal fun <A> Array<A>.arrangements(): List<Pair<A, A>> {
return result 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 { internal object MessageScopeCodegen {
object IterableMessageScopeBuildersCodegen : RegionCodegen("MessageScope.kt"), DefaultInvoke { object IterableMessageScopeBuildersCodegen : RegionCodegen("MessageScope.kt"), DefaultInvoke {
@JvmStatic @JvmStatic
@ -181,7 +177,7 @@ internal object MessageScopeCodegen {
ReplaceWith( ReplaceWith(
"this.asMessageScope()(action)", "this.asMessageScope()(action)",
"net.mamoe.mirai.console.util.asMessageScope", "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) 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" 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" override val funName = item.primitiveName.toLowerCase() + "List"
} }

View File

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

View File

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

View File

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

View File

@ -13,79 +13,36 @@ plugins {
} }
version = Versions.console version = Versions.console
description = "Console backend for mirai" description = "Mirai Console Backend"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType(JavaCompile::class.java) {
options.encoding = "UTF8"
}
kotlin { kotlin {
explicitApiWarning() 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 { dependencies {
compileAndTestRuntime("net.mamoe:mirai-core:${Versions.core}") compileAndTestRuntime(`mirai-core`)
compileAndTestRuntime(kotlin("stdlib", Versions.kotlinStdlib)) compileAndTestRuntime(`kotlin-stdlib`)
compileAndTestRuntime(kotlin("stdlib-jdk8", Versions.kotlinStdlib)) compileAndTestRuntime(`kotlin-stdlib-jdk8`)
compileAndTestRuntime("org.jetbrains.kotlinx:atomicfu:${Versions.atomicFU}") compileAndTestRuntime(`kotlinx-atomicfu`)
compileAndTestRuntime(kotlinx("coroutines-core", Versions.coroutines)) compileAndTestRuntime(`kotlinx-coroutines-core`)
compileAndTestRuntime(kotlinx("serialization-core", Versions.serialization)) compileAndTestRuntime(`kotlinx-serialization-core`)
compileAndTestRuntime(kotlin("reflect")) compileAndTestRuntime(`kotlinx-serialization-json`)
compileAndTestRuntime(`kotlin-reflect`)
smartImplementation("net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}") smartImplementation(yamlkt)
smartImplementation("org.jetbrains:annotations:19.0.0") smartImplementation(`jetbrains-annotations`)
smartApi(kotlinx("coroutines-jdk8", Versions.coroutines)) smartImplementation(`caller-finder`)
smartApi(`kotlinx-coroutines-jdk8`)
testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}") testApi(`mirai-core-qqandroid`)
testApi(kotlin("stdlib-jdk8")) 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")
} }
tasks { tasks {
"test"(Test::class) {
useJUnitPlatform()
}
val compileKotlin by getting {} val compileKotlin by getting {}
val fillBuildConstants by registering { register("fillBuildConstants") {
group = "mirai" group = "mirai"
doLast { doLast {
(compileKotlin as KotlinCompile).source.filter { it.name == "MiraiConsoleBuildConstants.kt" }.single() (compileKotlin as KotlinCompile).source.filter { it.name == "MiraiConsoleBuildConstants.kt" }.single()
@ -108,8 +65,4 @@ tasks {
} }
} }
// region PUBLISHING setupPublishing("mirai-console")
setupPublishing("mirai-console")
// endregion

View File

@ -155,7 +155,7 @@ public interface MiraiConsole : CoroutineScope {
return when (password) { return when (password) {
is ByteArray -> Bot(id, password, config) is ByteArray -> Bot(id, password, config)
is String -> 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 * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("unused")
package net.mamoe.mirai.console package net.mamoe.mirai.console
import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.utils.MiraiExperimentalAPI
/** /**
@ -38,6 +41,7 @@ public interface MiraiConsoleFrontEndDescription {
* *
* 返回 `null` 表示禁止 [MiraiConsole] 后端检查版本兼容性. * 返回 `null` 表示禁止 [MiraiConsole] 后端检查版本兼容性.
*/ */
@MiraiExperimentalAPI
public val compatibleBackendVersion: SemVersion? get() = null 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.command.ConsoleCommandSender
import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge 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.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInput
@ -177,6 +179,12 @@ public interface MiraiConsoleImplementation : CoroutineScope {
*/ */
public val isAnsiSupported: Boolean get() = false public val isAnsiSupported: Boolean get() = false
/**
* 前端预先定义的 [LoggerController], 以允许前端使用自己的配置系统
*/
public val loggerController: LoggerController get() = LoggerControllerImpl
public companion object { public companion object {
internal lateinit var instance: MiraiConsoleImplementation internal lateinit var instance: MiraiConsoleImplementation
private val initLock = ReentrantLock() 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.PermissionIdValueArgumentParser
import net.mamoe.mirai.console.command.descriptor.PermitteeIdValueArgumentParser import net.mamoe.mirai.console.command.descriptor.PermitteeIdValueArgumentParser
import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext 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
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands 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.internal.util.runIgnoreException
import net.mamoe.mirai.console.permission.Permission 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
import net.mamoe.mirai.console.permission.PermissionService.Companion.cancel 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.findCorrespondingPermissionOrFail
import net.mamoe.mirai.console.permission.PermissionService.Companion.getPermittedPermissions import net.mamoe.mirai.console.permission.PermissionService.Companion.getPermittedPermissions
import net.mamoe.mirai.console.permission.PermissionService.Companion.permit import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
import net.mamoe.mirai.console.permission.PermitteeId 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.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.ConsoleInternalApi
import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.event.events.EventCancelledException
import net.mamoe.mirai.message.nextMessageOrNull import net.mamoe.mirai.message.nextMessageOrNull
import net.mamoe.mirai.utils.secondsToMillis 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.concurrent.thread
import kotlin.math.floor
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -102,29 +118,31 @@ public object BuiltInCommands {
@Handler @Handler
public suspend fun CommandSender.handle() { public suspend fun CommandSender.handle() {
kotlin.runCatching { GlobalScope.launch {
closingLock.withLock { kotlin.runCatching {
sendMessage("Stopping mirai-console") closingLock.withLock {
kotlin.runCatching { if (!MiraiConsole.isActive) return@withLock
runIgnoreException<CancellationException> { MiraiConsole.job.cancelAndJoin() } sendMessage("Stopping mirai-console")
}.fold( kotlin.runCatching {
onSuccess = { MiraiConsole.job.cancelAndJoin()
runIgnoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") } }.fold(
}, onSuccess = {
onFailure = { runIgnoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
if (it is CancellationException) return@fold },
@OptIn(ConsoleInternalApi::class) onFailure = {
MiraiConsole.mainLogger.error("Exception in stop", it) @OptIn(ConsoleInternalApi::class)
runIgnoreException<EventCancelledException> { MiraiConsole.mainLogger.error("Exception in stop", it)
sendMessage( runIgnoreException<EventCancelledException> {
it.localizedMessage ?: it.message ?: it.toString() sendMessage(
) it.localizedMessage ?: it.message ?: it.toString()
)
}
} }
} )
) }
} }.exceptionOrNull()?.let(MiraiConsole.mainLogger::error)
}.exceptionOrNull()?.let(MiraiConsole.mainLogger::error) exitProcess(0)
exitProcess(0) }
} }
} }
@ -203,9 +221,19 @@ public object BuiltInCommands {
@SubCommand("permittedPermissions", "pp", "grantedPermissions", "gp") @SubCommand("permittedPermissions", "pp", "grantedPermissions", "gp")
public suspend fun CommandSender.permittedPermissions( public suspend fun CommandSender.permittedPermissions(
@Name("被许可人 ID") target: PermitteeId, @Name("被许可人 ID") target: PermitteeId,
@Name("包括重复") all: Boolean = false,
) { ) {
val grantedPermissions = target.getPermittedPermissions() var grantedPermissions = target.getPermittedPermissions().toList()
sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() }) 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("查看所有权限列表") @Description("查看所有权限列表")
@ -214,4 +242,203 @@ public object BuiltInCommands {
sendMessage(PermissionService.INSTANCE.getRegisteredPermissions().joinToString("\n") { it.id.toString() }) 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 package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.CommandExecuteResult.CommandExecuteStatus import net.mamoe.mirai.console.command.descriptor.*
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.command.resolve.InterceptedReason
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
import net.mamoe.mirai.console.util.ConsoleExperimentalApi 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 import kotlin.contracts.contract
/** /**
* 指令的执行返回 * 指令的执行返回
*
* 注意: 现阶段
*
* @see CommandExecuteStatus
*/ */
@ConsoleExperimentalApi("Not yet implemented")
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public sealed class CommandExecuteResult { public sealed class CommandExecuteResult {
/** 指令最终执行状态 */
public abstract val status: CommandExecuteStatus
/** 指令执行时发生的错误 (如果有) */ /** 指令执行时发生的错误 (如果有) */
public abstract val exception: Throwable? public abstract val exception: Throwable?
/** 尝试执行的指令 (如果匹配到) */ /** 尝试执行的指令 (如果匹配到) */
public abstract val command: Command? public abstract val command: Command?
/** 尝试执行的指令名 (如果匹配到) */ /** 解析的 [CommandCall] (如果匹配到) */
public abstract val commandName: String? public abstract val call: CommandCall?
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */ /** 解析的 [ResolvedCommandCall] (如果匹配到) */
public abstract val args: MessageChain? public abstract val resolvedCall: ResolvedCommandCall?
// abstract val to allow smart casting // abstract val to allow smart casting
@ -49,106 +42,140 @@ public sealed class CommandExecuteResult {
public class Success( public class Success(
/** 尝试执行的指令 */ /** 尝试执行的指令 */
public override val command: Command, public override val command: Command,
/** 尝试执行的指令名 */ /** 解析的 [CommandCall] (如果匹配到) */
public override val commandName: String, public override val call: CommandCall,
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */ /** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val args: MessageChain public override val resolvedCall: ResolvedCommandCall,
) : CommandExecuteResult() { ) : CommandExecuteResult() {
/** 指令执行时发生的错误, 总是 `null` */ /** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = 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 class IllegalArgument(
/** 指令执行时发生的错误 */ /** 指令执行时发生的错误 */
public override val exception: IllegalCommandArgumentException, public override val exception: IllegalCommandArgumentException,
/** 尝试执行的指令 */ /** 尝试执行的指令 */
public override val command: Command, public override val command: Command,
/** 尝试执行的指令名 */ /** 解析的 [CommandCall] (如果匹配到) */
public override val commandName: String, public override val call: CommandCall,
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */ /** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val args: MessageChain public override val resolvedCall: ResolvedCommandCall,
) : CommandExecuteResult() { ) : Failure()
/** 指令最终执行状态, 总是 [CommandExecuteStatus.EXECUTION_EXCEPTION] */
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.ILLEGAL_ARGUMENT
}
/** 指令执行过程出现了错误 */ /** 指令方法调用过程出现了错误 */
public class ExecutionFailed( public class ExecutionFailed(
/** 指令执行时发生的错误 */ /** 指令执行时发生的错误 */
public override val exception: Throwable, public override val exception: Throwable,
/** 尝试执行的指令 */ /** 尝试执行的指令 */
public override val command: Command, public override val command: Command,
/** 尝试执行的指令名 */ /** 解析的 [CommandCall] (如果匹配到) */
public override val commandName: String, public override val call: CommandCall,
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */ /** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val args: MessageChain public override val resolvedCall: ResolvedCommandCall,
) : CommandExecuteResult() { ) : Failure()
/** 指令最终执行状态, 总是 [CommandExecuteStatus.EXECUTION_EXCEPTION] */
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.EXECUTION_EXCEPTION
}
/** 没有匹配的指令 */ /** 没有匹配的指令 */
public class UnresolvedCall( public class UnresolvedCommand(
/** 尝试执行的指令名 */ /** 解析的 [CommandCall] (如果匹配到) */
public override val commandName: String, public override val call: CommandCall?,
) : CommandExecuteResult() { ) : Failure() {
/** 指令执行时发生的错误, 总是 `null` */ /** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = null public override val exception: Nothing? get() = null
/** 尝试执行的指令, 总是 `null` */ /** 尝试执行的指令, 总是 `null` */
public override val command: Nothing? get() = null public override val command: Nothing? get() = null
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */ /** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val args: Nothing? get() = null 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 class PermissionDenied(
/** 尝试执行的指令 */ /** 尝试执行的指令 */
public override val command: Command, public override val command: Command,
/** 尝试执行的指令名 */ /** 解析的 [CommandCall] (如果匹配到) */
public override val commandName: String public override val call: CommandCall,
) : CommandExecuteResult() { /** 解析的 [ResolvedCommandCall] (如果匹配到) */
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */ public override val resolvedCall: ResolvedCommandCall,
public override val args: Nothing? get() = null ) : 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` */ /** 指令执行时发生的错误, 总是 `null` */
public override val exception: Nothing? get() = null public override val exception: Nothing? get() = null
/** 指令最终执行状态, 总是 [CommandExecuteStatus.PERMISSION_DENIED] */ /** 解析的 [ResolvedCommandCall] (如果匹配到) */
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.PERMISSION_DENIED public override val resolvedCall: ResolvedCommandCall? get() = null
}
/**
* 指令的执行状态
*/
public enum class CommandExecuteStatus {
/** 指令执行成功 */
SUCCESSFUL,
/** 指令执行过程出现了错误 */
EXECUTION_EXCEPTION,
/** 没有匹配的指令 */
COMMAND_NOT_FOUND,
/** 权限不足 */
PERMISSION_DENIED,
/** 非法参数 */
ILLEGAL_ARGUMENT,
} }
} }
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
@Suppress("RemoveRedundantQualifierName") @ConsoleExperimentalApi
public typealias CommandExecuteStatus = CommandExecuteResult.CommandExecuteStatus 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` * [this] [CommandExecuteResult.Success] 时返回 `true`
@ -164,59 +191,7 @@ public fun CommandExecuteResult.isSuccess(): Boolean {
} }
/** /**
* [this] [CommandExecuteResult.IllegalArgument] 时返回 `true` * [this] [CommandExecuteResult.ExecutionFailed], [CommandExecuteResult.IllegalArgument] , [CommandExecuteResult.UnmatchedSignature] [CommandExecuteResult.UnresolvedCommand] 时返回 `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`
*/ */
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
@JvmSynthetic @JvmSynthetic

View File

@ -240,9 +240,9 @@ public suspend inline fun CommandSender.executeCommand(
@JvmSynthetic @JvmSynthetic
public suspend inline fun Command.execute( public suspend inline fun Command.execute(
sender: CommandSender, sender: CommandSender,
arguments: Message = EmptyMessageChain, vararg arguments: Message = emptyArray(),
checkPermission: Boolean = true, 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, sender: CommandSender,
arguments: String = "", arguments: String = "",
checkPermission: Boolean = true, 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 package net.mamoe.mirai.console.command
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole 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.asMemberCommandSender
import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender 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.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.data.castOrNull import net.mamoe.mirai.console.internal.data.castOrNull
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip 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.AbstractPermitteeId
import net.mamoe.mirai.console.permission.Permittee import net.mamoe.mirai.console.permission.Permittee
import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.permission.PermitteeId
@ -47,7 +43,7 @@ import kotlin.coroutines.CoroutineContext
/** /**
* 指令发送者. * 指令发送者.
* *
* 只有 [CommandSender] 才能 [执行指令][CommandManager.execute] * 只有 [CommandSender] 才能 [执行指令][CommandManager.executeCommand]
* *
* ## 获得指令发送者 * ## 获得指令发送者
* - [MessageEvent.toCommandSender] * - [MessageEvent.toCommandSender]
@ -74,7 +70,7 @@ import kotlin.coroutines.CoroutineContext
* - [AbstractUserCommandSender] 代表用户 * - [AbstractUserCommandSender] 代表用户
* - [ConsoleCommandSender] 代表控制台 * - [ConsoleCommandSender] 代表控制台
* *
* 二级子类, 当指令由插件 [主动执行][CommandManager.execute] , 插件应使用 [toCommandSender] [asCommandSender], 因此, * 二级子类, 当指令由插件 [主动执行][CommandManager.executeCommand] , 插件应使用 [toCommandSender] [asCommandSender], 因此,
* - 若在群聊环境, 对应 [CommandSender] [MemberCommandSender] * - 若在群聊环境, 对应 [CommandSender] [MemberCommandSender]
* - 若在私聊环境, 对应 [CommandSender] [FriendCommandSender] * - 若在私聊环境, 对应 [CommandSender] [FriendCommandSender]
* - 若在临时会话环境, 对应 [CommandSender] [TempCommandSender] * - 若在临时会话环境, 对应 [CommandSender] [TempCommandSender]
@ -173,9 +169,6 @@ public interface CommandSender : CoroutineScope, Permittee {
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun sendMessage(message: String): MessageReceipt<Contact>? 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 { public companion object {
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -274,154 +267,54 @@ public sealed class AbstractCommandSender : CommandSender, CoroutineScope {
public abstract override val subject: Contact? public abstract override val subject: Contact?
public abstract override val user: User? public abstract override val user: User?
public abstract override fun toString(): String 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` * [this] [ConsoleCommandSender] 时返回 `true`
*
* ### 契约
* 本函数定义契约,
* - 若返回 `true`, Kotlin 编译器认为 [this] [ConsoleCommandSender] 实例并执行智能类型转换.
* - 若返回 `false`, Kotlin 编译器认为 [this] [UserCommandSender] 实例并执行智能类型转换.
*/ */
public fun CommandSender.isConsole(): Boolean { public fun CommandSender.isConsole(): Boolean {
contract { contract {
returns(true) implies (this@isConsole is ConsoleCommandSender) returns(true) implies (this@isConsole is ConsoleCommandSender)
returns(false) implies (this@isConsole is UserCommandSender)
} }
this.checkIsAbstractCommandSender()
return this is ConsoleCommandSender return this is ConsoleCommandSender
} }
/** /**
* [this] 不为 [ConsoleCommandSender], 即为 [UserCommandSender] 时返回 `true`. * [this] 不为 [ConsoleCommandSender] 时返回 `true`
*
* ### 契约
* 本函数定义契约,
* - 若返回 `true`, Kotlin 编译器认为 [this] [UserCommandSender] 实例并执行智能类型转换.
* - 若返回 `false`, Kotlin 编译器认为 [this] [ConsoleCommandSender] 实例并执行智能类型转换.
*/ */
public fun CommandSender.isNotConsole(): Boolean { public fun CommandSender.isNotConsole(): Boolean {
contract { contract {
returns(true) implies (this@isNotConsole is UserCommandSender) returns(true) implies (this@isNotConsole !is ConsoleCommandSender)
returns(false) implies (this@isNotConsole is ConsoleCommandSender)
} }
this.checkIsAbstractCommandSender()
return this !is ConsoleCommandSender return this !is ConsoleCommandSender
} }
/** /**
* [this] [UserCommandSender] 时返回 `true` * [this] [UserCommandSender] 时返回 `true`
*
* ### 契约
* 本函数定义契约,
* - 若返回 `true`, Kotlin 编译器认为 [this] [UserCommandSender] 实例并执行智能类型转换.
* - 若返回 `false`, Kotlin 编译器认为 [this] [ConsoleCommandSender] 实例并执行智能类型转换.
*/ */
public fun CommandSender.isUser(): Boolean { public fun CommandSender.isUser(): Boolean {
contract { contract {
returns(true) implies (this@isUser is UserCommandSender) returns(true) implies (this@isUser is UserCommandSender)
returns(false) implies (this@isUser is ConsoleCommandSender)
} }
this.checkIsAbstractCommandSender()
return this is UserCommandSender return this is UserCommandSender
} }
/** /**
* [this] 不为 [UserCommandSender], 即为 [ConsoleCommandSender] 时返回 `true` * [this] 不为 [UserCommandSender], 即为 [ConsoleCommandSender] 时返回 `true`
*
* ### 契约
* 本函数定义契约,
* - 若返回 `true`, Kotlin 编译器认为 [this] [ConsoleCommandSender] 实例并执行智能类型转换.
* - 若返回 `false`, Kotlin 编译器认为 [this] [UserCommandSender] 实例并执行智能类型转换.
*/ */
public fun CommandSender.isNotUser(): Boolean { public fun CommandSender.isNotUser(): Boolean {
contract { contract {
returns(true) implies (this@isNotUser is ConsoleCommandSender) returns(true) implies (this@isNotUser is ConsoleCommandSender)
returns(false) implies (this@isNotUser is UserCommandSender)
} }
this.checkIsAbstractCommandSender()
return this !is UserCommandSender return this !is UserCommandSender
} }
/** /**
* 折叠 [AbstractCommandSender] 两种可能性. * 折叠 [AbstractCommandSender] 的可能性.
* *
* - [this] [ConsoleCommandSender] 时执行 [ifIsConsole] * - [this] [ConsoleCommandSender] 时执行 [ifIsConsole]
* - [this] [UserCommandSender] 时执行 [ifIsUser] * - [this] [UserCommandSender] 时执行 [ifIsUser]
* - 否则执行 [otherwise]
* *
* ### 示例 * ### 示例
* ``` * ```
@ -438,20 +331,23 @@ public fun CommandSender.isNotUser(): Boolean {
* ) * )
* ``` * ```
* *
* @return [ifIsConsole] [ifIsUser] 执行结果. * @return [ifIsConsole], [ifIsUser] [otherwise] 执行结果.
*/ */
@JvmSynthetic @JvmSynthetic
public inline fun <R> CommandSender.fold( public inline fun <R> CommandSender.fold(
ifIsConsole: ConsoleCommandSender.() -> R, ifIsConsole: ConsoleCommandSender.() -> R,
ifIsUser: UserCommandSender.() -> R, ifIsUser: UserCommandSender.() -> R,
otherwise: CommandSender.() -> R = { error("CommandSender ${this::class.qualifiedName} is not supported") },
): R { ): R {
contract { contract {
callsInPlace(ifIsConsole, InvocationKind.AT_MOST_ONCE) callsInPlace(ifIsConsole, InvocationKind.AT_MOST_ONCE)
callsInPlace(ifIsUser, 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 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(inGroup, InvocationKind.AT_MOST_ONCE)
callsInPlace(inPrivate, 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) is MemberCommandSender -> inGroup(sender)
else -> inPrivate(sender) else -> inPrivate(sender)
} }
@ -603,7 +499,7 @@ public sealed class AbstractUserCommandSender : UserCommandSender, AbstractComma
} }
/** /**
* 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.execute]) * 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand])
* @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令 * @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令
*/ */
public open class FriendCommandSender internal constructor( 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] 主动在群内发送消息执行指令. * @see MemberCommandSenderOnMessage 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令.
*/ */
public open class MemberCommandSender internal constructor( 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] 主动在临时会话发送消息执行指令 * @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令
*/ */
public open class TempCommandSender internal constructor( public open class TempCommandSender internal constructor(

View File

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

View File

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

View File

@ -339,7 +339,7 @@ public object ExistingMemberValueArgumentParser : InternalCommandValueArgumentPa
public object PermissionIdValueArgumentParser : InternalCommandValueArgumentParserExtensions<PermissionId>() { public object PermissionIdValueArgumentParser : InternalCommandValueArgumentParserExtensions<PermissionId>() {
override fun parse(raw: String, sender: CommandSender): PermissionId { override fun parse(raw: String, sender: CommandSender): PermissionId {
return kotlin.runCatching { PermissionId.parseFromString(raw) }.getOrElse { 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 { override fun parse(raw: String, sender: CommandSender): PermitteeId {
return if (raw == "~") sender.permitteeId return if (raw == "~") sender.permitteeId
else kotlin.runCatching { AbstractPermitteeId.parseFromString(raw) }.getOrElse { 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.AbstractCommandValueParameter.UserDefinedType.Companion.createRequired
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable
import net.mamoe.mirai.console.command.parse.CommandValueArgument 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.classifierAsKClass
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
import net.mamoe.mirai.console.internal.data.typeOf0 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 @ExperimentalCommandDescriptors
public interface CommandValueParameter<T : Any?> : CommandParameter<T> { public interface CommandValueParameter<T : Any?> : CommandParameter<T> {
public val isVararg: Boolean 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 = public fun accepts(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): Boolean =
accepting(argument, commandArgumentContext).isAcceptable accepting(argument, commandArgumentContext).isAcceptable
@ -75,15 +94,15 @@ public sealed class ArgumentAcceptance(
) { ) {
public object Direct : ArgumentAcceptance(Int.MAX_VALUE) public object Direct : ArgumentAcceptance(Int.MAX_VALUE)
public class WithTypeConversion( public data class WithTypeConversion(
public val typeVariant: TypeVariant<*>, public val typeVariant: TypeVariant<*>,
) : ArgumentAcceptance(20) ) : ArgumentAcceptance(20)
public class WithContextualConversion( public data class WithContextualConversion(
public val parser: CommandValueArgumentParser<*>, public val parser: CommandValueArgumentParser<*>,
) : ArgumentAcceptance(10) ) : ArgumentAcceptance(10)
public class ResolutionAmbiguity( public data class ResolutionAmbiguity(
public val candidates: List<TypeVariant<*>>, public val candidates: List<TypeVariant<*>>,
) : ArgumentAcceptance(0) ) : ArgumentAcceptance(0)
@ -101,7 +120,7 @@ public sealed class ArgumentAcceptance(
} }
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public class CommandReceiverParameter<T : CommandSender>( public data class CommandReceiverParameter<T : CommandSender>(
override val isOptional: Boolean, override val isOptional: Boolean,
override val type: KType, override val type: KType,
) : CommandParameter<T>, AbstractCommandParameter<T>() { ) : CommandParameter<T>, AbstractCommandParameter<T>() {
@ -165,10 +184,11 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
} }
@ConsoleExperimentalApi @ConsoleExperimentalApi
public class StringConstant( public data class StringConstant(
@ConsoleExperimentalApi @ConsoleExperimentalApi
public override val name: String?, public override val name: String?,
public val expectingValue: String, public val expectingValue: String,
public val ignoreCase: Boolean,
) : AbstractCommandValueParameter<String>() { ) : AbstractCommandValueParameter<String>() {
public override val type: KType get() = STRING_TYPE public override val type: KType get() = STRING_TYPE
public override val isOptional: Boolean get() = false 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 toString(): String = "<$expectingValue>"
override fun acceptingImpl(expectingType: KType, argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance { 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 ArgumentAcceptance.Direct
} else ArgumentAcceptance.Impossible } else ArgumentAcceptance.Impossible
} }
@ -201,12 +221,14 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
* @see createOptional * @see createOptional
* @see createRequired * @see createRequired
*/ */
public class UserDefinedType<T>( public data class UserDefinedType<T>(
public override val name: String?, public override val name: String?,
public override val isOptional: Boolean, public override val isOptional: Boolean,
public override val isVararg: Boolean, public override val isVararg: Boolean,
public override val type: KType, public override val type: KType,
) : AbstractCommandValueParameter<T>() { ) : AbstractCommandValueParameter<T>() {
override fun toString(): String = super.toString()
init { init {
requireNotNull(type.classifierAsKClassOrNull()) { requireNotNull(type.classifierAsKClassOrNull()) {
"type.classifier must be KClass." "type.classifier must be KClass."
@ -236,7 +258,5 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
* Extended by [CommandValueArgumentParser] * Extended by [CommandValueArgumentParser]
*/ */
@ConsoleExperimentalApi @ConsoleExperimentalApi
public abstract class Extended<T> : AbstractCommandValueParameter<T>() { public abstract class Extended<T> : AbstractCommandValueParameter<T>() // For implementer: take care of toString()
abstract override fun toString(): String
}
} }

View File

@ -58,9 +58,9 @@ public abstract class AbstractCommandSignature : CommandSignature {
override fun toString(): String { override fun toString(): String {
val receiverParameter = receiverParameter val receiverParameter = receiverParameter
return if (receiverParameter == null) { return if (receiverParameter == null) {
"CommandSignatureVariant(${valueParameters.joinToString()})" "CommandSignature(${valueParameters.joinToString()})"
} else { } 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.CommandSender
import net.mamoe.mirai.console.command.CompositeCommand import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.SimpleCommand 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.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.contact.*
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
@ -45,6 +48,9 @@ import kotlin.contracts.contract
* - [User]: [ExistingUserValueArgumentParser] * - [User]: [ExistingUserValueArgumentParser]
* - [Contact]: [ExistingContactValueArgumentParser] * - [Contact]: [ExistingContactValueArgumentParser]
* *
* - [PermitteeId]: [PermitteeIdValueArgumentParser]
* - [PermissionId]: [PermissionIdValueArgumentParser]
*
* *
* @see SimpleCommand 简单指令 * @see SimpleCommand 简单指令
* @see CompositeCommand 复合指令 * @see CompositeCommand 复合指令
@ -143,6 +149,9 @@ public abstract class AbstractCommandValueArgumentParser<T : Any> : CommandValue
} }
} }
/**
* @see CommandValueArgumentParser.map
*/
public class MappingCommandValueArgumentParser<T : Any, R : Any>( public class MappingCommandValueArgumentParser<T : Any, R : Any>(
private val original: CommandValueArgumentParser<T>, private val original: CommandValueArgumentParser<T>,
private val mapper: MappingCommandValueArgumentParser<T, R>.(T) -> R, private val mapper: MappingCommandValueArgumentParser<T, R>.(T) -> R,

View File

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

View File

@ -9,8 +9,6 @@
package net.mamoe.mirai.console.command.descriptor 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.command.parse.CommandValueArgument
import net.mamoe.mirai.console.internal.data.castOrNull import net.mamoe.mirai.console.internal.data.castOrNull
import net.mamoe.mirai.console.internal.data.kClassQualifiedName import net.mamoe.mirai.console.internal.data.kClassQualifiedName
@ -19,9 +17,18 @@ import kotlin.reflect.KType
import kotlin.reflect.typeOf 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 @ExperimentalCommandDescriptors
public interface TypeVariant<out OutType> { public interface TypeVariant<out OutType> {
@ -31,17 +38,22 @@ public interface TypeVariant<out OutType> {
public val outType: KType public val outType: KType
/** /**
* Maps an [valueArgument] to [outType]
*
* @see CommandValueArgument.value * @see CommandValueArgument.value
*/ */
public fun mapValue(valueParameter: Message): OutType public fun mapValue(valueArgument: Message): OutType
public companion object { public companion object {
/**
* Creates a [TypeVariant] with reified [OutType].
*/
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
@JvmSynthetic @JvmSynthetic
public inline operator fun <reified OutType> invoke(crossinline block: (valueParameter: Message) -> OutType): TypeVariant<OutType> { public inline operator fun <reified OutType> invoke(crossinline block: (valueParameter: Message) -> OutType): TypeVariant<OutType> {
return object : TypeVariant<OutType> { return object : TypeVariant<OutType> {
override val outType: KType = typeOf<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> { public object MessageContentTypeVariant : TypeVariant<MessageContent> {
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
override val outType: KType = typeOf<MessageContent>() override val outType: KType = typeOf<MessageContent>()
override fun mapValue(valueParameter: Message): MessageContent = override fun mapValue(valueArgument: Message): MessageContent =
valueParameter.castOrNull<MessageContent>() ?: error("Accepts MessageContent only but given ${valueParameter.kClassQualifiedName}") valueArgument.castOrNull<MessageContent>() ?: error("Accepts MessageContent only but given ${valueArgument.kClassQualifiedName}")
} }
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public object MessageChainTypeVariant : TypeVariant<MessageChain> { public object MessageChainTypeVariant : TypeVariant<MessageChain> {
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
override val outType: KType = typeOf<MessageChain>() override val outType: KType = typeOf<MessageChain>()
override fun mapValue(valueParameter: Message): MessageChain = valueParameter.asMessageChain() override fun mapValue(valueArgument: Message): MessageChain = valueArgument.asMessageChain()
} }
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public object ContentStringTypeVariant : TypeVariant<String> { public object ContentStringTypeVariant : TypeVariant<String> {
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
override val outType: KType = typeOf<String>() 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, parentPermission: Permission = owner.parentPermission,
) : CompositeCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = parentPermission) { ) : CompositeCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = parentPermission) {
/** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */ /** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */
public final override var description: String = "<no descriptions available>" public final override var description: String = super.description
protected set protected set
public final override var permission: Permission = super.permission public final override var permission: Permission = super.permission

View File

@ -12,25 +12,45 @@
package net.mamoe.mirai.console.command.parse package net.mamoe.mirai.console.command.parse
import net.mamoe.mirai.console.command.Command 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.CommandSender
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors 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]. * 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 @ExperimentalCommandDescriptors
public interface CommandCall { public interface CommandCall {
/**
* The [CommandSender] responsible to this call.
*/
public val caller: CommandSender 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 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> public val valueArguments: List<CommandValueArgument>
// maybe add contextual arguments, i.e. from MessageMetadata
} }
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors

View File

@ -42,6 +42,12 @@ public interface CommandValueArgument : CommandArgument {
* [MessageChain] is vararg * [MessageChain] is vararg
*/ */
public val value: Message public val value: Message
/**
* Intrinsic variants of this argument.
*
* @see TypeVariant
*/
public val typeVariants: List<TypeVariant<*>> public val typeVariants: List<TypeVariant<*>>
} }

View File

@ -9,16 +9,12 @@
package net.mamoe.mirai.console.command.resolve package net.mamoe.mirai.console.command.resolve
import net.mamoe.mirai.console.command.Command import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isNotAcceptable 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.CommandCall
import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.command.parse.CommandValueArgument
import net.mamoe.mirai.console.command.parse.DefaultCommandValueArgument 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.internal.data.classifierAsKClass
import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.safeCast import net.mamoe.mirai.console.util.safeCast
@ -31,21 +27,26 @@ import net.mamoe.mirai.message.data.asMessageChain
@ConsoleExperimentalApi @ConsoleExperimentalApi
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public object BuiltInCommandCallResolver : CommandCallResolver { public object BuiltInCommandCallResolver : CommandCallResolver {
public object Provider : CommandCallResolverProvider by CommandCallResolverProviderImpl(BuiltInCommandCallResolver) override fun resolve(call: CommandCall): CommandResolveResult {
val callee = CommandManager.matchCommand(call.calleeName) ?: return CommandResolveResult(CommandExecuteResult.UnresolvedCommand(call))
override fun resolve(call: CommandCall): ResolvedCommandCall? {
val callee = CommandManager.matchCommand(call.calleeName) ?: return null
val valueArguments = call.valueArguments val valueArguments = call.valueArguments
val context = callee.safeCast<CommandArgumentContextAware>()?.context 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(
callee, ResolvedCommandCallImpl(
signature.signature, call.caller,
signature.zippedArguments.map { it.second }, callee,
context ?: EmptyCommandArgumentContext) signature.signature,
signature.zippedArguments.map { it.second },
context ?: EmptyCommandArgumentContext
)
)
} }
private data class ResolveData( private data class ResolveData(
@ -62,76 +63,121 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
val acceptance: ArgumentAcceptance, val acceptance: ArgumentAcceptance,
) )
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,
valueArguments: List<CommandValueArgument>,
context: CommandArgumentContext?,
errorSink: ErrorSink,
): ResolveData? {
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
val zipped = valueParameters.zip(valueArguments).toMutableList()
val remainingParameters = valueParameters.drop(zipped.size).toMutableList()
if (remainingParameters.any { !it.isOptional && !it.isVararg }) {
errorSink.reportUnmatched(UnmatchedCommandSignature(signature, FailureReason.NotEnoughArguments))// not enough args. // vararg can be empty.
return null
}
return if (zipped.isEmpty()) {
ResolveData(
signature = signature,
zippedArguments = emptyList(),
argumentAcceptances = emptyList(),
remainingParameters = remainingParameters,
)
} else {
if (valueArguments.size > valueParameters.size && zipped.last().first.isVararg) {
// merge vararg arguments
val (varargParameter, _)
= zipped.removeLast()
zipped.add(varargParameter to DefaultCommandValueArgument(valueArguments.drop(zipped.size).map { it.value }.asMessageChain()))
} else {
// add default empty vararg argument
val remainingVararg = remainingParameters.find { it.isVararg }
if (remainingVararg != null) {
zipped.add(remainingVararg to DefaultCommandValueArgument(EmptyMessageChain))
remainingParameters.remove(remainingVararg)
}
}
ResolveData(
signature = signature,
zippedArguments = zipped,
argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) ->
val accepting = parameter.accepting(argument, context)
if (accepting.isNotAcceptable) {
errorSink.reportUnmatched(UnmatchedCommandSignature(signature,
FailureReason.InapplicableValueArgument(parameter, argument)))// argument type not assignable
return null
}
ArgumentAcceptanceWithIndex(index, accepting)
},
remainingParameters = remainingParameters
)
}
}
private fun resolveImpl( private fun resolveImpl(
caller: CommandSender, caller: CommandSender,
callee: Command, callee: Command,
valueArguments: List<CommandValueArgument>, valueArguments: List<CommandValueArgument>,
context: CommandArgumentContext?, context: CommandArgumentContext?,
errorSink: ErrorSink,
): ResolveData? { ): ResolveData? {
callee.overloads callee.overloads
.mapNotNull l@{ signature -> .mapNotNull l@{ signature ->
if (signature.receiverParameter?.type?.classifierAsKClass()?.isInstance(caller) == false) { signature.toResolveData(caller, valueArguments, context, errorSink)
return@l null // not compatible receiver
}
val valueParameters = signature.valueParameters
val zipped = valueParameters.zip(valueArguments).toMutableList()
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 (zipped.isEmpty()) {
ResolveData(
signature = signature,
zippedArguments = emptyList(),
argumentAcceptances = emptyList(),
remainingParameters = remainingParameters,
)
} else {
if (valueArguments.size > valueParameters.size && zipped.last().first.isVararg) {
// merge vararg arguments
val (varargParameter, _)
= zipped.removeLast()
zipped.add(varargParameter to DefaultCommandValueArgument(valueArguments.drop(zipped.size).map { it.value }.asMessageChain()))
} else {
// add default empty vararg argument
val remainingVararg = remainingParameters.find { it.isVararg }
if (remainingVararg != null) {
zipped.add(remainingVararg to DefaultCommandValueArgument(EmptyMessageChain))
remainingParameters.remove(remainingVararg)
}
}
ResolveData(
signature = signature,
zippedArguments = zipped,
argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) ->
val accepting = parameter.accepting(argument, context)
if (accepting.isNotAcceptable) {
return@l null // argument type not assignable
}
ArgumentAcceptanceWithIndex(index, accepting)
},
remainingParameters = remainingParameters
)
}
} }
.also { result -> result.singleOrNull()?.let { return it } } .also { result -> result.takeSingleResolveData()?.let { return it } }
.takeLongestMatches() .takeLongestMatches()
.ifEmpty { return null } .ifEmpty { return null }
.also { result -> result.singleOrNull()?.let { return it } } .also { result -> result.takeSingleResolveData()?.let { return it } }
// take single ArgumentAcceptance.Direct // take single ArgumentAcceptance.Direct
.also { list -> .also { list ->
val candidates = list val candidates = list
.asSequence().filterIsInstance<ResolveData>()
.flatMap { phase -> .flatMap { phase ->
phase.argumentAcceptances.filter { it.acceptance is ArgumentAcceptance.Direct }.map { phase to it } phase.argumentAcceptances.filter { it.acceptance is ArgumentAcceptance.Direct }.map { phase to it }
} }.toList()
candidates.singleOrNull()?.let { return it.first } // single Direct candidates.singleOrNull()?.let { return it.first } // single Direct
if (candidates.distinctBy { it.second.index }.size != candidates.size) { if (candidates.distinctBy { it.second.index }.size != candidates.size) {
// Resolution ambiguity // Resolution ambiguity
/* /*
@ -145,13 +191,17 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
fun foo(a: AA, c: C) = 1 fun foo(a: AA, c: C) = 1
*/ */
// The call is foo(AA(), C()) or foo(A(), CC()) // The call is foo(AA(), C()) or foo(A(), CC())
return null
candidates.forEach { candidate -> errorSink.reportAmbiguity(candidate.first.signature) }
} }
} }
return null 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 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.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandCall
import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.extensions.CommandCallResolverProvider
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.util.ConsoleExperimentalApi 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 [] * The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered []
@ -23,19 +69,17 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi
*/ */
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public interface CommandCallResolver { public interface CommandCallResolver {
public fun resolve(call: CommandCall): ResolvedCommandCall? public fun resolve(call: CommandCall): CommandResolveResult
public companion object { public companion object {
@JvmName("resolveCall") @JvmName("resolveCall")
@ConsoleExperimentalApi @ConsoleExperimentalApi
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public fun CommandCall.resolve(): ResolvedCommandCall? { public fun CommandCall.resolve(): CommandResolveResult {
GlobalComponentStorage.run { GlobalComponentStorage.run {
CommandCallResolverProvider.useExtensions { provider -> val instance = CommandCallResolverProvider.findSingletonInstance(CommandCallResolverProvider.builtinImplementation)
provider.instance.resolve(this@resolve)?.let { return it } return instance.resolve(this@resolve)
}
} }
return null
} }
} }
} }

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.internal.data.classifierAsKClass
import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.cast import net.mamoe.mirai.console.util.cast
import kotlin.LazyThreadSafetyMode.PUBLICATION
/** /**
* The resolved [CommandCall]. * 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 * @see ResolvedCommandCallImpl
*/ */
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public interface ResolvedCommandCall { public interface ResolvedCommandCall {
/**
* The [CommandSender] responsible to this call.
*/
public val caller: CommandSender public val caller: CommandSender
/** /**
@ -49,7 +55,7 @@ public interface ResolvedCommandCall {
/** /**
* Resolved value arguments arranged mapping the [CommandSignature.valueParameters] by index. * Resolved value arguments arranged mapping the [CommandSignature.valueParameters] by index.
* *
* **Implementation details**: Lazy calculation. * **Default implementation details**: Lazy calculation.
*/ */
@ConsoleExperimentalApi @ConsoleExperimentalApi
public val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> public val resolvedValueArguments: List<ResolvedCommandValueArgument<*>>
@ -57,18 +63,30 @@ public interface ResolvedCommandCall {
public companion object public companion object
} }
/**
* Resolved [CommandValueParameter] for [ResolvedCommandCall.resolvedValueArguments]
*/
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public data class ResolvedCommandValueArgument<T>( public data class ResolvedCommandValueArgument<T>(
val parameter: CommandValueParameter<T>, val parameter: CommandValueParameter<T>,
/**
* Argument value expected by the [parameter]
*/
val value: T, val value: T,
) )
// Don't move into companion, compilation error // Don't move into companion, compilation error
/**
* Invoke this resolved call.
*/
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public suspend inline fun ResolvedCommandCall.call() { public suspend inline fun ResolvedCommandCall.call() {
return this@call.calleeSignature.call(this@call) return this@call.calleeSignature.call(this@call)
} }
/**
* Default implementation.
*/
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public class ResolvedCommandCallImpl( public class ResolvedCommandCallImpl(
override val caller: CommandSender, override val caller: CommandSender,
@ -77,7 +95,7 @@ public class ResolvedCommandCallImpl(
override val rawValueArguments: List<CommandValueArgument>, override val rawValueArguments: List<CommandValueArgument>,
private val context: CommandArgumentContext, private val context: CommandArgumentContext,
) : ResolvedCommandCall { ) : ResolvedCommandCall {
override val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> by lazy(PUBLICATION) { override val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> by lazy {
calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) -> calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) ->
val value = argument.mapToTypeOrNull(parameter.type) ?: context[parameter.type.classifierAsKClass()]?.parse(argument.value, caller) val value = argument.mapToTypeOrNull(parameter.type) ?: context[parameter.type.classifierAsKClass()]?.parse(argument.value, caller)
?: throw NoValueArgumentMappingException(argument, parameter.type) ?: throw NoValueArgumentMappingException(argument, parameter.type)

View File

@ -161,7 +161,7 @@ public fun <T> AbstractPluginData.findBackingFieldValue(property: KProperty<T>):
@ConsoleExperimentalApi @ConsoleExperimentalApi
public fun <T> AbstractPluginData.findBackingFieldValue(propertyValueName: String): Value<out T>? { public fun <T> AbstractPluginData.findBackingFieldValue(propertyValueName: String): Value<out T>? {
@Suppress("UNCHECKED_CAST") @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 { internal val debuggingLogger1 by lazy {
DefaultLogger("debug").withSwitch(false) DefaultLogger("console.debug").withSwitch(false)
} }
@Suppress("RESULT_CLASS_IN_RETURN_TYPE") @Suppress("RESULT_CLASS_IN_RETURN_TYPE")
@ -170,4 +170,5 @@ internal inline fun <R> MiraiLogger.runCatchingLog(message: (Throwable) -> Strin
}.getOrNull() }.getOrNull()
} }
@Suppress("SpellCheckingInspection")
private const val MAGIC_NUMBER_CFST_INIT: Long = Long.MAX_VALUE private const val MAGIC_NUMBER_CFST_INIT: Long = Long.MAX_VALUE

View File

@ -31,13 +31,15 @@ import kotlinx.serialization.SerialInfo
* map: * map:
* a: b * a: b
* ``` * ```
*
* @see net.mamoe.yamlkt.Comment
*/ */
@SerialInfo @SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
public annotation class ValueDescription( public annotation class ValueDescription(
/** /**
* 将会被 [String.trimIndent] 处理. * 将会被 [String.trimIndent] 处理
*/ */
val value: String, val value: String,
) )

View File

@ -23,7 +23,7 @@ package net.mamoe.mirai.console.data
* 将被保存为配置 (YAML 作为示例): * 将被保存为配置 (YAML 作为示例):
* ```yaml * ```yaml
* AccountPluginData: * AccountPluginData:
* map: * info:
* a: b * 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.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCallParser 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.command.resolve.CommandCallResolver
import net.mamoe.mirai.console.extensions.* import net.mamoe.mirai.console.extensions.*
import net.mamoe.mirai.console.internal.extension.AbstractConcurrentComponentStorage import net.mamoe.mirai.console.internal.extension.AbstractConcurrentComponentStorage
@ -128,4 +129,19 @@ public class PluginComponentStorage(
@OverloadResolutionByLambdaReturnType @OverloadResolutionByLambdaReturnType
public fun contributeCommandCallParser(provider: CommandCallResolverProvider): Unit = public fun contributeCommandCallParser(provider: CommandCallResolverProvider): Unit =
contribute(CommandCallResolverProvider, plugin, provider) 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.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.resolve.BuiltInCommandCallResolver import net.mamoe.mirai.console.command.resolve.BuiltInCommandCallResolver
import net.mamoe.mirai.console.command.resolve.CommandCallResolver import net.mamoe.mirai.console.command.resolve.CommandCallResolver
import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint import net.mamoe.mirai.console.extension.AbstractSingletonExtensionPoint
import net.mamoe.mirai.console.extension.InstanceExtension import net.mamoe.mirai.console.extension.SingletonExtension
@ExperimentalCommandDescriptors @ExperimentalCommandDescriptors
public interface CommandCallResolverProvider : InstanceExtension<CommandCallResolver> { public interface CommandCallResolverProvider : SingletonExtension<CommandCallResolver> {
public companion object ExtensionPoint : public companion object ExtensionPoint :
AbstractInstanceExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(CommandCallResolverProvider::class, AbstractSingletonExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(CommandCallResolverProvider::class, BuiltInCommandCallResolver)
BuiltInCommandCallResolver.Provider)
} }
@ExperimentalCommandDescriptors @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.AbstractSingletonExtensionPoint
import net.mamoe.mirai.console.extension.SingletonExtension 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.internal.permission.BuiltInPermissionService
import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
/** /**
* [权限服务][PermissionService] 提供器. * [权限服务][PermissionService] 提供器.
@ -21,7 +24,16 @@ import net.mamoe.mirai.console.permission.PermissionService
*/ */
public interface PermissionServiceProvider : SingletonExtension<PermissionService<*>> { public interface PermissionServiceProvider : SingletonExtension<PermissionService<*>> {
public companion object ExtensionPoint : 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) internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants)
@JvmStatic @JvmStatic
val buildDate: Instant = Instant.ofEpochSecond(1604041264) val buildDate: Instant = Instant.ofEpochSecond(1606348297)
const val versionConst: String = "1.0-RC-1" const val versionConst: String = "1.0.1-dev-2"
@JvmStatic @JvmStatic
val version: SemVersion = SemVersion(versionConst) 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.PermissionServiceProvider
import net.mamoe.mirai.console.extensions.PostStartupExtension import net.mamoe.mirai.console.extensions.PostStartupExtension
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector 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
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.MD5
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN
import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope import net.mamoe.mirai.console.internal.data.builtins.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.BuiltInSingletonExtensionSelector
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage 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.permission.BuiltInPermissionService
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.internal.util.autoHexToBytes 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
import net.mamoe.mirai.console.permission.PermissionService.Companion.permit import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
import net.mamoe.mirai.console.permission.RootPermission import net.mamoe.mirai.console.permission.RootPermission
import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter 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.loader.PluginLoader
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.console.util.SemVersion
@ -87,14 +93,29 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver = override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver =
instance.createLoginSolver(requesterBot, configuration) instance.createLoginSolver(requesterBot, configuration)
override val loggerController: LoggerController by instance::loggerController
init { init {
DefaultLogger = this::createLogger 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") @Suppress("RemoveRedundantBackticks")
internal fun doStart() { internal fun doStart() {
phase `setup logger controller`@{
if (loggerController === LoggerControllerImpl) {
// Reload LoggerConfig.
ConsoleDataScope.addAndReloadConfig(LoggerConfig)
LoggerControllerImpl.initialized = true
}
}
phase `greeting`@{ phase `greeting`@{
val buildDateFormatted = val buildDateFormatted =
buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) 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`@{ phase `load configurations`@{
mainLogger.verbose { "Loading configurations..." } mainLogger.verbose { "Loading configurations..." }
ConsoleDataScope.addAndReloadConfig(CommandConfig)
ConsoleDataScope.reloadAll() ConsoleDataScope.reloadAll()
} }
@ -145,14 +167,6 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." } 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`@{ phase `load SingletonExtensionSelector`@{
SingletonExtensionSelector.init() SingletonExtensionSelector.init()
val instance = SingletonExtensionSelector.instance val instance = SingletonExtensionSelector.instance
@ -161,27 +175,28 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
} }
} }
phase `load PermissionService`@{ phase `load PermissionService`@{
mainLogger.verbose { "Loading PermissionService..." } mainLogger.verbose { "Loading PermissionService..." }
PermissionServiceProvider.selectedInstance // init
PermissionService.INSTANCE.let { ps -> PermissionService.INSTANCE.let { ps ->
if (ps is BuiltInPermissionService) { if (ps is BuiltInPermissionService) {
ConsoleDataScope.addAndReloadConfig(ps.config) ConsoleDataScope.addAndReloadConfig(ps.config)
mainLogger.verbose { "Reloaded PermissionService settings." } 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`@{ phase `prepare commands`@{
mainLogger.verbose { "Loading built-in commands..." } mainLogger.verbose { "Loading built-in commands..." }
BuiltInCommands.registerAll() 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 CommandManager
CommandManagerImpl.commandListener // start // CommandManagerImpl.commandListener // start
} }
phase `enable plugins`@{ phase `enable plugins`@{
@ -198,20 +213,44 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
phase `auto-login bots`@{ phase `auto-login bots`@{
runBlocking { runBlocking {
for ((id, password) in AutoLoginConfig.plainPasswords.filterNot { it.key == 123456654321L }) { val accounts = AutoLoginConfig.accounts.toList()
mainLogger.info { "Auto-login $id" } for (account in accounts) {
MiraiConsole.addBot(id, password).alsoLogin() 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 package net.mamoe.mirai.console.internal.command
import kotlinx.atomicfu.locks.withLock import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.Command.Companion.allNames import net.mamoe.mirai.console.command.Command.Companion.allNames
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.findDuplicate 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.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.parse.CommandCallParser.Companion.parseCommandCall 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.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.permission.PermissionService.Companion.testPermission
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope 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.Message
import net.mamoe.mirai.message.data.asMessageChain import net.mamoe.mirai.message.data.asMessageChain
import net.mamoe.mirai.message.data.content
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
@ -61,49 +58,12 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiCons
} }
return optionalPrefixCommandMap[commandName.toLowerCase()] 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 ///// IMPL
override fun getRegisteredCommands(owner: CommandOwner): List<Command> = _registeredCommands.filter { it.owner == owner } override fun getRegisteredCommands(owner: CommandOwner): List<Command> = _registeredCommands.filter { it.owner == owner }
override val allRegisteredCommands: List<Command> get() = _registeredCommands.toList() // copy 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) { override fun unregisterAllCommands(owner: CommandOwner) {
for (registeredCommand in getRegisteredCommands(owner)) { for (registeredCommand in getRegisteredCommands(owner)) {
unregisterCommand(registeredCommand) unregisterCommand(registeredCommand)
@ -167,24 +127,43 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiCons
// Don't move into CommandManager, compilation error / VerifyError // Don't move into CommandManager, compilation error / VerifyError
@OptIn(ExperimentalCommandDescriptors::class) @OptIn(ExperimentalCommandDescriptors::class)
internal suspend fun executeCommandImpl( internal suspend fun executeCommandImpl(
message: Message, message0: Message,
caller: CommandSender, caller: CommandSender,
checkPermission: Boolean, checkPermission: Boolean,
): CommandExecuteResult { ): CommandExecuteResult {
val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.UnresolvedCall("") val message = message0
val resolved = call.resolve() ?: return CommandExecuteResult.UnresolvedCall(call.calleeName) .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 val command = resolved.callee
if (checkPermission && !command.permission.testPermission(caller)) { if (checkPermission && !command.permission.testPermission(caller)) {
return CommandExecuteResult.PermissionDenied(command, call.calleeName) return CommandExecuteResult.PermissionDenied(command, call, resolved)
} }
return try { return try {
resolved.calleeSignature.call(resolved) 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) { } catch (e: Throwable) {
CommandExecuteResult.ExecutionFailed(e, resolved.callee, call.calleeName, EmptyMessageChain) CommandExecuteResult.ExecutionFailed(e, resolved.callee, call, resolved)
} }
} }

View File

@ -144,37 +144,42 @@ internal class CommandReflector(
} }
fun generateUsage(overloads: Iterable<CommandSignatureFromKFunction>): String { fun generateUsage(overloads: Iterable<CommandSignatureFromKFunction>): String {
return overloads.joinToString("\n") { subcommand -> return generateUsage(command, annotationResolver, overloads)
buildString { }
if (command.prefixOptional) {
append("(") companion object {
append(CommandManager.commandPrefix) fun generateUsage(command: Command, annotationResolver: SubCommandAnnotationResolver?, overloads: Iterable<CommandSignature>): String {
append(")") return overloads.joinToString("\n") { subcommand ->
} else { buildString {
append(CommandManager.commandPrefix) if (command.prefixOptional) {
} append("(")
//if (command is CompositeCommand) { append(CommandManager.commandPrefix)
append(command.primaryName) append(")")
append(" ") } else {
//} append(CommandManager.commandPrefix)
append(subcommand.valueParameters.joinToString(" ") { it.render() }) }
annotationResolver.getDescription(command, subcommand.originFunction)?.let { description -> //if (command is CompositeCommand) {
append(" # ") append(command.primaryName)
append(description) append(" ")
//}
append(subcommand.valueParameters.joinToString(" ") { it.render() })
if (annotationResolver != null && subcommand is CommandSignatureFromKFunction) {
annotationResolver.getDescription(command, subcommand.originFunction)?.let { description ->
append(" # ")
append(description)
}
}
} }
} }
} }
}
companion object {
private fun <T> AbstractCommandValueParameter<T>.render(): String { private fun <T> AbstractCommandValueParameter<T>.render(): String {
return when (this) { return when (this) {
is AbstractCommandValueParameter.Extended, is AbstractCommandValueParameter.Extended,
is AbstractCommandValueParameter.UserDefinedType<*>, 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 -> { is AbstractCommandValueParameter.StringConstant -> {
this.expectingValue this.expectingValue
@ -236,7 +241,7 @@ internal class CommandReflector(
.map { (name, function) -> .map { (name, function) ->
val functionNameAsValueParameter = val functionNameAsValueParameter =
name?.split(' ')?.mapIndexed { index, s -> createStringConstantParameter(index, s) } name?.split(' ')?.mapIndexed { index, s -> createStringConstantParameterForName(index, s) }
.orEmpty() .orEmpty()
val functionValueParameters = val functionValueParameters =
@ -292,8 +297,8 @@ internal class CommandReflector(
return CommandReceiverParameter(this.type.isMarkedNullable, this.type) return CommandReceiverParameter(this.type.isMarkedNullable, this.type)
} }
private fun createStringConstantParameter(index: Int, expectingValue: String): AbstractCommandValueParameter.StringConstant { private fun createStringConstantParameterForName(index: Int, expectingValue: String): AbstractCommandValueParameter.StringConstant {
return AbstractCommandValueParameter.StringConstant("#$index", expectingValue) return AbstractCommandValueParameter.StringConstant("#$index", expectingValue, true)
} }
private fun KParameter.toUserDefinedCommandParameter(): AbstractCommandValueParameter.UserDefinedType<*> { 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 * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("EXPOSED_SUPER_CLASS")
package net.mamoe.mirai.console.internal.data.builtins 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.AutoSavePluginConfig
import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.ValueDescription
import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.data.value
import net.mamoe.mirai.console.internal.util.md5 import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.internal.util.toUHexString import net.mamoe.yamlkt.Comment
import net.mamoe.yamlkt.YamlDynamicSerializer
internal object AutoLoginConfig : AutoSavePluginConfig("AutoLogin") { @ConsoleExperimentalApi
@ValueDescription( @ValueDescription("自动登录配置")
""" public object AutoLoginConfig : AutoSavePluginConfig("AutoLogin") {
账号和明文密码列表
"""
)
val plainPasswords: MutableMap<Long, String> by value(mutableMapOf(123456654321L to "example"))
@Serializable
@ValueDescription( public data class Account(
""" @Comment("账号, 现只支持 QQ 数字账号")
账号和 MD5 密码列表 val account: String,
""" val password: Password,
) @Comment("""
val md5Passwords: MutableMap<Long, String> by value( 账号配置. 可用配置列表 (注意大小写):
mutableMapOf( "protocol": "ANDROID_PHONE" / "ANDROID_PAD" / "ANDROID_WATCH"
123456654321L to "example".toByteArray().md5().toUHexString() """)
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,
) )
)
@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 kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder import net.mamoe.mirai.console.data.*
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.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.utils.minutesToMillis 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 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 比较的方法 // 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> { internal inline fun <T> dynamicList(crossinline supplier: () -> List<T>): List<T> {
return object : List<T> { return object : List<T> {
override val size: Int get() = supplier().size 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> { internal inline fun <T> dynamicMutableSet(crossinline supplier: () -> MutableSet<T>): MutableSet<T> {
return object : MutableSet<T> { return object : MutableSet<T> {
override val size: Int get() = supplier().size 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() override fun hashCode(): Int = supplier().hashCode()
} }
} }
*/
@Suppress("UNCHECKED_CAST", "USELESS_CAST") // type inference bug @Suppress("UNCHECKED_CAST", "USELESS_CAST") // type inference bug
internal inline fun <K, V> MutableMap<K, V>.observable(crossinline onChanged: () -> Unit): MutableMap<K, V> { 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>) { 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) @OptIn(InternalSerializationApi::class)
internal fun <R : Any> Any.smartCastPrimitive(clazz: KClass<R>): R { internal fun <R : Any> Any.smartCastPrimitive(clazz: KClass<R>): R {
kotlin.runCatching { 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 * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("unused")
package net.mamoe.mirai.console.internal.data package net.mamoe.mirai.console.internal.data
import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.PluginData

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("unused")
package net.mamoe.mirai.console.internal.data package net.mamoe.mirai.console.internal.data
import kotlinx.serialization.ExperimentalSerializationApi 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) { return when (this) {
Byte::class -> 0.toByte() Byte::class -> 0.toByte()
Short::class -> 0.toShort() Short::class -> 0.toShort()

View File

@ -50,6 +50,8 @@ internal data class DataExtensionRegistry<out E : Extension>(
) : ExtensionRegistry<E> ) : ExtensionRegistry<E>
internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
private val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
internal fun <T : Extension> ExtensionPoint<out T>.getExtensions(): Set<ExtensionRegistry<T>> { internal fun <T : Extension> ExtensionPoint<out T>.getExtensions(): Set<ExtensionRegistry<T>> {
val userDefined = instances.getOrPut(this, ::CopyOnWriteArraySet) as 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 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) { internal fun mergeWith(another: AbstractConcurrentComponentStorage) {
for ((ep, list) in another.instances) { for ((ep, list) in another.instances) {
for (extensionRegistry in list) { 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 = internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T, plugin: Plugin?) -> Unit): Unit =
withExtensions(block) withExtensions(block)
val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
override fun <T : Extension> contribute( override fun <T : Extension> contribute(
extensionPoint: ExtensionPoint<T>, extensionPoint: ExtensionPoint<T>,
plugin: Plugin, 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) val arg = str.substring(1)
if (arg == "*") return AnyContact if (arg == "*") return AnyContact
} }
'm' -> kotlin.run { 'm' -> run {
val arg = str.substring(1) val arg = str.substring(1)
if (arg == "*") return AnyMemberFromAnyGroup if (arg == "*") return AnyMemberFromAnyGroup
else { else {
@ -56,7 +56,7 @@ internal fun parseFromStringImpl(string: String): AbstractPermitteeId {
} }
} }
} }
't' -> kotlin.run { 't' -> run {
val arg = str.substring(1) val arg = str.substring(1)
if (arg == "*") return AnyTempFromAnyGroup if (arg == "*") return AnyTempFromAnyGroup
else { 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.jvm.*
import net.mamoe.mirai.console.plugin.loader.AbstractFilePluginLoader import net.mamoe.mirai.console.plugin.loader.AbstractFilePluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoadException 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.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import java.io.File import java.io.File
@ -70,7 +71,6 @@ internal object BuiltInJvmPluginLoaderImpl :
f to pluginClassLoader.findServices( f to pluginClassLoader.findServices(
JvmPlugin::class, JvmPlugin::class,
KotlinPlugin::class, KotlinPlugin::class,
AbstractJvmPlugin::class,
JavaPlugin::class JavaPlugin::class
).loadAllServices() ).loadAllServices()
}.flatMap { (f, list) -> }.flatMap { (f, list) ->
@ -94,20 +94,25 @@ internal object BuiltInJvmPluginLoaderImpl :
return filePlugins.toSet().map { it.value } return filePlugins.toSet().map { it.value }
} }
private val loadedPlugins = ConcurrentHashMap<JvmPlugin, Unit>()
@Throws(PluginLoadException::class) @Throws(PluginLoadException::class)
override fun load(plugin: JvmPlugin) { override fun load(plugin: JvmPlugin) {
ensureActive() ensureActive()
if (loadedPlugins.put(plugin, Unit) != null) {
error("Plugin '${plugin.name}' is already loaded and cannot be reloaded.")
}
runCatching { runCatching {
check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin" } check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin to be loaded by JvmPluginLoader.BuiltIn" }
plugin.internalOnLoad(plugin.componentStorage) plugin.internalOnLoad()
}.getOrElse { }.getOrElse {
throw PluginLoadException("Exception while loading ${plugin.description.name}", it) throw PluginLoadException("Exception while loading ${plugin.description.name}", it)
} }
} }
override fun enable(plugin: JvmPlugin) { 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() ensureActive()
runCatching { runCatching {
if (plugin is JvmPluginInternal) { if (plugin is JvmPluginInternal) {
@ -119,7 +124,7 @@ internal object BuiltInJvmPluginLoaderImpl :
} }
override fun disable(plugin: JvmPlugin) { 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) if (MiraiConsole.isActive)
ensureActive() 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.data.runCatchingLog
import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.extension.PluginComponentStorage
import net.mamoe.mirai.console.internal.data.mkdir 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.Permission
import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.Plugin
@ -43,9 +44,6 @@ internal abstract class JvmPluginInternal(
parentCoroutineContext: CoroutineContext, parentCoroutineContext: CoroutineContext,
) : JvmPlugin, CoroutineScope { ) : JvmPlugin, CoroutineScope {
@Suppress("LeakingThis")
internal val componentStorage: PluginComponentStorage = PluginComponentStorage(this)
final override val parentPermission: Permission by lazy { final override val parentPermission: Permission by lazy {
PermissionService.INSTANCE.register( PermissionService.INSTANCE.register(
PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*"), PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*"),
@ -54,6 +52,7 @@ internal abstract class JvmPluginInternal(
} }
final override var isEnabled: Boolean = false final override var isEnabled: Boolean = false
internal set
private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() } private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() }
final override fun getResourceAsStream(path: String): InputStream? = final override fun getResourceAsStream(path: String): InputStream? =
@ -100,13 +99,16 @@ internal abstract class JvmPluginInternal(
} }
@Throws(Throwable::class) @Throws(Throwable::class)
internal fun internalOnLoad(componentStorage: PluginComponentStorage) { internal fun internalOnLoad() {
val componentStorage = PluginComponentStorage(this)
onLoad(componentStorage) onLoad(componentStorage)
GlobalComponentStorage.mergeWith(componentStorage)
} }
internal fun internalOnEnable(): Boolean { internal fun internalOnEnable(): Boolean {
parentPermission parentPermission
if (!firstRun) refreshCoroutineContext() if (!firstRun) refreshCoroutineContext()
kotlin.runCatching { kotlin.runCatching {
onEnable() onEnable()
}.fold( }.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.loader.PluginLoader
import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope 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 net.mamoe.mirai.utils.info
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
@ -248,8 +248,8 @@ internal fun List<PluginDescription>.findDependency(dependency: PluginDependency
} }
internal fun PluginDescription.checkSatisfies(dependency: PluginDependency, plugin: PluginDescription) { internal fun PluginDescription.checkSatisfies(dependency: PluginDependency, plugin: PluginDescription) {
val requirement = dependency.versionRequirement val requirement = dependency.versionRequirement ?: return
if (requirement != null && this.version !in requirement) { 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}") 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 * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:JvmName("CommonUtils") @file:JvmName("CommonUtils") // maintain binary compatibility
package net.mamoe.mirai.console.internal.util 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? { internal inline fun <reified E : Throwable, R> runIgnoreException(block: () -> R): R? {
try { try {
return block() return block()
@ -27,4 +32,43 @@ internal inline fun <reified E : Throwable> runIgnoreException(block: () -> Unit
if (e is E) return null if (e is E) return null
throw e 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 * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("unused")
package net.mamoe.mirai.console.internal.util package net.mamoe.mirai.console.internal.util
import net.mamoe.mirai.console.internal.data.cast import net.mamoe.mirai.console.internal.data.cast
@ -18,7 +20,6 @@ import java.util.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
import java.lang.reflect.Member as JReflectionMember import java.lang.reflect.Member as JReflectionMember
@Suppress("unused")
internal class ServiceList<T>( internal class ServiceList<T>(
internal val classLoader: ClassLoader, internal val classLoader: ClassLoader,
internal val delegate: List<String> internal val delegate: List<String>
@ -38,8 +39,8 @@ internal object PluginServiceHelper {
return delegate.mapNotNull { classLoader.loadService<T>(it) } return delegate.mapNotNull { classLoader.loadService<T>(it) }
} }
fun <T : Any> ClassLoader.loadService( private fun <T : Any> ClassLoader.loadService(
classname: String classname: String,
): T? { ): T? {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return kotlin.runCatching { 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) { return when (token) {
is Token.Group -> { is Token.Group -> {
if (token.values.size == 1) { if (token.values.size == 1) {
@ -206,7 +206,7 @@ internal object RangeTokenReader {
}.map { parse(source, it) }.toList() }.map { parse(source, it) }.toList()
when (logic.first()) { when (logic.first()) {
TokenType.OR -> { TokenType.OR -> {
return object : SemVersion.Requirement { return object : RequirementInternal {
override fun test(version: SemVersion): Boolean { override fun test(version: SemVersion): Boolean {
rules.forEach { if (it.test(version)) return true } rules.forEach { if (it.test(version)) return true }
return false return false
@ -214,7 +214,7 @@ internal object RangeTokenReader {
} }
} }
TokenType.AND -> { TokenType.AND -> {
return object : SemVersion.Requirement { return object : RequirementInternal {
override fun test(version: SemVersion): Boolean { override fun test(version: SemVersion): Boolean {
rules.forEach { if (!it.test(version)) return false } rules.forEach { if (!it.test(version)) return false }
return true 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 @JvmStatic
internal fun parseRule(rule: String): SemVersion.Requirement { internal fun parseRule(rule: String): RequirementInternal {
val trimmed = rule.trim() val trimmed = rule.trim()
if (directVersion.matches(trimmed)) { if (directVersion.matches(trimmed)) {
val parsed = SemVersion.invoke(trimmed) val parsed = SemVersion.invoke(trimmed)
return object : SemVersion.Requirement { return object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version.compareTo(parsed) == 0 override fun test(version: SemVersion): Boolean = version.compareTo(parsed) == 0
} }
} }
@ -89,7 +89,7 @@ internal object SemVersionInternal {
.replace("x", ".+") + .replace("x", ".+") +
"$" "$"
).toRegex() ).toRegex()
return object : SemVersion.Requirement { return object : RequirementInternal {
override fun test(version: SemVersion): Boolean = regex.matches(version.toString()) override fun test(version: SemVersion): Boolean = regex.matches(version.toString())
} }
} }
@ -120,7 +120,7 @@ internal object SemVersionInternal {
'(', ')' -> ({ it < end }) '(', ')' -> ({ it < end })
else -> throw AssertionError() else -> throw AssertionError()
} }
return object : SemVersion.Requirement { return object : RequirementInternal {
override fun test(version: SemVersion): Boolean = a(version) && b(version) override fun test(version: SemVersion): Boolean = a(version) && b(version)
} }
} }
@ -129,32 +129,32 @@ internal object SemVersionInternal {
val version1 = SemVersion.invoke(result.groupValues[8]) val version1 = SemVersion.invoke(result.groupValues[8])
return when (operator) { return when (operator) {
">=" -> { ">=" -> {
object : SemVersion.Requirement { object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version >= version1 override fun test(version: SemVersion): Boolean = version >= version1
} }
} }
">" -> { ">" -> {
object : SemVersion.Requirement { object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version > version1 override fun test(version: SemVersion): Boolean = version > version1
} }
} }
"<=" -> { "<=" -> {
object : SemVersion.Requirement { object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version <= version1 override fun test(version: SemVersion): Boolean = version <= version1
} }
} }
"<" -> { "<" -> {
object : SemVersion.Requirement { object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version < version1 override fun test(version: SemVersion): Boolean = version < version1
} }
} }
"=" -> { "=" -> {
object : SemVersion.Requirement { object : RequirementInternal {
override fun test(version: SemVersion): Boolean = version.compareTo(version1) == 0 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 override fun test(version: SemVersion): Boolean = version.compareTo(version1) != 0
} }
} }
@ -164,15 +164,9 @@ internal object SemVersionInternal {
throw IllegalArgumentException("Cannot parse $rule") 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 @JvmStatic
fun parseRangeRequirement(requirement: String): SemVersion.Requirement { fun parseRangeRequirement(requirement: String): RequirementInternal {
if (requirement.isBlank()) { if (requirement.isBlank()) {
throw IllegalArgumentException("Invalid requirement: Empty requirement rule.") throw IllegalArgumentException("Invalid requirement: Empty requirement rule.")
} }
@ -180,7 +174,7 @@ internal object SemVersionInternal {
val collected = RangeTokenReader.collect(requirement, tokens.iterator(), true) val collected = RangeTokenReader.collect(requirement, tokens.iterator(), true)
RangeTokenReader.check(requirement, collected.iterator(), null) RangeTokenReader.check(requirement, collected.iterator(), null)
return kotlin.runCatching { return kotlin.runCatching {
RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0)).withRule(requirement) RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0))
}.onFailure { error -> }.onFailure { error ->
throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString { throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString {
collected.forEach { dump("", it) } 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, @ResolveContext(PERMISSION_NAME) public val name: String,
) { ) {
init { init {
require(namespace.none { it.isWhitespace() }) { checkPermissionIdName(name)
"' ' is not allowed in namespace" checkPermissionIdName(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"
}
} }
public object PermissionIdAsStringSerializer : KSerializer<PermissionId> by String.serializer().map( 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) { public fun checkPermissionIdName(@ResolveContext(PERMISSION_NAME) name: String) {
when { when {
name.isBlank() -> throw IllegalArgumentException("PermissionId.name should not be blank.") 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.") 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) { public fun checkPermissionIdNamespace(@ResolveContext(PERMISSION_NAME) namespace: String) {
when { when {
namespace.isBlank() -> throw IllegalArgumentException("PermissionId.namespace should not be blank.") 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.") 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 class PermissionRegistryConflictException(
public val newInstance: Permission, public val newInstance: Permission,
public val existingInstance: 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 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
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_ID 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 import net.mamoe.mirai.console.util.SemVersion
/** /**
@ -20,6 +25,7 @@ import net.mamoe.mirai.console.util.SemVersion
* *
* @see PluginDescription.dependencies * @see PluginDescription.dependencies
*/ */
@Serializable(with = PluginDependency.PluginDependencyAsStringSerializer::class)
public data class PluginDependency @JvmOverloads constructor( public data class PluginDependency @JvmOverloads constructor(
/** /**
* 依赖插件 ID, [PluginDescription.id] * 依赖插件 ID, [PluginDescription.id]
@ -32,7 +38,7 @@ public data class PluginDependency @JvmOverloads constructor(
* *
* @see SemVersion.Requirement * @see SemVersion.Requirement
*/ */
public val versionRequirement: SemVersion.Requirement? = null, @ResolveContext(VERSION_REQUIREMENT) public val versionRequirement: String? = null,
/** /**
* 若为 `false`, 插件在找不到此依赖时也能正常加载. * 若为 `false`, 插件在找不到此依赖时也能正常加载.
*/ */
@ -41,6 +47,7 @@ public data class PluginDependency @JvmOverloads constructor(
init { init {
kotlin.runCatching { kotlin.runCatching {
PluginDescription.checkPluginId(id) PluginDescription.checkPluginId(id)
if (versionRequirement != null) SemVersion.parseRangeRequirement(versionRequirement)
}.getOrElse { }.getOrElse {
throw IllegalArgumentException(it) throw IllegalArgumentException(it)
} }
@ -55,4 +62,40 @@ public data class PluginDependency @JvmOverloads constructor(
) : this( ) : this(
id, null, isOptional 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 @ConsoleExperimentalApi
public interface ExportManager { public interface ExportManager {

View File

@ -11,11 +11,17 @@
package net.mamoe.mirai.console.plugin.jvm 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
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.* 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.PluginDependency
import net.mamoe.mirai.console.plugin.description.PluginDescription 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.mirai.console.util.SemVersion
import net.mamoe.yamlkt.Yaml
/** /**
* JVM 插件的描述. 通常作为 `plugin.yml` * JVM 插件的描述. 通常作为 `plugin.yml`
@ -30,51 +36,69 @@ import net.mamoe.mirai.console.util.SemVersion
public interface JvmPluginDescription : PluginDescription { public interface JvmPluginDescription : PluginDescription {
public companion object { public companion object {
/** /**
* 构建 [JvmPluginDescription] * [pluginClassloader] 读取资源文件 [filename] 并以 YAML 格式解析为 [SimpleJvmPluginDescription]
* @see JvmPluginDescriptionBuilder *
* @param filename [ClassLoader.getResourceAsStream] 的参数 `name`
* @param pluginClassloader 默认通过 [CallerFinder.getCaller] 获取调用方 [StackFrame] 然后获取其 [Class.getClassLoader].
*/ */
@JvmName("create") // @JvmOverloads // compiler error
@JvmSynthetic @JvmStatic
public inline operator fun invoke( @ConsoleExperimentalApi
/** public fun loadFromResource(
* @see [PluginDescription.id] filename: String = "plugin.yml",
*/ pluginClassloader: ClassLoader = CallerFinder.getCaller()?.findLoader() ?: error("Cannot find caller classloader, please specify manually."),
@ResolveContext(PLUGIN_ID) id: String, ): JvmPluginDescription {
/** val stream = pluginClassloader.getResourceAsStream(filename) ?: error("Cannot find plugin description resource '$filename'")
* @see [PluginDescription.version]
*/
@ResolveContext(SEMANTIC_VERSION) version: String,
/**
* @see [PluginDescription.name]
*/
@ResolveContext(PLUGIN_NAME) name: String = id,
block: JvmPluginDescriptionBuilder.() -> Unit = {},
): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build()
/** val bytes = stream.use { it.readBytes() }
* 构建 [JvmPluginDescription]
* @see JvmPluginDescriptionBuilder return Yaml.default.decodeFromString(SimpleJvmPluginDescription.serializer(), String(bytes))
*/ }
@JvmName("create")
@JvmSynthetic
public inline operator fun invoke(
/**
* @see [PluginDescription.id]
*/
@ResolveContext(PLUGIN_ID) id: String,
/**
* @see [PluginDescription.version]
*/
version: SemVersion,
/**
* @see [PluginDescription.name]
*/
@ResolveContext(PLUGIN_NAME) name: String = id,
block: JvmPluginDescriptionBuilder.() -> Unit = {},
): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build()
} }
} }
/**
* 构建 [JvmPluginDescription]
* @see JvmPluginDescriptionBuilder
*/
@JvmSynthetic
public inline fun JvmPluginDescription(
/**
* @see [PluginDescription.id]
*/
@ResolveContext(PLUGIN_ID) id: String,
/**
* @see [PluginDescription.version]
*/
@ResolveContext(SEMANTIC_VERSION) version: String,
/**
* @see [PluginDescription.name]
*/
@ResolveContext(PLUGIN_NAME) name: String = id,
block: JvmPluginDescriptionBuilder.() -> Unit = {},
): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build()
/**
* 构建 [JvmPluginDescription]
* @see JvmPluginDescriptionBuilder
*/
@JvmSynthetic
public inline fun JvmPluginDescription(
/**
* @see [PluginDescription.id]
*/
@ResolveContext(PLUGIN_ID) id: String,
/**
* @see [PluginDescription.version]
*/
version: SemVersion,
/**
* @see [PluginDescription.name]
*/
@ResolveContext(PLUGIN_NAME) name: String = id,
block: JvmPluginDescriptionBuilder.() -> Unit = {},
): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build()
/** /**
* [JvmPluginDescription] 构建器. * [JvmPluginDescription] 构建器.
* *
@ -94,7 +118,7 @@ public interface JvmPluginDescription : PluginDescription {
* .build(); * .build();
* ``` * ```
* *
* @see [JvmPluginDescription.invoke] * @see [JvmPluginDescription]
*/ */
public class JvmPluginDescriptionBuilder( public class JvmPluginDescriptionBuilder(
@ResolveContext(PLUGIN_ID) private var id: String, @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 * @see PluginDependency
*/ */
@ -171,7 +181,7 @@ public class JvmPluginDescriptionBuilder(
@ResolveContext(VERSION_REQUIREMENT) versionRequirement: String, @ResolveContext(VERSION_REQUIREMENT) versionRequirement: String,
isOptional: Boolean = false, isOptional: Boolean = false,
): JvmPluginDescriptionBuilder = apply { ): 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 = public fun build(): JvmPluginDescription =
@Suppress("DEPRECATION_ERROR") @Suppress("DEPRECATION_ERROR")
SimpleJvmPluginDescription(name, version, id, author, info, dependencies) SimpleJvmPluginDescription(id, name, version, author, info, dependencies)
/** /**
* 标注一个 [JvmPluginDescription] DSL * 标注一个 [JvmPluginDescription] DSL
@ -208,11 +218,12 @@ public class JvmPluginDescriptionBuilder(
* *
* @see JvmPluginDescription * @see JvmPluginDescription
*/ */
@Serializable // Keep this file in public API files. Might turn to `public` in the future.
internal data class SimpleJvmPluginDescription internal data class SimpleJvmPluginDescription
@JvmOverloads constructor( @JvmOverloads constructor(
override val id: String,
override val name: String, override val name: String,
override val version: SemVersion, override val version: SemVersion,
override val id: String = name,
override val author: String = "", override val author: String = "",
override val info: String = "", override val info: String = "",
override val dependencies: Set<PluginDependency> = setOf(), override val dependencies: Set<PluginDependency> = setOf(),
@ -221,13 +232,13 @@ internal data class SimpleJvmPluginDescription
@Suppress("DEPRECATION_ERROR") @Suppress("DEPRECATION_ERROR")
@JvmOverloads @JvmOverloads
constructor( constructor(
name: String, id: String,
name: String = id,
version: String, version: String,
id: String = name,
author: String = "", author: String = "",
info: String = "", info: String = "",
dependencies: Set<PluginDependency> = setOf(), dependencies: Set<PluginDependency> = setOf(),
) : this(name, SemVersion(version), id, author, info, dependencies) ) : this(id, name, SemVersion(version), author, info, dependencies)
init { init {
PluginDescription.checkPluginDescription(this) PluginDescription.checkPluginDescription(this)

View File

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

View File

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

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("unused")
package net.mamoe.mirai.console.util package net.mamoe.mirai.console.util
import net.mamoe.mirai.message.data.MessageChain 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.internal.util.semver.SemVersionInternal
import net.mamoe.mirai.console.util.SemVersion.Companion.equals import net.mamoe.mirai.console.util.SemVersion.Companion.equals
import net.mamoe.mirai.console.util.SemVersion.Requirement import net.mamoe.mirai.console.util.SemVersion.Requirement
import net.mamoe.mirai.console.util.SemVersion.SemVersionAsStringSerializer
import kotlin.LazyThreadSafetyMode.PUBLICATION import kotlin.LazyThreadSafetyMode.PUBLICATION
/** /**
* [语义化版本](https://semver.org/lang/zh-CN/) 支持 * [语义化版本](https://semver.org/lang/zh-CN/) 支持
* *
* 解析示例: * ### 解析示例
* *
* `1.0.0-M4+c25733b8` 将会解析出下面的内容, * `1.0.0-M4+c25733b8` 将会解析出下面的内容,
* [major] (主本号), [minor] (次版本号), [patch] (修订号), [identifier] (先行版本号) [metadata] (元数据). * [major] (主本号), [minor] (次版本号), [patch] (修订号), [identifier] (先行版本号) [metadata] (元数据).
@ -48,10 +49,13 @@ import kotlin.LazyThreadSafetyMode.PUBLICATION
* *
* 对于核心版本号, 此实现稍微比语义化版本规范宽松一些, 允许 x.y 的存在. * 对于核心版本号, 此实现稍微比语义化版本规范宽松一些, 允许 x.y 的存在.
* *
* ### 序列化
* 使用 [SemVersionAsStringSerializer], [SemVersion] 被序列化为 [toString] 的字符串.
*
* @see Requirement 版本号要修 * @see Requirement 版本号要修
* @see SemVersion.invoke 由字符串解析 * @see SemVersion.invoke 由字符串解析
*/ */
@Serializable(with = SemVersion.SemVersionAsStringSerializer::class) @Serializable(with = SemVersionAsStringSerializer::class)
public data class SemVersion public data class SemVersion
/** /**
* @see SemVersion.invoke 字符串解析 * @see SemVersion.invoke 字符串解析
@ -81,11 +85,47 @@ internal constructor(
* 一条依赖规则 * 一条依赖规则
* @see [parseRangeRequirement] * @see [parseRangeRequirement]
*/ */
public interface Requirement { @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 */ /** 在 [version] 满足此要求时返回 true */
public fun test(version: SemVersion): Boolean 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( public object SemVersionAsStringSerializer : KSerializer<SemVersion> by String.serializer().map(
serializer = { it.toString() }, serializer = { it.toString() },
deserializer = { SemVersion(it) } deserializer = { SemVersion(it) }
@ -148,8 +188,8 @@ internal constructor(
*/ */
@JvmStatic @JvmStatic
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
public fun parseRangeRequirement(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Requirement = public fun parseRangeRequirement(@ResolveContext(VERSION_REQUIREMENT) rule: String): Requirement =
SemVersionInternal.parseRangeRequirement(requirement) Requirement(rule)
/** @see [Requirement.test] */ /** @see [Requirement.test] */
@JvmStatic @JvmStatic
@ -201,7 +241,11 @@ internal constructor(
public override fun toString(): String = toString 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 { public fun toStructuredString(): String {
return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)" 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 * 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:JvmMultifileClass
@file:JvmName("ConsoleUtils") @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.ConsoleInternalApi
import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.PlatformLogger
import java.nio.file.Path import java.nio.file.Path
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.io.path.createTempDirectory
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
@OptIn(ConsoleInternalApi::class) @OptIn(ConsoleInternalApi::class, kotlin.io.path.ExperimentalPathApi::class)
fun initTestEnvironment() { fun initTestEnvironment() {
object : MiraiConsoleImplementation { object : MiraiConsoleImplementation {
override val rootPath: Path = createTempDir().toPath() override val rootPath: Path = createTempDirectory()
@ConsoleExperimentalApi @ConsoleExperimentalApi
override val frontEndDescription: MiraiConsoleFrontEndDescription 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") { object TestSimpleCommand : RawCommand(owner, "testSimple", "tsS") {
override suspend fun CommandSender.onCommand(args: MessageChain) { 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 @Test
fun `test flattenCommandArgs`() { fun `test flattenCommandArgs`() {
val result = arrayOf("test", image).flattenCommandComponents().toTypedArray() val result = arrayOf("test", image).flattenCommandComponents().toTypedArray()
@ -161,8 +181,8 @@ internal class TestCommand {
@Test @Test
fun `composite command descriptors`() { fun `composite command descriptors`() {
val overloads = TestCompositeCommand.overloads val overloads = TestCompositeCommand.overloads
assertEquals("CommandSignatureVariant(<mute>, seconds: Int = ...)", overloads[0].toString()) assertEquals("CommandSignature(<mute>, seconds: Int = ...)", overloads[0].toString())
assertEquals("CommandSignatureVariant(<mute>, target: Long, seconds: Int)", overloads[1].toString()) assertEquals("CommandSignature(<mute>, target: Long, seconds: Int)", overloads[1].toString())
} }
@Test @Test

View File

@ -23,6 +23,7 @@ internal class PluginDataTest {
val map2: MutableMap<String, MutableMap<String, String>> by value() val map2: MutableMap<String, MutableMap<String, String>> by value()
} }
@Suppress("unused")
private val jsonPrettyPrint = Json { prettyPrint = true } private val jsonPrettyPrint = Json { prettyPrint = true }
private val json = Json {} 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 net.mamoe.mirai.console.util.SemVersion.Companion.test
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import kotlin.test.assertFails
internal class TestSemVersion { internal class TestSemVersion {
@Test @Test
@ -42,6 +43,16 @@ internal class TestSemVersion {
assert("1.0.0-rc.1".sem() < "1.0.0".sem()) 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 @Test
internal fun testRequirement() { internal fun testRequirement() {
fun SemVersion.Requirement.assert(version: String): SemVersion.Requirement { fun SemVersion.Requirement.assert(version: String): SemVersion.Requirement {
@ -50,9 +61,9 @@ internal class TestSemVersion {
} }
fun assertInvalid(requirement: String) { fun assertInvalid(requirement: String) {
kotlin.runCatching { assertFails(requirement) {
SemVersion.parseRangeRequirement(requirement) SemVersion.parseRangeRequirement(requirement)
}.onSuccess { assert(false) { requirement } } }
} }
fun SemVersion.Requirement.assertFalse(version: String): SemVersion.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") maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
jcenter() jcenter()
mavenCentral() mavenCentral()
maven(url = "https://dl.bintray.com/karlatemp/misc")
} }
} }
@ -43,14 +44,23 @@ subprojects {
} }
val experimentalAnnotations = arrayOf( val experimentalAnnotations = arrayOf(
"kotlin.Experimental",
"kotlin.RequiresOptIn", "kotlin.RequiresOptIn",
"kotlin.ExperimentalUnsignedTypes", "kotlin.ExperimentalUnsignedTypes",
// "kotlin.ExperimentalStdlibApi", "kotlin.ExperimentalStdlibApi",
"kotlin.contracts.ExperimentalContracts", "kotlin.contracts.ExperimentalContracts",
"kotlin.time.ExperimentalTime",
"kotlin.experimental.ExperimentalTypeInference", "kotlin.experimental.ExperimentalTypeInference",
// "kotlinx.coroutines.ExperimentalCoroutinesApi", "kotlinx.coroutines.ExperimentalCoroutinesApi",
"kotlinx.serialization.ExperimentalSerializationApi",
"kotlin.io.path.ExperimentalPathApi",
"io.ktor.util.KtorExperimentalAPI", "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() { fun Project.configureKotlinExperimentalUsages() {
val sourceSets = kotlinSourceSets ?: return val sourceSets = kotlinSourceSets ?: return
for (target in sourceSets) { for (target in sourceSets) target.languageSettings.run {
enableLanguageFeature("InlineClasses")
progressiveMode = true
experimentalAnnotations.forEach { a -> experimentalAnnotations.forEach { a ->
target.languageSettings.useExperimentalAnnotation(a) useExperimentalAnnotation(a)
//target.languageSettings.enableLanguageFeature("InlineClasses")
} }
} }
} }

View File

@ -16,9 +16,6 @@ import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.bundling.Jar
import org.gradle.kotlin.dsl.* import org.gradle.kotlin.dsl.*
import upload.Bintray import upload.Bintray
import java.io.InputStream
import java.io.OutputStream
import java.security.MessageDigest
import java.util.* import java.util.*
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@ -35,7 +32,7 @@ import kotlin.reflect.KProperty
* Configures the [bintray][com.jfrog.bintray.gradle.BintrayExtension] extension. * Configures the [bintray][com.jfrog.bintray.gradle.BintrayExtension] extension.
*/ */
@PublishedApi @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) (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("bintray", configure)
@PublishedApi @PublishedApi
@ -47,7 +44,7 @@ internal operator fun <U : Task> RegisteringDomainObjectDelegateProviderWithType
) )
@PublishedApi @PublishedApi
internal val org.gradle.api.Project.`sourceSets`: org.gradle.api.tasks.SourceSetContainer internal val Project.`sourceSets`: org.gradle.api.tasks.SourceSetContainer
get() = get() =
(this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("sourceSets") as org.gradle.api.tasks.SourceSetContainer (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. * Configures the [publishing][org.gradle.api.publish.PublishingExtension] extension.
*/ */
@PublishedApi @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) (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( inline fun Project.setupPublishing(
artifactId: String, artifactId: String,
bintrayRepo: String = "mirai", bintrayRepo: String = "mirai",
@ -125,6 +84,10 @@ inline fun Project.setupPublishing(
user = Bintray.getUser(project) user = Bintray.getUser(project)
key = Bintray.getKey(project) key = Bintray.getKey(project)
publish = true
override = true
setPublications("mavenJava") setPublications("mavenJava")
setConfigurations("archives") setConfigurations("archives")

View File

@ -7,18 +7,23 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("MemberVisibilityCanBePrivate", "ObjectPropertyName", "unused")
object Versions { object Versions {
const val core = "1.3.2" const val core = "1.3.3"
const val console = "1.0-RC-1" const val console = "1.0.1"
const val consoleGraphical = "0.0.7" const val consoleGraphical = "0.0.7"
const val consoleTerminal = console const val consoleTerminal = console
const val kotlinCompiler = "1.4.10" const val kotlinCompiler = "1.4.20"
const val kotlinStdlib = kotlinCompiler const val kotlinStdlib = "1.4.20"
const val coroutines = "1.3.9" const val kotlinIntellijPlugin = "1.4.20-release-IJ2020.2-1" // keep to newest as kotlinCompiler
const val collectionsImmutable = "0.3.2" const val intellij = "2020.2.1" // don't update easily unless you want your disk space -= 500MB
const val serialization = "1.0.0-RC"
const val coroutines = "1.4.0"
const val serialization = "1.0.1"
const val ktor = "1.4.1" const val ktor = "1.4.1"
const val atomicFU = "0.14.4" const val atomicFU = "0.14.4"
@ -26,6 +31,38 @@ object Versions {
const val bintray = "1.8.5" const val bintray = "1.8.5"
const val blockingBridge = "1.1.0" const val blockingBridge = "1.4.1"
const val yamlkt = "0.5.3"
} @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") @Suppress("unused")
fun DependencyHandler.compileAndTestRuntime(any: Any) { fun DependencyHandler.compileAndTestRuntime(any: Any) {
add("compileOnly", any) add("compileOnly", any)
add("testRuntimeOnly", any) add("testImplementation", any)
} }
fun DependencyHandler.smartApi( 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( object MySimpleCommand : SimpleCommand(
MyPluginMain, "tell", "私聊", MyPluginMain, "tell", "私聊",
description = "Tell somebody privately", description = "Tell somebody privately",
usage = "/tell <target> <message>" // usage 如不设置则自动根据带有 @Handler 的方法生成 usage = "/tell <target> <message>", // usage 如不设置则自动根据带有 @Handler 的方法生成
) { ) {
@Handler // 标记这是指令处理器 // 函数名随意 @Handler // 标记这是指令处理器 // 函数名随意
suspend fun CommandSender.handle(target: User, message: String) { // 这两个参数会被作为指令参数要求 suspend fun CommandSender.handle(target: User, message: String) { // 这两个参数会被作为指令参数要求

View File

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

View File

@ -26,7 +26,35 @@ Mirai Console 项目由四个模块组成后端前端Gradle 插件In
### 构建 ### 构建
```shell script ```shell script
gradlew build ./gradlew build
``` ```
首次加载和构建 mirai-console 项目可能要花费数小时时间。 首次加载和构建 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 前端开发文档。 Mirai Console 前端开发文档。
[`MiraiConsole`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt [`MiraiConsole`]: ../backend/mirai-console/src/MiraiConsole.kt
## 实现前端 ## 实现前端
@ -10,10 +10,8 @@ Mirai Console 前端开发文档。
`build.gradle``build.gradle.kts` 添加: `build.gradle``build.gradle.kts` 添加:
```kotlin ```kotlin
kotlin { kotlin.sourceSets.all {
sourceSets.all { languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
}
} }
``` ```
@ -22,9 +20,9 @@ kotlin {
### 实现 Mirai Console ### 实现 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()` 通过 `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`]。 [`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. 必须通过临时会话 | | 精确临时会话 | t123456.789 | 群 123456 内的成员 789. 必须通过临时会话 |
| 精确群成员 | m123456.789 | 群 123456 内的成员 789. 同时包含临时会话. | | 精确群成员 | m123456.789 | 群 123456 内的成员 789. 同时包含临时会话. |
| 精确用户 | u123456 | 同时包含群成员, 好友, 临时会话 | | 精确用户 | u123456 | 同时包含群成员, 好友, 临时会话 |
| 任意群 | g* | | | 任意群 | g\* | g 意为 group |
| 任意群的任意群员 | m* | | | 任意群的任意群员 | m\* | m 意为 member |
| 精确群的任意群员 | m123456.* | 群 123456 内的任意成员. 同时包含临时会话. | | 精确群的任意群员 | m123456.\* | 群 123456 内的任意成员. 同时包含临时会话. |
| 任意群的任意临时会话 | t* | 必须通过临时会话 | | 任意群的任意临时会话 | t\* | t 意为 temp. 必须通过临时会话 |
| 精确群的任意临时会话 | t123456.* | 群 123456 内的任意成员. 必须通过临时会话 | | 精确群的任意临时会话 | t123456.\* | 群 123456 内的任意成员. 必须通过临时会话 |
| 任意好友 | f* | | | 任意好友 | f\* | f 意为 friend |
| 任意用户 | u* | 任何人在任何环境 | | 任意用户 | u\* | u 意为 user. 任何人在任何环境 |
| 任意对象 | * | 即任何人, 任何群, 控制台 | | 任意对象 | \* | 即任何人, 任何群, 控制台 |
### 获取被许可人 ### 获取被许可人
@ -164,8 +164,9 @@ fun Permission.testPermission(PermitteeId): Boolean
### 使用内置权限服务指令 ### 使用内置权限服务指令
**指令**: "/permission", "/perm", "/权限" **指令**: "/permission", "/perm", "/权限"
使用指令而不带参数可以获取如下用法:
``` ```
/permission cancel <被许可人 ID> <权限 ID> 取消授权一个权限 /permission cancel <被许可人 ID> <权限 ID> 取消授权一个权限
/permission cancelall <被许可人 ID> <权限 ID> 取消授权一个权限及其所有子权限 /permission cancelall <被许可人 ID> <权限 ID> 取消授权一个权限及其所有子权限

View File

@ -43,6 +43,9 @@
[为什么不支持热加载和卸载插件?]: QA.md#为什么不支持热加载和卸载插件 [为什么不支持热加载和卸载插件?]: QA.md#为什么不支持热加载和卸载插件
[使用 AutoService]: QA.md#使用-autoservice [使用 AutoService]: QA.md#使用-autoservice
[MCI]: ../tools/intellij-plugin/
[MiraiPixel]: ../tools/intellij-plugin/resources/icons/pluginMainDeclaration.png
Mirai Console 运行在 [JVM],支持使用 [Kotlin] 或 [Java] 语言编写的插件。 Mirai Console 运行在 [JVM],支持使用 [Kotlin] 或 [Java] 语言编写的插件。
## 通用的插件接口 - [`Plugin`] ## 通用的插件接口 - [`Plugin`]
@ -63,23 +66,75 @@ interface Plugin : CommandOwner { // CommandOwner 是空的 interface
[`Plugin`] 接口拥有强扩展性,以支持 Mirai Console 统一管理使用其他编程语言编写的插件 (详见进阶章节 [扩展 - PluginLoader](Extensions.md))。 [`Plugin`] 接口拥有强扩展性,以支持 Mirai Console 统一管理使用其他编程语言编写的插件 (详见进阶章节 [扩展 - PluginLoader](Extensions.md))。
## 插件加载器 - [`PluginLoader`]
Mirai Console 使用不同的插件加载器来加载不同类型插件。
Mirai Console 内置 [`JvmPluginLoader`] 以加载 JVM 平台插件(见下文),并允许这些插件注册扩展的插件加载器(见章节 [扩展](Extensions.md))
## JVM 平台插件接口 - [`JvmPlugin`] ## JVM 平台插件接口 - [`JvmPlugin`]
所有的 JVM 插件都必须实现 [`JvmPlugin`](否则不会被 [`JvmPluginLoader`] 加载)。 所有的 JVM 插件(特别地,`jar` 插件)都必须实现 [`JvmPlugin`](否则不会被 [`JvmPluginLoader`] 加载)。
Mirai Console 提供一些基础的实现,即 [`AbstractJvmPlugin`],并将 [`JvmPlugin`] 分为 [`KotlinPlugin`] 和 [`JavaPlugin`]。 Mirai Console 提供一些基础的实现,即 [`AbstractJvmPlugin`],并将 [`JvmPlugin`] 分为 [`KotlinPlugin`] 和 [`JavaPlugin`]。
### 主类和描述 ### 主类
JVM 平台插件的主类应被实现为一个单例Kotlin `object`Java 静态初始化的类,详见下文示例)。
**Kotlin 使用者的插件主类应继承 [`KotlinPlugin`]。** **Kotlin 使用者的插件主类应继承 [`KotlinPlugin`]。**
**其他 JVM 语言(如 Java使用者的插件主类应继承 [`JavaPlugin`]。** **其他 JVM 语言(如 Java使用者的插件主类应继承 [`JavaPlugin`]。**
#### 描述 #### 定义主类
插件描述需要在主类构造器传递给 `super`。因此插件不需要 `plugin.yml`, `plugin.xml` 等配置文件来指示信息。
Mirai Console 使用类似 `ServiceLoader` 的机制加载插件。 Mirai Console 使用类似 Java `ServiceLoader` 但更灵活的机制加载插件。
在 Kotlin[使用 AutoService])自动配置 service 信息。
在 Kotlin 或其他语言,可手动创建 service 文件: 在 `jar``META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin` 文件内存放插件主类全名(以纯文本 UTF-8 存储,文件内容只包含一行插件主类全名).
一个正确的主类定义可以是以下三种任一:
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` - 插件自身的版本要求遵循 [语义化版本 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 插件主类
一个 Kotlin 插件的主类通常需: 一个 Kotlin 插件的主类通常需:
@ -109,7 +168,7 @@ object SchedulePlugin : KotlinPlugin(
} }
``` ```
#### 实现 Java 插件 #### 实现 Java 插件主类
一个 Java 插件的主类通常需: 一个 Java 插件的主类通常需:
- 继承 [`JavaPlugin`] - 继承 [`JavaPlugin`]
@ -135,7 +194,7 @@ public final class JExample extends JavaPlugin {
由 Console 初始化(仅在某些静态初始化不可用的情况下使用): 由 Console 初始化(仅在某些静态初始化不可用的情况下使用):
```java ```java
public final class JExample extends JavaPlugin { public final class JExample extends JavaPlugin {
private static final JExample instance; private static JExample instance;
public static JExample getInstance() { public static JExample getInstance() {
return instance; return instance;
} }

View File

@ -43,7 +43,7 @@
- Java 中的「方法」在 Kotlin 中均被称为「函数」。 - Java 中的「方法」在 Kotlin 中均被称为「函数」。
- Kotlin 默认的访问权限是 `public`。如 Kotlin `class Test` 相当于 Java 的 `public class Test {}` - 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` 函数 ### 在 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 前端可用。 只有 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-console-terminal 前端
mirai 在版本发布时会同时发布打包依赖的 Shadow JAR存放在 [mirai-repo]。 1. 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`)(详见 [下载模块](#从-jcenter-下载模块))
- mirai-core-all
1. 在 [mirai-repo] 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`)
- mirai-core-qqandroid
- mirai-console - mirai-console
- mirai-console-terminal - mirai-console-terminal
@ -51,19 +75,70 @@ pause
Linux: Linux:
```shell script ```shell script
#!/usr/bin/env bash #!/usr/bin/env bash
echo -e '\033]2;Mirai Console\007'
java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $* java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $*
``` ```
然后就可以开始使用 mirai-console 了 然后就可以开始使用 mirai-console 了
#### mirai-console-terminal 前端参数 #### 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-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 可以嵌入一个 JVM 应用启动。
- 下载 `mirai-console-terminal` 改成下载 `mirai-console-pure`
- 启动入口从 `net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader` 改成 `net.mamoe.mirai.console.pure.MiraiConsolePureLoader` ### 环境
- 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 * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:OptIn(ExperimentalCommandDescriptors::class)
package net.mamoe.mirai.console.terminal package net.mamoe.mirai.console.terminal
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.* 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.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.terminal.noconsole.NoConsole
import net.mamoe.mirai.console.util.ConsoleInternalApi 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.requestInput
import net.mamoe.mirai.console.util.safeCast
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.warning
import org.jline.reader.EndOfFileException import org.jline.reader.EndOfFileException
import org.jline.reader.UserInterruptException import org.jline.reader.UserInterruptException
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
val consoleLogger by lazy { DefaultLogger("console") } val consoleLogger by lazy { DefaultLogger("console") }
@ -30,12 +40,13 @@ internal fun startupConsoleThread() {
if (terminal is NoConsole) return if (terminal is NoConsole) return
MiraiConsole.launch(CoroutineName("Input Cancelling Daemon")) { MiraiConsole.launch(CoroutineName("Input Cancelling Daemon")) {
while (true) { while (isActive) {
delay(2000) delay(2000)
} }
}.invokeOnCompletion { }.invokeOnCompletion {
runCatching<Unit> { runCatching<Unit> {
terminal.close() // 应该仅关闭用户输入
terminal.reader().shutdown()
ConsoleInputImpl.thread.shutdownNow() ConsoleInputImpl.thread.shutdownNow()
runCatching { runCatching {
ConsoleInputImpl.executingCoroutine?.cancel(EndOfFileException()) ConsoleInputImpl.executingCoroutine?.cancel(EndOfFileException())
@ -57,21 +68,29 @@ internal fun startupConsoleThread() {
continue continue
} }
// consoleLogger.debug("INPUT> $next") // consoleLogger.debug("INPUT> $next")
val result = ConsoleCommandSender.executeCommand(next) when (val result = ConsoleCommandSender.executeCommand(next)) {
when (result.status) { is Success -> {
CommandExecuteStatus.SUCCESSFUL -> {
} }
CommandExecuteStatus.ILLEGAL_ARGUMENT -> { is IllegalArgument -> { // user wouldn't want stacktrace for a parser error unless it is in debugging mode (to do).
result.exception?.message?.let { consoleLogger.warning(it) } val message = result.exception.message
if (message != null) {
consoleLogger.warning(message)
} else consoleLogger.warning(result.exception)
} }
CommandExecuteStatus.EXECUTION_EXCEPTION -> { is ExecutionFailed -> {
result.exception?.let(consoleLogger::error) consoleLogger.error(result.exception)
} }
CommandExecuteStatus.COMMAND_NOT_FOUND -> { is UnresolvedCommand -> {
consoleLogger.warning("未知指令: ${result.commandName}, 输入 ? 获取帮助") consoleLogger.warning { "未知指令: ${next}, 输入 ? 获取帮助" }
} }
CommandExecuteStatus.PERMISSION_DENIED -> { is PermissionDenied -> {
consoleLogger.warning("Permission denied.") consoleLogger.warning { "权限不足." }
}
is UnmatchedSignature -> {
consoleLogger.warning { "参数不匹配, 你是否想执行: \n" + result.failureReasons.render(result.command, result.call) }
}
is Failure -> {
consoleLogger.warning { result.toString() }
} }
} }
} catch (e: InterruptedException) { } 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,53 +104,44 @@ val lineReader: LineReader by lazy {
val terminal: Terminal = run { val terminal: Terminal = run {
if (ConsoleTerminalSettings.noConsole) return@run NoConsole if (ConsoleTerminalSettings.noConsole) return@run NoConsole
val dumb = System.getProperty("java.class.path") TerminalBuilder.builder()
.contains("idea_rt.jar") || System.getProperty("mirai.idea") !== null || System.getenv("mirai.idea") !== null .name("Mirai Console")
.system(true)
runCatching { .jansi(true)
TerminalBuilder.builder() .dumb(true)
.dumb(dumb) .paused(true)
.paused(true) .build()
.build() .let { terminal ->
.let { terminal -> if (terminal is AbstractWindowsTerminal) {
if (terminal is AbstractWindowsTerminal) { val pumpField = runCatching {
val pumpField = runCatching { AbstractWindowsTerminal::class.java.getDeclaredField("pump").also {
AbstractWindowsTerminal::class.java.getDeclaredField("pump").also { it.isAccessible = true
it.isAccessible = true
}
}.onFailure { err ->
err.printStackTrace()
return@let terminal.also { it.resume() }
}.getOrThrow()
var response = terminal
terminal.setOnClose {
response = NoConsole
} }
terminal.resume() }.onFailure { err ->
val pumpThread = pumpField[terminal] as? Thread ?: return@let NoConsole err.printStackTrace()
@Suppress("ControlFlowWithEmptyBody") return@let terminal.also { it.resume() }
while (pumpThread.state == Thread.State.NEW); }.getOrThrow()
Thread.sleep(1000) var response = terminal
terminal.setOnClose(null) terminal.setOnClose {
return@let response response = NoConsole
} }
terminal.resume() terminal.resume()
terminal val pumpThread = pumpField[terminal] as? Thread ?: return@let NoConsole
@Suppress("ControlFlowWithEmptyBody")
while (pumpThread.state == Thread.State.NEW);
Thread.sleep(1000)
terminal.setOnClose(null)
return@let response
} }
}.recoverCatching { terminal.resume()
TerminalBuilder.builder() terminal
.jansi(true) }
.build()
}.recoverCatching {
TerminalBuilder.builder()
.system(true)
.build()
}.getOrThrow()
} }
private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription { private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription {
override val name: String get() = "Terminal" override val name: String get() = "Terminal"
override val vendor: String get() = "Mamoe Technologies" override val vendor: String get() = "Mamoe Technologies"
// net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version // net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
// is console's version not frontend's version // is console's version not frontend's version
override val version: SemVersion = SemVersion(net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.versionConst) 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