mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 10:30:13 +08:00
Merge branch 'master' into ansi
This commit is contained in:
commit
4fd28ef68f
64
.github/workflows/DevTagPublishing.yml
vendored
Normal file
64
.github/workflows/DevTagPublishing.yml
vendored
Normal 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
30
.github/workflows/TagRelease.yml
vendored
Normal 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
|
48
.github/workflows/cui.yml
vendored
48
.github/workflows/cui.yml
vendored
@ -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"
|
48
.github/workflows/shadow.yml
vendored
48
.github/workflows/shadow.yml
vendored
@ -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"
|
@ -2,5 +2,5 @@
|
||||
|
||||
后端代码生成模块,用于最小化重复代码的人工成本。
|
||||
|
||||
- `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/main/kotlin/net/mamoe/mirai/console/codegen/MessageScopeCodegen.kt#L33)
|
||||
- `Value` 和 `PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/main/kotlin/net/mamoe/mirai/console/codegen/ValuePluginDataCodegen.kt#L18)
|
||||
- `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/MessageScopeCodegen.kt#L33)
|
||||
- `Value` 和 `PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/ValuePluginDataCodegen.kt#L18)
|
||||
|
@ -7,7 +7,7 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("FunctionName", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "PRE_RELEASE_CLASS")
|
||||
@file:Suppress("FunctionName", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "PRE_RELEASE_CLASS", "unused")
|
||||
|
||||
package net.mamoe.mirai.console.codegen
|
||||
|
||||
@ -59,9 +59,9 @@ class CodegenScope : MutableList<Replacer> by mutableListOf() {
|
||||
|
||||
@CodegenDsl
|
||||
operator fun Codegen.invoke(ktTypes: Collection<KtType>) {
|
||||
add(Replacer {
|
||||
it + buildString {
|
||||
ktTypes.forEach { applyTo(this, it) }
|
||||
add(Replacer { str ->
|
||||
str + buildString {
|
||||
ktTypes.forEach { ktType -> applyTo(this, ktType) }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -26,10 +26,6 @@ internal fun <A> Array<A>.arrangements(): List<Pair<A, A>> {
|
||||
return result
|
||||
}
|
||||
|
||||
internal fun <A> Array<A>.distinctArrangements(): List<Pair<A, A>> {
|
||||
return this.arrangements().distinctBy { it.first.toString().hashCode() + it.second.toString().hashCode() }
|
||||
}
|
||||
|
||||
internal object MessageScopeCodegen {
|
||||
object IterableMessageScopeBuildersCodegen : RegionCodegen("MessageScope.kt"), DefaultInvoke {
|
||||
@JvmStatic
|
||||
@ -181,7 +177,7 @@ internal object MessageScopeCodegen {
|
||||
ReplaceWith(
|
||||
"this.asMessageScope()(action)",
|
||||
"net.mamoe.mirai.console.util.asMessageScope",
|
||||
"net.mamoe.mirai.console.util.invoke"
|
||||
"net.mamoe.mirai.console.util.invoke",
|
||||
)
|
||||
)
|
||||
public inline fun <R> ${a}.scopeWith(action: MessageScope.() -> R): R = asMessageScope()(action)
|
||||
|
@ -20,7 +20,7 @@ open class JClazz(val primitiveName: String, val packageName: String) {
|
||||
open val funName: String = "value"
|
||||
}
|
||||
|
||||
class JListClazz(val item: JClazz) : JClazz("List<${item.packageName}>", "List<${item.packageName}>") {
|
||||
class JListClazz(item: JClazz) : JClazz("List<${item.packageName}>", "List<${item.packageName}>") {
|
||||
override val funName = item.primitiveName.toLowerCase() + "List"
|
||||
}
|
||||
|
||||
|
@ -57,6 +57,7 @@ import kotlin.internal.LowPriorityInOverloadResolution
|
||||
""".trimIndent()
|
||||
|
||||
fun genAllValueUseSite(): String = buildString {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
fun appendln(@Language("kt") code: String) {
|
||||
this.appendLine(code.trimIndent())
|
||||
}
|
||||
@ -112,6 +113,7 @@ fun genAllValueUseSite(): String = buildString {
|
||||
|
||||
// SPECIAL
|
||||
appendLine()
|
||||
@Suppress("unused", "SpellCheckingInspection", "KDocUnresolvedReference")
|
||||
appendln(
|
||||
"""
|
||||
fun <T : PluginData> PluginData.value(default: T): Value<T> {
|
||||
@ -153,7 +155,7 @@ fun genAllValueUseSite(): String = buildString {
|
||||
""${'"'}
|
||||
这种只保存引用的 Value 可能会导致意料之外的结果, 在使用时须保持谨慎.
|
||||
对值的改变不会触发自动保存, 也不会同步到 UI 中. 在 UI 中只能编辑序列化之后的值.
|
||||
""${'"'}, level = RequiresOptIn.Level.WARNING
|
||||
""${'"'}, level = RequiresOptIn.Level.WARNING,
|
||||
)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
|
@ -52,6 +52,7 @@ import kotlinx.serialization.builtins.*
|
||||
import net.mamoe.mirai.console.data.*
|
||||
""".trimIndent()
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
fun genAllValueImpl(): String = buildString {
|
||||
fun appendln(@Language("kt") code: String) {
|
||||
this.appendLine(code.trimIndent())
|
||||
@ -118,6 +119,7 @@ fun genAllValueImpl(): String = buildString {
|
||||
|
||||
for (collectionName in listOf("List", "Set")) {
|
||||
for (number in NUMBERS + OTHER_PRIMITIVES) {
|
||||
@Suppress("unused")
|
||||
appendln(
|
||||
"""
|
||||
@JvmName("valueImplMutable${number}${collectionName}")
|
||||
@ -164,6 +166,7 @@ fun genAllValueImpl(): String = buildString {
|
||||
appendLine()
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
appendln(
|
||||
"""
|
||||
internal fun <T : PluginData> PluginData.valueImpl(default: T): Value<T> {
|
||||
@ -230,6 +233,7 @@ fun genPrimitiveValueImpl(
|
||||
""".trimIndent() + "\n"
|
||||
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
fun genCollectionValueImpl(
|
||||
collectionName: String,
|
||||
kotlinTypeName: String,
|
||||
|
@ -107,10 +107,8 @@ fun codegen(targetFile: String, block: CodegenScope.() -> Unit) {
|
||||
println("Codegen target: ${it.absolutePath}")
|
||||
}.apply {
|
||||
writeText(
|
||||
CodegenScope().apply(block).also { list ->
|
||||
list.forEach {
|
||||
println("Applying replacement: $it")
|
||||
}
|
||||
CodegenScope().apply(block).onEach {
|
||||
println("Applying replacement: $it")
|
||||
}.applyTo(readText())
|
||||
)
|
||||
}
|
||||
|
@ -13,79 +13,36 @@ plugins {
|
||||
}
|
||||
|
||||
version = Versions.console
|
||||
description = "Console backend for mirai"
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile::class.java) {
|
||||
options.encoding = "UTF8"
|
||||
}
|
||||
description = "Mirai Console Backend"
|
||||
|
||||
kotlin {
|
||||
explicitApiWarning()
|
||||
|
||||
sourceSets.all {
|
||||
target.compilations.all {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all"
|
||||
//useIR = true
|
||||
}
|
||||
}
|
||||
languageSettings.apply {
|
||||
enableLanguageFeature("InlineClasses")
|
||||
progressiveMode = true
|
||||
|
||||
useExperimentalAnnotation("kotlin.Experimental")
|
||||
useExperimentalAnnotation("kotlin.RequiresOptIn")
|
||||
|
||||
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
|
||||
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
|
||||
useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
|
||||
useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleExperimentalApi")
|
||||
useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
|
||||
useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
|
||||
useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
|
||||
useExperimentalAnnotation("kotlinx.serialization.ExperimentalSerializationApi")
|
||||
useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleInternalApi")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileAndTestRuntime("net.mamoe:mirai-core:${Versions.core}")
|
||||
compileAndTestRuntime(kotlin("stdlib", Versions.kotlinStdlib))
|
||||
compileAndTestRuntime(kotlin("stdlib-jdk8", Versions.kotlinStdlib))
|
||||
compileAndTestRuntime(`mirai-core`)
|
||||
compileAndTestRuntime(`kotlin-stdlib`)
|
||||
compileAndTestRuntime(`kotlin-stdlib-jdk8`)
|
||||
|
||||
compileAndTestRuntime("org.jetbrains.kotlinx:atomicfu:${Versions.atomicFU}")
|
||||
compileAndTestRuntime(kotlinx("coroutines-core", Versions.coroutines))
|
||||
compileAndTestRuntime(kotlinx("serialization-core", Versions.serialization))
|
||||
compileAndTestRuntime(kotlin("reflect"))
|
||||
compileAndTestRuntime(`kotlinx-atomicfu`)
|
||||
compileAndTestRuntime(`kotlinx-coroutines-core`)
|
||||
compileAndTestRuntime(`kotlinx-serialization-core`)
|
||||
compileAndTestRuntime(`kotlinx-serialization-json`)
|
||||
compileAndTestRuntime(`kotlin-reflect`)
|
||||
|
||||
smartImplementation("net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}")
|
||||
smartImplementation("org.jetbrains:annotations:19.0.0")
|
||||
smartApi(kotlinx("coroutines-jdk8", Versions.coroutines))
|
||||
smartImplementation(yamlkt)
|
||||
smartImplementation(`jetbrains-annotations`)
|
||||
smartImplementation(`caller-finder`)
|
||||
smartApi(`kotlinx-coroutines-jdk8`)
|
||||
|
||||
testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}")
|
||||
testApi(kotlin("stdlib-jdk8"))
|
||||
testApi(kotlin("test"))
|
||||
testApi(kotlin("test-junit5"))
|
||||
|
||||
testApi("org.junit.jupiter:junit-jupiter-api:5.2.0")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0")
|
||||
testApi(`mirai-core-qqandroid`)
|
||||
testApi(`kotlin-stdlib-jdk8`)
|
||||
}
|
||||
|
||||
tasks {
|
||||
"test"(Test::class) {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
val compileKotlin by getting {}
|
||||
|
||||
val fillBuildConstants by registering {
|
||||
register("fillBuildConstants") {
|
||||
group = "mirai"
|
||||
doLast {
|
||||
(compileKotlin as KotlinCompile).source.filter { it.name == "MiraiConsoleBuildConstants.kt" }.single()
|
||||
@ -108,8 +65,4 @@ tasks {
|
||||
}
|
||||
}
|
||||
|
||||
// region PUBLISHING
|
||||
|
||||
setupPublishing("mirai-console")
|
||||
|
||||
// endregion
|
||||
setupPublishing("mirai-console")
|
@ -155,7 +155,7 @@ public interface MiraiConsole : CoroutineScope {
|
||||
return when (password) {
|
||||
is ByteArray -> Bot(id, password, config)
|
||||
is String -> Bot(id, password, config)
|
||||
else -> null!!
|
||||
else -> throw IllegalArgumentException("Bad password type: `${password.javaClass.name}`. Require ByteArray or String")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,9 +7,12 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console
|
||||
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
|
||||
|
||||
/**
|
||||
@ -38,6 +41,7 @@ public interface MiraiConsoleFrontEndDescription {
|
||||
*
|
||||
* 返回 `null` 表示禁止 [MiraiConsole] 后端检查版本兼容性.
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
public val compatibleBackendVersion: SemVersion? get() = null
|
||||
|
||||
/**
|
||||
|
@ -18,6 +18,8 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
|
||||
import net.mamoe.mirai.console.logging.LoggerController
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleInput
|
||||
@ -177,6 +179,12 @@ public interface MiraiConsoleImplementation : CoroutineScope {
|
||||
*/
|
||||
public val isAnsiSupported: Boolean get() = false
|
||||
|
||||
/**
|
||||
* 前端预先定义的 [LoggerController], 以允许前端使用自己的配置系统
|
||||
*/
|
||||
public val loggerController: LoggerController get() = LoggerControllerImpl
|
||||
|
||||
|
||||
public companion object {
|
||||
internal lateinit var instance: MiraiConsoleImplementation
|
||||
private val initLock = ReentrantLock()
|
||||
|
@ -20,22 +20,38 @@ import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Com
|
||||
import net.mamoe.mirai.console.command.descriptor.PermissionIdValueArgumentParser
|
||||
import net.mamoe.mirai.console.command.descriptor.PermitteeIdValueArgumentParser
|
||||
import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext
|
||||
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands
|
||||
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig
|
||||
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.*
|
||||
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN
|
||||
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
|
||||
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
|
||||
import net.mamoe.mirai.console.internal.util.runIgnoreException
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.cancel
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermissionOrFail
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.getPermittedPermissions
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
|
||||
import net.mamoe.mirai.console.permission.PermitteeId
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.plugin.version
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.message.nextMessageOrNull
|
||||
import net.mamoe.mirai.utils.secondsToMillis
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.lang.management.MemoryUsage
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.math.floor
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
@ -102,29 +118,31 @@ public object BuiltInCommands {
|
||||
|
||||
@Handler
|
||||
public suspend fun CommandSender.handle() {
|
||||
kotlin.runCatching {
|
||||
closingLock.withLock {
|
||||
sendMessage("Stopping mirai-console")
|
||||
kotlin.runCatching {
|
||||
runIgnoreException<CancellationException> { MiraiConsole.job.cancelAndJoin() }
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
runIgnoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
|
||||
},
|
||||
onFailure = {
|
||||
if (it is CancellationException) return@fold
|
||||
@OptIn(ConsoleInternalApi::class)
|
||||
MiraiConsole.mainLogger.error("Exception in stop", it)
|
||||
runIgnoreException<EventCancelledException> {
|
||||
sendMessage(
|
||||
it.localizedMessage ?: it.message ?: it.toString()
|
||||
)
|
||||
GlobalScope.launch {
|
||||
kotlin.runCatching {
|
||||
closingLock.withLock {
|
||||
if (!MiraiConsole.isActive) return@withLock
|
||||
sendMessage("Stopping mirai-console")
|
||||
kotlin.runCatching {
|
||||
MiraiConsole.job.cancelAndJoin()
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
runIgnoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
|
||||
},
|
||||
onFailure = {
|
||||
@OptIn(ConsoleInternalApi::class)
|
||||
MiraiConsole.mainLogger.error("Exception in stop", it)
|
||||
runIgnoreException<EventCancelledException> {
|
||||
sendMessage(
|
||||
it.localizedMessage ?: it.message ?: it.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}.exceptionOrNull()?.let(MiraiConsole.mainLogger::error)
|
||||
exitProcess(0)
|
||||
)
|
||||
}
|
||||
}.exceptionOrNull()?.let(MiraiConsole.mainLogger::error)
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,9 +221,19 @@ public object BuiltInCommands {
|
||||
@SubCommand("permittedPermissions", "pp", "grantedPermissions", "gp")
|
||||
public suspend fun CommandSender.permittedPermissions(
|
||||
@Name("被许可人 ID") target: PermitteeId,
|
||||
@Name("包括重复") all: Boolean = false,
|
||||
) {
|
||||
val grantedPermissions = target.getPermittedPermissions()
|
||||
sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() })
|
||||
var grantedPermissions = target.getPermittedPermissions().toList()
|
||||
if (!all) {
|
||||
grantedPermissions = grantedPermissions.filter { thisPerm ->
|
||||
grantedPermissions.none { other -> thisPerm.parentsWithSelf.drop(1).any { it == other } }
|
||||
}
|
||||
}
|
||||
if (grantedPermissions.isEmpty()) {
|
||||
sendMessage("${target.asString()} 未被授予任何权限. 使用 `${CommandManager.commandPrefix}permission grant` 给予权限.")
|
||||
} else {
|
||||
sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() })
|
||||
}
|
||||
}
|
||||
|
||||
@Description("查看所有权限列表")
|
||||
@ -214,4 +242,203 @@ public object BuiltInCommands {
|
||||
sendMessage(PermissionService.INSTANCE.getRegisteredPermissions().joinToString("\n") { it.id.toString() })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public object AutoLoginCommand : CompositeCommand(
|
||||
ConsoleCommandOwner, "autoLogin", "自动登录",
|
||||
description = "自动登录设置",
|
||||
overrideContext = buildCommandArgumentContext {
|
||||
ConfigurationKey::class with ConfigurationKey.Parser
|
||||
}
|
||||
), BuiltInCommandInternal {
|
||||
@Description("查看自动登录账号列表")
|
||||
@SubCommand
|
||||
public suspend fun CommandSender.list() {
|
||||
sendMessage(buildString {
|
||||
for (account in AutoLoginConfig.accounts) {
|
||||
if (account.account == "123456") continue
|
||||
append("- ")
|
||||
append("账号: ")
|
||||
append(account.account)
|
||||
appendLine()
|
||||
append(" 密码: ")
|
||||
append(account.password.value)
|
||||
appendLine()
|
||||
|
||||
if (account.configuration.isNotEmpty()) {
|
||||
appendLine(" 配置:")
|
||||
for ((key, value) in account.configuration) {
|
||||
append(" $key = $value")
|
||||
}
|
||||
appendLine()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Description("添加自动登录")
|
||||
@SubCommand
|
||||
public suspend fun CommandSender.add(account: Long, password: String, passwordKind: PasswordKind = PLAIN) {
|
||||
val accountStr = account.toString()
|
||||
if (AutoLoginConfig.accounts.any { it.account == accountStr }) {
|
||||
sendMessage("已有相同账号在自动登录配置中. 请先删除该账号.")
|
||||
return
|
||||
}
|
||||
AutoLoginConfig.accounts.add(AutoLoginConfig.Account(accountStr, Password(passwordKind, password)))
|
||||
sendMessage("已成功添加 '$account'.")
|
||||
}
|
||||
|
||||
@Description("清除所有配置")
|
||||
@SubCommand
|
||||
public suspend fun CommandSender.clear() {
|
||||
AutoLoginConfig.accounts.clear()
|
||||
sendMessage("已清除所有自动登录配置.")
|
||||
}
|
||||
|
||||
@Description("删除一个账号")
|
||||
@SubCommand
|
||||
public suspend fun CommandSender.remove(account: Long) {
|
||||
val accountStr = account.toString()
|
||||
if (AutoLoginConfig.accounts.removeIf { it.account == accountStr }) {
|
||||
sendMessage("已成功删除 '$account'.")
|
||||
return
|
||||
}
|
||||
sendMessage("账号 '$account' 未配置自动登录.")
|
||||
}
|
||||
|
||||
@Description("设置一个账号的一个配置项")
|
||||
@SubCommand
|
||||
public suspend fun CommandSender.setConfig(account: Long, configKey: ConfigurationKey, value: String) {
|
||||
val accountStr = account.toString()
|
||||
|
||||
val oldAccount = AutoLoginConfig.accounts.find { it.account == accountStr } ?: kotlin.run {
|
||||
sendMessage("未找到账号 $account.")
|
||||
return
|
||||
}
|
||||
|
||||
if (value.isEmpty()) return removeConfig(account, configKey)
|
||||
|
||||
val newAccount = oldAccount.copy(configuration = oldAccount.configuration.toMutableMap().apply {
|
||||
put(configKey, value)
|
||||
})
|
||||
|
||||
AutoLoginConfig.accounts.remove(oldAccount)
|
||||
AutoLoginConfig.accounts.add(newAccount)
|
||||
|
||||
sendMessage("成功修改 '$account' 的配置 '$configKey' 为 '$value'")
|
||||
}
|
||||
|
||||
@Description("删除一个账号的一个配置项")
|
||||
@SubCommand
|
||||
public suspend fun CommandSender.removeConfig(account: Long, configKey: ConfigurationKey) {
|
||||
val accountStr = account.toString()
|
||||
|
||||
val oldAccount = AutoLoginConfig.accounts.find { it.account == accountStr } ?: kotlin.run {
|
||||
sendMessage("未找到账号 $account.")
|
||||
return
|
||||
}
|
||||
|
||||
val newAccount = oldAccount.copy(configuration = oldAccount.configuration.toMutableMap().apply {
|
||||
remove(configKey)
|
||||
})
|
||||
|
||||
AutoLoginConfig.accounts.remove(oldAccount)
|
||||
AutoLoginConfig.accounts.add(newAccount)
|
||||
|
||||
sendMessage("成功删除 '$account' 的配置 '$configKey'.")
|
||||
}
|
||||
}
|
||||
|
||||
public object StatusCommand : SimpleCommand(
|
||||
ConsoleCommandOwner, "status", "states", "状态",
|
||||
description = "获取 Mirai Console 运行状态"
|
||||
), BuiltInCommandInternal {
|
||||
@Handler
|
||||
public suspend fun CommandSender.handle() {
|
||||
sendMessage(buildString {
|
||||
val buildDateFormatted =
|
||||
MiraiConsoleBuildConstants.buildDate.atZone(ZoneId.systemDefault())
|
||||
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||
|
||||
append("Running MiraiConsole v${MiraiConsoleBuildConstants.versionConst}, built on ").append(buildDateFormatted)
|
||||
.append(".\n")
|
||||
append(MiraiConsoleImplementationBridge.frontEndDescription.render()).append("\n\n")
|
||||
append("Permission Service: ").append(
|
||||
if (PermissionService.INSTANCE is BuiltInPermissionService) {
|
||||
"Built In Permission Service"
|
||||
} else {
|
||||
val plugin = PermissionServiceProvider.providerPlugin
|
||||
if (plugin == null) {
|
||||
PermissionService.INSTANCE.toString()
|
||||
} else {
|
||||
"${plugin.name} v${plugin.version}"
|
||||
}
|
||||
}
|
||||
)
|
||||
append("\n\n")
|
||||
|
||||
append("Plugins: ")
|
||||
if (PluginManagerImpl.resolvedPlugins.isEmpty()) {
|
||||
append("<none>")
|
||||
} else {
|
||||
PluginManagerImpl.resolvedPlugins.joinTo(this) { plugin ->
|
||||
"${plugin.name} v${plugin.version}"
|
||||
}
|
||||
}
|
||||
append("\n\n")
|
||||
val memoryMXBean = ManagementFactory.getMemoryMXBean()
|
||||
|
||||
append("Object Pending Finalization Count: ")
|
||||
.append(memoryMXBean.objectPendingFinalizationCount)
|
||||
.append("\n")
|
||||
|
||||
append(" Heap Memory: ")
|
||||
renderMemoryUsage(memoryMXBean.heapMemoryUsage)
|
||||
append("\nNon-Heap Memory: ")
|
||||
renderMemoryUsage(memoryMXBean.nonHeapMemoryUsage)
|
||||
})
|
||||
}
|
||||
|
||||
private const val MEM_B = 1024L
|
||||
private const val MEM_KB = 1024L shl 10
|
||||
private const val MEM_MB = 1024L shl 20
|
||||
private const val MEM_GB = 1024L shl 30
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun StringBuilder.appendDouble(number: Double): StringBuilder =
|
||||
append(floor(number * 100) / 100)
|
||||
|
||||
private fun StringBuilder.renderMemoryUsageNumber(num: Long) {
|
||||
when {
|
||||
num == -1L -> {
|
||||
append(num)
|
||||
}
|
||||
num < MEM_B -> {
|
||||
append(num).append("B")
|
||||
}
|
||||
num < MEM_KB -> {
|
||||
appendDouble(num / 1024.0).append("KB")
|
||||
}
|
||||
num < MEM_MB -> {
|
||||
appendDouble((num ushr 10) / 1024.0).append("MB")
|
||||
}
|
||||
else -> {
|
||||
appendDouble((num ushr 20) / 1024.0).append("GB")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun StringBuilder.renderMemoryUsage(usage: MemoryUsage) {
|
||||
append("(committed / init / used / max) [")
|
||||
renderMemoryUsageNumber(usage.committed)
|
||||
append(", ")
|
||||
renderMemoryUsageNumber(usage.init)
|
||||
append(", ")
|
||||
renderMemoryUsageNumber(usage.used)
|
||||
append(", ")
|
||||
renderMemoryUsageNumber(usage.max)
|
||||
append("]")
|
||||
}
|
||||
}
|
||||
}
|
@ -11,37 +11,30 @@
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandExecuteResult.CommandExecuteStatus
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.command.resolve.InterceptedReason
|
||||
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import kotlin.contracts.contract
|
||||
|
||||
/**
|
||||
* 指令的执行返回
|
||||
*
|
||||
* 注意: 现阶段
|
||||
*
|
||||
* @see CommandExecuteStatus
|
||||
*/
|
||||
@ConsoleExperimentalApi("Not yet implemented")
|
||||
@ExperimentalCommandDescriptors
|
||||
public sealed class CommandExecuteResult {
|
||||
/** 指令最终执行状态 */
|
||||
public abstract val status: CommandExecuteStatus
|
||||
|
||||
/** 指令执行时发生的错误 (如果有) */
|
||||
public abstract val exception: Throwable?
|
||||
|
||||
/** 尝试执行的指令 (如果匹配到) */
|
||||
public abstract val command: Command?
|
||||
|
||||
/** 尝试执行的指令名 (如果匹配到) */
|
||||
public abstract val commandName: String?
|
||||
/** 解析的 [CommandCall] (如果匹配到) */
|
||||
public abstract val call: CommandCall?
|
||||
|
||||
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
|
||||
public abstract val args: MessageChain?
|
||||
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
|
||||
public abstract val resolvedCall: ResolvedCommandCall?
|
||||
|
||||
// abstract val to allow smart casting
|
||||
|
||||
@ -49,106 +42,140 @@ public sealed class CommandExecuteResult {
|
||||
public class Success(
|
||||
/** 尝试执行的指令 */
|
||||
public override val command: Command,
|
||||
/** 尝试执行的指令名 */
|
||||
public override val commandName: String,
|
||||
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
|
||||
public override val args: MessageChain
|
||||
/** 解析的 [CommandCall] (如果匹配到) */
|
||||
public override val call: CommandCall,
|
||||
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
|
||||
public override val resolvedCall: ResolvedCommandCall,
|
||||
) : CommandExecuteResult() {
|
||||
/** 指令执行时发生的错误, 总是 `null` */
|
||||
public override val exception: Nothing? get() = null
|
||||
|
||||
/** 指令最终执行状态, 总是 [CommandExecuteStatus.SUCCESSFUL] */
|
||||
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.SUCCESSFUL
|
||||
}
|
||||
|
||||
/** 指令执行失败 */
|
||||
public abstract class Failure : CommandExecuteResult()
|
||||
|
||||
/** 执行执行时发生了一个非法参数错误 */
|
||||
public class IllegalArgument(
|
||||
/** 指令执行时发生的错误 */
|
||||
public override val exception: IllegalCommandArgumentException,
|
||||
/** 尝试执行的指令 */
|
||||
public override val command: Command,
|
||||
/** 尝试执行的指令名 */
|
||||
public override val commandName: String,
|
||||
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
|
||||
public override val args: MessageChain
|
||||
) : CommandExecuteResult() {
|
||||
/** 指令最终执行状态, 总是 [CommandExecuteStatus.EXECUTION_EXCEPTION] */
|
||||
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.ILLEGAL_ARGUMENT
|
||||
}
|
||||
/** 解析的 [CommandCall] (如果匹配到) */
|
||||
public override val call: CommandCall,
|
||||
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
|
||||
public override val resolvedCall: ResolvedCommandCall,
|
||||
) : Failure()
|
||||
|
||||
/** 指令执行过程出现了错误 */
|
||||
/** 指令方法调用过程出现了错误 */
|
||||
public class ExecutionFailed(
|
||||
/** 指令执行时发生的错误 */
|
||||
public override val exception: Throwable,
|
||||
/** 尝试执行的指令 */
|
||||
public override val command: Command,
|
||||
/** 尝试执行的指令名 */
|
||||
public override val commandName: String,
|
||||
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
|
||||
public override val args: MessageChain
|
||||
) : CommandExecuteResult() {
|
||||
/** 指令最终执行状态, 总是 [CommandExecuteStatus.EXECUTION_EXCEPTION] */
|
||||
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.EXECUTION_EXCEPTION
|
||||
}
|
||||
/** 解析的 [CommandCall] (如果匹配到) */
|
||||
public override val call: CommandCall,
|
||||
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
|
||||
public override val resolvedCall: ResolvedCommandCall,
|
||||
) : Failure()
|
||||
|
||||
/** 没有匹配的指令 */
|
||||
public class UnresolvedCall(
|
||||
/** 尝试执行的指令名 */
|
||||
public override val commandName: String,
|
||||
) : CommandExecuteResult() {
|
||||
public class UnresolvedCommand(
|
||||
/** 解析的 [CommandCall] (如果匹配到) */
|
||||
public override val call: CommandCall?,
|
||||
) : Failure() {
|
||||
/** 指令执行时发生的错误, 总是 `null` */
|
||||
public override val exception: Nothing? get() = null
|
||||
|
||||
/** 尝试执行的指令, 总是 `null` */
|
||||
public override val command: Nothing? get() = null
|
||||
|
||||
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
|
||||
public override val args: Nothing? get() = null
|
||||
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
|
||||
public override val resolvedCall: ResolvedCommandCall? get() = null
|
||||
}
|
||||
|
||||
/** 指令最终执行状态, 总是 [CommandExecuteStatus.COMMAND_NOT_FOUND] */
|
||||
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.COMMAND_NOT_FOUND
|
||||
/** 没有匹配的指令 */
|
||||
public class Intercepted(
|
||||
/** 解析的 [CommandCall] (如果匹配到) */
|
||||
public override val call: CommandCall?,
|
||||
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
|
||||
public override val resolvedCall: ResolvedCommandCall?,
|
||||
/** 尝试执行的指令 (如果匹配到) */
|
||||
public override val command: Command?,
|
||||
/** 拦截原因 */
|
||||
public val reason: InterceptedReason,
|
||||
) : Failure() {
|
||||
/** 指令执行时发生的错误, 总是 `null` */
|
||||
public override val exception: Nothing? get() = null
|
||||
}
|
||||
|
||||
/** 权限不足 */
|
||||
public class PermissionDenied(
|
||||
/** 尝试执行的指令 */
|
||||
public override val command: Command,
|
||||
/** 尝试执行的指令名 */
|
||||
public override val commandName: String
|
||||
) : CommandExecuteResult() {
|
||||
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
|
||||
public override val args: Nothing? get() = null
|
||||
/** 解析的 [CommandCall] (如果匹配到) */
|
||||
public override val call: CommandCall,
|
||||
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
|
||||
public override val resolvedCall: ResolvedCommandCall,
|
||||
) : Failure() {
|
||||
/** 指令执行时发生的错误, 总是 `null` */
|
||||
public override val exception: Nothing? get() = null
|
||||
}
|
||||
|
||||
/** 没有匹配的指令 */
|
||||
public class UnmatchedSignature(
|
||||
/** 尝试执行的指令 */
|
||||
public override val command: Command,
|
||||
/** 解析的 [CommandCall] (如果匹配到) */
|
||||
public override val call: CommandCall,
|
||||
/** 尝试执行的指令 */
|
||||
@ExperimentalCommandDescriptors
|
||||
@ConsoleExperimentalApi
|
||||
public val failureReasons: List<UnmatchedCommandSignature>,
|
||||
) : Failure() {
|
||||
/** 指令执行时发生的错误, 总是 `null` */
|
||||
public override val exception: Nothing? get() = null
|
||||
|
||||
/** 指令最终执行状态, 总是 [CommandExecuteStatus.PERMISSION_DENIED] */
|
||||
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.PERMISSION_DENIED
|
||||
}
|
||||
|
||||
/**
|
||||
* 指令的执行状态
|
||||
*/
|
||||
public enum class CommandExecuteStatus {
|
||||
/** 指令执行成功 */
|
||||
SUCCESSFUL,
|
||||
|
||||
/** 指令执行过程出现了错误 */
|
||||
EXECUTION_EXCEPTION,
|
||||
|
||||
/** 没有匹配的指令 */
|
||||
COMMAND_NOT_FOUND,
|
||||
|
||||
/** 权限不足 */
|
||||
PERMISSION_DENIED,
|
||||
/** 非法参数 */
|
||||
ILLEGAL_ARGUMENT,
|
||||
/** 解析的 [ResolvedCommandCall] (如果匹配到) */
|
||||
public override val resolvedCall: ResolvedCommandCall? get() = null
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
@Suppress("RemoveRedundantQualifierName")
|
||||
public typealias CommandExecuteStatus = CommandExecuteResult.CommandExecuteStatus
|
||||
@ConsoleExperimentalApi
|
||||
public class UnmatchedCommandSignature(
|
||||
public val signature: CommandSignature,
|
||||
public val failureReason: FailureReason,
|
||||
)
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
@ConsoleExperimentalApi
|
||||
public sealed class FailureReason {
|
||||
public class InapplicableReceiverArgument(
|
||||
public override val parameter: CommandReceiverParameter<*>,
|
||||
public val argument: CommandSender,
|
||||
) : InapplicableArgument()
|
||||
|
||||
public class InapplicableValueArgument(
|
||||
public override val parameter: CommandValueParameter<*>,
|
||||
public val argument: CommandValueArgument,
|
||||
) : InapplicableArgument()
|
||||
|
||||
public abstract class InapplicableArgument : FailureReason() {
|
||||
public abstract val parameter: CommandParameter<*>
|
||||
}
|
||||
|
||||
public abstract class ArgumentLengthMismatch : FailureReason()
|
||||
|
||||
public data class ResolutionAmbiguity(
|
||||
/**
|
||||
* Including [self][UnmatchedCommandSignature.signature].
|
||||
*/
|
||||
public val allCandidates: List<CommandSignature>,
|
||||
) : FailureReason()
|
||||
|
||||
public object TooManyArguments : ArgumentLengthMismatch()
|
||||
public object NotEnoughArguments : ArgumentLengthMismatch()
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [CommandExecuteResult.Success] 时返回 `true`
|
||||
@ -164,59 +191,7 @@ public fun CommandExecuteResult.isSuccess(): Boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [CommandExecuteResult.IllegalArgument] 时返回 `true`
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmSynthetic
|
||||
public fun CommandExecuteResult.isIllegalArgument(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isIllegalArgument is CommandExecuteResult.IllegalArgument)
|
||||
returns(false) implies (this@isIllegalArgument !is CommandExecuteResult.IllegalArgument)
|
||||
}
|
||||
return this is CommandExecuteResult.IllegalArgument
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [CommandExecuteResult.ExecutionFailed] 时返回 `true`
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmSynthetic
|
||||
public fun CommandExecuteResult.isExecutionException(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isExecutionException is CommandExecuteResult.ExecutionFailed)
|
||||
returns(false) implies (this@isExecutionException !is CommandExecuteResult.ExecutionFailed)
|
||||
}
|
||||
return this is CommandExecuteResult.ExecutionFailed
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [CommandExecuteResult.PermissionDenied] 时返回 `true`
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmSynthetic
|
||||
public fun CommandExecuteResult.isPermissionDenied(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isPermissionDenied is CommandExecuteResult.PermissionDenied)
|
||||
returns(false) implies (this@isPermissionDenied !is CommandExecuteResult.PermissionDenied)
|
||||
}
|
||||
return this is CommandExecuteResult.PermissionDenied
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [CommandExecuteResult.UnresolvedCall] 时返回 `true`
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmSynthetic
|
||||
public fun CommandExecuteResult.isCommandNotFound(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isCommandNotFound is CommandExecuteResult.UnresolvedCall)
|
||||
returns(false) implies (this@isCommandNotFound !is CommandExecuteResult.UnresolvedCall)
|
||||
}
|
||||
return this is CommandExecuteResult.UnresolvedCall
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [CommandExecuteResult.ExecutionFailed], [CommandExecuteResult.IllegalArgument] 或 [CommandExecuteResult.UnresolvedCall] 时返回 `true`
|
||||
* 当 [this] 为 [CommandExecuteResult.ExecutionFailed], [CommandExecuteResult.IllegalArgument] , [CommandExecuteResult.UnmatchedSignature] 或 [CommandExecuteResult.UnresolvedCommand] 时返回 `true`
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmSynthetic
|
||||
|
@ -240,9 +240,9 @@ public suspend inline fun CommandSender.executeCommand(
|
||||
@JvmSynthetic
|
||||
public suspend inline fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: Message = EmptyMessageChain,
|
||||
vararg arguments: Message = emptyArray(),
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult = CommandManager.executeCommand(sender, this, arguments, checkPermission)
|
||||
): CommandExecuteResult = CommandManager.executeCommand(sender, this, arguments.asMessageChain(), checkPermission)
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
@ -255,4 +255,4 @@ public suspend inline fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: String = "",
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult = execute(sender, PlainText(arguments), checkPermission)
|
||||
): CommandExecuteResult = execute(sender, PlainText(arguments), checkPermission = checkPermission)
|
||||
|
@ -14,9 +14,7 @@
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
@ -24,11 +22,9 @@ import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.asMemberCommandSender
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.internal.data.castOrNull
|
||||
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.internal.plugin.rootCauseOrSelf
|
||||
import net.mamoe.mirai.console.permission.AbstractPermitteeId
|
||||
import net.mamoe.mirai.console.permission.Permittee
|
||||
import net.mamoe.mirai.console.permission.PermitteeId
|
||||
@ -47,7 +43,7 @@ import kotlin.coroutines.CoroutineContext
|
||||
/**
|
||||
* 指令发送者.
|
||||
*
|
||||
* 只有 [CommandSender] 才能 [执行指令][CommandManager.execute]
|
||||
* 只有 [CommandSender] 才能 [执行指令][CommandManager.executeCommand]
|
||||
*
|
||||
* ## 获得指令发送者
|
||||
* - [MessageEvent.toCommandSender]
|
||||
@ -74,7 +70,7 @@ import kotlin.coroutines.CoroutineContext
|
||||
* - [AbstractUserCommandSender] 代表用户
|
||||
* - [ConsoleCommandSender] 代表控制台
|
||||
*
|
||||
* 二级子类, 当指令由插件 [主动执行][CommandManager.execute] 时, 插件应使用 [toCommandSender] 或 [asCommandSender], 因此,
|
||||
* 二级子类, 当指令由插件 [主动执行][CommandManager.executeCommand] 时, 插件应使用 [toCommandSender] 或 [asCommandSender], 因此,
|
||||
* - 若在群聊环境, 对应 [CommandSender] 为 [MemberCommandSender]
|
||||
* - 若在私聊环境, 对应 [CommandSender] 为 [FriendCommandSender]
|
||||
* - 若在临时会话环境, 对应 [CommandSender] 为 [TempCommandSender]
|
||||
@ -173,9 +169,6 @@ public interface CommandSender : CoroutineScope, Permittee {
|
||||
@JvmBlockingBridge
|
||||
public suspend fun sendMessage(message: String): MessageReceipt<Contact>?
|
||||
|
||||
@ConsoleExperimentalApi("This is unstable and might get changed")
|
||||
public suspend fun catchExecutionException(e: Throwable)
|
||||
|
||||
public companion object {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@ -274,154 +267,54 @@ public sealed class AbstractCommandSender : CommandSender, CoroutineScope {
|
||||
public abstract override val subject: Contact?
|
||||
public abstract override val user: User?
|
||||
public abstract override fun toString(): String
|
||||
|
||||
@ConsoleExperimentalApi("This is unstable and might get changed")
|
||||
override suspend fun catchExecutionException(e: Throwable) {
|
||||
if (this is CommandSenderOnMessage<*>) {
|
||||
val cause = e.rootCauseOrSelf
|
||||
|
||||
// TODO: 2020/10/17
|
||||
// CommandArgumentParserException 作为 IllegalCommandArgumentException 不会再进入此函数
|
||||
// 已在
|
||||
// - [console] CommandManagerImpl.commandListener
|
||||
// - [terminal] ConsoleThread.kt
|
||||
// 处理
|
||||
|
||||
val message = cause
|
||||
.takeIf { it is CommandArgumentParserException }?.message
|
||||
?: "${cause::class.simpleName.orEmpty()}: ${cause.message}"
|
||||
|
||||
// TODO: 2020/8/30 优化 net.mamoe.mirai.console.command.CommandSender.catchExecutionException
|
||||
|
||||
sendMessage(message) // \n\n60 秒内发送 stacktrace 查看堆栈信息
|
||||
this@AbstractCommandSender.launch(CoroutineName("stacktrace delayer from command")) {
|
||||
if (fromEvent.nextMessageOrNull(60_000) {
|
||||
it.message.contentEquals("stacktrace") || it.message.contentEquals("stack")
|
||||
} != null) {
|
||||
sendMessage(e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendMessage(e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [AbstractCommandSender] 时返回.
|
||||
*
|
||||
* 正常情况下, 所有 [CommandSender] 都应该继承 [AbstractCommandSender]
|
||||
*
|
||||
* 在需要类型智能转换等情况时可使用此函数.
|
||||
*
|
||||
* ### 契约
|
||||
* 本函数定义契约,
|
||||
* - 若函数正常返回, Kotlin 编译器认为 [this] 是 [AbstractCommandSender] 实例并执行智能类型转换.
|
||||
*
|
||||
* @return `this`
|
||||
*/
|
||||
public fun CommandSender.checkIsAbstractCommandSender(): AbstractCommandSender {
|
||||
contract {
|
||||
returns() implies (this@checkIsAbstractCommandSender is AbstractCommandSender)
|
||||
}
|
||||
check(this is AbstractCommandSender) { "A CommandSender must extend AbstractCommandSender" }
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [AbstractUserCommandSender] 时返回.
|
||||
*
|
||||
* 正常情况下, 所有 [UserCommandSender] 都应该继承 [AbstractUserCommandSender]
|
||||
*
|
||||
* 在需要类型智能转换等情况时可使用此函数.
|
||||
*
|
||||
* ### 契约
|
||||
* 本函数定义契约,
|
||||
* - 若函数正常返回, Kotlin 编译器认为 [this] 是 [AbstractUserCommandSender] 实例并执行智能类型转换.
|
||||
*
|
||||
* @return `this`
|
||||
*/
|
||||
public fun UserCommandSender.checkIsAbstractUserCommandSender(): AbstractUserCommandSender {
|
||||
contract {
|
||||
returns() implies (this@checkIsAbstractUserCommandSender is AbstractUserCommandSender)
|
||||
}
|
||||
check(this is AbstractUserCommandSender) { "A UserCommandSender must extend AbstractUserCommandSender" }
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [ConsoleCommandSender] 时返回 `true`
|
||||
*
|
||||
* ### 契约
|
||||
* 本函数定义契约,
|
||||
* - 若返回 `true`, Kotlin 编译器认为 [this] 是 [ConsoleCommandSender] 实例并执行智能类型转换.
|
||||
* - 若返回 `false`, Kotlin 编译器认为 [this] 是 [UserCommandSender] 实例并执行智能类型转换.
|
||||
*/
|
||||
public fun CommandSender.isConsole(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isConsole is ConsoleCommandSender)
|
||||
returns(false) implies (this@isConsole is UserCommandSender)
|
||||
}
|
||||
this.checkIsAbstractCommandSender()
|
||||
return this is ConsoleCommandSender
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 不为 [ConsoleCommandSender], 即为 [UserCommandSender] 时返回 `true`.
|
||||
*
|
||||
* ### 契约
|
||||
* 本函数定义契约,
|
||||
* - 若返回 `true`, Kotlin 编译器认为 [this] 是 [UserCommandSender] 实例并执行智能类型转换.
|
||||
* - 若返回 `false`, Kotlin 编译器认为 [this] 是 [ConsoleCommandSender] 实例并执行智能类型转换.
|
||||
* 当 [this] 不为 [ConsoleCommandSender] 时返回 `true`
|
||||
*/
|
||||
public fun CommandSender.isNotConsole(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isNotConsole is UserCommandSender)
|
||||
returns(false) implies (this@isNotConsole is ConsoleCommandSender)
|
||||
returns(true) implies (this@isNotConsole !is ConsoleCommandSender)
|
||||
}
|
||||
this.checkIsAbstractCommandSender()
|
||||
return this !is ConsoleCommandSender
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [UserCommandSender] 时返回 `true`
|
||||
*
|
||||
* ### 契约
|
||||
* 本函数定义契约,
|
||||
* - 若返回 `true`, Kotlin 编译器认为 [this] 是 [UserCommandSender] 实例并执行智能类型转换.
|
||||
* - 若返回 `false`, Kotlin 编译器认为 [this] 是 [ConsoleCommandSender] 实例并执行智能类型转换.
|
||||
*/
|
||||
public fun CommandSender.isUser(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isUser is UserCommandSender)
|
||||
returns(false) implies (this@isUser is ConsoleCommandSender)
|
||||
}
|
||||
this.checkIsAbstractCommandSender()
|
||||
return this is UserCommandSender
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 不为 [UserCommandSender], 即为 [ConsoleCommandSender] 时返回 `true`
|
||||
*
|
||||
* ### 契约
|
||||
* 本函数定义契约,
|
||||
* - 若返回 `true`, Kotlin 编译器认为 [this] 是 [ConsoleCommandSender] 实例并执行智能类型转换.
|
||||
* - 若返回 `false`, Kotlin 编译器认为 [this] 是 [UserCommandSender] 实例并执行智能类型转换.
|
||||
*/
|
||||
public fun CommandSender.isNotUser(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isNotUser is ConsoleCommandSender)
|
||||
returns(false) implies (this@isNotUser is UserCommandSender)
|
||||
}
|
||||
this.checkIsAbstractCommandSender()
|
||||
return this !is UserCommandSender
|
||||
}
|
||||
|
||||
/**
|
||||
* 折叠 [AbstractCommandSender] 的两种可能性.
|
||||
* 折叠 [AbstractCommandSender] 的可能性.
|
||||
*
|
||||
* - 当 [this] 为 [ConsoleCommandSender] 时执行 [ifIsConsole]
|
||||
* - 当 [this] 为 [UserCommandSender] 时执行 [ifIsUser]
|
||||
* - 否则执行 [otherwise]
|
||||
*
|
||||
* ### 示例
|
||||
* ```
|
||||
@ -438,20 +331,23 @@ public fun CommandSender.isNotUser(): Boolean {
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @return [ifIsConsole] 或 [ifIsUser] 执行结果.
|
||||
* @return [ifIsConsole], [ifIsUser] 或 [otherwise] 执行结果.
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline fun <R> CommandSender.fold(
|
||||
ifIsConsole: ConsoleCommandSender.() -> R,
|
||||
ifIsUser: UserCommandSender.() -> R,
|
||||
otherwise: CommandSender.() -> R = { error("CommandSender ${this::class.qualifiedName} is not supported") },
|
||||
): R {
|
||||
contract {
|
||||
callsInPlace(ifIsConsole, InvocationKind.AT_MOST_ONCE)
|
||||
callsInPlace(ifIsUser, InvocationKind.AT_MOST_ONCE)
|
||||
callsInPlace(otherwise, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return when (val sender = this.checkIsAbstractCommandSender()) {
|
||||
return when (val sender = this) {
|
||||
is ConsoleCommandSender -> ifIsConsole(sender)
|
||||
is AbstractUserCommandSender -> ifIsUser(sender)
|
||||
is UserCommandSender -> ifIsUser(sender)
|
||||
else -> otherwise(sender)
|
||||
}
|
||||
}
|
||||
|
||||
@ -477,7 +373,7 @@ public inline fun <R> UserCommandSender.foldContext(
|
||||
callsInPlace(inGroup, InvocationKind.AT_MOST_ONCE)
|
||||
callsInPlace(inPrivate, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return when (val sender = this.checkIsAbstractUserCommandSender()) {
|
||||
return when (val sender = this) {
|
||||
is MemberCommandSender -> inGroup(sender)
|
||||
else -> inPrivate(sender)
|
||||
}
|
||||
@ -603,7 +499,7 @@ public sealed class AbstractUserCommandSender : UserCommandSender, AbstractComma
|
||||
}
|
||||
|
||||
/**
|
||||
* 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.execute])
|
||||
* 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand])
|
||||
* @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令
|
||||
*/
|
||||
public open class FriendCommandSender internal constructor(
|
||||
@ -622,7 +518,7 @@ public open class FriendCommandSender internal constructor(
|
||||
}
|
||||
|
||||
/**
|
||||
* 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式, 也有可能是由插件在代码直接执行 ([CommandManager.execute])
|
||||
* 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand])
|
||||
* @see MemberCommandSenderOnMessage 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令.
|
||||
*/
|
||||
public open class MemberCommandSender internal constructor(
|
||||
@ -644,7 +540,7 @@ public open class MemberCommandSender internal constructor(
|
||||
}
|
||||
|
||||
/**
|
||||
* 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.execute])
|
||||
* 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand])
|
||||
* @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令
|
||||
*/
|
||||
public open class TempCommandSender internal constructor(
|
||||
|
@ -18,11 +18,12 @@ import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
|
||||
*
|
||||
* [message] 将会发送给指令调用方.
|
||||
*
|
||||
* 如果指令调用方是 [ConsoleCommandSender],
|
||||
* 还会将 [cause], [suppressedExceptions] 发送给 [ConsoleCommandSender] (如果存在)
|
||||
*
|
||||
* @see CommandArgumentParserException
|
||||
*/
|
||||
public open class IllegalCommandArgumentException : IllegalArgumentException {
|
||||
public constructor() : super()
|
||||
public constructor(message: String?) : super(message)
|
||||
public constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||
public constructor(cause: Throwable?) : super(cause)
|
||||
}
|
||||
public open class IllegalCommandArgumentException @JvmOverloads constructor(
|
||||
message: String,
|
||||
cause: Throwable? = null,
|
||||
) : IllegalArgumentException(message, cause)
|
@ -71,7 +71,7 @@ public interface CommandArgumentContext {
|
||||
*
|
||||
* @see EmptyCommandArgumentContext
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmField // public static final CommandArgumentContext EMPTY;
|
||||
public val EMPTY: CommandArgumentContext = EmptyCommandArgumentContext
|
||||
}
|
||||
|
||||
|
@ -339,7 +339,7 @@ public object ExistingMemberValueArgumentParser : InternalCommandValueArgumentPa
|
||||
public object PermissionIdValueArgumentParser : InternalCommandValueArgumentParserExtensions<PermissionId>() {
|
||||
override fun parse(raw: String, sender: CommandSender): PermissionId {
|
||||
return kotlin.runCatching { PermissionId.parseFromString(raw) }.getOrElse {
|
||||
illegalArgument("无法解析 $raw 为 PermissionId")
|
||||
illegalArgument("无法解析 $raw 为权限 ID.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -348,7 +348,7 @@ public object PermitteeIdValueArgumentParser : InternalCommandValueArgumentParse
|
||||
override fun parse(raw: String, sender: CommandSender): PermitteeId {
|
||||
return if (raw == "~") sender.permitteeId
|
||||
else kotlin.runCatching { AbstractPermitteeId.parseFromString(raw) }.getOrElse {
|
||||
illegalArgument("无法解析 $raw 为 PermissibleIdentifier")
|
||||
illegalArgument("无法解析 $raw 为被许可人 ID.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.
|
||||
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.UserDefinedType.Companion.createRequired
|
||||
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.command.resolve.ResolvedCommandValueArgument
|
||||
import net.mamoe.mirai.console.internal.data.classifierAsKClass
|
||||
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
|
||||
import net.mamoe.mirai.console.internal.data.typeOf0
|
||||
@ -52,13 +53,31 @@ public abstract class AbstractCommandParameter<T> : CommandParameter<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherited instances must be [AbstractCommandValueParameter]
|
||||
* Inherited instances must be [AbstractCommandValueParameter].
|
||||
*
|
||||
* ### Implementation details
|
||||
*
|
||||
* [CommandValueParameter] should:
|
||||
* - implement [equals], [hashCode] since used in [ResolvedCommandValueArgument].
|
||||
* - implement [toString] to produce user-friendly textual representation of this parameter with type info.
|
||||
*
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandValueParameter<T : Any?> : CommandParameter<T> {
|
||||
|
||||
public val isVararg: Boolean
|
||||
|
||||
/**
|
||||
* Checks whether this [CommandValueParameter] accepts [argument].
|
||||
*
|
||||
* An [argument] can be accepted if:
|
||||
* - [CommandValueArgument.type] is subtype of, or equals to [CommandValueParameter.type] (nullability considered), or
|
||||
* - [CommandValueArgument.typeVariants] produces
|
||||
*
|
||||
* @return `true` if [argument] may be accepted through any approach mentioned above.
|
||||
*
|
||||
* @see accepting
|
||||
*/
|
||||
public fun accepts(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): Boolean =
|
||||
accepting(argument, commandArgumentContext).isAcceptable
|
||||
|
||||
@ -75,15 +94,15 @@ public sealed class ArgumentAcceptance(
|
||||
) {
|
||||
public object Direct : ArgumentAcceptance(Int.MAX_VALUE)
|
||||
|
||||
public class WithTypeConversion(
|
||||
public data class WithTypeConversion(
|
||||
public val typeVariant: TypeVariant<*>,
|
||||
) : ArgumentAcceptance(20)
|
||||
|
||||
public class WithContextualConversion(
|
||||
public data class WithContextualConversion(
|
||||
public val parser: CommandValueArgumentParser<*>,
|
||||
) : ArgumentAcceptance(10)
|
||||
|
||||
public class ResolutionAmbiguity(
|
||||
public data class ResolutionAmbiguity(
|
||||
public val candidates: List<TypeVariant<*>>,
|
||||
) : ArgumentAcceptance(0)
|
||||
|
||||
@ -101,7 +120,7 @@ public sealed class ArgumentAcceptance(
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public class CommandReceiverParameter<T : CommandSender>(
|
||||
public data class CommandReceiverParameter<T : CommandSender>(
|
||||
override val isOptional: Boolean,
|
||||
override val type: KType,
|
||||
) : CommandParameter<T>, AbstractCommandParameter<T>() {
|
||||
@ -165,10 +184,11 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public class StringConstant(
|
||||
public data class StringConstant(
|
||||
@ConsoleExperimentalApi
|
||||
public override val name: String?,
|
||||
public val expectingValue: String,
|
||||
public val ignoreCase: Boolean,
|
||||
) : AbstractCommandValueParameter<String>() {
|
||||
public override val type: KType get() = STRING_TYPE
|
||||
public override val isOptional: Boolean get() = false
|
||||
@ -186,7 +206,7 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
|
||||
override fun toString(): String = "<$expectingValue>"
|
||||
|
||||
override fun acceptingImpl(expectingType: KType, argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance {
|
||||
return if (argument.value.content == expectingValue) {
|
||||
return if (argument.value.content.equals(expectingValue, ignoreCase)) {
|
||||
ArgumentAcceptance.Direct
|
||||
} else ArgumentAcceptance.Impossible
|
||||
}
|
||||
@ -201,12 +221,14 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
|
||||
* @see createOptional
|
||||
* @see createRequired
|
||||
*/
|
||||
public class UserDefinedType<T>(
|
||||
public data class UserDefinedType<T>(
|
||||
public override val name: String?,
|
||||
public override val isOptional: Boolean,
|
||||
public override val isVararg: Boolean,
|
||||
public override val type: KType,
|
||||
) : AbstractCommandValueParameter<T>() {
|
||||
override fun toString(): String = super.toString()
|
||||
|
||||
init {
|
||||
requireNotNull(type.classifierAsKClassOrNull()) {
|
||||
"type.classifier must be KClass."
|
||||
@ -236,7 +258,5 @@ public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>,
|
||||
* Extended by [CommandValueArgumentParser]
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public abstract class Extended<T> : AbstractCommandValueParameter<T>() {
|
||||
abstract override fun toString(): String
|
||||
}
|
||||
public abstract class Extended<T> : AbstractCommandValueParameter<T>() // For implementer: take care of toString()
|
||||
}
|
@ -58,9 +58,9 @@ public abstract class AbstractCommandSignature : CommandSignature {
|
||||
override fun toString(): String {
|
||||
val receiverParameter = receiverParameter
|
||||
return if (receiverParameter == null) {
|
||||
"CommandSignatureVariant(${valueParameters.joinToString()})"
|
||||
"CommandSignature(${valueParameters.joinToString()})"
|
||||
} else {
|
||||
"CommandSignatureVariant($receiverParameter, ${valueParameters.joinToString()})"
|
||||
"CommandSignature($receiverParameter, ${valueParameters.joinToString()})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,10 @@ import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.CompositeCommand
|
||||
import net.mamoe.mirai.console.command.SimpleCommand
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.map
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.parse
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.permission.PermitteeId
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import kotlin.contracts.InvocationKind
|
||||
@ -45,6 +48,9 @@ import kotlin.contracts.contract
|
||||
* - [User]: [ExistingUserValueArgumentParser]
|
||||
* - [Contact]: [ExistingContactValueArgumentParser]
|
||||
*
|
||||
* - [PermitteeId]: [PermitteeIdValueArgumentParser]
|
||||
* - [PermissionId]: [PermissionIdValueArgumentParser]
|
||||
*
|
||||
*
|
||||
* @see SimpleCommand 简单指令
|
||||
* @see CompositeCommand 复合指令
|
||||
@ -143,6 +149,9 @@ public abstract class AbstractCommandValueArgumentParser<T : Any> : CommandValue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CommandValueArgumentParser.map
|
||||
*/
|
||||
public class MappingCommandValueArgumentParser<T : Any, R : Any>(
|
||||
private val original: CommandValueArgumentParser<T>,
|
||||
private val mapper: MappingCommandValueArgumentParser<T, R>.(T) -> R,
|
||||
|
@ -58,9 +58,7 @@ public open class CommandDeclarationException : RuntimeException {
|
||||
* @see CommandValueArgumentParser
|
||||
* @see AbstractCommandValueArgumentParser.illegalArgument
|
||||
*/
|
||||
public class CommandArgumentParserException : IllegalCommandArgumentException {
|
||||
public constructor() : super()
|
||||
public constructor(message: String?) : super(message)
|
||||
public constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||
public constructor(cause: Throwable?) : super(cause)
|
||||
}
|
||||
public class CommandArgumentParserException @JvmOverloads constructor(
|
||||
message: String,
|
||||
cause: Throwable? = null,
|
||||
) : IllegalCommandArgumentException(message, cause)
|
@ -9,8 +9,6 @@
|
||||
|
||||
package net.mamoe.mirai.console.command.descriptor
|
||||
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.command.parse.CommandCallParser
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.internal.data.castOrNull
|
||||
import net.mamoe.mirai.console.internal.data.kClassQualifiedName
|
||||
@ -19,9 +17,18 @@ import kotlin.reflect.KType
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
/**
|
||||
* Implicit type variant specified by [CommandCallParser].
|
||||
* Intrinsic variant of an [CommandValueArgument].
|
||||
*
|
||||
* [TypeVariant] is not necessary for all [CommandCall]s.
|
||||
* The *intrinsic* reveals the independent conversion property of this type.
|
||||
* Conversion with [TypeVariant] is out of any contextual resource,
|
||||
* except the [output type][TypeVariant.outType] declared by the [TypeVariant] itself.
|
||||
*
|
||||
*
|
||||
* [TypeVariant] is not necessary for all [CommandValueArgument]s.
|
||||
*
|
||||
* @param OutType the type this [TypeVariant] can map a argument [Message] to .
|
||||
*
|
||||
* @see CommandValueArgument.typeVariants
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface TypeVariant<out OutType> {
|
||||
@ -31,17 +38,22 @@ public interface TypeVariant<out OutType> {
|
||||
public val outType: KType
|
||||
|
||||
/**
|
||||
* Maps an [valueArgument] to [outType]
|
||||
*
|
||||
* @see CommandValueArgument.value
|
||||
*/
|
||||
public fun mapValue(valueParameter: Message): OutType
|
||||
public fun mapValue(valueArgument: Message): OutType
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* Creates a [TypeVariant] with reified [OutType].
|
||||
*/
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@JvmSynthetic
|
||||
public inline operator fun <reified OutType> invoke(crossinline block: (valueParameter: Message) -> OutType): TypeVariant<OutType> {
|
||||
return object : TypeVariant<OutType> {
|
||||
override val outType: KType = typeOf<OutType>()
|
||||
override fun mapValue(valueParameter: Message): OutType = block(valueParameter)
|
||||
override fun mapValue(valueArgument: Message): OutType = block(valueArgument)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -51,20 +63,20 @@ public interface TypeVariant<out OutType> {
|
||||
public object MessageContentTypeVariant : TypeVariant<MessageContent> {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override val outType: KType = typeOf<MessageContent>()
|
||||
override fun mapValue(valueParameter: Message): MessageContent =
|
||||
valueParameter.castOrNull<MessageContent>() ?: error("Accepts MessageContent only but given ${valueParameter.kClassQualifiedName}")
|
||||
override fun mapValue(valueArgument: Message): MessageContent =
|
||||
valueArgument.castOrNull<MessageContent>() ?: error("Accepts MessageContent only but given ${valueArgument.kClassQualifiedName}")
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public object MessageChainTypeVariant : TypeVariant<MessageChain> {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override val outType: KType = typeOf<MessageChain>()
|
||||
override fun mapValue(valueParameter: Message): MessageChain = valueParameter.asMessageChain()
|
||||
override fun mapValue(valueArgument: Message): MessageChain = valueArgument.asMessageChain()
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public object ContentStringTypeVariant : TypeVariant<String> {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override val outType: KType = typeOf<String>()
|
||||
override fun mapValue(valueParameter: Message): String = valueParameter.content
|
||||
override fun mapValue(valueArgument: Message): String = valueArgument.content
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ public abstract class JCompositeCommand
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
) : CompositeCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = parentPermission) {
|
||||
/** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */
|
||||
public final override var description: String = "<no descriptions available>"
|
||||
public final override var description: String = super.description
|
||||
protected set
|
||||
|
||||
public final override var permission: Permission = super.permission
|
||||
|
@ -12,25 +12,45 @@
|
||||
package net.mamoe.mirai.console.command.parse
|
||||
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
|
||||
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
|
||||
/**
|
||||
* Unresolved [CommandCall].
|
||||
*
|
||||
* ### Implementation details
|
||||
* [CommandCall] should be _immutable_,
|
||||
* meaning all of its properties must be *pure* and should be implemented as an immutable property, or delegated by a lazy initializer.
|
||||
*
|
||||
* @see CommandCallParser
|
||||
* @see CommandCallResolver
|
||||
*
|
||||
* @see ResolvedCommandCall
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandCall {
|
||||
/**
|
||||
* The [CommandSender] responsible to this call.
|
||||
*/
|
||||
public val caller: CommandSender
|
||||
|
||||
/**
|
||||
* One of callee [Command]'s [Command.allNames]
|
||||
* One of callee [Command]'s [Command.allNames].
|
||||
*
|
||||
* Generally [CommandCallResolver] use [calleeName] to find target [Command] registered in [CommandManager]
|
||||
*/
|
||||
public val calleeName: String
|
||||
|
||||
/**
|
||||
* Explicit value arguments
|
||||
* Explicit value arguments parsed from raw [MessageChain] or implicit ones deduced by the [CommandCallResolver].
|
||||
*/
|
||||
public val valueArguments: List<CommandValueArgument>
|
||||
|
||||
// maybe add contextual arguments, i.e. from MessageMetadata
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
|
@ -42,6 +42,12 @@ public interface CommandValueArgument : CommandArgument {
|
||||
* [MessageChain] is vararg
|
||||
*/
|
||||
public val value: Message
|
||||
|
||||
/**
|
||||
* Intrinsic variants of this argument.
|
||||
*
|
||||
* @see TypeVariant
|
||||
*/
|
||||
public val typeVariants: List<TypeVariant<*>>
|
||||
}
|
||||
|
||||
|
@ -9,16 +9,12 @@
|
||||
|
||||
package net.mamoe.mirai.console.command.resolve
|
||||
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isNotAcceptable
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.command.parse.DefaultCommandValueArgument
|
||||
import net.mamoe.mirai.console.extensions.CommandCallResolverProvider
|
||||
import net.mamoe.mirai.console.extensions.CommandCallResolverProviderImpl
|
||||
import net.mamoe.mirai.console.internal.data.classifierAsKClass
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.safeCast
|
||||
@ -31,21 +27,26 @@ import net.mamoe.mirai.message.data.asMessageChain
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public object BuiltInCommandCallResolver : CommandCallResolver {
|
||||
public object Provider : CommandCallResolverProvider by CommandCallResolverProviderImpl(BuiltInCommandCallResolver)
|
||||
|
||||
override fun resolve(call: CommandCall): ResolvedCommandCall? {
|
||||
val callee = CommandManager.matchCommand(call.calleeName) ?: return null
|
||||
override fun resolve(call: CommandCall): CommandResolveResult {
|
||||
val callee = CommandManager.matchCommand(call.calleeName) ?: return CommandResolveResult(CommandExecuteResult.UnresolvedCommand(call))
|
||||
|
||||
val valueArguments = call.valueArguments
|
||||
val context = callee.safeCast<CommandArgumentContextAware>()?.context
|
||||
|
||||
val signature = resolveImpl(call.caller, callee, valueArguments, context) ?: return null
|
||||
val errorSink = ErrorSink()
|
||||
val signature = resolveImpl(call.caller, callee, valueArguments, context, errorSink) ?: kotlin.run {
|
||||
return CommandResolveResult(errorSink.createFailure(call, callee))
|
||||
}
|
||||
|
||||
return ResolvedCommandCallImpl(call.caller,
|
||||
callee,
|
||||
signature.signature,
|
||||
signature.zippedArguments.map { it.second },
|
||||
context ?: EmptyCommandArgumentContext)
|
||||
return CommandResolveResult(
|
||||
ResolvedCommandCallImpl(
|
||||
call.caller,
|
||||
callee,
|
||||
signature.signature,
|
||||
signature.zippedArguments.map { it.second },
|
||||
context ?: EmptyCommandArgumentContext
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private data class ResolveData(
|
||||
@ -62,76 +63,121 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
|
||||
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(
|
||||
caller: CommandSender,
|
||||
callee: Command,
|
||||
valueArguments: List<CommandValueArgument>,
|
||||
context: CommandArgumentContext?,
|
||||
errorSink: ErrorSink,
|
||||
): ResolveData? {
|
||||
|
||||
callee.overloads
|
||||
.mapNotNull l@{ signature ->
|
||||
if (signature.receiverParameter?.type?.classifierAsKClass()?.isInstance(caller) == false) {
|
||||
return@l null // not compatible receiver
|
||||
}
|
||||
|
||||
val 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
|
||||
)
|
||||
}
|
||||
signature.toResolveData(caller, valueArguments, context, errorSink)
|
||||
}
|
||||
.also { result -> result.singleOrNull()?.let { return it } }
|
||||
.also { result -> result.takeSingleResolveData()?.let { return it } }
|
||||
.takeLongestMatches()
|
||||
.ifEmpty { return null }
|
||||
.also { result -> result.singleOrNull()?.let { return it } }
|
||||
.also { result -> result.takeSingleResolveData()?.let { return it } }
|
||||
// take single ArgumentAcceptance.Direct
|
||||
.also { list ->
|
||||
|
||||
val candidates = list
|
||||
.asSequence().filterIsInstance<ResolveData>()
|
||||
.flatMap { phase ->
|
||||
phase.argumentAcceptances.filter { it.acceptance is ArgumentAcceptance.Direct }.map { phase to it }
|
||||
}
|
||||
}.toList()
|
||||
|
||||
candidates.singleOrNull()?.let { return it.first } // single Direct
|
||||
|
||||
if (candidates.distinctBy { it.second.index }.size != candidates.size) {
|
||||
// Resolution ambiguity
|
||||
/*
|
||||
@ -145,13 +191,17 @@ public object BuiltInCommandCallResolver : CommandCallResolver {
|
||||
fun foo(a: AA, c: C) = 1
|
||||
*/
|
||||
// The call is foo(AA(), C()) or foo(A(), CC())
|
||||
return null
|
||||
|
||||
candidates.forEach { candidate -> errorSink.reportAmbiguity(candidate.first.signature) }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun Collection<Any>.takeSingleResolveData() = asSequence().filterIsInstance<ResolveData>().singleOrNull()
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
@ -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
|
@ -9,11 +9,57 @@
|
||||
|
||||
package net.mamoe.mirai.console.command.resolve
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandExecuteResult
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.extensions.CommandCallResolverProvider
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.safeCast
|
||||
import org.jetbrains.annotations.Contract
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public class CommandResolveResult private constructor(
|
||||
internal val value: Any?,
|
||||
) {
|
||||
@get:Contract(pure = true)
|
||||
public val call: ResolvedCommandCall?
|
||||
get() = value.safeCast()
|
||||
|
||||
@get:Contract(pure = true)
|
||||
public val failure: CommandExecuteResult.Failure?
|
||||
get() = value.safeCast()
|
||||
|
||||
public constructor(call: ResolvedCommandCall) : this(call as Any?)
|
||||
public constructor(failure: CommandExecuteResult.Failure) : this(failure as Any)
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public inline fun <R> CommandResolveResult.fold(
|
||||
onSuccess: (ResolvedCommandCall?) -> R,
|
||||
onFailure: (CommandExecuteResult.Failure) -> R,
|
||||
): R {
|
||||
contract {
|
||||
callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE)
|
||||
callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
failure?.let(onFailure)?.let { return it }
|
||||
return call.let(onSuccess)
|
||||
}
|
||||
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public inline fun CommandResolveResult.getOrElse(
|
||||
onFailure: (CommandExecuteResult.Failure) -> ResolvedCommandCall?,
|
||||
): ResolvedCommandCall {
|
||||
contract {
|
||||
callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
failure?.let(onFailure)?.let { return it }
|
||||
return call!!
|
||||
}
|
||||
|
||||
/**
|
||||
* The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered []
|
||||
@ -23,19 +69,17 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandCallResolver {
|
||||
public fun resolve(call: CommandCall): ResolvedCommandCall?
|
||||
public fun resolve(call: CommandCall): CommandResolveResult
|
||||
|
||||
public companion object {
|
||||
@JvmName("resolveCall")
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public fun CommandCall.resolve(): ResolvedCommandCall? {
|
||||
public fun CommandCall.resolve(): CommandResolveResult {
|
||||
GlobalComponentStorage.run {
|
||||
CommandCallResolverProvider.useExtensions { provider ->
|
||||
provider.instance.resolve(this@resolve)?.let { return it }
|
||||
}
|
||||
val instance = CommandCallResolverProvider.findSingletonInstance(CommandCallResolverProvider.builtinImplementation)
|
||||
return instance.resolve(this@resolve)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -20,15 +20,21 @@ import net.mamoe.mirai.console.command.parse.mapToTypeOrNull
|
||||
import net.mamoe.mirai.console.internal.data.classifierAsKClass
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.cast
|
||||
import kotlin.LazyThreadSafetyMode.PUBLICATION
|
||||
|
||||
/**
|
||||
* The resolved [CommandCall].
|
||||
*
|
||||
* ### Implementation details
|
||||
* [ResolvedCommandCall] should be _immutable_,
|
||||
* meaning all of its properties must be *pure* and should be implemented as an immutable property, or delegated by a lazy initializer.
|
||||
*
|
||||
* @see ResolvedCommandCallImpl
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface ResolvedCommandCall {
|
||||
/**
|
||||
* The [CommandSender] responsible to this call.
|
||||
*/
|
||||
public val caller: CommandSender
|
||||
|
||||
/**
|
||||
@ -49,7 +55,7 @@ public interface ResolvedCommandCall {
|
||||
/**
|
||||
* Resolved value arguments arranged mapping the [CommandSignature.valueParameters] by index.
|
||||
*
|
||||
* **Implementation details**: Lazy calculation.
|
||||
* **Default implementation details**: Lazy calculation.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public val resolvedValueArguments: List<ResolvedCommandValueArgument<*>>
|
||||
@ -57,18 +63,30 @@ public interface ResolvedCommandCall {
|
||||
public companion object
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolved [CommandValueParameter] for [ResolvedCommandCall.resolvedValueArguments]
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public data class ResolvedCommandValueArgument<T>(
|
||||
val parameter: CommandValueParameter<T>,
|
||||
/**
|
||||
* Argument value expected by the [parameter]
|
||||
*/
|
||||
val value: T,
|
||||
)
|
||||
|
||||
// Don't move into companion, compilation error
|
||||
/**
|
||||
* Invoke this resolved call.
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public suspend inline fun ResolvedCommandCall.call() {
|
||||
return this@call.calleeSignature.call(this@call)
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation.
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public class ResolvedCommandCallImpl(
|
||||
override val caller: CommandSender,
|
||||
@ -77,7 +95,7 @@ public class ResolvedCommandCallImpl(
|
||||
override val rawValueArguments: List<CommandValueArgument>,
|
||||
private val context: CommandArgumentContext,
|
||||
) : ResolvedCommandCall {
|
||||
override val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> by lazy(PUBLICATION) {
|
||||
override val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> by lazy {
|
||||
calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) ->
|
||||
val value = argument.mapToTypeOrNull(parameter.type) ?: context[parameter.type.classifierAsKClass()]?.parse(argument.value, caller)
|
||||
?: throw NoValueArgumentMappingException(argument, parameter.type)
|
||||
|
@ -161,7 +161,7 @@ public fun <T> AbstractPluginData.findBackingFieldValue(property: KProperty<T>):
|
||||
@ConsoleExperimentalApi
|
||||
public fun <T> AbstractPluginData.findBackingFieldValue(propertyValueName: String): Value<out T>? {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return this.valueNodes.find { it.valueName == propertyValueName }?.value as Value<out T>
|
||||
return this.valueNodes.find { it.valueName == propertyValueName }?.value as Value<out T>?
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,7 +146,7 @@ public open class AutoSavePluginData private constructor(
|
||||
}
|
||||
|
||||
internal val debuggingLogger1 by lazy {
|
||||
DefaultLogger("debug").withSwitch(false)
|
||||
DefaultLogger("console.debug").withSwitch(false)
|
||||
}
|
||||
|
||||
@Suppress("RESULT_CLASS_IN_RETURN_TYPE")
|
||||
@ -170,4 +170,5 @@ internal inline fun <R> MiraiLogger.runCatchingLog(message: (Throwable) -> Strin
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private const val MAGIC_NUMBER_CFST_INIT: Long = Long.MAX_VALUE
|
||||
|
@ -31,13 +31,15 @@ import kotlinx.serialization.SerialInfo
|
||||
* map:
|
||||
* a: b
|
||||
* ```
|
||||
*
|
||||
* @see net.mamoe.yamlkt.Comment
|
||||
*/
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
public annotation class ValueDescription(
|
||||
/**
|
||||
* 将会被 [String.trimIndent] 处理.
|
||||
* 将会被 [String.trimIndent] 处理
|
||||
*/
|
||||
val value: String,
|
||||
)
|
@ -23,7 +23,7 @@ package net.mamoe.mirai.console.data
|
||||
* 将被保存为配置 (YAML 作为示例):
|
||||
* ```yaml
|
||||
* AccountPluginData:
|
||||
* map:
|
||||
* info:
|
||||
* a: b
|
||||
* ```
|
||||
*
|
||||
|
@ -11,6 +11,7 @@ package net.mamoe.mirai.console.extension
|
||||
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCallParser
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
|
||||
import net.mamoe.mirai.console.extensions.*
|
||||
import net.mamoe.mirai.console.internal.extension.AbstractConcurrentComponentStorage
|
||||
@ -128,4 +129,19 @@ public class PluginComponentStorage(
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributeCommandCallParser(provider: CommandCallResolverProvider): Unit =
|
||||
contribute(CommandCallResolverProvider, plugin, provider)
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
/** 注册一个 [CommandCallInterceptorProvider] */
|
||||
@ExperimentalCommandDescriptors
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributeCommandCallInterceptor(lazyInstance: () -> CommandCallInterceptor): Unit =
|
||||
contribute(CommandCallInterceptorProvider, plugin, CommandCallInterceptorProviderImplLazy(lazyInstance))
|
||||
|
||||
/** 注册一个 [CommandCallInterceptorProvider] */
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmName("contributeCommandCallInterceptorProvider")
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributeCommandCallParser(provider: CommandCallInterceptorProvider): Unit =
|
||||
contribute(CommandCallInterceptorProvider, plugin, provider)
|
||||
}
|
@ -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)
|
||||
}
|
@ -12,14 +12,13 @@ package net.mamoe.mirai.console.extensions
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.resolve.BuiltInCommandCallResolver
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
|
||||
import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.InstanceExtension
|
||||
import net.mamoe.mirai.console.extension.AbstractSingletonExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.SingletonExtension
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandCallResolverProvider : InstanceExtension<CommandCallResolver> {
|
||||
public interface CommandCallResolverProvider : SingletonExtension<CommandCallResolver> {
|
||||
public companion object ExtensionPoint :
|
||||
AbstractInstanceExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(CommandCallResolverProvider::class,
|
||||
BuiltInCommandCallResolver.Provider)
|
||||
AbstractSingletonExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(CommandCallResolverProvider::class, BuiltInCommandCallResolver)
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
|
@ -11,8 +11,11 @@ package net.mamoe.mirai.console.extensions
|
||||
|
||||
import net.mamoe.mirai.console.extension.AbstractSingletonExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.SingletonExtension
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
|
||||
/**
|
||||
* [权限服务][PermissionService] 提供器.
|
||||
@ -21,7 +24,16 @@ import net.mamoe.mirai.console.permission.PermissionService
|
||||
*/
|
||||
public interface PermissionServiceProvider : SingletonExtension<PermissionService<*>> {
|
||||
public companion object ExtensionPoint :
|
||||
AbstractSingletonExtensionPoint<PermissionServiceProvider, PermissionService<*>>(PermissionServiceProvider::class, BuiltInPermissionService)
|
||||
AbstractSingletonExtensionPoint<PermissionServiceProvider, PermissionService<*>>(PermissionServiceProvider::class, BuiltInPermissionService) {
|
||||
@ConsoleExperimentalApi
|
||||
public val providerPlugin: Plugin? by lazy {
|
||||
GlobalComponentStorage.run {
|
||||
val instance = PermissionService.INSTANCE
|
||||
if (instance is BuiltInPermissionService) return@lazy null
|
||||
PermissionServiceProvider.getExtensions().find { it.extension.instance === instance }?.plugin
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,8 +14,8 @@ import java.time.Instant
|
||||
|
||||
internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants)
|
||||
@JvmStatic
|
||||
val buildDate: Instant = Instant.ofEpochSecond(1604041264)
|
||||
const val versionConst: String = "1.0-RC-1"
|
||||
val buildDate: Instant = Instant.ofEpochSecond(1606348297)
|
||||
const val versionConst: String = "1.0.1-dev-2"
|
||||
|
||||
@JvmStatic
|
||||
val version: SemVersion = SemVersion(versionConst)
|
||||
|
@ -28,22 +28,28 @@ import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
|
||||
import net.mamoe.mirai.console.extensions.PostStartupExtension
|
||||
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
||||
import net.mamoe.mirai.console.internal.command.CommandConfig
|
||||
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig
|
||||
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.MD5
|
||||
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN
|
||||
import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope
|
||||
import net.mamoe.mirai.console.internal.data.castOrNull
|
||||
import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig
|
||||
import net.mamoe.mirai.console.internal.extension.BuiltInSingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
|
||||
import net.mamoe.mirai.console.internal.logging.MiraiConsoleLogger
|
||||
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
|
||||
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
|
||||
import net.mamoe.mirai.console.internal.util.autoHexToBytes
|
||||
import net.mamoe.mirai.console.internal.util.runIgnoreException
|
||||
import net.mamoe.mirai.console.logging.LoggerController
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
|
||||
import net.mamoe.mirai.console.permission.RootPermission
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
||||
import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInput
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
@ -87,14 +93,29 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver =
|
||||
instance.createLoginSolver(requesterBot, configuration)
|
||||
|
||||
override val loggerController: LoggerController by instance::loggerController
|
||||
|
||||
init {
|
||||
DefaultLogger = this::createLogger
|
||||
}
|
||||
|
||||
override fun createLogger(identity: String?): MiraiLogger = instance.createLogger(identity)
|
||||
|
||||
override fun createLogger(identity: String?): MiraiLogger {
|
||||
val controller = loggerController
|
||||
return MiraiConsoleLogger(controller, instance.createLogger(identity))
|
||||
}
|
||||
|
||||
@Suppress("RemoveRedundantBackticks")
|
||||
internal fun doStart() {
|
||||
|
||||
phase `setup logger controller`@{
|
||||
if (loggerController === LoggerControllerImpl) {
|
||||
// Reload LoggerConfig.
|
||||
ConsoleDataScope.addAndReloadConfig(LoggerConfig)
|
||||
LoggerControllerImpl.initialized = true
|
||||
}
|
||||
}
|
||||
|
||||
phase `greeting`@{
|
||||
val buildDateFormatted =
|
||||
buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
||||
@ -123,6 +144,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
|
||||
phase `load configurations`@{
|
||||
mainLogger.verbose { "Loading configurations..." }
|
||||
ConsoleDataScope.addAndReloadConfig(CommandConfig)
|
||||
ConsoleDataScope.reloadAll()
|
||||
}
|
||||
|
||||
@ -145,14 +167,6 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." }
|
||||
}
|
||||
|
||||
phase `collect extensions`@{
|
||||
for (resolvedPlugin in PluginManagerImpl.resolvedPlugins) {
|
||||
resolvedPlugin.castOrNull<AbstractJvmPlugin>()?.let {
|
||||
GlobalComponentStorage.mergeWith(it.componentStorage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
phase `load SingletonExtensionSelector`@{
|
||||
SingletonExtensionSelector.init()
|
||||
val instance = SingletonExtensionSelector.instance
|
||||
@ -161,27 +175,28 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
phase `load PermissionService`@{
|
||||
mainLogger.verbose { "Loading PermissionService..." }
|
||||
|
||||
PermissionServiceProvider.selectedInstance // init
|
||||
|
||||
PermissionService.INSTANCE.let { ps ->
|
||||
if (ps is BuiltInPermissionService) {
|
||||
ConsoleDataScope.addAndReloadConfig(ps.config)
|
||||
mainLogger.verbose { "Reloaded PermissionService settings." }
|
||||
} else {
|
||||
mainLogger.info { "Loaded PermissionService from plugin ${PermissionServiceProvider.providerPlugin?.name}" }
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleCommandSender.permit(RootPermission)
|
||||
runIgnoreException<UnsupportedOperationException> { ConsoleCommandSender.permit(RootPermission) }
|
||||
}
|
||||
|
||||
phase `prepare commands`@{
|
||||
mainLogger.verbose { "Loading built-in commands..." }
|
||||
BuiltInCommands.registerAll()
|
||||
mainLogger.verbose { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
|
||||
mainLogger.info { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
|
||||
CommandManager
|
||||
CommandManagerImpl.commandListener // start
|
||||
// CommandManagerImpl.commandListener // start
|
||||
}
|
||||
|
||||
phase `enable plugins`@{
|
||||
@ -198,20 +213,44 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
|
||||
phase `auto-login bots`@{
|
||||
runBlocking {
|
||||
for ((id, password) in AutoLoginConfig.plainPasswords.filterNot { it.key == 123456654321L }) {
|
||||
mainLogger.info { "Auto-login $id" }
|
||||
MiraiConsole.addBot(id, password).alsoLogin()
|
||||
val accounts = AutoLoginConfig.accounts.toList()
|
||||
for (account in accounts) {
|
||||
val id = kotlin.runCatching {
|
||||
account.account.toLong()
|
||||
}.getOrElse {
|
||||
error("Bad auto-login account: '${account.account}'")
|
||||
}
|
||||
if (id == 123456L) continue
|
||||
fun BotConfiguration.configBot() {
|
||||
mainLogger.info { "Auto-login ${account.account}" }
|
||||
|
||||
account.configuration[AutoLoginConfig.Account.ConfigurationKey.protocol]
|
||||
?.let { protocol ->
|
||||
this.protocol = runCatching {
|
||||
BotConfiguration.MiraiProtocol.valueOf(protocol.toString())
|
||||
}.getOrElse {
|
||||
throw IllegalArgumentException(
|
||||
"Bad auto-login config value for `protocol` for account $id",
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
when (account.password.kind) {
|
||||
PLAIN -> {
|
||||
MiraiConsole.addBot(id, account.password.value, BotConfiguration::configBot).alsoLogin()
|
||||
}
|
||||
MD5 -> {
|
||||
val md5 = kotlin.runCatching {
|
||||
account.password.value.autoHexToBytes()
|
||||
}.getOrElse {
|
||||
error("Bad auto-login md5: '${account.password.value}' for account $id")
|
||||
}
|
||||
MiraiConsole.addBot(id, md5, BotConfiguration::configBot).alsoLogin()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((id, password) in AutoLoginConfig.md5Passwords.filterNot { it.key == 123456654321L }) {
|
||||
mainLogger.info { "Auto-login $id" }
|
||||
val x = runCatching {
|
||||
password.autoHexToBytes()
|
||||
}.getOrElse {
|
||||
error("Bad auto-login md5: '$password'")
|
||||
}
|
||||
MiraiConsole.addBot(id, x).alsoLogin()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,25 +10,22 @@
|
||||
package net.mamoe.mirai.console.internal.command
|
||||
|
||||
import kotlinx.atomicfu.locks.withLock
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.Command.Companion.allNames
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.findDuplicate
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCallParser.Companion.parseCommandCall
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor.Companion.intercepted
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallResolver.Companion.resolve
|
||||
import net.mamoe.mirai.console.command.resolve.getOrElse
|
||||
import net.mamoe.mirai.console.internal.util.ifNull
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
import net.mamoe.mirai.event.Listener
|
||||
import net.mamoe.mirai.event.subscribeAlways
|
||||
import net.mamoe.mirai.message.MessageEvent
|
||||
import net.mamoe.mirai.message.data.EmptyMessageChain
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.asMessageChain
|
||||
import net.mamoe.mirai.message.data.content
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
@ -61,49 +58,12 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiCons
|
||||
}
|
||||
return optionalPrefixCommandMap[commandName.toLowerCase()]
|
||||
}
|
||||
|
||||
internal val commandListener: Listener<MessageEvent> by lazy {
|
||||
subscribeAlways(
|
||||
coroutineContext = CoroutineExceptionHandler { _, throwable ->
|
||||
logger.error(throwable)
|
||||
},
|
||||
concurrency = Listener.ConcurrencyKind.CONCURRENT,
|
||||
priority = Listener.EventPriority.HIGH
|
||||
) {
|
||||
val sender = this.toCommandSender()
|
||||
|
||||
when (val result = executeCommand(sender, message)) {
|
||||
is CommandExecuteResult.PermissionDenied -> {
|
||||
if (!result.command.prefixOptional || message.content.startsWith(CommandManager.commandPrefix)) {
|
||||
sender.sendMessage("权限不足")
|
||||
intercept()
|
||||
}
|
||||
}
|
||||
is CommandExecuteResult.IllegalArgument -> {
|
||||
result.exception.message?.let { sender.sendMessage(it) }
|
||||
intercept()
|
||||
}
|
||||
is CommandExecuteResult.Success -> {
|
||||
intercept()
|
||||
}
|
||||
is CommandExecuteResult.ExecutionFailed -> {
|
||||
sender.catchExecutionException(result.exception)
|
||||
intercept()
|
||||
}
|
||||
is CommandExecuteResult.UnresolvedCall -> {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///// IMPL
|
||||
|
||||
|
||||
override fun getRegisteredCommands(owner: CommandOwner): List<Command> = _registeredCommands.filter { it.owner == owner }
|
||||
override val allRegisteredCommands: List<Command> get() = _registeredCommands.toList() // copy
|
||||
override val commandPrefix: String get() = "/"
|
||||
override val commandPrefix: String get() = CommandConfig.commandPrefix
|
||||
override fun unregisterAllCommands(owner: CommandOwner) {
|
||||
for (registeredCommand in getRegisteredCommands(owner)) {
|
||||
unregisterCommand(registeredCommand)
|
||||
@ -167,24 +127,43 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiCons
|
||||
// Don't move into CommandManager, compilation error / VerifyError
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal suspend fun executeCommandImpl(
|
||||
message: Message,
|
||||
message0: Message,
|
||||
caller: CommandSender,
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult {
|
||||
val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.UnresolvedCall("")
|
||||
val resolved = call.resolve() ?: return CommandExecuteResult.UnresolvedCall(call.calleeName)
|
||||
val message = message0
|
||||
.intercepted(caller)
|
||||
.getOrElse { return CommandExecuteResult.Intercepted(null, null, null, it) }
|
||||
|
||||
val call = message.asMessageChain()
|
||||
.parseCommandCall(caller)
|
||||
.ifNull { return CommandExecuteResult.UnresolvedCommand(null) }
|
||||
.let { raw ->
|
||||
raw.intercepted()
|
||||
.getOrElse { return CommandExecuteResult.Intercepted(raw, null, null, it) }
|
||||
}
|
||||
|
||||
val resolved = call
|
||||
.resolve()
|
||||
.getOrElse { return it }
|
||||
.let { raw ->
|
||||
raw.intercepted()
|
||||
.getOrElse { return CommandExecuteResult.Intercepted(call, raw, null, it) }
|
||||
}
|
||||
|
||||
val command = resolved.callee
|
||||
|
||||
if (checkPermission && !command.permission.testPermission(caller)) {
|
||||
return CommandExecuteResult.PermissionDenied(command, call.calleeName)
|
||||
return CommandExecuteResult.PermissionDenied(command, call, resolved)
|
||||
}
|
||||
|
||||
return try {
|
||||
resolved.calleeSignature.call(resolved)
|
||||
CommandExecuteResult.Success(resolved.callee, call.calleeName, EmptyMessageChain)
|
||||
CommandExecuteResult.Success(resolved.callee, call, resolved)
|
||||
} catch (e: CommandArgumentParserException) {
|
||||
CommandExecuteResult.IllegalArgument(e, resolved.callee, call, resolved)
|
||||
} catch (e: Throwable) {
|
||||
CommandExecuteResult.ExecutionFailed(e, resolved.callee, call.calleeName, EmptyMessageChain)
|
||||
CommandExecuteResult.ExecutionFailed(e, resolved.callee, call, resolved)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,37 +144,42 @@ internal class CommandReflector(
|
||||
}
|
||||
|
||||
fun generateUsage(overloads: Iterable<CommandSignatureFromKFunction>): String {
|
||||
return overloads.joinToString("\n") { subcommand ->
|
||||
buildString {
|
||||
if (command.prefixOptional) {
|
||||
append("(")
|
||||
append(CommandManager.commandPrefix)
|
||||
append(")")
|
||||
} else {
|
||||
append(CommandManager.commandPrefix)
|
||||
}
|
||||
//if (command is CompositeCommand) {
|
||||
append(command.primaryName)
|
||||
append(" ")
|
||||
//}
|
||||
append(subcommand.valueParameters.joinToString(" ") { it.render() })
|
||||
annotationResolver.getDescription(command, subcommand.originFunction)?.let { description ->
|
||||
append(" # ")
|
||||
append(description)
|
||||
return generateUsage(command, annotationResolver, overloads)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun generateUsage(command: Command, annotationResolver: SubCommandAnnotationResolver?, overloads: Iterable<CommandSignature>): String {
|
||||
return overloads.joinToString("\n") { subcommand ->
|
||||
buildString {
|
||||
if (command.prefixOptional) {
|
||||
append("(")
|
||||
append(CommandManager.commandPrefix)
|
||||
append(")")
|
||||
} else {
|
||||
append(CommandManager.commandPrefix)
|
||||
}
|
||||
//if (command is CompositeCommand) {
|
||||
append(command.primaryName)
|
||||
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 {
|
||||
return when (this) {
|
||||
is AbstractCommandValueParameter.Extended,
|
||||
is AbstractCommandValueParameter.UserDefinedType<*>,
|
||||
-> {
|
||||
"<${this.name ?: this.type.classifierAsKClass().simpleName}>"
|
||||
val nameToRender = this.name ?: this.type.classifierAsKClass().simpleName
|
||||
if (isOptional) "[$nameToRender]" else "<$nameToRender>"
|
||||
}
|
||||
is AbstractCommandValueParameter.StringConstant -> {
|
||||
this.expectingValue
|
||||
@ -236,7 +241,7 @@ internal class CommandReflector(
|
||||
.map { (name, function) ->
|
||||
|
||||
val functionNameAsValueParameter =
|
||||
name?.split(' ')?.mapIndexed { index, s -> createStringConstantParameter(index, s) }
|
||||
name?.split(' ')?.mapIndexed { index, s -> createStringConstantParameterForName(index, s) }
|
||||
.orEmpty()
|
||||
|
||||
val functionValueParameters =
|
||||
@ -292,8 +297,8 @@ internal class CommandReflector(
|
||||
return CommandReceiverParameter(this.type.isMarkedNullable, this.type)
|
||||
}
|
||||
|
||||
private fun createStringConstantParameter(index: Int, expectingValue: String): AbstractCommandValueParameter.StringConstant {
|
||||
return AbstractCommandValueParameter.StringConstant("#$index", expectingValue)
|
||||
private fun createStringConstantParameterForName(index: Int, expectingValue: String): AbstractCommandValueParameter.StringConstant {
|
||||
return AbstractCommandValueParameter.StringConstant("#$index", expectingValue, true)
|
||||
}
|
||||
|
||||
private fun KParameter.toUserDefinedCommandParameter(): AbstractCommandValueParameter.UserDefinedType<*> {
|
||||
|
30
backend/mirai-console/src/internal/command/CommnadConfig.kt
Normal file
30
backend/mirai-console/src/internal/command/CommnadConfig.kt
Normal 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("/")
|
||||
}
|
@ -7,31 +7,76 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("EXPOSED_SUPER_CLASS")
|
||||
|
||||
package net.mamoe.mirai.console.internal.data.builtins
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser
|
||||
import net.mamoe.mirai.console.command.descriptor.InternalCommandValueArgumentParserExtensions
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginConfig
|
||||
import net.mamoe.mirai.console.data.ValueDescription
|
||||
import net.mamoe.mirai.console.data.value
|
||||
import net.mamoe.mirai.console.internal.util.md5
|
||||
import net.mamoe.mirai.console.internal.util.toUHexString
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.yamlkt.Comment
|
||||
import net.mamoe.yamlkt.YamlDynamicSerializer
|
||||
|
||||
internal object AutoLoginConfig : AutoSavePluginConfig("AutoLogin") {
|
||||
@ValueDescription(
|
||||
"""
|
||||
账号和明文密码列表
|
||||
"""
|
||||
)
|
||||
val plainPasswords: MutableMap<Long, String> by value(mutableMapOf(123456654321L to "example"))
|
||||
@ConsoleExperimentalApi
|
||||
@ValueDescription("自动登录配置")
|
||||
public object AutoLoginConfig : AutoSavePluginConfig("AutoLogin") {
|
||||
|
||||
|
||||
@ValueDescription(
|
||||
"""
|
||||
账号和 MD5 密码列表
|
||||
"""
|
||||
)
|
||||
val md5Passwords: MutableMap<Long, String> by value(
|
||||
mutableMapOf(
|
||||
123456654321L to "example".toByteArray().md5().toUHexString()
|
||||
@Serializable
|
||||
public data class Account(
|
||||
@Comment("账号, 现只支持 QQ 数字账号")
|
||||
val account: String,
|
||||
val password: Password,
|
||||
@Comment("""
|
||||
账号配置. 可用配置列表 (注意大小写):
|
||||
"protocol": "ANDROID_PHONE" / "ANDROID_PAD" / "ANDROID_WATCH"
|
||||
""")
|
||||
val configuration: Map<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"))
|
||||
))
|
||||
}
|
@ -11,10 +11,7 @@ package net.mamoe.mirai.console.internal.data.builtins
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder
|
||||
import net.mamoe.mirai.console.data.PluginConfig
|
||||
import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import net.mamoe.mirai.console.data.*
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
import net.mamoe.mirai.utils.minutesToMillis
|
||||
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
}
|
@ -11,11 +11,6 @@
|
||||
|
||||
package net.mamoe.mirai.console.internal.data
|
||||
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.serializer
|
||||
import net.mamoe.yamlkt.Yaml
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
// TODO: 2020/6/24 优化性能: 引入一个 comparator 之类来替代将 Int 包装为 Value<Int> 后进行 containsKey 比较的方法
|
||||
|
||||
|
||||
@ -245,6 +240,8 @@ internal inline fun <E, R> MutableSet<E>.shadowMap(
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
internal inline fun <T> dynamicList(crossinline supplier: () -> List<T>): List<T> {
|
||||
return object : List<T> {
|
||||
override val size: Int get() = supplier().size
|
||||
@ -304,7 +301,6 @@ internal inline fun <T> dynamicMutableList(crossinline supplier: () -> MutableLi
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal inline fun <T> dynamicMutableSet(crossinline supplier: () -> MutableSet<T>): MutableSet<T> {
|
||||
return object : MutableSet<T> {
|
||||
override val size: Int get() = supplier().size
|
||||
@ -322,7 +318,7 @@ internal inline fun <T> dynamicMutableSet(crossinline supplier: () -> MutableSet
|
||||
override fun hashCode(): Int = supplier().hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST", "USELESS_CAST") // type inference bug
|
||||
internal inline fun <K, V> MutableMap<K, V>.observable(crossinline onChanged: () -> Unit): MutableMap<K, V> {
|
||||
return object : MutableMap<K, V>, Map<K, V> by (this as Map<K, V>) {
|
||||
@ -472,7 +468,7 @@ internal inline fun <T> MutableSet<T>.observable(crossinline onChanged: () -> Un
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
@OptIn(InternalSerializationApi::class)
|
||||
internal fun <R : Any> Any.smartCastPrimitive(clazz: KClass<R>): R {
|
||||
kotlin.runCatching {
|
||||
@ -482,3 +478,4 @@ internal fun <R : Any> Any.smartCastPrimitive(clazz: KClass<R>): R {
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.internal.data
|
||||
|
||||
import net.mamoe.mirai.console.data.PluginData
|
||||
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.internal.data
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
|
@ -102,7 +102,7 @@ internal fun PluginData.valueFromKTypeImpl(type: KType): SerializerAwareValue<*>
|
||||
}
|
||||
}
|
||||
|
||||
internal fun KClass<*>.createInstanceSmart(): Any? {
|
||||
internal fun KClass<*>.createInstanceSmart(): Any {
|
||||
return when (this) {
|
||||
Byte::class -> 0.toByte()
|
||||
Short::class -> 0.toShort()
|
||||
|
@ -50,6 +50,8 @@ internal data class DataExtensionRegistry<out E : Extension>(
|
||||
) : ExtensionRegistry<E>
|
||||
|
||||
internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
private val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun <T : Extension> ExtensionPoint<out T>.getExtensions(): Set<ExtensionRegistry<T>> {
|
||||
val userDefined = instances.getOrPut(this, ::CopyOnWriteArraySet) as Set<ExtensionRegistry<T>>
|
||||
@ -61,6 +63,13 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
return builtins?.plus(userDefined) ?: userDefined
|
||||
}
|
||||
|
||||
// unused for now
|
||||
internal fun removeExtensionsRegisteredByPlugin(plugin: Plugin) {
|
||||
instances.forEach { (_, u) ->
|
||||
u.removeAll { it.plugin == plugin }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun mergeWith(another: AbstractConcurrentComponentStorage) {
|
||||
for ((ep, list) in another.instances) {
|
||||
for (extensionRegistry in list) {
|
||||
@ -154,7 +163,6 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T, plugin: Plugin?) -> Unit): Unit =
|
||||
withExtensions(block)
|
||||
|
||||
val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
|
||||
override fun <T : Extension> contribute(
|
||||
extensionPoint: ExtensionPoint<T>,
|
||||
plugin: Plugin,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ internal fun parseFromStringImpl(string: String): AbstractPermitteeId {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyContact
|
||||
}
|
||||
'm' -> kotlin.run {
|
||||
'm' -> run {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyMemberFromAnyGroup
|
||||
else {
|
||||
@ -56,7 +56,7 @@ internal fun parseFromStringImpl(string: String): AbstractPermitteeId {
|
||||
}
|
||||
}
|
||||
}
|
||||
't' -> kotlin.run {
|
||||
't' -> run {
|
||||
val arg = str.substring(1)
|
||||
if (arg == "*") return AnyTempFromAnyGroup
|
||||
else {
|
||||
|
@ -20,6 +20,7 @@ import net.mamoe.mirai.console.internal.util.PluginServiceHelper.loadAllServices
|
||||
import net.mamoe.mirai.console.plugin.jvm.*
|
||||
import net.mamoe.mirai.console.plugin.loader.AbstractFilePluginLoader
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoadException
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.io.File
|
||||
@ -70,7 +71,6 @@ internal object BuiltInJvmPluginLoaderImpl :
|
||||
f to pluginClassLoader.findServices(
|
||||
JvmPlugin::class,
|
||||
KotlinPlugin::class,
|
||||
AbstractJvmPlugin::class,
|
||||
JavaPlugin::class
|
||||
).loadAllServices()
|
||||
}.flatMap { (f, list) ->
|
||||
@ -94,20 +94,25 @@ internal object BuiltInJvmPluginLoaderImpl :
|
||||
return filePlugins.toSet().map { it.value }
|
||||
}
|
||||
|
||||
private val loadedPlugins = ConcurrentHashMap<JvmPlugin, Unit>()
|
||||
|
||||
@Throws(PluginLoadException::class)
|
||||
override fun load(plugin: JvmPlugin) {
|
||||
ensureActive()
|
||||
|
||||
if (loadedPlugins.put(plugin, Unit) != null) {
|
||||
error("Plugin '${plugin.name}' is already loaded and cannot be reloaded.")
|
||||
}
|
||||
runCatching {
|
||||
check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin" }
|
||||
plugin.internalOnLoad(plugin.componentStorage)
|
||||
check(plugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin to be loaded by JvmPluginLoader.BuiltIn" }
|
||||
plugin.internalOnLoad()
|
||||
}.getOrElse {
|
||||
throw PluginLoadException("Exception while loading ${plugin.description.name}", it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun enable(plugin: JvmPlugin) {
|
||||
if (plugin.isEnabled) return
|
||||
if (plugin.isEnabled) error("Plugin '${plugin.name}' is already enabled and cannot be re-enabled.")
|
||||
ensureActive()
|
||||
runCatching {
|
||||
if (plugin is JvmPluginInternal) {
|
||||
@ -119,7 +124,7 @@ internal object BuiltInJvmPluginLoaderImpl :
|
||||
}
|
||||
|
||||
override fun disable(plugin: JvmPlugin) {
|
||||
if (!plugin.isEnabled) return
|
||||
if (!plugin.isEnabled) error("Plugin '${plugin.name}' is not already disabled and cannot be re-disabled.")
|
||||
|
||||
if (MiraiConsole.isActive)
|
||||
ensureActive()
|
||||
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.data.runCatchingLog
|
||||
import net.mamoe.mirai.console.extension.PluginComponentStorage
|
||||
import net.mamoe.mirai.console.internal.data.mkdir
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
@ -43,9 +44,6 @@ internal abstract class JvmPluginInternal(
|
||||
parentCoroutineContext: CoroutineContext,
|
||||
) : JvmPlugin, CoroutineScope {
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
internal val componentStorage: PluginComponentStorage = PluginComponentStorage(this)
|
||||
|
||||
final override val parentPermission: Permission by lazy {
|
||||
PermissionService.INSTANCE.register(
|
||||
PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*"),
|
||||
@ -54,6 +52,7 @@ internal abstract class JvmPluginInternal(
|
||||
}
|
||||
|
||||
final override var isEnabled: Boolean = false
|
||||
internal set
|
||||
|
||||
private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() }
|
||||
final override fun getResourceAsStream(path: String): InputStream? =
|
||||
@ -100,13 +99,16 @@ internal abstract class JvmPluginInternal(
|
||||
}
|
||||
|
||||
@Throws(Throwable::class)
|
||||
internal fun internalOnLoad(componentStorage: PluginComponentStorage) {
|
||||
internal fun internalOnLoad() {
|
||||
val componentStorage = PluginComponentStorage(this)
|
||||
onLoad(componentStorage)
|
||||
GlobalComponentStorage.mergeWith(componentStorage)
|
||||
}
|
||||
|
||||
internal fun internalOnEnable(): Boolean {
|
||||
parentPermission
|
||||
if (!firstRun) refreshCoroutineContext()
|
||||
|
||||
kotlin.runCatching {
|
||||
onEnable()
|
||||
}.fold(
|
||||
|
@ -28,7 +28,7 @@ import net.mamoe.mirai.console.plugin.loader.PluginLoadException
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
import net.mamoe.mirai.console.util.SemVersion.Companion.contains
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import net.mamoe.mirai.utils.info
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
@ -248,8 +248,8 @@ internal fun List<PluginDescription>.findDependency(dependency: PluginDependency
|
||||
}
|
||||
|
||||
internal fun PluginDescription.checkSatisfies(dependency: PluginDependency, plugin: PluginDescription) {
|
||||
val requirement = dependency.versionRequirement
|
||||
if (requirement != null && this.version !in requirement) {
|
||||
val requirement = dependency.versionRequirement ?: return
|
||||
if (SemVersion.parseRangeRequirement(requirement).test(this.version)) {
|
||||
throw PluginLoadException("Plugin '${plugin.id}' ('${plugin.id}') requires '${dependency.id}' with version $requirement while the resolved is ${this.version}")
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,15 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:JvmName("CommonUtils")
|
||||
@file:JvmName("CommonUtils") // maintain binary compatibility
|
||||
|
||||
package net.mamoe.mirai.console.internal.util
|
||||
|
||||
import io.github.karlatemp.caller.StackFrame
|
||||
import net.mamoe.mirai.console.internal.plugin.BuiltInJvmPluginLoaderImpl
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
internal inline fun <reified E : Throwable, R> runIgnoreException(block: () -> R): R? {
|
||||
try {
|
||||
return block()
|
||||
@ -27,4 +32,43 @@ internal inline fun <reified E : Throwable> runIgnoreException(block: () -> Unit
|
||||
if (e is E) return null
|
||||
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
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.internal.util
|
||||
|
||||
import net.mamoe.mirai.console.internal.data.cast
|
||||
@ -18,7 +20,6 @@ import java.util.*
|
||||
import kotlin.reflect.KClass
|
||||
import java.lang.reflect.Member as JReflectionMember
|
||||
|
||||
@Suppress("unused")
|
||||
internal class ServiceList<T>(
|
||||
internal val classLoader: ClassLoader,
|
||||
internal val delegate: List<String>
|
||||
@ -38,8 +39,8 @@ internal object PluginServiceHelper {
|
||||
return delegate.mapNotNull { classLoader.loadService<T>(it) }
|
||||
}
|
||||
|
||||
fun <T : Any> ClassLoader.loadService(
|
||||
classname: String
|
||||
private fun <T : Any> ClassLoader.loadService(
|
||||
classname: String,
|
||||
): T? {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return kotlin.runCatching {
|
||||
|
@ -189,7 +189,7 @@ internal object RangeTokenReader {
|
||||
}
|
||||
}
|
||||
|
||||
fun parse(source: String, token: Token): SemVersion.Requirement {
|
||||
fun parse(source: String, token: Token): RequirementInternal {
|
||||
return when (token) {
|
||||
is Token.Group -> {
|
||||
if (token.values.size == 1) {
|
||||
@ -206,7 +206,7 @@ internal object RangeTokenReader {
|
||||
}.map { parse(source, it) }.toList()
|
||||
when (logic.first()) {
|
||||
TokenType.OR -> {
|
||||
return object : SemVersion.Requirement {
|
||||
return object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean {
|
||||
rules.forEach { if (it.test(version)) return true }
|
||||
return false
|
||||
@ -214,7 +214,7 @@ internal object RangeTokenReader {
|
||||
}
|
||||
}
|
||||
TokenType.AND -> {
|
||||
return object : SemVersion.Requirement {
|
||||
return object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean {
|
||||
rules.forEach { if (!it.test(version)) return false }
|
||||
return true
|
||||
|
@ -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
|
||||
}
|
@ -75,11 +75,11 @@ internal object SemVersionInternal {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
internal fun parseRule(rule: String): SemVersion.Requirement {
|
||||
internal fun parseRule(rule: String): RequirementInternal {
|
||||
val trimmed = rule.trim()
|
||||
if (directVersion.matches(trimmed)) {
|
||||
val parsed = SemVersion.invoke(trimmed)
|
||||
return object : SemVersion.Requirement {
|
||||
return object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean = version.compareTo(parsed) == 0
|
||||
}
|
||||
}
|
||||
@ -89,7 +89,7 @@ internal object SemVersionInternal {
|
||||
.replace("x", ".+") +
|
||||
"$"
|
||||
).toRegex()
|
||||
return object : SemVersion.Requirement {
|
||||
return object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean = regex.matches(version.toString())
|
||||
}
|
||||
}
|
||||
@ -120,7 +120,7 @@ internal object SemVersionInternal {
|
||||
'(', ')' -> ({ it < end })
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
return object : SemVersion.Requirement {
|
||||
return object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean = a(version) && b(version)
|
||||
}
|
||||
}
|
||||
@ -129,32 +129,32 @@ internal object SemVersionInternal {
|
||||
val version1 = SemVersion.invoke(result.groupValues[8])
|
||||
return when (operator) {
|
||||
">=" -> {
|
||||
object : SemVersion.Requirement {
|
||||
object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean = version >= version1
|
||||
}
|
||||
}
|
||||
">" -> {
|
||||
object : SemVersion.Requirement {
|
||||
object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean = version > version1
|
||||
}
|
||||
}
|
||||
"<=" -> {
|
||||
object : SemVersion.Requirement {
|
||||
object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean = version <= version1
|
||||
}
|
||||
}
|
||||
"<" -> {
|
||||
object : SemVersion.Requirement {
|
||||
object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean = version < version1
|
||||
}
|
||||
}
|
||||
"=" -> {
|
||||
object : SemVersion.Requirement {
|
||||
object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean = version.compareTo(version1) == 0
|
||||
}
|
||||
}
|
||||
"!=" -> {
|
||||
object : SemVersion.Requirement {
|
||||
object : RequirementInternal {
|
||||
override fun test(version: SemVersion): Boolean = version.compareTo(version1) != 0
|
||||
}
|
||||
}
|
||||
@ -164,15 +164,9 @@ internal object SemVersionInternal {
|
||||
throw IllegalArgumentException("Cannot parse $rule")
|
||||
}
|
||||
|
||||
private fun SemVersion.Requirement.withRule(rule: String): SemVersion.Requirement {
|
||||
return object : SemVersion.Requirement {
|
||||
override fun test(version: SemVersion): Boolean = this@withRule.test(version)
|
||||
override fun toString(): String = rule
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseRangeRequirement(requirement: String): SemVersion.Requirement {
|
||||
fun parseRangeRequirement(requirement: String): RequirementInternal {
|
||||
if (requirement.isBlank()) {
|
||||
throw IllegalArgumentException("Invalid requirement: Empty requirement rule.")
|
||||
}
|
||||
@ -180,7 +174,7 @@ internal object SemVersionInternal {
|
||||
val collected = RangeTokenReader.collect(requirement, tokens.iterator(), true)
|
||||
RangeTokenReader.check(requirement, collected.iterator(), null)
|
||||
return kotlin.runCatching {
|
||||
RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0)).withRule(requirement)
|
||||
RangeTokenReader.parse(requirement, RangeTokenReader.Token.Group(collected, 0))
|
||||
}.onFailure { error ->
|
||||
throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString {
|
||||
collected.forEach { dump("", it) }
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
26
backend/mirai-console/src/logging/LoggerController.kt
Normal file
26
backend/mirai-console/src/logging/LoggerController.kt
Normal 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
|
||||
|
||||
}
|
@ -31,19 +31,8 @@ public data class PermissionId(
|
||||
@ResolveContext(PERMISSION_NAME) public val name: String,
|
||||
) {
|
||||
init {
|
||||
require(namespace.none { it.isWhitespace() }) {
|
||||
"' ' is not allowed in namespace"
|
||||
}
|
||||
require(name.none { it.isWhitespace() }) {
|
||||
"' ' is not allowed in name"
|
||||
}
|
||||
|
||||
require(!namespace.contains(':')) {
|
||||
"':' is not allowed in namespace"
|
||||
}
|
||||
require(!name.contains(':')) {
|
||||
"':' is not allowed in name"
|
||||
}
|
||||
checkPermissionIdName(name)
|
||||
checkPermissionIdName(namespace)
|
||||
}
|
||||
|
||||
public object PermissionIdAsStringSerializer : KSerializer<PermissionId> by String.serializer().map(
|
||||
@ -79,7 +68,7 @@ public data class PermissionId(
|
||||
public fun checkPermissionIdName(@ResolveContext(PERMISSION_NAME) name: String) {
|
||||
when {
|
||||
name.isBlank() -> throw IllegalArgumentException("PermissionId.name should not be blank.")
|
||||
name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.name.")
|
||||
name.any(Char::isWhitespace) -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.name.")
|
||||
name.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.name.")
|
||||
}
|
||||
}
|
||||
@ -92,7 +81,7 @@ public data class PermissionId(
|
||||
public fun checkPermissionIdNamespace(@ResolveContext(PERMISSION_NAME) namespace: String) {
|
||||
when {
|
||||
namespace.isBlank() -> throw IllegalArgumentException("PermissionId.namespace should not be blank.")
|
||||
namespace.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.namespace.")
|
||||
namespace.any(Char::isWhitespace) -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.namespace.")
|
||||
namespace.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.namespace.")
|
||||
}
|
||||
}
|
||||
|
@ -17,4 +17,4 @@ package net.mamoe.mirai.console.permission
|
||||
public class PermissionRegistryConflictException(
|
||||
public val newInstance: Permission,
|
||||
public val existingInstance: Permission,
|
||||
) : Exception("Conflicted Permission registry. new: $newInstance, existing: $existingInstance")
|
||||
) : Exception("Conflicting Permission registry. new: $newInstance, existing: $existingInstance")
|
@ -11,8 +11,13 @@
|
||||
|
||||
package net.mamoe.mirai.console.plugin.description
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_ID
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.VERSION_REQUIREMENT
|
||||
import net.mamoe.mirai.console.internal.data.map
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
|
||||
/**
|
||||
@ -20,6 +25,7 @@ import net.mamoe.mirai.console.util.SemVersion
|
||||
*
|
||||
* @see PluginDescription.dependencies
|
||||
*/
|
||||
@Serializable(with = PluginDependency.PluginDependencyAsStringSerializer::class)
|
||||
public data class PluginDependency @JvmOverloads constructor(
|
||||
/**
|
||||
* 依赖插件 ID, [PluginDescription.id]
|
||||
@ -32,7 +38,7 @@ public data class PluginDependency @JvmOverloads constructor(
|
||||
*
|
||||
* @see SemVersion.Requirement
|
||||
*/
|
||||
public val versionRequirement: SemVersion.Requirement? = null,
|
||||
@ResolveContext(VERSION_REQUIREMENT) public val versionRequirement: String? = null,
|
||||
/**
|
||||
* 若为 `false`, 插件在找不到此依赖时也能正常加载.
|
||||
*/
|
||||
@ -41,6 +47,7 @@ public data class PluginDependency @JvmOverloads constructor(
|
||||
init {
|
||||
kotlin.runCatching {
|
||||
PluginDescription.checkPluginId(id)
|
||||
if (versionRequirement != null) SemVersion.parseRangeRequirement(versionRequirement)
|
||||
}.getOrElse {
|
||||
throw IllegalArgumentException(it)
|
||||
}
|
||||
@ -55,4 +62,40 @@ public data class PluginDependency @JvmOverloads constructor(
|
||||
) : this(
|
||||
id, null, isOptional
|
||||
)
|
||||
|
||||
public override fun toString(): String = buildString {
|
||||
append(id)
|
||||
versionRequirement?.let {
|
||||
append(':')
|
||||
append(it)
|
||||
}
|
||||
if (isOptional) {
|
||||
append('?')
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 解析 "$id:$versionRequirement?"
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
public fun parseFromString(string: String): PluginDependency {
|
||||
require(string.isNotEmpty()) { "string is empty." }
|
||||
val optional = string.endsWith('?')
|
||||
val (id, version) = string.removeSuffix("?").let { rule ->
|
||||
if (rule.contains(':')) {
|
||||
rule.substringBeforeLast(':') to rule.substringAfterLast(':')
|
||||
} else {
|
||||
rule to null
|
||||
}
|
||||
}
|
||||
return PluginDependency(id, version, optional)
|
||||
}
|
||||
}
|
||||
|
||||
public object PluginDependencyAsStringSerializer : KSerializer<PluginDependency> by String.serializer().map(
|
||||
serializer = { it.toString() },
|
||||
deserializer = { parseFromString(it) }
|
||||
)
|
||||
}
|
@ -76,6 +76,7 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see StandardExportManagers
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public interface ExportManager {
|
||||
|
@ -11,11 +11,17 @@
|
||||
|
||||
package net.mamoe.mirai.console.plugin.jvm
|
||||
|
||||
import io.github.karlatemp.caller.CallerFinder
|
||||
import io.github.karlatemp.caller.StackFrame
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.*
|
||||
import net.mamoe.mirai.console.internal.util.findLoader
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDependency
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import net.mamoe.yamlkt.Yaml
|
||||
|
||||
/**
|
||||
* JVM 插件的描述. 通常作为 `plugin.yml`
|
||||
@ -30,51 +36,69 @@ import net.mamoe.mirai.console.util.SemVersion
|
||||
public interface JvmPluginDescription : PluginDescription {
|
||||
public companion object {
|
||||
/**
|
||||
* 构建 [JvmPluginDescription]
|
||||
* @see JvmPluginDescriptionBuilder
|
||||
* 从 [pluginClassloader] 读取资源文件 [filename] 并以 YAML 格式解析为 [SimpleJvmPluginDescription]
|
||||
*
|
||||
* @param filename [ClassLoader.getResourceAsStream] 的参数 `name`
|
||||
* @param pluginClassloader 默认通过 [CallerFinder.getCaller] 获取调用方 [StackFrame] 然后获取其 [Class.getClassLoader].
|
||||
*/
|
||||
@JvmName("create")
|
||||
@JvmSynthetic
|
||||
public inline operator fun invoke(
|
||||
/**
|
||||
* @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()
|
||||
// @JvmOverloads // compiler error
|
||||
@JvmStatic
|
||||
@ConsoleExperimentalApi
|
||||
public fun loadFromResource(
|
||||
filename: String = "plugin.yml",
|
||||
pluginClassloader: ClassLoader = CallerFinder.getCaller()?.findLoader() ?: error("Cannot find caller classloader, please specify manually."),
|
||||
): JvmPluginDescription {
|
||||
val stream = pluginClassloader.getResourceAsStream(filename) ?: error("Cannot find plugin description resource '$filename'")
|
||||
|
||||
/**
|
||||
* 构建 [JvmPluginDescription]
|
||||
* @see JvmPluginDescriptionBuilder
|
||||
*/
|
||||
@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()
|
||||
val bytes = stream.use { it.readBytes() }
|
||||
|
||||
return Yaml.default.decodeFromString(SimpleJvmPluginDescription.serializer(), String(bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 [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] 构建器.
|
||||
*
|
||||
@ -94,7 +118,7 @@ public interface JvmPluginDescription : PluginDescription {
|
||||
* .build();
|
||||
* ```
|
||||
*
|
||||
* @see [JvmPluginDescription.invoke]
|
||||
* @see [JvmPluginDescription]
|
||||
*/
|
||||
public class JvmPluginDescriptionBuilder(
|
||||
@ResolveContext(PLUGIN_ID) private var id: String,
|
||||
@ -148,20 +172,6 @@ public class JvmPluginDescriptionBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* isOptional = false
|
||||
*
|
||||
* @see PluginDependency
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
@ResolveContext(PLUGIN_ID) pluginId: String,
|
||||
versionRequirement: SemVersion.Requirement,
|
||||
isOptional: Boolean = false,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
this.dependencies.add(PluginDependency(pluginId, versionRequirement, isOptional))
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PluginDependency
|
||||
*/
|
||||
@ -171,7 +181,7 @@ public class JvmPluginDescriptionBuilder(
|
||||
@ResolveContext(VERSION_REQUIREMENT) versionRequirement: String,
|
||||
isOptional: Boolean = false,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
this.dependencies.add(PluginDependency(pluginId, SemVersion.parseRangeRequirement(versionRequirement), isOptional))
|
||||
this.dependencies.add(PluginDependency(pluginId, versionRequirement, isOptional))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -192,7 +202,7 @@ public class JvmPluginDescriptionBuilder(
|
||||
|
||||
public fun build(): JvmPluginDescription =
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
SimpleJvmPluginDescription(name, version, id, author, info, dependencies)
|
||||
SimpleJvmPluginDescription(id, name, version, author, info, dependencies)
|
||||
|
||||
/**
|
||||
* 标注一个 [JvmPluginDescription] DSL
|
||||
@ -208,11 +218,12 @@ public class JvmPluginDescriptionBuilder(
|
||||
*
|
||||
* @see JvmPluginDescription
|
||||
*/
|
||||
@Serializable // Keep this file in public API files. Might turn to `public` in the future.
|
||||
internal data class SimpleJvmPluginDescription
|
||||
@JvmOverloads constructor(
|
||||
override val id: String,
|
||||
override val name: String,
|
||||
override val version: SemVersion,
|
||||
override val id: String = name,
|
||||
override val author: String = "",
|
||||
override val info: String = "",
|
||||
override val dependencies: Set<PluginDependency> = setOf(),
|
||||
@ -221,13 +232,13 @@ internal data class SimpleJvmPluginDescription
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
name: String,
|
||||
id: String,
|
||||
name: String = id,
|
||||
version: String,
|
||||
id: String = name,
|
||||
author: String = "",
|
||||
info: String = "",
|
||||
dependencies: Set<PluginDependency> = setOf(),
|
||||
) : this(name, SemVersion(version), id, author, info, dependencies)
|
||||
) : this(id, name, SemVersion(version), author, info, dependencies)
|
||||
|
||||
init {
|
||||
PluginDescription.checkPluginDescription(this)
|
||||
|
@ -76,7 +76,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
|
||||
* @throws IllegalStateException 在插件已经被加载时抛出. 这属于意料之外的情况.
|
||||
*/
|
||||
@Throws(PluginLoadException::class)
|
||||
@Throws(IllegalStateException::class, PluginLoadException::class)
|
||||
public fun load(plugin: P)
|
||||
|
||||
/**
|
||||
|
@ -555,7 +555,7 @@ private class CombinedScope(
|
||||
private class CommandSenderAsMessageScope(
|
||||
private val sender: CommandSender,
|
||||
) : MessageScope {
|
||||
override val realTarget: Any?
|
||||
override val realTarget: Any
|
||||
get() = sender.user ?: sender // ConsoleCommandSender
|
||||
|
||||
override suspend fun sendMessage(message: Message) {
|
||||
@ -570,7 +570,7 @@ private class CommandSenderAsMessageScope(
|
||||
private class ContactAsMessageScope(
|
||||
private val sender: Contact,
|
||||
) : MessageScope {
|
||||
override val realTarget: Any?
|
||||
override val realTarget: Any
|
||||
get() = sender
|
||||
|
||||
override suspend fun sendMessage(message: Message) {
|
||||
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.util
|
||||
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
|
@ -26,12 +26,13 @@ import net.mamoe.mirai.console.internal.data.map
|
||||
import net.mamoe.mirai.console.internal.util.semver.SemVersionInternal
|
||||
import net.mamoe.mirai.console.util.SemVersion.Companion.equals
|
||||
import net.mamoe.mirai.console.util.SemVersion.Requirement
|
||||
import net.mamoe.mirai.console.util.SemVersion.SemVersionAsStringSerializer
|
||||
import kotlin.LazyThreadSafetyMode.PUBLICATION
|
||||
|
||||
/**
|
||||
* [语义化版本](https://semver.org/lang/zh-CN/) 支持
|
||||
*
|
||||
* 解析示例:
|
||||
* ### 解析示例
|
||||
*
|
||||
* `1.0.0-M4+c25733b8` 将会解析出下面的内容,
|
||||
* [major] (主本号), [minor] (次版本号), [patch] (修订号), [identifier] (先行版本号) 和 [metadata] (元数据).
|
||||
@ -48,10 +49,13 @@ import kotlin.LazyThreadSafetyMode.PUBLICATION
|
||||
*
|
||||
* 对于核心版本号, 此实现稍微比语义化版本规范宽松一些, 允许 x.y 的存在.
|
||||
*
|
||||
* ### 序列化
|
||||
* 使用 [SemVersionAsStringSerializer], [SemVersion] 被序列化为 [toString] 的字符串.
|
||||
*
|
||||
* @see Requirement 版本号要修
|
||||
* @see SemVersion.invoke 由字符串解析
|
||||
*/
|
||||
@Serializable(with = SemVersion.SemVersionAsStringSerializer::class)
|
||||
@Serializable(with = SemVersionAsStringSerializer::class)
|
||||
public data class SemVersion
|
||||
/**
|
||||
* @see SemVersion.invoke 字符串解析
|
||||
@ -81,11 +85,47 @@ internal constructor(
|
||||
* 一条依赖规则
|
||||
* @see [parseRangeRequirement]
|
||||
*/
|
||||
public interface Requirement {
|
||||
@Serializable(Requirement.RequirementAsStringSerializer::class)
|
||||
public data class Requirement internal constructor(
|
||||
/**
|
||||
* 规则的字符串表示方式
|
||||
*
|
||||
* @see [SemVersion.parseRangeRequirement]
|
||||
*/
|
||||
val rule: String,
|
||||
) {
|
||||
|
||||
@Transient
|
||||
internal val impl = kotlin.runCatching {
|
||||
SemVersionInternal.parseRangeRequirement(rule)
|
||||
}.getOrElse {
|
||||
throw java.lang.IllegalArgumentException("Syntax error: $rule", it)
|
||||
}
|
||||
|
||||
/** 在 [version] 满足此要求时返回 true */
|
||||
public fun test(version: SemVersion): Boolean
|
||||
public fun test(version: SemVersion): Boolean = impl.test(version)
|
||||
|
||||
/**
|
||||
* 序列化为字符串, [rule]. 从字符串反序列化, [parseRangeRequirement].
|
||||
*/
|
||||
public object RequirementAsStringSerializer : KSerializer<Requirement> by String.serializer().map(
|
||||
serializer = { it.rule },
|
||||
deserializer = { parseRangeRequirement(it) }
|
||||
)
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* @see parseRangeRequirement
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public operator fun invoke(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Requirement =
|
||||
parseRangeRequirement(requirement)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 [SemVersion.toString] 序列化, 使用 [SemVersion.invoke] 反序列化.
|
||||
*/
|
||||
public object SemVersionAsStringSerializer : KSerializer<SemVersion> by String.serializer().map(
|
||||
serializer = { it.toString() },
|
||||
deserializer = { SemVersion(it) }
|
||||
@ -148,8 +188,8 @@ internal constructor(
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
public fun parseRangeRequirement(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Requirement =
|
||||
SemVersionInternal.parseRangeRequirement(requirement)
|
||||
public fun parseRangeRequirement(@ResolveContext(VERSION_REQUIREMENT) rule: String): Requirement =
|
||||
Requirement(rule)
|
||||
|
||||
/** @see [Requirement.test] */
|
||||
@JvmStatic
|
||||
@ -201,7 +241,11 @@ internal constructor(
|
||||
public override fun toString(): String = toString
|
||||
|
||||
/**
|
||||
* 将 [SemVersion] 转为 Kotlin data class 风格的 [String]
|
||||
* 将 [SemVersion] 转为 Kotlin data class 风格的 [String].
|
||||
*
|
||||
* ```
|
||||
* return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)"
|
||||
* ```
|
||||
*/
|
||||
public fun toStructuredString(): String {
|
||||
return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)"
|
||||
|
@ -7,7 +7,7 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
|
||||
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE", "unused")
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("ConsoleUtils")
|
||||
|
||||
|
@ -21,20 +21,18 @@ import net.mamoe.mirai.console.util.ConsoleInput
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.LoginSolver
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.PlatformLogger
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.nio.file.Path
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.io.path.createTempDirectory
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@OptIn(ConsoleInternalApi::class)
|
||||
@OptIn(ConsoleInternalApi::class, kotlin.io.path.ExperimentalPathApi::class)
|
||||
fun initTestEnvironment() {
|
||||
object : MiraiConsoleImplementation {
|
||||
override val rootPath: Path = createTempDir().toPath()
|
||||
override val rootPath: Path = createTempDirectory()
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
override val frontEndDescription: MiraiConsoleFrontEndDescription
|
||||
|
@ -48,6 +48,15 @@ object TestCompositeCommand : CompositeCommand(
|
||||
}
|
||||
}
|
||||
|
||||
object TestRawCommand : RawCommand(
|
||||
ConsoleCommandOwner,
|
||||
"testRaw"
|
||||
) {
|
||||
override suspend fun CommandSender.onCommand(args: MessageChain) {
|
||||
Testing.ok(args)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object TestSimpleCommand : RawCommand(owner, "testSimple", "tsS") {
|
||||
override suspend fun CommandSender.onCommand(args: MessageChain) {
|
||||
@ -104,6 +113,17 @@ internal class TestCommand {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test raw command`() = runBlocking {
|
||||
TestRawCommand.withRegistration {
|
||||
val result = withTesting<MessageChain> {
|
||||
assertSuccess(TestRawCommand.execute(sender, PlainText("a1"), PlainText("a2"), PlainText("a3")))
|
||||
}
|
||||
assertEquals(3, result.size)
|
||||
assertEquals("a1, a2, a3", result.joinToString())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test flattenCommandArgs`() {
|
||||
val result = arrayOf("test", image).flattenCommandComponents().toTypedArray()
|
||||
@ -161,8 +181,8 @@ internal class TestCommand {
|
||||
@Test
|
||||
fun `composite command descriptors`() {
|
||||
val overloads = TestCompositeCommand.overloads
|
||||
assertEquals("CommandSignatureVariant(<mute>, seconds: Int = ...)", overloads[0].toString())
|
||||
assertEquals("CommandSignatureVariant(<mute>, target: Long, seconds: Int)", overloads[1].toString())
|
||||
assertEquals("CommandSignature(<mute>, seconds: Int = ...)", overloads[0].toString())
|
||||
assertEquals("CommandSignature(<mute>, target: Long, seconds: Int)", overloads[1].toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -23,6 +23,7 @@ internal class PluginDataTest {
|
||||
val map2: MutableMap<String, MutableMap<String, String>> by value()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
private val jsonPrettyPrint = Json { prettyPrint = true }
|
||||
private val json = Json {}
|
||||
|
||||
|
@ -16,6 +16,7 @@ package net.mamoe.mirai.console.util
|
||||
|
||||
import net.mamoe.mirai.console.util.SemVersion.Companion.test
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertFails
|
||||
|
||||
internal class TestSemVersion {
|
||||
@Test
|
||||
@ -42,6 +43,16 @@ internal class TestSemVersion {
|
||||
assert("1.0.0-rc.1".sem() < "1.0.0".sem())
|
||||
}
|
||||
|
||||
@Test
|
||||
internal fun testRequirementCopy() {
|
||||
fun SemVersion.Requirement.check(a: SemVersion.Requirement.() -> SemVersion.Requirement) {
|
||||
assert(a().impl !== this.impl)
|
||||
}
|
||||
SemVersion.parseRangeRequirement("1.0").check { copy() }
|
||||
SemVersion.parseRangeRequirement("1.0").check { copy("2.0") }
|
||||
SemVersion.parseRangeRequirement("1.0").check { copy("1.0") }
|
||||
}
|
||||
|
||||
@Test
|
||||
internal fun testRequirement() {
|
||||
fun SemVersion.Requirement.assert(version: String): SemVersion.Requirement {
|
||||
@ -50,9 +61,9 @@ internal class TestSemVersion {
|
||||
}
|
||||
|
||||
fun assertInvalid(requirement: String) {
|
||||
kotlin.runCatching {
|
||||
assertFails(requirement) {
|
||||
SemVersion.parseRangeRequirement(requirement)
|
||||
}.onSuccess { assert(false) { requirement } }
|
||||
}
|
||||
}
|
||||
|
||||
fun SemVersion.Requirement.assertFalse(version: String): SemVersion.Requirement {
|
||||
|
@ -26,6 +26,7 @@ allprojects {
|
||||
maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven(url = "https://dl.bintray.com/karlatemp/misc")
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,14 +44,23 @@ subprojects {
|
||||
}
|
||||
|
||||
val experimentalAnnotations = arrayOf(
|
||||
"kotlin.Experimental",
|
||||
"kotlin.RequiresOptIn",
|
||||
"kotlin.ExperimentalUnsignedTypes",
|
||||
// "kotlin.ExperimentalStdlibApi",
|
||||
"kotlin.ExperimentalStdlibApi",
|
||||
"kotlin.contracts.ExperimentalContracts",
|
||||
"kotlin.time.ExperimentalTime",
|
||||
"kotlin.experimental.ExperimentalTypeInference",
|
||||
// "kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"kotlinx.serialization.ExperimentalSerializationApi",
|
||||
"kotlin.io.path.ExperimentalPathApi",
|
||||
"io.ktor.util.KtorExperimentalAPI",
|
||||
"kotlin.time.ExperimentalTime"
|
||||
|
||||
"net.mamoe.mirai.utils.MiraiInternalAPI",
|
||||
"net.mamoe.mirai.utils.MiraiExperimentalAPI",
|
||||
"net.mamoe.mirai.console.ConsoleFrontEndImplementation",
|
||||
"net.mamoe.mirai.console.util.ConsoleExperimentalApi",
|
||||
"net.mamoe.mirai.console.util.ConsoleInternalApi"
|
||||
)
|
||||
|
||||
|
||||
@ -139,10 +149,11 @@ fun Project.configureSourceSets() {
|
||||
fun Project.configureKotlinExperimentalUsages() {
|
||||
val sourceSets = kotlinSourceSets ?: return
|
||||
|
||||
for (target in sourceSets) {
|
||||
for (target in sourceSets) target.languageSettings.run {
|
||||
enableLanguageFeature("InlineClasses")
|
||||
progressiveMode = true
|
||||
experimentalAnnotations.forEach { a ->
|
||||
target.languageSettings.useExperimentalAnnotation(a)
|
||||
//target.languageSettings.enableLanguageFeature("InlineClasses")
|
||||
useExperimentalAnnotation(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,6 @@ import org.gradle.api.tasks.TaskContainer
|
||||
import org.gradle.api.tasks.bundling.Jar
|
||||
import org.gradle.kotlin.dsl.*
|
||||
import upload.Bintray
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
@ -35,7 +32,7 @@ import kotlin.reflect.KProperty
|
||||
* Configures the [bintray][com.jfrog.bintray.gradle.BintrayExtension] extension.
|
||||
*/
|
||||
@PublishedApi
|
||||
internal fun org.gradle.api.Project.`bintray`(configure: com.jfrog.bintray.gradle.BintrayExtension.() -> Unit): Unit =
|
||||
internal fun Project.`bintray`(configure: com.jfrog.bintray.gradle.BintrayExtension.() -> Unit): Unit =
|
||||
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("bintray", configure)
|
||||
|
||||
@PublishedApi
|
||||
@ -47,7 +44,7 @@ internal operator fun <U : Task> RegisteringDomainObjectDelegateProviderWithType
|
||||
)
|
||||
|
||||
@PublishedApi
|
||||
internal val org.gradle.api.Project.`sourceSets`: org.gradle.api.tasks.SourceSetContainer
|
||||
internal val Project.`sourceSets`: org.gradle.api.tasks.SourceSetContainer
|
||||
get() =
|
||||
(this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("sourceSets") as org.gradle.api.tasks.SourceSetContainer
|
||||
|
||||
@ -59,48 +56,10 @@ internal operator fun <T> ExistingDomainObjectDelegate<out T>.getValue(receiver:
|
||||
* Configures the [publishing][org.gradle.api.publish.PublishingExtension] extension.
|
||||
*/
|
||||
@PublishedApi
|
||||
internal fun org.gradle.api.Project.`publishing`(configure: org.gradle.api.publish.PublishingExtension.() -> Unit): Unit =
|
||||
internal fun Project.`publishing`(configure: org.gradle.api.publish.PublishingExtension.() -> Unit): Unit =
|
||||
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("publishing", configure)
|
||||
|
||||
|
||||
fun InputStream.md5(): ByteArray {
|
||||
val digest = MessageDigest.getInstance("md5")
|
||||
digest.reset()
|
||||
use { input ->
|
||||
object : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
digest.update(b.toByte())
|
||||
}
|
||||
}.use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
return digest.digest()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
@JvmOverloads
|
||||
fun ByteArray.toUHexString(
|
||||
separator: String = " ",
|
||||
offset: Int = 0,
|
||||
length: Int = this.size - offset
|
||||
): String {
|
||||
if (length == 0) {
|
||||
return ""
|
||||
}
|
||||
val lastIndex = offset + length
|
||||
return buildString(length * 2) {
|
||||
this@toUHexString.forEachIndexed { index, it ->
|
||||
if (index in offset until lastIndex) {
|
||||
var ret = it.toUByte().toString(16).toUpperCase()
|
||||
if (ret.length == 1) ret = "0$ret"
|
||||
append(ret)
|
||||
if (index < lastIndex - 1) append(separator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Project.setupPublishing(
|
||||
artifactId: String,
|
||||
bintrayRepo: String = "mirai",
|
||||
@ -125,6 +84,10 @@ inline fun Project.setupPublishing(
|
||||
|
||||
user = Bintray.getUser(project)
|
||||
key = Bintray.getKey(project)
|
||||
|
||||
publish = true
|
||||
override = true
|
||||
|
||||
setPublications("mavenJava")
|
||||
setConfigurations("archives")
|
||||
|
||||
|
@ -7,18 +7,23 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("MemberVisibilityCanBePrivate", "ObjectPropertyName", "unused")
|
||||
|
||||
object Versions {
|
||||
const val core = "1.3.2"
|
||||
const val console = "1.0-RC-1"
|
||||
const val core = "1.3.3"
|
||||
const val console = "1.0.1"
|
||||
const val consoleGraphical = "0.0.7"
|
||||
const val consoleTerminal = console
|
||||
|
||||
const val kotlinCompiler = "1.4.10"
|
||||
const val kotlinStdlib = kotlinCompiler
|
||||
const val kotlinCompiler = "1.4.20"
|
||||
const val kotlinStdlib = "1.4.20"
|
||||
|
||||
const val coroutines = "1.3.9"
|
||||
const val collectionsImmutable = "0.3.2"
|
||||
const val serialization = "1.0.0-RC"
|
||||
const val kotlinIntellijPlugin = "1.4.20-release-IJ2020.2-1" // keep to newest as kotlinCompiler
|
||||
const val intellij = "2020.2.1" // don't update easily unless you want your disk space -= 500MB
|
||||
|
||||
|
||||
const val coroutines = "1.4.0"
|
||||
const val serialization = "1.0.1"
|
||||
const val ktor = "1.4.1"
|
||||
const val atomicFU = "0.14.4"
|
||||
|
||||
@ -26,6 +31,38 @@ object Versions {
|
||||
|
||||
const val bintray = "1.8.5"
|
||||
|
||||
const val blockingBridge = "1.1.0"
|
||||
const val yamlkt = "0.5.3"
|
||||
}
|
||||
const val blockingBridge = "1.4.1"
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
const val yamlkt = "0.7.4"
|
||||
|
||||
const val intellijGradlePlugin = "0.4.16"
|
||||
}
|
||||
|
||||
const val `kotlin-compiler` = "org.jetbrains.kotlin:kotlin-compiler:${Versions.kotlinCompiler}"
|
||||
|
||||
const val `kotlin-stdlib` = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinStdlib}"
|
||||
const val `kotlin-stdlib-jdk8` = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlinStdlib}"
|
||||
const val `kotlin-reflect` = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlinStdlib}"
|
||||
const val `kotlin-test` = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlinStdlib}"
|
||||
const val `kotlin-test-junit5` = "org.jetbrains.kotlin:kotlin-test-junit5:${Versions.kotlinStdlib}"
|
||||
|
||||
const val `kotlinx-coroutines-core` = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}"
|
||||
const val `kotlinx-coroutines-jdk8` = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:${Versions.coroutines}"
|
||||
|
||||
const val `kotlinx-serialization-core` = "org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.serialization}"
|
||||
const val `kotlinx-serialization-json` = "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.serialization}"
|
||||
const val `kotlinx-serialization-protobuf` = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf:${Versions.serialization}"
|
||||
|
||||
const val `kotlinx-atomicfu` = "org.jetbrains.kotlinx:atomicfu:${Versions.atomicFU}"
|
||||
|
||||
const val `mirai-core` = "net.mamoe:mirai-core:${Versions.core}"
|
||||
const val `mirai-core-qqandroid` = "net.mamoe:mirai-core-qqandroid:${Versions.core}"
|
||||
const val `mirai-core-api` = "net.mamoe:mirai-core-api:${Versions.core}"
|
||||
|
||||
const val yamlkt = "net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}"
|
||||
|
||||
const val `jetbrains-annotations` = "org.jetbrains:annotations:19.0.0"
|
||||
|
||||
|
||||
const val `caller-finder` = "io.github.karlatemp:caller:1.0.1"
|
||||
|
@ -21,7 +21,7 @@ fun DependencyHandlerScope.ktor(id: String, version: String = Versions.ktor) = "
|
||||
@Suppress("unused")
|
||||
fun DependencyHandler.compileAndTestRuntime(any: Any) {
|
||||
add("compileOnly", any)
|
||||
add("testRuntimeOnly", any)
|
||||
add("testImplementation", any)
|
||||
}
|
||||
|
||||
fun DependencyHandler.smartApi(
|
||||
|
BIN
docs/.images/PluginMainDeclaration.png
Normal file
BIN
docs/.images/PluginMainDeclaration.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
BIN
docs/.images/PluginMainServiceNotConfigured.png
Normal file
BIN
docs/.images/PluginMainServiceNotConfigured.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
@ -127,7 +127,7 @@ Mirai Console 内建 [`SimpleCommand`] 与 [`CompositeCommand`] 拥有 [`Command
|
||||
object MySimpleCommand : SimpleCommand(
|
||||
MyPluginMain, "tell", "私聊",
|
||||
description = "Tell somebody privately",
|
||||
usage = "/tell <target> <message>" // usage 如不设置则自动根据带有 @Handler 的方法生成
|
||||
usage = "/tell <target> <message>", // usage 如不设置则自动根据带有 @Handler 的方法生成
|
||||
) {
|
||||
@Handler // 标记这是指令处理器 // 函数名随意
|
||||
suspend fun CommandSender.handle(target: User, message: String) { // 这两个参数会被作为指令参数要求
|
||||
|
@ -20,8 +20,8 @@ console 由后端和前端一起工作. 使用时必须选择一个前端.
|
||||
|
||||
| 版本类型 | 版本号 |
|
||||
|:------:|:------------------------------:|
|
||||
| 稳定 | - |
|
||||
| 预览 | 1.0-RC-1 |
|
||||
| 稳定 | 1.0.1 |
|
||||
| 预览 | - |
|
||||
| 开发 | [![Version]][Bintray Download] |
|
||||
|
||||
## 配置项目
|
||||
|
@ -26,7 +26,35 @@ Mirai Console 项目由四个模块组成:后端,前端,Gradle 插件,In
|
||||
|
||||
### 构建
|
||||
```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 第 1,2 步的修改为同一个 commit,commit 备注为版本号名称,如 `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 前几步的修改为同一个 commit,commit 备注为版本号名称,如 `1.1.0`;
|
||||
7. 在 GitHub release 根据 Git commit 记录编写更新记录:
|
||||
- 描述所有来自社区的 PR 记录;
|
||||
- 完整列举公开 API 的任何变动,简要描述或省略内部变动;
|
||||
- 为更改按 “后端”,“前端”,“IDE 插件”,“Gradle 插件” 分类;
|
||||
8. 点击 `Publish`。GitHub Actions 将会检测到 tag 更新并执行 JCenter 发布。
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
Mirai Console 前端开发文档。
|
||||
|
||||
[`MiraiConsole`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt
|
||||
[`MiraiConsole`]: ../backend/mirai-console/src/MiraiConsole.kt
|
||||
|
||||
## 实现前端
|
||||
|
||||
@ -10,10 +10,8 @@ Mirai Console 前端开发文档。
|
||||
|
||||
在 `build.gradle` 或 `build.gradle.kts` 添加:
|
||||
```kotlin
|
||||
kotlin {
|
||||
sourceSets.all {
|
||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
|
||||
}
|
||||
kotlin.sourceSets.all {
|
||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
|
||||
}
|
||||
```
|
||||
|
||||
@ -22,9 +20,9 @@ kotlin {
|
||||
|
||||
### 实现 Mirai Console
|
||||
|
||||
[`MiraiConsole`] 是后端的公开对象,由 [MiraiConsoleImplementationBridge](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/MiraiConsoleImplementationBridge.kt) 代理,与前端链接。
|
||||
[`MiraiConsole`] 是后端的公开对象,由 [MiraiConsoleImplementationBridge](../backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt) 代理,与前端链接。
|
||||
|
||||
前端需要实现 [MiraiConsoleImplementation.kt](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt)。
|
||||
前端需要实现 [MiraiConsoleImplementation.kt](../backend/mirai-console/src/MiraiConsoleImplementation)。
|
||||
|
||||
由于实现前端需要一定的技术能力,相信实现者都能理解源码内注释。
|
||||
|
||||
@ -32,4 +30,4 @@ kotlin {
|
||||
|
||||
通过 `public fun MiraiConsoleImplementation.start()`。
|
||||
|
||||
[MiraiConsoleImplementation.kt: Line 161](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt#L161)
|
||||
[MiraiConsoleImplementation.kt: Line 161](../backend/mirai-console/src/MiraiConsoleImplementation.kt#L161)
|
||||
|
@ -40,7 +40,7 @@ data class PermissionId(
|
||||
|
||||
[`PermissionId`] 是 [`Permission`] 的唯一标识符。知道 [`PermissionId`] 就可以获取到对应的 [`Permission`]。
|
||||
|
||||
字符串表示为 "$namespace:$name",如 "console:command.stop", "*:*"
|
||||
字符串表示为 "$namespace:$name",如 "console:command.stop", "\*:\*"
|
||||
|
||||
#### 命名空间
|
||||
|
||||
@ -68,7 +68,7 @@ data class PermissionId(
|
||||
|
||||
#### 根权限
|
||||
|
||||
[`RootPermission`] 是所有权限的父权限。其 ID 为 "*:*"
|
||||
[`RootPermission`] 是所有权限的父权限。其 ID 为 "\*:\*"
|
||||
|
||||
## 被许可人
|
||||
|
||||
@ -121,14 +121,14 @@ interface PermitteeId {
|
||||
| 精确临时会话 | t123456.789 | 群 123456 内的成员 789. 必须通过临时会话 |
|
||||
| 精确群成员 | m123456.789 | 群 123456 内的成员 789. 同时包含临时会话. |
|
||||
| 精确用户 | u123456 | 同时包含群成员, 好友, 临时会话 |
|
||||
| 任意群 | g* | |
|
||||
| 任意群的任意群员 | m* | |
|
||||
| 精确群的任意群员 | m123456.* | 群 123456 内的任意成员. 同时包含临时会话. |
|
||||
| 任意群的任意临时会话 | t* | 必须通过临时会话 |
|
||||
| 精确群的任意临时会话 | t123456.* | 群 123456 内的任意成员. 必须通过临时会话 |
|
||||
| 任意好友 | f* | |
|
||||
| 任意用户 | u* | 任何人在任何环境 |
|
||||
| 任意对象 | * | 即任何人, 任何群, 控制台 |
|
||||
| 任意群 | g\* | g 意为 group |
|
||||
| 任意群的任意群员 | m\* | m 意为 member |
|
||||
| 精确群的任意群员 | m123456.\* | 群 123456 内的任意成员. 同时包含临时会话. |
|
||||
| 任意群的任意临时会话 | t\* | t 意为 temp. 必须通过临时会话 |
|
||||
| 精确群的任意临时会话 | t123456.\* | 群 123456 内的任意成员. 必须通过临时会话 |
|
||||
| 任意好友 | f\* | f 意为 friend |
|
||||
| 任意用户 | u\* | u 意为 user. 任何人在任何环境 |
|
||||
| 任意对象 | \* | 即任何人, 任何群, 控制台 |
|
||||
|
||||
### 获取被许可人
|
||||
|
||||
@ -164,8 +164,9 @@ fun Permission.testPermission(PermitteeId): Boolean
|
||||
|
||||
### 使用内置权限服务指令
|
||||
|
||||
**根指令**: "/permission", "/perm", "/权限"
|
||||
**指令**: "/permission", "/perm", "/权限"
|
||||
|
||||
使用指令而不带参数可以获取如下用法:
|
||||
```
|
||||
/permission cancel <被许可人 ID> <权限 ID> 取消授权一个权限
|
||||
/permission cancelall <被许可人 ID> <权限 ID> 取消授权一个权限及其所有子权限
|
||||
|
@ -43,6 +43,9 @@
|
||||
[为什么不支持热加载和卸载插件?]: QA.md#为什么不支持热加载和卸载插件
|
||||
[使用 AutoService]: QA.md#使用-autoservice
|
||||
|
||||
[MCI]: ../tools/intellij-plugin/
|
||||
[MiraiPixel]: ../tools/intellij-plugin/resources/icons/pluginMainDeclaration.png
|
||||
|
||||
Mirai Console 运行在 [JVM],支持使用 [Kotlin] 或 [Java] 语言编写的插件。
|
||||
|
||||
## 通用的插件接口 - [`Plugin`]
|
||||
@ -63,23 +66,75 @@ interface Plugin : CommandOwner { // CommandOwner 是空的 interface
|
||||
|
||||
[`Plugin`] 接口拥有强扩展性,以支持 Mirai Console 统一管理使用其他编程语言编写的插件 (详见进阶章节 [扩展 - PluginLoader](Extensions.md))。
|
||||
|
||||
## 插件加载器 - [`PluginLoader`]
|
||||
|
||||
Mirai Console 使用不同的插件加载器来加载不同类型插件。
|
||||
|
||||
Mirai Console 内置 [`JvmPluginLoader`] 以加载 JVM 平台插件(见下文),并允许这些插件注册扩展的插件加载器(见章节 [扩展](Extensions.md))
|
||||
|
||||
## JVM 平台插件接口 - [`JvmPlugin`]
|
||||
|
||||
所有的 JVM 插件都必须实现 [`JvmPlugin`](否则不会被 [`JvmPluginLoader`] 加载)。
|
||||
所有的 JVM 插件(特别地,`jar` 插件)都必须实现 [`JvmPlugin`](否则不会被 [`JvmPluginLoader`] 加载)。
|
||||
Mirai Console 提供一些基础的实现,即 [`AbstractJvmPlugin`],并将 [`JvmPlugin`] 分为 [`KotlinPlugin`] 和 [`JavaPlugin`]。
|
||||
|
||||
### 主类和描述
|
||||
### 主类
|
||||
|
||||
JVM 平台插件的主类应被实现为一个单例(Kotlin `object`,Java 静态初始化的类,详见下文示例)。
|
||||
|
||||
**Kotlin 使用者的插件主类应继承 [`KotlinPlugin`]。**
|
||||
**其他 JVM 语言(如 Java)使用者的插件主类应继承 [`JavaPlugin`]。**
|
||||
|
||||
#### 描述
|
||||
插件描述需要在主类构造器传递给 `super`。因此插件不需要 `plugin.yml`, `plugin.xml` 等配置文件来指示信息。
|
||||
#### 定义主类
|
||||
|
||||
Mirai Console 使用类似 `ServiceLoader` 的机制加载插件。
|
||||
在 Kotlin,可([使用 AutoService])自动配置 service 信息。
|
||||
在 Kotlin 或其他语言,可手动创建 service 文件: 在 `jar` 内 `META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin` 文件内存放插件主类全名(以纯文本 UTF-8 存储,文件内容只包含一行插件主类全名).
|
||||
Mirai Console 使用类似 Java `ServiceLoader` 但更灵活的机制加载插件。
|
||||
|
||||
一个正确的主类定义可以是以下三种任一:
|
||||
|
||||
1. Kotlin (`public`) `object`
|
||||
```kotlin
|
||||
object A : KotlinPlugin( /* 描述 */ )
|
||||
```
|
||||
|
||||
2. Java (`public`) 静态初始化单例 `class`
|
||||
```java
|
||||
public final class A extends JavaPlugin {
|
||||
public static final A INSTANCE = new A(); // 必须 public static, 必须名为 INSTANCE
|
||||
private A() {
|
||||
super( /* 描述 */ );
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Java (`public`) `class`
|
||||
注意:这种由 Mirai Console 构造插件实例的方法是不推荐的。请首选上述静态初始化方法。
|
||||
```java
|
||||
public final class A extends JavaPlugin {
|
||||
public A() { // 必须公开且无参
|
||||
super( /* 描述 */ );
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 确认主类正确定义
|
||||
|
||||
在 [Mirai Console IntelliJ 插件][MCI] 的帮助下,一个正确的插件主类定义的行号处会显示 Mirai 像素风格形象图:![MiraiPixel]
|
||||
|
||||
![PluginMainDeclaration](.images/PluginMainDeclaration.png)
|
||||
|
||||
#### 配置主类服务
|
||||
|
||||
[Mirai Console IntelliJ 插件][MCI] 会自动检查主类服务的配置。在没有正确配置时,IDE 将会警告并为你自动配置:
|
||||
![PluginMainServiceNotConfigured](.images/PluginMainServiceNotConfigured.png)
|
||||
|
||||
##### 手动配置主类服务
|
||||
|
||||
若无法使用 IntelliJ 插件,可在资源目录 `META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin` 文件内存放插件主类全名(以纯文本 UTF-8 存储,文件内容只包含一行插件主类全名)。
|
||||
|
||||
在 Kotlin,也可([使用 AutoService])自动配置 service 信息。
|
||||
|
||||
### 描述
|
||||
|
||||
插件描述需要在主类构造器传递给 `super`。可以选择直接提供或从 JAR 资源文件读取。
|
||||
|
||||
有关插件版本号的限制:
|
||||
- 插件自身的版本要求遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/) 规范, 合格的版本例如: `1.0.0`, `1.0`, `1.0-M1`, `1.0-pre-1`
|
||||
@ -87,6 +142,10 @@ Mirai Console 使用类似 `ServiceLoader` 的机制加载插件。
|
||||
|
||||
有关描述的详细信息可在开发时查看源码内文档。
|
||||
|
||||
### 主类的完整示例
|
||||
|
||||
基于上文,你现在有以下三种主类定义方式。
|
||||
|
||||
#### 实现 Kotlin 插件主类
|
||||
|
||||
一个 Kotlin 插件的主类通常需:
|
||||
@ -109,7 +168,7 @@ object SchedulePlugin : KotlinPlugin(
|
||||
}
|
||||
```
|
||||
|
||||
#### 实现 Java 插件
|
||||
#### 实现 Java 插件主类
|
||||
|
||||
一个 Java 插件的主类通常需:
|
||||
- 继承 [`JavaPlugin`]
|
||||
@ -135,7 +194,7 @@ public final class JExample extends JavaPlugin {
|
||||
由 Console 初始化(仅在某些静态初始化不可用的情况下使用):
|
||||
```java
|
||||
public final class JExample extends JavaPlugin {
|
||||
private static final JExample instance;
|
||||
private static JExample instance;
|
||||
public static JExample getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
@ -43,7 +43,7 @@
|
||||
|
||||
- Java 中的「方法」在 Kotlin 中均被称为「函数」。
|
||||
- Kotlin 默认的访问权限是 `public`。如 Kotlin `class Test` 相当于 Java 的 `public class Test {}`
|
||||
- Kotlin 的函数定义 `fun test(int: Int): String` 相当于 Java 的方法定义 `public String test(int int)`
|
||||
- Kotlin 的函数定义 `fun test(int: Int): String` 相当于 Java 的方法定义 `public String test(int integer)`
|
||||
|
||||
### 在 Java 使用 Mirai Console 中的 Kotlin `suspend` 函数
|
||||
|
||||
|
97
docs/Run.md
97
docs/Run.md
@ -9,8 +9,10 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。
|
||||
|
||||
## 手动配置独立启动
|
||||
|
||||
强烈建议使用自动启动工具,若无法使用,可以参考如下手动启动方式。
|
||||
|
||||
### 环境
|
||||
- JRE 11+ / JDK 11+
|
||||
- JRE 8+ / JDK 8+
|
||||
|
||||
### 准备文件
|
||||
|
||||
@ -22,12 +24,34 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。
|
||||
|
||||
只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 terminal 前端可用。
|
||||
|
||||
#### 从 JCenter 下载模块
|
||||
|
||||
mirai 在版本发布时会将发布的构建存放与 [mirai-bintray-repo]。
|
||||
|
||||
- mirai-core 会提供 [mirai-core-all]
|
||||
- mirai-console 与其各个模块都会提供 `-all` 的 Shadowed 构建
|
||||
|
||||
```shell script
|
||||
# 注: 自行更换对应版本号
|
||||
|
||||
# Download mirai-core-all
|
||||
|
||||
curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-core-all/1.3.3/mirai-core-all-1.3.3-all.jar -o mirai-core-all-1.3.3.jar
|
||||
|
||||
# Download mirai-console
|
||||
|
||||
curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-console/1.0.0/mirai-console-1.0.0-all.jar -o mirai-console-1.0.0.jar
|
||||
|
||||
# Download mirai-console-terminal
|
||||
|
||||
curl -L https://maven.aliyun.com/repository/public/net/mamoe/mirai-console-terminal/1.0.0/mirai-console-terminal-1.0.0-all.jar -o mirai-console-terminal-1.0.0.jar
|
||||
|
||||
```
|
||||
|
||||
### 启动 mirai-console-terminal 前端
|
||||
|
||||
mirai 在版本发布时会同时发布打包依赖的 Shadow JAR,存放在 [mirai-repo]。
|
||||
|
||||
1. 在 [mirai-repo] 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`):
|
||||
- mirai-core-qqandroid
|
||||
1. 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`)(详见 [下载模块](#从-jcenter-下载模块)):
|
||||
- mirai-core-all
|
||||
- mirai-console
|
||||
- mirai-console-terminal
|
||||
|
||||
@ -51,19 +75,70 @@ pause
|
||||
Linux:
|
||||
```shell script
|
||||
#!/usr/bin/env bash
|
||||
echo -e '\033]2;Mirai Console\007'
|
||||
java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $*
|
||||
```
|
||||
|
||||
然后就可以开始使用 mirai-console 了
|
||||
然后就可以开始使用 mirai-console 了。
|
||||
|
||||
#### mirai-console-terminal 前端参数
|
||||
使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数
|
||||
使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数。
|
||||
|
||||
[mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow
|
||||
[mirai-bintray-repo]: https://bintray.com/him188moe/mirai
|
||||
[mirai-core-all]: https://bintray.com/him188moe/mirai/mirai-core-all
|
||||
|
||||
|
||||
### 启动 mirai-console-pure 前端
|
||||
## 嵌入应用启动(实验性)
|
||||
|
||||
与启动 `mirai-console-terminal` 前端大体相同
|
||||
- 下载 `mirai-console-terminal` 改成下载 `mirai-console-pure`
|
||||
- 启动入口从 `net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader` 改成 `net.mamoe.mirai.console.pure.MiraiConsolePureLoader`
|
||||
Mirai Console 可以嵌入一个 JVM 应用启动。
|
||||
|
||||
### 环境
|
||||
|
||||
- JDK 1.8+ / Android SDK 26+ (Android 8+)
|
||||
- Kotlin 1.4+
|
||||
|
||||
### 添加依赖
|
||||
|
||||
[选择版本](ConfiguringProjects.md#选择版本)
|
||||
|
||||
`build.gradle.kts`:
|
||||
```kotlin
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
implementation("net.mamoe:mirai-console:1.0.1")
|
||||
implementation("net.mamoe:mirai-console-terminal:1.0.1")
|
||||
implementation("net.mamoe:mirai-core:1.3.3")
|
||||
}
|
||||
```
|
||||
|
||||
### 启动 Terminal 前端
|
||||
|
||||
一行启动:
|
||||
```kotlin
|
||||
MiraiConsoleTerminalLoader.startAsDaemon()
|
||||
```
|
||||
|
||||
注意, Mirai Console 将会以 '守护进程' 形式启动,不会阻止主线程退出。
|
||||
|
||||
### 从内存加载 JVM 插件(实验性)
|
||||
|
||||
在嵌入使用时,插件可以直接加载:
|
||||
|
||||
```kotlin
|
||||
```kotlin
|
||||
MiraiConsoleTerminalLoader.startAsDaemon()
|
||||
// 先启动 Mirai Console
|
||||
|
||||
// Kotlin
|
||||
Plugin.load() // 扩展函数
|
||||
Plugin.enable() // 扩展函数
|
||||
|
||||
// Java
|
||||
PluginManager.INSTANCE.loadPlugin(Plugin)
|
||||
PluginManager.INSTANCE.enablePlugin(Plugin)
|
||||
```
|
||||
|
||||
但注意:这种方法目前是实验性的——一些特定的功能如注册扩展可能不会正常工作。
|
@ -7,21 +7,31 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCommandDescriptors::class)
|
||||
|
||||
package net.mamoe.mirai.console.terminal
|
||||
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.CommandExecuteResult.*
|
||||
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.StringConstant
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandReceiverParameter
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandValueParameter
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.terminal.noconsole.NoConsole
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.console.util.cast
|
||||
import net.mamoe.mirai.console.util.requestInput
|
||||
import net.mamoe.mirai.console.util.safeCast
|
||||
import net.mamoe.mirai.utils.DefaultLogger
|
||||
import net.mamoe.mirai.utils.warning
|
||||
import org.jline.reader.EndOfFileException
|
||||
import org.jline.reader.UserInterruptException
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
|
||||
val consoleLogger by lazy { DefaultLogger("console") }
|
||||
|
||||
@ -30,12 +40,13 @@ internal fun startupConsoleThread() {
|
||||
if (terminal is NoConsole) return
|
||||
|
||||
MiraiConsole.launch(CoroutineName("Input Cancelling Daemon")) {
|
||||
while (true) {
|
||||
while (isActive) {
|
||||
delay(2000)
|
||||
}
|
||||
}.invokeOnCompletion {
|
||||
runCatching<Unit> {
|
||||
terminal.close()
|
||||
// 应该仅关闭用户输入
|
||||
terminal.reader().shutdown()
|
||||
ConsoleInputImpl.thread.shutdownNow()
|
||||
runCatching {
|
||||
ConsoleInputImpl.executingCoroutine?.cancel(EndOfFileException())
|
||||
@ -57,21 +68,29 @@ internal fun startupConsoleThread() {
|
||||
continue
|
||||
}
|
||||
// consoleLogger.debug("INPUT> $next")
|
||||
val result = ConsoleCommandSender.executeCommand(next)
|
||||
when (result.status) {
|
||||
CommandExecuteStatus.SUCCESSFUL -> {
|
||||
when (val result = ConsoleCommandSender.executeCommand(next)) {
|
||||
is Success -> {
|
||||
}
|
||||
CommandExecuteStatus.ILLEGAL_ARGUMENT -> {
|
||||
result.exception?.message?.let { consoleLogger.warning(it) }
|
||||
is IllegalArgument -> { // user wouldn't want stacktrace for a parser error unless it is in debugging mode (to do).
|
||||
val message = result.exception.message
|
||||
if (message != null) {
|
||||
consoleLogger.warning(message)
|
||||
} else consoleLogger.warning(result.exception)
|
||||
}
|
||||
CommandExecuteStatus.EXECUTION_EXCEPTION -> {
|
||||
result.exception?.let(consoleLogger::error)
|
||||
is ExecutionFailed -> {
|
||||
consoleLogger.error(result.exception)
|
||||
}
|
||||
CommandExecuteStatus.COMMAND_NOT_FOUND -> {
|
||||
consoleLogger.warning("未知指令: ${result.commandName}, 输入 ? 获取帮助")
|
||||
is UnresolvedCommand -> {
|
||||
consoleLogger.warning { "未知指令: ${next}, 输入 ? 获取帮助" }
|
||||
}
|
||||
CommandExecuteStatus.PERMISSION_DENIED -> {
|
||||
consoleLogger.warning("Permission denied.")
|
||||
is PermissionDenied -> {
|
||||
consoleLogger.warning { "权限不足." }
|
||||
}
|
||||
is UnmatchedSignature -> {
|
||||
consoleLogger.warning { "参数不匹配, 你是否想执行: \n" + result.failureReasons.render(result.command, result.call) }
|
||||
}
|
||||
is Failure -> {
|
||||
consoleLogger.warning { result.toString() }
|
||||
}
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
@ -90,3 +109,61 @@ internal fun startupConsoleThread() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
private fun List<UnmatchedCommandSignature>.render(command: Command, call: CommandCall): String {
|
||||
val list =
|
||||
this.filter lambda@{ signature ->
|
||||
if (signature.failureReason.safeCast<FailureReason.InapplicableValueArgument>()?.parameter is StringConstant) return@lambda false
|
||||
if (signature.signature.valueParameters.anyStringConstantUnmatched(call.valueArguments)) return@lambda false
|
||||
true
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
return command.usage
|
||||
}
|
||||
return list.joinToString("\n") { it.render(command) }
|
||||
}
|
||||
|
||||
private fun List<CommandValueParameter<*>>.anyStringConstantUnmatched(arguments: List<CommandValueArgument>): Boolean {
|
||||
return this.zip(arguments).any { (parameter, argument) ->
|
||||
parameter is StringConstant && !parameter.accepts(argument, null)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal fun UnmatchedCommandSignature.render(command: Command): String {
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
val usage = net.mamoe.mirai.console.internal.command.CommandReflector.generateUsage(command, null, listOf(this.signature))
|
||||
return usage.trim() + " (${failureReason.render()})"
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal fun FailureReason.render(): String {
|
||||
return when (this) {
|
||||
is FailureReason.InapplicableArgument -> "参数类型错误"
|
||||
is FailureReason.InapplicableReceiverArgument -> "需要由 ${this.parameter.renderAsName()} 执行"
|
||||
is FailureReason.TooManyArguments -> "参数过多"
|
||||
is FailureReason.NotEnoughArguments -> "参数不足"
|
||||
is FailureReason.ResolutionAmbiguity -> "调用歧义"
|
||||
is FailureReason.ArgumentLengthMismatch -> {
|
||||
// should not happen, render it anyway.
|
||||
"参数长度不匹配"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal fun CommandReceiverParameter<*>.renderAsName(): String {
|
||||
val classifier = this.type.classifier.cast<KClass<out CommandSender>>()
|
||||
return when {
|
||||
classifier.isSubclassOf(ConsoleCommandSender::class) -> "控制台"
|
||||
classifier.isSubclassOf(FriendCommandSenderOnMessage::class) -> "好友私聊"
|
||||
classifier.isSubclassOf(FriendCommandSender::class) -> "好友"
|
||||
classifier.isSubclassOf(MemberCommandSenderOnMessage::class) -> "群内发言"
|
||||
classifier.isSubclassOf(MemberCommandSender::class) -> "群成员"
|
||||
classifier.isSubclassOf(TempCommandSenderOnMessage::class) -> "临时会话"
|
||||
classifier.isSubclassOf(TempCommandSender::class) -> "临时好友"
|
||||
classifier.isSubclassOf(UserCommandSender::class) -> "用户"
|
||||
else -> classifier.simpleName ?: classifier.toString()
|
||||
}
|
||||
}
|
@ -104,53 +104,44 @@ val lineReader: LineReader by lazy {
|
||||
val terminal: Terminal = run {
|
||||
if (ConsoleTerminalSettings.noConsole) return@run NoConsole
|
||||
|
||||
val dumb = System.getProperty("java.class.path")
|
||||
.contains("idea_rt.jar") || System.getProperty("mirai.idea") !== null || System.getenv("mirai.idea") !== null
|
||||
|
||||
runCatching {
|
||||
TerminalBuilder.builder()
|
||||
.dumb(dumb)
|
||||
.paused(true)
|
||||
.build()
|
||||
.let { terminal ->
|
||||
if (terminal is AbstractWindowsTerminal) {
|
||||
val pumpField = runCatching {
|
||||
AbstractWindowsTerminal::class.java.getDeclaredField("pump").also {
|
||||
it.isAccessible = true
|
||||
}
|
||||
}.onFailure { err ->
|
||||
err.printStackTrace()
|
||||
return@let terminal.also { it.resume() }
|
||||
}.getOrThrow()
|
||||
var response = terminal
|
||||
terminal.setOnClose {
|
||||
response = NoConsole
|
||||
TerminalBuilder.builder()
|
||||
.name("Mirai Console")
|
||||
.system(true)
|
||||
.jansi(true)
|
||||
.dumb(true)
|
||||
.paused(true)
|
||||
.build()
|
||||
.let { terminal ->
|
||||
if (terminal is AbstractWindowsTerminal) {
|
||||
val pumpField = runCatching {
|
||||
AbstractWindowsTerminal::class.java.getDeclaredField("pump").also {
|
||||
it.isAccessible = true
|
||||
}
|
||||
terminal.resume()
|
||||
val pumpThread = pumpField[terminal] as? Thread ?: return@let NoConsole
|
||||
@Suppress("ControlFlowWithEmptyBody")
|
||||
while (pumpThread.state == Thread.State.NEW);
|
||||
Thread.sleep(1000)
|
||||
terminal.setOnClose(null)
|
||||
return@let response
|
||||
}.onFailure { err ->
|
||||
err.printStackTrace()
|
||||
return@let terminal.also { it.resume() }
|
||||
}.getOrThrow()
|
||||
var response = terminal
|
||||
terminal.setOnClose {
|
||||
response = NoConsole
|
||||
}
|
||||
terminal.resume()
|
||||
terminal
|
||||
val pumpThread = pumpField[terminal] as? Thread ?: return@let NoConsole
|
||||
@Suppress("ControlFlowWithEmptyBody")
|
||||
while (pumpThread.state == Thread.State.NEW);
|
||||
Thread.sleep(1000)
|
||||
terminal.setOnClose(null)
|
||||
return@let response
|
||||
}
|
||||
}.recoverCatching {
|
||||
TerminalBuilder.builder()
|
||||
.jansi(true)
|
||||
.build()
|
||||
}.recoverCatching {
|
||||
TerminalBuilder.builder()
|
||||
.system(true)
|
||||
.build()
|
||||
}.getOrThrow()
|
||||
terminal.resume()
|
||||
terminal
|
||||
}
|
||||
}
|
||||
|
||||
private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription {
|
||||
override val name: String get() = "Terminal"
|
||||
override val vendor: String get() = "Mamoe Technologies"
|
||||
|
||||
// net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
|
||||
// is console's version not frontend's version
|
||||
override val version: SemVersion = SemVersion(net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.versionConst)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user