mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 18:40:15 +08:00
Merge remote-tracking branch 'origin/master' into dependencies
# Conflicts: # backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt # backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginClassLoader.kt
This commit is contained in:
commit
bf40b6036b
5
.editorconfig
Normal file
5
.editorconfig
Normal file
@ -0,0 +1,5 @@
|
||||
[*.{kt, kts}]
|
||||
max_line_length = 160
|
||||
tab_width = 4
|
||||
ij_continuation_indent_size = 4
|
||||
indent_size = 4
|
63
.github/workflows/Publishing.yml
vendored
Normal file
63
.github/workflows/Publishing.yml
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Bintray Publish
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- created
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Gradle clean
|
||||
run: ./gradlew clean
|
||||
- name: Gradle build
|
||||
run: ./gradlew build # if test's failed, don't publish
|
||||
- name: Check keys
|
||||
run: ./gradlew
|
||||
:mirai-console:ensureBintrayAvailable
|
||||
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
|
||||
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
|
||||
- name: Gradle :mirai-console:fillBuildConstants
|
||||
run: ./gradlew
|
||||
:mirai-console:fillBuildConstants
|
||||
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
|
||||
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
|
||||
- name: Gradle :mirai-console:bintrayUpload
|
||||
run: ./gradlew
|
||||
:mirai-console:bintrayUpload
|
||||
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
|
||||
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
|
||||
- name: Gradle :mirai-console-terminal:bintrayUpload
|
||||
run: ./gradlew
|
||||
:mirai-console-terminal:bintrayUpload
|
||||
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
|
||||
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
|
||||
- name: Gradle :mirai-console-compiler-common:bintrayUpload
|
||||
run: ./gradlew
|
||||
:mirai-console-compiler-common:bintrayUpload
|
||||
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
|
||||
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
|
||||
- name: Gradle :mirai-console-intellij:bintrayUpload
|
||||
run: ./gradlew
|
||||
:mirai-console-intellij:bintrayUpload
|
||||
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
|
||||
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
|
||||
- name: Publish Gradle plugin
|
||||
run: ./gradlew
|
||||
:mirai-console-gradle:publishPlugins
|
||||
-Dgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Pgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }}
|
||||
-Dgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }}
|
37
.github/workflows/bintray.yml
vendored
37
.github/workflows/bintray.yml
vendored
@ -1,37 +0,0 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: Bintray Publish
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- created
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Gradle clean
|
||||
run: ./gradlew clean
|
||||
- name: Gradle build
|
||||
run: ./gradlew build # if test's failed, don't publish
|
||||
- name: Check keys
|
||||
run: ./gradlew :mirai-console:ensureBintrayAvailable -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
|
||||
- name: Gradle :mirai-console:fillBuildConstants
|
||||
run: ./gradlew :mirai-console:fillBuildConstants -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
|
||||
- name: Gradle :mirai-console:bintrayUpload
|
||||
run: ./gradlew :mirai-console:bintrayUpload -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
|
||||
- name: Gradle :mirai-console-pure:bintrayUpload
|
||||
run: ./gradlew :mirai-console-pure:bintrayUpload -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
|
||||
|
4
.github/workflows/shadow.yml
vendored
4
.github/workflows/shadow.yml
vendored
@ -28,8 +28,8 @@ jobs:
|
||||
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-pure:githubUpload
|
||||
run: ./gradlew :mirai-console-pure: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
|
||||
|
BIN
.idea/icon.png
Normal file
BIN
.idea/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
60
README.md
60
README.md
@ -14,51 +14,33 @@ Mirai 是一个在全平台下运行,提供 QQ 协议支持的高效率机器
|
||||
</div>
|
||||
|
||||
# mirai-console
|
||||
[ ![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-console/images/download.svg?) ](https://bintray.com/him188moe/mirai/mirai-console/)
|
||||
|
||||
高效率插件支持 QQ 机器人框架, 机器人核心来自 [mirai](https://github.com/mamoe/mirai)
|
||||
高效率 QQ 机器人框架,机器人核心来自 [mirai](https://github.com/mamoe/mirai)
|
||||
|
||||
## 模块说明
|
||||
![Gradle CI](https://github.com/mamoe/mirai-console/workflows/Gradle%20CI/badge.svg?branch=master)
|
||||
[![Gitter](https://badges.gitter.im/mamoe/mirai.svg)](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
console 由后端和前端一起工作. 使用时必须选择一个前端.
|
||||
## 开发
|
||||
|
||||
- `mirai-console`: console 的后端, 包含插件管理, 指令系统, 配置系统.
|
||||
- **[准备工作 - 环境和前置知识](docs/Preparations.md)**
|
||||
- **[配置项目](docs/ConfiguringProjects.md)**
|
||||
- **[启动 Console](docs/Run.md)**
|
||||
|
||||
### 后端插件开发基础
|
||||
|
||||
- 插件 - [Plugin 模块](docs/Plugins.md)
|
||||
- 指令 - [Command 模块](docs/Commands.md)
|
||||
- 存储 - [PluginData 模块](docs/PluginData.md)
|
||||
- 权限 - [Permission 模块](docs/Permissions.md)
|
||||
|
||||
|
||||
**示例插件**:
|
||||
- [mirai-console-example-plugin (Kotlin DSL)](https://github.com/Him188/mirai-console-example-plugin)
|
||||
- [mirai-console-example-plugin (Groovy DSL)](https://github.com/Karlatemp/mirai-console-example-plugin)
|
||||
|
||||
前端:
|
||||
### 后端插件开发进阶
|
||||
|
||||
- `mirai-console-pure`: console 的轻量命令行前端.
|
||||
- `mirai-console-graphical`: console 的 JavaFX 图形化界面前端. (开发中)
|
||||
- `mirai-console-terminal`: console 的 Unix 终端界面前端. (开发中)
|
||||
- 扩展 - [Extension 模块和扩展点](docs/Extensions.md)
|
||||
|
||||
|
||||
**注意:`mirai-console` 后端和 pure 前端正在进行完全的重构, 所有 API 都不具有稳定性**
|
||||
|
||||
### 使用
|
||||
|
||||
**查看示例插件**: [mirai-console-example-plugin](https://github.com/Him188/mirai-console-example-plugin)
|
||||
|
||||
正在更新中的文档:[参考文档](docs/README.md)
|
||||
|
||||
#### Gradle
|
||||
`CORE_VERSION`: [ ![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg?) ](https://bintray.com/him188moe/mirai/mirai-core/)
|
||||
`CONSOLE_VERSION`: [ ![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-console/images/download.svg?) ](https://bintray.com/him188moe/mirai/mirai-console/)
|
||||
|
||||
|
||||
build.gradle.kts
|
||||
```kotlin
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("net.mamoe:mirai-core:$CORE_VERSION") // mirai-core 的 API
|
||||
implementation("net.mamoe:mirai-console:$CONSOLE_VERSION") // 后端
|
||||
|
||||
testImplementation("net.mamoe:mirai-console-pure:$CONSOLE_VERSION") // 前端, 用于启动测试
|
||||
}
|
||||
```
|
||||
|
||||
#### Maven
|
||||
同理 Gradle
|
||||
### 实现前端
|
||||
- [FrontEnd](docs/FrontEnd.md)
|
6
backend/codegen/README.md
Normal file
6
backend/codegen/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Mirai Console - Backend.codegen
|
||||
|
||||
后端代码生成模块,用于最小化重复代码的人工成本。
|
||||
|
||||
- `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)
|
3
backend/mirai-console/README.md
Normal file
3
backend/mirai-console/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Mirai Console - Backend
|
||||
|
||||
Mirai Console 后端模块. 发布为 `net.mamoe:mirai-console`.
|
@ -56,50 +56,26 @@ kotlin {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("net.mamoe:mirai-core:${Versions.core}")
|
||||
compileAndTestRuntime("net.mamoe:mirai-core:${Versions.core}")
|
||||
compileAndTestRuntime(kotlin("stdlib", Versions.kotlinStdlib))
|
||||
compileAndTestRuntime(kotlin("stdlib-jdk8", Versions.kotlinStdlib))
|
||||
|
||||
implementation(kotlinx("serialization-core", Versions.serialization))
|
||||
implementation(kotlin("reflect"))
|
||||
compileAndTestRuntime("org.jetbrains.kotlinx:atomicfu:${Versions.atomicFU}")
|
||||
compileAndTestRuntime(kotlinx("coroutines-core", Versions.coroutines))
|
||||
compileAndTestRuntime(kotlinx("serialization-core", Versions.serialization))
|
||||
compileAndTestRuntime(kotlin("reflect"))
|
||||
|
||||
api("net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}")
|
||||
implementation("org.jetbrains.kotlinx:atomicfu:${Versions.atomicFU}")
|
||||
api("org.jetbrains:annotations:19.0.0")
|
||||
api(kotlinx("coroutines-jdk8", Versions.coroutines))
|
||||
smartImplementation("net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}")
|
||||
smartImplementation("org.jetbrains:annotations:19.0.0")
|
||||
smartApi(kotlinx("coroutines-jdk8", Versions.coroutines))
|
||||
|
||||
api("com.vdurmont:semver4j:3.1.0")
|
||||
|
||||
//api(kotlinx("collections-immutable", Versions.collectionsImmutable))
|
||||
|
||||
testApi(kotlinx("serialization-core", Versions.serialization))
|
||||
testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}")
|
||||
testApi(kotlin("stdlib-jdk8"))
|
||||
testApi(kotlin("test"))
|
||||
testApi(kotlin("test-junit5"))
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.2.0")
|
||||
testApi("org.junit.jupiter:junit-jupiter-api:5.2.0")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.2.0")
|
||||
|
||||
|
||||
// val autoService = "1.0-rc7"
|
||||
// kapt("com.google.auto.service", "auto-service", autoService)
|
||||
// compileOnly("com.google.auto.service", "auto-service-annotations", autoService)
|
||||
}
|
||||
|
||||
ext.apply {
|
||||
// 傻逼 compileAndRuntime 没 exclude 掉
|
||||
// 傻逼 gradle 第二次配置 task 会覆盖掉第一次的配置
|
||||
val x: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.() -> Unit = {
|
||||
dependencyFilter.exclude {
|
||||
when ("${it.moduleGroup}:${it.moduleName}") {
|
||||
"net.mamoe:mirai-core" -> true
|
||||
"org.jetbrains.kotlin:kotlin-stdlib" -> true
|
||||
"org.jetbrains.kotlin:kotlin-stdlib-jdk8" -> true
|
||||
"net.mamoe:mirai-core-qqandroid" -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
set("shadowJar", x)
|
||||
}
|
||||
|
||||
tasks {
|
||||
@ -120,14 +96,14 @@ tasks {
|
||||
Regex("""val buildDate: Instant = Instant.ofEpochSecond\(.*\)""")
|
||||
) {
|
||||
"""val buildDate: Instant = Instant.ofEpochSecond(${
|
||||
Instant.now().getEpochSecond()
|
||||
Instant.now().epochSecond
|
||||
})"""
|
||||
}
|
||||
.replace(
|
||||
Regex("""val version: Semver = Semver\(".*", Semver.SemverType.LOOSE\)""")
|
||||
) { """val version: Semver = Semver("${project.version}", Semver.SemverType.LOOSE)""" }
|
||||
Regex("""const val versionConst:\s+String\s+=\s+".*"""")
|
||||
) { """const val versionConst: String = "${project.version}"""" }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,12 +12,12 @@
|
||||
|
||||
package net.mamoe.mirai.console
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.MiraiConsole.INSTANCE
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||
import net.mamoe.mirai.console.command.BuiltInCommands
|
||||
import net.mamoe.mirai.console.extensions.BotConfigurationAlterer
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
@ -28,6 +28,7 @@ import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.io.File
|
||||
@ -78,7 +79,7 @@ public interface MiraiConsole : CoroutineScope {
|
||||
/**
|
||||
* 此 Console 后端版本号
|
||||
*/
|
||||
public val version: Semver
|
||||
public val version: SemVersion
|
||||
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
@ -146,6 +147,10 @@ public interface MiraiConsole : CoroutineScope {
|
||||
else -> null!!
|
||||
}
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi("This is a low-level API and might be removed in the future.")
|
||||
public val isActive: Boolean
|
||||
get() = job.isActive
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,8 @@
|
||||
|
||||
package net.mamoe.mirai.console
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
|
||||
|
||||
/**
|
||||
* 有关前端实现的信息
|
||||
@ -28,7 +29,7 @@ public interface MiraiConsoleFrontEndDescription {
|
||||
/**
|
||||
* 此前端实现的名称
|
||||
*/
|
||||
public val version: Semver
|
||||
public val version: SemVersion
|
||||
|
||||
/**
|
||||
* 兼容的 [MiraiConsole] 后端版本号
|
||||
@ -37,11 +38,10 @@ public interface MiraiConsoleFrontEndDescription {
|
||||
*
|
||||
* 返回 `null` 表示禁止 [MiraiConsole] 后端检查版本兼容性.
|
||||
*/
|
||||
public val compatibleBackendVersion: Semver? get() = null
|
||||
public val compatibleBackendVersion: SemVersion? get() = null
|
||||
|
||||
/**
|
||||
* 返回显示在 [MiraiConsole] 启动时的信息
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun render(): String = "Frontend ${name}: version ${version}, provided by $vendor"
|
||||
}
|
@ -108,12 +108,10 @@ public interface MiraiConsoleImplementation : CoroutineScope {
|
||||
|
||||
|
||||
@JvmSynthetic
|
||||
@JvmDefault
|
||||
public override suspend fun sendMessage(message: Message): Unit =
|
||||
withContext(Dispatchers.IO) { sendMessageJ(message) }
|
||||
|
||||
@JvmSynthetic
|
||||
@JvmDefault
|
||||
public override suspend fun sendMessage(message: String): Unit =
|
||||
withContext(Dispatchers.IO) { sendMessageJ(message) }
|
||||
}
|
||||
@ -175,11 +173,21 @@ public interface MiraiConsoleImplementation : CoroutineScope {
|
||||
internal lateinit var instance: MiraiConsoleImplementation
|
||||
private val initLock = ReentrantLock()
|
||||
|
||||
/**
|
||||
* 可由前端调用, 获取当前的 [MiraiConsoleImplementation] 实例
|
||||
*
|
||||
* 必须在 [start] 之后才能使用.
|
||||
*/
|
||||
@JvmStatic
|
||||
@ConsoleFrontEndImplementation
|
||||
public fun getInstance(): MiraiConsoleImplementation = instance
|
||||
|
||||
/** 由前端调用, 初始化 [MiraiConsole] 实例并启动 */
|
||||
@JvmStatic
|
||||
@ConsoleFrontEndImplementation
|
||||
@Throws(MalformedMiraiConsoleImplementationError::class)
|
||||
public fun MiraiConsoleImplementation.start(): Unit = initLock.withLock {
|
||||
if (::instance.isInitialized) error("Mirai Console is already initialized.")
|
||||
this@Companion.instance = this
|
||||
MiraiConsoleImplementationBridge.doStart()
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
|
||||
/**
|
||||
* [Command] 的基础实现
|
||||
*
|
||||
* @see SimpleCommand
|
||||
* @see CompositeCommand
|
||||
* @see RawCommand
|
||||
*/
|
||||
public abstract class AbstractCommand
|
||||
@JvmOverloads constructor(
|
||||
public final override val owner: CommandOwner,
|
||||
public final override val primaryName: String,
|
||||
public final override val secondaryNames: Array<out String>,
|
||||
public override val description: String = "<no description available>",
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||
public override val prefixOptional: Boolean = false,
|
||||
) : Command {
|
||||
init {
|
||||
Command.checkCommandName(primaryName)
|
||||
secondaryNames.forEach(Command.Companion::checkCommandName)
|
||||
}
|
||||
|
||||
public override val usage: String get() = description
|
||||
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
|
||||
}
|
@ -132,12 +132,12 @@ public object BuiltInCommands {
|
||||
onFailure = { throwable ->
|
||||
sendMessage(
|
||||
"Login failed: ${throwable.localizedMessage ?: throwable.message ?: throwable.toString()}" +
|
||||
if (this is CommandSenderOnMessage<*>) {
|
||||
CommandManagerImpl.launch(CoroutineName("stacktrace delayer from Login")) {
|
||||
fromEvent.nextMessageOrNull(60.secondsToMillis) { it.message.contentEquals("stacktrace") }
|
||||
}
|
||||
"\n 1 分钟内发送 stacktrace 以获取堆栈信息"
|
||||
} else ""
|
||||
if (this is CommandSenderOnMessage<*>) {
|
||||
CommandManagerImpl.launch(CoroutineName("stacktrace delayer from Login")) {
|
||||
fromEvent.nextMessageOrNull(60.secondsToMillis) { it.message.contentEquals("stacktrace") }
|
||||
}
|
||||
"\n 1 分钟内发送 stacktrace 以获取堆栈信息"
|
||||
} else ""
|
||||
)
|
||||
|
||||
throw throwable
|
||||
@ -148,7 +148,7 @@ public object BuiltInCommands {
|
||||
|
||||
public object PermissionCommand : CompositeCommand(
|
||||
ConsoleCommandOwner, "permission", "权限", "perm",
|
||||
description = "Manage permissions",
|
||||
description = "管理权限",
|
||||
overrideContext = buildCommandArgumentContext {
|
||||
PermitteeId::class with PermitteeIdArgumentParser
|
||||
Permission::class with PermissionIdArgumentParser.map { id ->
|
||||
@ -159,30 +159,47 @@ public object BuiltInCommands {
|
||||
},
|
||||
), BuiltInCommandInternal {
|
||||
// TODO: 2020/9/10 improve Permission command
|
||||
|
||||
@Description("授权一个权限")
|
||||
@SubCommand("permit", "grant", "add")
|
||||
public suspend fun CommandSender.permit(target: PermitteeId, permission: Permission) {
|
||||
public suspend fun CommandSender.permit(
|
||||
@Name("被许可人 ID") target: PermitteeId,
|
||||
@Name("权限 ID") permission: Permission,
|
||||
) {
|
||||
target.grantPermission(permission)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@Description("取消授权一个权限")
|
||||
@SubCommand("cancel", "deny", "remove")
|
||||
public suspend fun CommandSender.cancel(target: PermitteeId, permission: Permission) {
|
||||
public suspend fun CommandSender.cancel(
|
||||
@Name("被许可人 ID") target: PermitteeId,
|
||||
@Name("权限 ID") permission: Permission,
|
||||
) {
|
||||
target.denyPermission(permission, false)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@Description("取消授权一个权限及其所有子权限")
|
||||
@SubCommand("cancelAll", "denyAll", "removeAll")
|
||||
public suspend fun CommandSender.cancelAll(target: PermitteeId, permission: Permission) {
|
||||
public suspend fun CommandSender.cancelAll(
|
||||
@Name("被许可人 ID") target: PermitteeId,
|
||||
@Name("权限 ID") permission: Permission,
|
||||
) {
|
||||
target.denyPermission(permission, true)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@Description("查看被授权权限列表")
|
||||
@SubCommand("permittedPermissions", "pp", "grantedPermissions", "gp")
|
||||
public suspend fun CommandSender.permittedPermissions(target: PermitteeId) {
|
||||
public suspend fun CommandSender.permittedPermissions(
|
||||
@Name("被许可人 ID") target: PermitteeId,
|
||||
) {
|
||||
val grantedPermissions = target.getPermittedPermissions()
|
||||
sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() })
|
||||
}
|
||||
|
||||
@Description("查看所有权限列表")
|
||||
@SubCommand("listPermissions", "lp")
|
||||
public suspend fun CommandSender.listPermissions() {
|
||||
sendMessage(PermissionService.INSTANCE.getRegisteredPermissions().joinToString("\n") { it.id.toString() })
|
||||
|
@ -15,11 +15,11 @@ import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||||
import net.mamoe.mirai.console.command.java.JCommand
|
||||
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
||||
import net.mamoe.mirai.console.internal.command.isValidSubName
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
|
||||
/**
|
||||
* 指令
|
||||
@ -34,11 +34,19 @@ import net.mamoe.mirai.message.data.SingleMessage
|
||||
*/
|
||||
public interface Command {
|
||||
/**
|
||||
* 指令名. 需要至少有一个元素. 所有元素都不能带有空格
|
||||
* 主指令名. 将会参与构成 [Permission.id].
|
||||
*
|
||||
* @see Command.primaryName 获取主要指令名
|
||||
* 不允许包含 [空格][Char.isWhitespace], '.', ':'.
|
||||
*/
|
||||
public val names: Array<out String>
|
||||
@ResolveContext(COMMAND_NAME)
|
||||
public val primaryName: String
|
||||
|
||||
/**
|
||||
* 次要指令名
|
||||
* @see Command.primaryName 获取主指令名
|
||||
*/
|
||||
@ResolveContext(COMMAND_NAME)
|
||||
public val secondaryNames: Array<out String>
|
||||
|
||||
/**
|
||||
* 用法说明, 用于发送给用户. [usage] 一般包含 [description].
|
||||
@ -51,12 +59,18 @@ public interface Command {
|
||||
public val description: String
|
||||
|
||||
/**
|
||||
* 指令权限
|
||||
* 此指令所分配的权限.
|
||||
*
|
||||
* ### 实现约束
|
||||
* - [Permission.id] 应由 [CommandOwner.permissionId] 创建. 因此保证相同的 [PermissionId.namespace]
|
||||
* - [PermissionId.name] 应为 [主指令名][primaryName]
|
||||
*/
|
||||
public val permission: Permission
|
||||
|
||||
/**
|
||||
* 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选
|
||||
* 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选.
|
||||
*
|
||||
* 会影响聊天语境中的解析.
|
||||
*/
|
||||
public val prefixOptional: Boolean
|
||||
|
||||
@ -69,7 +83,7 @@ public interface Command {
|
||||
/**
|
||||
* 在指令被执行时调用.
|
||||
*
|
||||
* @param args 指令参数. 数组元素类型可能是 [SingleMessage] 或 [String]. 且已经以 ' ' 分割.
|
||||
* @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数.
|
||||
*
|
||||
* @see CommandManager.executeCommand 查看更多信息
|
||||
*/
|
||||
@ -77,41 +91,37 @@ public interface Command {
|
||||
public suspend fun CommandSender.onCommand(args: MessageChain)
|
||||
|
||||
public companion object {
|
||||
|
||||
/**
|
||||
* 主要指令名. 为 [Command.names] 的第一个元素.
|
||||
* 获取所有指令名称 (包含 [primaryName] 和 [secondaryNames]).
|
||||
*
|
||||
* @return 数组大小至少为 1. 第一个元素总是 [primaryName]. 随后是保持原顺序的 [secondaryNames]
|
||||
*/
|
||||
@JvmStatic
|
||||
public val Command.primaryName: String
|
||||
get() = names[0]
|
||||
public val Command.allNames: Array<String>
|
||||
get() = arrayOf(primaryName, *secondaryNames)
|
||||
|
||||
/**
|
||||
* 检查指令名的合法性. 在非法时抛出 [IllegalArgumentException]
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
public fun checkCommandName(@ResolveContext(COMMAND_NAME) name: String) {
|
||||
when {
|
||||
name.isBlank() -> throw IllegalArgumentException("Command name should not be blank.")
|
||||
name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in command name.")
|
||||
name.contains(':') -> throw IllegalArgumentException("':' is forbidden in command name.")
|
||||
name.contains('.') -> throw IllegalArgumentException("'.' is forbidden in command name.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 [Command.onCommand]
|
||||
* @see Command.onCommand
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit =
|
||||
sender.onCommand(args)
|
||||
|
||||
/**
|
||||
* [Command] 的基础实现
|
||||
*
|
||||
* @see SimpleCommand
|
||||
* @see CompositeCommand
|
||||
* @see RawCommand
|
||||
*/
|
||||
public abstract class AbstractCommand
|
||||
@JvmOverloads constructor(
|
||||
/** 指令拥有者. */
|
||||
public override val owner: CommandOwner,
|
||||
vararg names: String,
|
||||
description: String = "<no description available>",
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||
public override val prefixOptional: Boolean = false,
|
||||
) : Command {
|
||||
public override val description: String = description.trimIndent()
|
||||
public override val names: Array<out String> =
|
||||
names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).also { list ->
|
||||
list.firstOrNull { !it.isValidSubName() }?.let { error("Invalid name: $it") }
|
||||
}.toTypedArray()
|
||||
|
||||
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
|
||||
}
|
@ -11,7 +11,6 @@
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.Command.Companion.primaryName
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
|
||||
/**
|
||||
|
@ -26,11 +26,15 @@ import net.mamoe.mirai.message.data.*
|
||||
public interface CommandManager {
|
||||
/**
|
||||
* 获取已经注册了的属于这个 [CommandOwner] 的指令列表.
|
||||
*
|
||||
* @return 这一时刻的浅拷贝.
|
||||
*/
|
||||
public val CommandOwner.registeredCommands: List<Command>
|
||||
|
||||
/**
|
||||
* 获取所有已经注册了指令列表.
|
||||
*
|
||||
* @return 这一时刻的浅拷贝.
|
||||
*/
|
||||
public val allRegisteredCommands: List<Command>
|
||||
|
||||
@ -49,8 +53,8 @@ public interface CommandManager {
|
||||
*
|
||||
* @param override 是否覆盖重名指令.
|
||||
*
|
||||
* 若原有指令 P, 其 [Command.names] 为 'a', 'b', 'c'.
|
||||
* 新指令 Q, 其 [Command.names] 为 'b', 将会覆盖原指令 A 注册的 'b'.
|
||||
* 若原有指令 P, 其 [Command.secondaryNames] 为 'a', 'b', 'c'.
|
||||
* 新指令 Q, 其 [Command.secondaryNames] 为 'b', 将会覆盖原指令 A 注册的 'b'.
|
||||
*
|
||||
* 即注册完成后, 'a' 和 'c' 将会解析到指令 P, 而 'b' 会解析到指令 Q.
|
||||
*
|
||||
@ -71,19 +75,23 @@ public interface CommandManager {
|
||||
public fun Command.findDuplicate(): Command?
|
||||
|
||||
/**
|
||||
* 取消注册这个指令. 若指令未注册, 返回 `false`.
|
||||
* 取消注册这个指令.
|
||||
*
|
||||
* 若指令未注册, 返回 `false`.
|
||||
*/
|
||||
@JvmName("unregisterCommand")
|
||||
public fun Command.unregister(): Boolean
|
||||
|
||||
/**
|
||||
* 当 [this] 已经 [注册][register] 后返回 `true`
|
||||
* 当 [this] 已经 [注册][register] 时返回 `true`
|
||||
*/
|
||||
@JvmName("isCommandRegistered")
|
||||
public fun Command.isRegistered(): Boolean
|
||||
|
||||
/**
|
||||
* 解析并执行一个指令
|
||||
* 解析并执行一个指令.
|
||||
*
|
||||
* 如要避免参数解析, 请使用 [Command.onCommand]
|
||||
*
|
||||
* ### 指令解析流程
|
||||
* 1. [message] 的第一个消息元素的 [内容][Message.contentToString] 被作为指令名, 在已注册指令列表中搜索. (包含 [Command.prefixOptional] 相关的处理)
|
||||
@ -116,7 +124,6 @@ public interface CommandManager {
|
||||
* @return 执行结果
|
||||
* @see executeCommand
|
||||
*/
|
||||
@JvmDefault
|
||||
@JvmBlockingBridge
|
||||
public suspend fun CommandSender.executeCommand(
|
||||
message: String,
|
||||
@ -139,7 +146,6 @@ public interface CommandManager {
|
||||
* 执行一个确切的指令
|
||||
* @see executeCommand 获取更多信息
|
||||
*/
|
||||
@JvmDefault
|
||||
@JvmBlockingBridge
|
||||
@JvmName("executeCommand")
|
||||
public suspend fun Command.execute(
|
||||
@ -151,7 +157,7 @@ public interface CommandManager {
|
||||
public companion object INSTANCE : CommandManager by CommandManagerImpl {
|
||||
// TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191
|
||||
|
||||
override val CommandOwner.registeredCommands: List<Command> get() = CommandManagerImpl.run { registeredCommands }
|
||||
override val CommandOwner.registeredCommands: List<Command> get() = CommandManagerImpl.run { this@registeredCommands.registeredCommands }
|
||||
override fun CommandOwner.unregisterAllCommands(): Unit = CommandManagerImpl.run { unregisterAllCommands() }
|
||||
override fun Command.register(override: Boolean): Boolean = CommandManagerImpl.run { register(override) }
|
||||
override fun Command.findDuplicate(): Command? = CommandManagerImpl.run { findDuplicate() }
|
||||
|
@ -10,6 +10,8 @@
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAME
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.permission.PermissionIdNamespace
|
||||
@ -36,5 +38,7 @@ public interface CommandOwner : PermissionIdNamespace {
|
||||
internal object ConsoleCommandOwner : CommandOwner {
|
||||
override val parentPermission: Permission get() = BuiltInCommands.parentPermission
|
||||
|
||||
override fun permissionId(name: String): PermissionId = PermissionId("console", "command.$name")
|
||||
override fun permissionId(
|
||||
@ResolveContext(PERMISSION_NAME) name: String,
|
||||
): PermissionId = PermissionId("console", name)
|
||||
}
|
@ -9,7 +9,6 @@
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.Command.Companion.primaryName
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
|
||||
/**
|
||||
|
@ -171,7 +171,6 @@ public interface CommandSender : CoroutineScope, Permittee {
|
||||
*
|
||||
* 对于 [MemberCommandSender], 这个函数总是发送给所在群
|
||||
*/
|
||||
@JvmDefault
|
||||
@JvmBlockingBridge
|
||||
public suspend fun sendMessage(message: String): MessageReceipt<Contact>?
|
||||
|
||||
@ -501,7 +500,6 @@ public fun CommandSender.getBotOrNull(): Bot? {
|
||||
*
|
||||
* 控制台拥有一切指令的执行权限.
|
||||
*/
|
||||
// 前端实现
|
||||
public object ConsoleCommandSender : AbstractCommandSender() {
|
||||
public const val NAME: String = "ConsoleCommandSender"
|
||||
|
||||
@ -514,12 +512,15 @@ public object ConsoleCommandSender : AbstractCommandSender() {
|
||||
public override val permitteeId: AbstractPermitteeId.Console = AbstractPermitteeId.Console
|
||||
|
||||
public override val coroutineContext: CoroutineContext by lazy { MiraiConsole.childScopeContext(NAME) }
|
||||
|
||||
@JvmBlockingBridge
|
||||
public override suspend fun sendMessage(message: Message): Nothing? {
|
||||
MiraiConsoleImplementationBridge.consoleCommandSender.sendMessage(message)
|
||||
return null
|
||||
}
|
||||
|
||||
public override suspend fun sendMessage(message: String): MessageReceipt<User>? {
|
||||
@JvmBlockingBridge
|
||||
public override suspend fun sendMessage(message: String): Nothing? {
|
||||
MiraiConsoleImplementationBridge.consoleCommandSender.sendMessage(message)
|
||||
return null
|
||||
}
|
||||
|
@ -18,9 +18,12 @@
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.description.*
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
|
||||
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
@ -81,12 +84,13 @@ import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
*/
|
||||
public abstract class CompositeCommand(
|
||||
owner: CommandOwner,
|
||||
vararg names: String,
|
||||
@ResolveContext(COMMAND_NAME) primaryName: String,
|
||||
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
|
||||
description: String = "no description available",
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
prefixOptional: Boolean = false,
|
||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
|
||||
) : Command, AbstractReflectionCommand(owner, names, description, parentPermission, prefixOptional),
|
||||
) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
|
||||
CommandArgumentContextAware {
|
||||
|
||||
/**
|
||||
@ -105,7 +109,9 @@ public abstract class CompositeCommand(
|
||||
*/
|
||||
@Retention(RUNTIME)
|
||||
@Target(FUNCTION)
|
||||
protected annotation class SubCommand(vararg val value: String)
|
||||
protected annotation class SubCommand(
|
||||
@ResolveContext(COMMAND_NAME) vararg val value: String,
|
||||
)
|
||||
|
||||
/** 指令描述 */
|
||||
@Retention(RUNTIME)
|
||||
@ -113,6 +119,7 @@ public abstract class CompositeCommand(
|
||||
protected annotation class Description(val value: String)
|
||||
|
||||
/** 参数名, 将参与构成 [usage] */
|
||||
@ConsoleExperimentalApi("Classname might change")
|
||||
@Retention(RUNTIME)
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||
protected annotation class Name(val value: String)
|
||||
|
@ -14,6 +14,8 @@ package net.mamoe.mirai.console.command
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.java.JRawCommand
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
@ -35,11 +37,13 @@ public abstract class RawCommand(
|
||||
* @see CommandOwner
|
||||
*/
|
||||
public override val owner: CommandOwner,
|
||||
/** 指令名. 需要至少有一个元素. 所有元素都不能带有空格 */
|
||||
public override vararg val names: String,
|
||||
/** 主指令名. */
|
||||
@ResolveContext(COMMAND_NAME) public override val primaryName: String,
|
||||
/** 次要指令名. */
|
||||
@ResolveContext(COMMAND_NAME) public override vararg val secondaryNames: String,
|
||||
/** 用法说明, 用于发送给用户 */
|
||||
public override val usage: String = "<no usages given>",
|
||||
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
|
||||
/** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */
|
||||
public override val description: String = "<no descriptions given>",
|
||||
/** 指令父权限 */
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
|
@ -20,6 +20,8 @@ package net.mamoe.mirai.console.command
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.description.*
|
||||
import net.mamoe.mirai.console.command.java.JSimpleCommand
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
|
||||
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
@ -50,12 +52,13 @@ import net.mamoe.mirai.message.data.MessageChain
|
||||
*/
|
||||
public abstract class SimpleCommand(
|
||||
owner: CommandOwner,
|
||||
vararg names: String,
|
||||
@ResolveContext(COMMAND_NAME) primaryName: String,
|
||||
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
|
||||
description: String = "no description available",
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
prefixOptional: Boolean = false,
|
||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
|
||||
) : Command, AbstractReflectionCommand(owner, names, description, parentPermission, prefixOptional),
|
||||
) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
|
||||
CommandArgumentContextAware {
|
||||
|
||||
/**
|
||||
|
@ -20,6 +20,8 @@ import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.permission.PermitteeId
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
@ -75,6 +77,9 @@ public interface CommandArgumentContext {
|
||||
Double::class with DoubleArgumentParser
|
||||
Float::class with FloatArgumentParser
|
||||
|
||||
Image::class with ImageArgumentParser
|
||||
PlainText::class with PlainTextArgumentParser
|
||||
|
||||
Contact::class with ExistingContactArgumentParser
|
||||
User::class with ExistingUserArgumentParser
|
||||
Member::class with ExistingMemberArgumentParser
|
||||
|
@ -77,7 +77,6 @@ public interface CommandArgumentParser<out T : Any> {
|
||||
* @see CommandArgumentParserException
|
||||
*/
|
||||
@Throws(CommandArgumentParserException::class)
|
||||
@JvmDefault
|
||||
public fun parse(raw: MessageContent, sender: CommandSender): T = parse(raw.content, sender)
|
||||
}
|
||||
|
||||
|
@ -19,10 +19,7 @@ import net.mamoe.mirai.console.permission.PermitteeId
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.getFriendOrNull
|
||||
import net.mamoe.mirai.getGroupOrNull
|
||||
import net.mamoe.mirai.message.data.At
|
||||
import net.mamoe.mirai.message.data.MessageContent
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
import net.mamoe.mirai.message.data.content
|
||||
import net.mamoe.mirai.message.data.*
|
||||
|
||||
|
||||
/**
|
||||
@ -80,15 +77,44 @@ public object StringArgumentParser : InternalCommandArgumentParserExtensions<Str
|
||||
public override fun parse(raw: String, sender: CommandSender): String = raw
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 [String] 通过 [Image].
|
||||
*/
|
||||
public object ImageArgumentParser : InternalCommandArgumentParserExtensions<Image> {
|
||||
public override fun parse(raw: String, sender: CommandSender): Image {
|
||||
return kotlin.runCatching {
|
||||
Image(raw)
|
||||
}.getOrElse {
|
||||
illegalArgument("无法解析 $raw 为图片.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun parse(raw: MessageContent, sender: CommandSender): Image {
|
||||
if (raw is Image) return raw
|
||||
return super.parse(raw, sender)
|
||||
}
|
||||
}
|
||||
|
||||
public object PlainTextArgumentParser : InternalCommandArgumentParserExtensions<PlainText> {
|
||||
public override fun parse(raw: String, sender: CommandSender): PlainText {
|
||||
return PlainText(raw)
|
||||
}
|
||||
|
||||
override fun parse(raw: MessageContent, sender: CommandSender): PlainText {
|
||||
if (raw is PlainText) return raw
|
||||
return super.parse(raw, sender)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当字符串内容为(不区分大小写) "true", "yes", "enabled"
|
||||
*/
|
||||
public object BooleanArgumentParser : InternalCommandArgumentParserExtensions<Boolean> {
|
||||
public override fun parse(raw: String, sender: CommandSender): Boolean = raw.trim().let { str ->
|
||||
str.equals("true", ignoreCase = true)
|
||||
|| str.equals("yes", ignoreCase = true)
|
||||
|| str.equals("enabled", ignoreCase = true)
|
||||
|| str.equals("on", ignoreCase = true)
|
||||
|| str.equals("yes", ignoreCase = true)
|
||||
|| str.equals("enabled", ignoreCase = true)
|
||||
|| str.equals("on", ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,10 +391,10 @@ internal interface InternalCommandArgumentParserExtensions<T : Any> : CommandArg
|
||||
} else {
|
||||
var index = 1
|
||||
illegalArgument("无法找到成员 $idOrCard。 多个成员满足搜索结果或匹配度不足: \n\n" +
|
||||
candidates.joinToString("\n", limit = 6) {
|
||||
val percentage = (it.second * 100).toDecimalPlace(0)
|
||||
"#${index++}(${percentage}%)${it.first.nameCardOrNick.truncate(10)}(${it.first.id})" // #1 15.4%
|
||||
}
|
||||
candidates.joinToString("\n", limit = 6) {
|
||||
val percentage = (it.second * 100).toDecimalPlace(0)
|
||||
"#${index++}(${percentage}%)${it.first.nameCardOrNick.truncate(10)}(${it.first.id})" // #1 15.4%
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
|
||||
/**
|
||||
* 为 Java 用户添加协程帮助的 [Command].
|
||||
@ -33,9 +32,9 @@ public interface JCommand : Command {
|
||||
/**
|
||||
* 在指令被执行时调用.
|
||||
*
|
||||
* @param args 指令参数. 数组元素类型可能是 [SingleMessage] 或 [String]. 且已经以 ' ' 分割.
|
||||
* @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数.
|
||||
*
|
||||
* @see CommandManager.executeCommand 查看更多信息
|
||||
*/
|
||||
public fun onCommand(sender: CommandSender, args: MessageChain) // overrides bridge
|
||||
public fun onCommand(sender: CommandSender, args: MessageChain) // overrides blocking bridge
|
||||
}
|
@ -14,6 +14,8 @@ import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.CommandOwner
|
||||
import net.mamoe.mirai.console.command.CompositeCommand
|
||||
import net.mamoe.mirai.console.command.description.buildCommandArgumentContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
|
||||
/**
|
||||
@ -24,7 +26,7 @@ import net.mamoe.mirai.console.permission.Permission
|
||||
* public final class MyCompositeCommand extends CompositeCommand {
|
||||
* public static final MyCompositeCommand INSTANCE = new MyCompositeCommand();
|
||||
*
|
||||
* public MyCompositeCommand() {
|
||||
* private MyCompositeCommand() {
|
||||
* super(MyPluginMain.INSTANCE, "manage") // "manage" 是主指令名
|
||||
* }
|
||||
*
|
||||
@ -69,11 +71,12 @@ import net.mamoe.mirai.console.permission.Permission
|
||||
public abstract class JCompositeCommand
|
||||
@JvmOverloads constructor(
|
||||
owner: CommandOwner,
|
||||
vararg names: String,
|
||||
@ResolveContext(COMMAND_NAME) primaryName: String,
|
||||
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
) : CompositeCommand(owner, *names, parentPermission = parentPermission) {
|
||||
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
|
||||
public final override var description: String = "<no descriptions given>"
|
||||
) : CompositeCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = parentPermission) {
|
||||
/** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */
|
||||
public final override var description: String = "<no descriptions available>"
|
||||
protected set
|
||||
|
||||
public final override var permission: Permission = super.permission
|
||||
|
@ -13,6 +13,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
@ -51,15 +53,15 @@ public abstract class JRawCommand
|
||||
* @see CommandOwner
|
||||
*/
|
||||
public override val owner: CommandOwner,
|
||||
/** 指令名. 需要至少有一个元素. 所有元素都不能带有空格 */
|
||||
public override vararg val names: String,
|
||||
@ResolveContext(COMMAND_NAME) public override val primaryName: String,
|
||||
@ResolveContext(COMMAND_NAME) public override vararg val secondaryNames: String,
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
) : Command {
|
||||
/** 用法说明, 用于发送给用户 */
|
||||
public override var usage: String = "<no usages given>"
|
||||
protected set
|
||||
|
||||
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
|
||||
/** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */
|
||||
public final override var description: String = "<no descriptions given>"
|
||||
protected set
|
||||
|
||||
|
@ -14,12 +14,15 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.CommandOwner
|
||||
import net.mamoe.mirai.console.command.SimpleCommand
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
|
||||
/**
|
||||
* Java 实现:
|
||||
* ```java
|
||||
* public final class MySimpleCommand extends JSimpleCommand {
|
||||
* public static final MySimpleCommand INSTANCE = new MySimpleCommand();
|
||||
* private MySimpleCommand() {
|
||||
* super(MyPlugin.INSTANCE, "tell")
|
||||
* // 可选设置如下属性
|
||||
@ -41,9 +44,10 @@ import net.mamoe.mirai.console.permission.Permission
|
||||
*/
|
||||
public abstract class JSimpleCommand(
|
||||
owner: CommandOwner,
|
||||
vararg names: String,
|
||||
@ResolveContext(COMMAND_NAME) primaryName: String,
|
||||
@ResolveContext(COMMAND_NAME) vararg secondaryNames: String,
|
||||
basePermission: Permission,
|
||||
) : SimpleCommand(owner, *names, parentPermission = basePermission) {
|
||||
) : SimpleCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = basePermission) {
|
||||
public override var description: String = super.description
|
||||
protected set
|
||||
|
||||
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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")
|
||||
|
||||
package net.mamoe.mirai.console.compiler.common
|
||||
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
|
||||
/**
|
||||
* 标记一个参数的语境类型, 用于帮助编译器和 IntelliJ 插件进行语境推断.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@Target(
|
||||
VALUE_PARAMETER,
|
||||
PROPERTY, FIELD,
|
||||
FUNCTION,
|
||||
TYPE, TYPE_PARAMETER
|
||||
)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
public annotation class ResolveContext(
|
||||
val kind: Kind,
|
||||
) {
|
||||
/**
|
||||
* 元素数量可能在任意时间被改动
|
||||
*/
|
||||
public enum class Kind {
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// ConstantKind
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PLUGIN_ID, // ILLEGAL_PLUGIN_DESCRIPTION
|
||||
PLUGIN_NAME, // ILLEGAL_PLUGIN_DESCRIPTION
|
||||
PLUGIN_VERSION, // ILLEGAL_PLUGIN_DESCRIPTION
|
||||
|
||||
COMMAND_NAME, // ILLEGAL_COMMAND_NAME
|
||||
|
||||
PERMISSION_NAMESPACE, // ILLEGAL_COMMAND_NAMESPACE
|
||||
PERMISSION_NAME, // ILLEGAL_COMMAND_NAME
|
||||
PERMISSION_ID, // ILLEGAL_COMMAND_ID
|
||||
|
||||
RESTRICTED_NO_ARG_CONSTRUCTOR, // NOT_CONSTRUCTABLE_TYPE
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.compiler.common
|
||||
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
|
||||
/**
|
||||
* 标记一个函数, 在其函数体内限制特定一些函数的使用.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@Target(FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
public annotation class RestrictedScope(
|
||||
vararg val kinds: Kind,
|
||||
) {
|
||||
public enum class Kind {
|
||||
PERMISSION_REGISTER, // ILLEGAL_PERMISSION_REGISTER_USE
|
||||
COMMAND_REGISTER, // ILLEGAL_COMMAND_REGISTER_USE
|
||||
}
|
||||
}
|
@ -7,13 +7,18 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE")
|
||||
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package net.mamoe.mirai.console.data
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import net.mamoe.mirai.console.data.PluginData.ValueNode
|
||||
import kotlinx.serialization.modules.EmptySerializersModule
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import net.mamoe.mirai.console.internal.data.PluginDataImpl
|
||||
import net.mamoe.mirai.console.internal.data.getAnnotationListForValueSerialization
|
||||
import net.mamoe.mirai.console.internal.data.valueName
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* [PluginData] 的默认实现. 支持使用 `by value()` 等委托方法创建 [Value] 并跟踪其改动.
|
||||
@ -21,6 +26,11 @@ import net.mamoe.mirai.console.internal.data.PluginDataImpl
|
||||
* @see PluginData
|
||||
*/
|
||||
public abstract class AbstractPluginData : PluginData, PluginDataImpl() {
|
||||
/**
|
||||
* 这个 [PluginData] 保存时使用的名称.
|
||||
*/
|
||||
public abstract override val saveName: String
|
||||
|
||||
/**
|
||||
* 添加了追踪的 [ValueNode] 列表, 即通过 `by value` 初始化的属性列表.
|
||||
*
|
||||
@ -28,22 +38,145 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() {
|
||||
*
|
||||
* @see provideDelegate
|
||||
*/
|
||||
public override val valueNodes: MutableList<ValueNode<*>> = mutableListOf()
|
||||
@ConsoleExperimentalApi
|
||||
public val valueNodes: MutableList<ValueNode<*>> = mutableListOf()
|
||||
|
||||
/**
|
||||
* 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate]
|
||||
*/
|
||||
public override fun <T : SerializerAwareValue<*>> T.track(valueName: String, annotations: List<Annotation>): T =
|
||||
apply { valueNodes.add(ValueNode(valueName, this, annotations, this.serializer)) }
|
||||
@ConsoleExperimentalApi
|
||||
public fun <T : SerializerAwareValue<*>> track(
|
||||
value: T,
|
||||
/**
|
||||
* 值名称.
|
||||
*
|
||||
* 如果属性带有 [ValueName], 则使用 [ValueName.value],
|
||||
* 否则使用 [属性名称][KProperty.name]
|
||||
*
|
||||
* @see [ValueNode.value]
|
||||
*/
|
||||
valueName: String,
|
||||
annotations: List<Annotation>,
|
||||
): T = value.apply { this@AbstractPluginData.valueNodes.add(ValueNode(valueName, this, annotations, this.serializer)) }
|
||||
|
||||
/**
|
||||
* 所有 [valueNodes] 更新和保存序列化器. 仅供内部使用
|
||||
* 使用 `by value()` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪, 并创建 [ValueNode] 加入 [valueNodes]
|
||||
*/
|
||||
public operator fun <T : SerializerAwareValue<*>> T.provideDelegate(
|
||||
thisRef: Any?,
|
||||
property: KProperty<*>,
|
||||
): T = track(this, property.valueName, property.getAnnotationListForValueSerialization())
|
||||
|
||||
/**
|
||||
* 所有 [valueNodes] 更新和保存序列化器.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public final override val updaterSerializer: KSerializer<Unit>
|
||||
get() = super.updaterSerializer
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public override val serializersModule: SerializersModule = EmptySerializersModule
|
||||
|
||||
/**
|
||||
* 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用.
|
||||
*/
|
||||
public abstract override fun onValueChanged(value: Value<*>)
|
||||
@ConsoleExperimentalApi
|
||||
public override fun onValueChanged(value: Value<*>) {
|
||||
// no-op by default
|
||||
}
|
||||
|
||||
/**
|
||||
* 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) {
|
||||
// no-op by default
|
||||
}
|
||||
|
||||
/**
|
||||
* 由 [track] 创建, 来自一个通过 `by value` 初始化的属性节点.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public data class ValueNode<T>(
|
||||
/**
|
||||
* 节点名称.
|
||||
*
|
||||
* 如果属性带有 [ValueName], 则使用 [ValueName.value],
|
||||
* 否则使用 [属性名称][KProperty.name]
|
||||
*/
|
||||
val valueName: String,
|
||||
/**
|
||||
* 属性值代理
|
||||
*/
|
||||
val value: Value<out T>,
|
||||
/**
|
||||
* 注解列表
|
||||
*/
|
||||
val annotations: List<Annotation>,
|
||||
/**
|
||||
* 属性值更新器
|
||||
*/
|
||||
val updaterSerializer: KSerializer<Unit>,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取这个 [KProperty] 委托的 [Value]
|
||||
*
|
||||
* 示例:
|
||||
* ```
|
||||
* object MyData : AutoSavePluginData(PluginMain) {
|
||||
* val list: List<String> by value()
|
||||
* }
|
||||
*
|
||||
* val value: Value<List<String>> = MyData.findBackingFieldValue(MyData::list)
|
||||
* ```
|
||||
*
|
||||
* @see PluginData
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public fun <T> AbstractPluginData.findBackingFieldValue(property: KProperty<T>): Value<out T>? =
|
||||
findBackingFieldValue(property.valueName)
|
||||
|
||||
/**
|
||||
* 获取这个 [KProperty] 委托的 [Value]
|
||||
*
|
||||
* 示例:
|
||||
* ```
|
||||
* object MyData : AutoSavePluginData(PluginMain) {
|
||||
* @ValueName("theList")
|
||||
* val list: List<String> by value()
|
||||
* val int: Int by value()
|
||||
* }
|
||||
*
|
||||
* val value: Value<List<String>> = MyData.findBackingFieldValue("theList") // 需使用 @ValueName 标注的名称
|
||||
* val intValue: Value<Int> = MyData.findBackingFieldValue("int")
|
||||
* ```
|
||||
*
|
||||
* @see PluginData
|
||||
*/
|
||||
@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>
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取这个 [KProperty] 委托的 [Value]
|
||||
*
|
||||
* 示例:
|
||||
* ```
|
||||
* object MyData : AutoSavePluginData(PluginMain) {
|
||||
* val list: List<String> by value()
|
||||
* }
|
||||
*
|
||||
* val value: PluginData.ValueNode<List<String>> = MyData.findBackingFieldValueNode(MyData::list)
|
||||
* ```
|
||||
*
|
||||
* @see PluginData
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public fun <T> AbstractPluginData.findBackingFieldValueNode(property: KProperty<T>): AbstractPluginData.ValueNode<out T>? {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return this.valueNodes.find { it == property } as AbstractPluginData.ValueNode<out T>?
|
||||
}
|
@ -23,4 +23,10 @@ import kotlinx.coroutines.Job
|
||||
* @see PluginConfig
|
||||
* @see AutoSavePluginData
|
||||
*/
|
||||
public open class AutoSavePluginConfig : AutoSavePluginData(), PluginConfig
|
||||
public open class AutoSavePluginConfig : AutoSavePluginData, PluginConfig {
|
||||
@Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("AutoSavePluginConfig(\"把我改成保存名称\")"))
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
public constructor() : super()
|
||||
|
||||
public constructor(saveName: String) : super(saveName)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.internal.plugin.updateWhen
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
|
||||
/**
|
||||
* 链接自动保存的 [PluginData].
|
||||
@ -29,14 +30,29 @@ import net.mamoe.mirai.utils.*
|
||||
* @see PluginData
|
||||
*/
|
||||
public open class AutoSavePluginData private constructor(
|
||||
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?
|
||||
// KEEP THIS PRIMARY CONSTRUCTOR FOR FUTURE USE: WE'LL SUPPORT SERIALIZERS_MODULE FOR POLYMORPHISM
|
||||
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?,
|
||||
) : AbstractPluginData() {
|
||||
private lateinit var owner_: AutoSavePluginDataHolder
|
||||
private val autoSaveIntervalMillis_: LongRange get() = owner_.autoSaveIntervalMillis
|
||||
private lateinit var storage_: PluginDataStorage
|
||||
|
||||
public constructor() : this(null)
|
||||
public final override val saveName: String
|
||||
get() = _saveName
|
||||
|
||||
private lateinit var _saveName: String
|
||||
|
||||
public constructor(saveName: String) : this(null) {
|
||||
_saveName = saveName
|
||||
}
|
||||
|
||||
@Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("AutoSavePluginData(\"把我改成保存名称\")"))
|
||||
public constructor() : this(null) {
|
||||
val clazz = this::class
|
||||
_saveName = clazz.findAnnotation<ValueName>()?.value
|
||||
?: clazz.qualifiedName
|
||||
?: throw IllegalArgumentException("Cannot find a serial name for ${this::class}")
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) {
|
||||
@ -57,7 +73,7 @@ public open class AutoSavePluginData private constructor(
|
||||
?.let { return@invokeOnCompletion }
|
||||
MiraiConsole.mainLogger.error(
|
||||
"An exception occurred when saving config ${this@AutoSavePluginData::class.qualifiedNameOrTip} " +
|
||||
"but CoroutineExceptionHandler not found in PluginDataHolder.coroutineContext for ${owner::class.qualifiedNameOrTip}",
|
||||
"but CoroutineExceptionHandler not found in PluginDataHolder.coroutineContext for ${owner::class.qualifiedNameOrTip}",
|
||||
e
|
||||
)
|
||||
}
|
||||
@ -121,6 +137,7 @@ public open class AutoSavePluginData private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public final override fun onValueChanged(value: Value<*>) {
|
||||
debuggingLogger1.error { "onValueChanged: $value" }
|
||||
if (::owner_.isInitialized) {
|
||||
|
@ -37,4 +37,10 @@ import net.mamoe.mirai.console.data.java.JAutoSavePluginConfig
|
||||
*
|
||||
* @see PluginData
|
||||
*/
|
||||
public interface PluginConfig : PluginData
|
||||
public interface PluginConfig : PluginData {
|
||||
/**
|
||||
* 警告: [PluginConfig] 的实现处于实验性阶段.
|
||||
*
|
||||
* 自主实现 [PluginConfig] 将得不到兼容性保障. 请仅考虑使用 [AutoSavePluginConfig]
|
||||
*/
|
||||
}
|
@ -18,20 +18,27 @@
|
||||
package net.mamoe.mirai.console.data
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.serializersModuleOf
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_NO_ARG_CONSTRUCTOR
|
||||
import net.mamoe.mirai.console.data.java.JAutoSavePluginData
|
||||
import net.mamoe.mirai.console.internal.data.*
|
||||
import net.mamoe.mirai.console.internal.data.createInstanceSmart
|
||||
import net.mamoe.mirai.console.internal.data.typeOf0
|
||||
import net.mamoe.mirai.console.internal.data.valueFromKTypeImpl
|
||||
import net.mamoe.mirai.console.internal.data.valueImpl
|
||||
import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.reloadPluginData
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
|
||||
/**
|
||||
* 一个插件内部的, 对用户隐藏的数据对象. 可包含对多个 [Value] 的值变更的跟踪.
|
||||
* 一个插件内部的, 对用户隐藏的数据对象. 可包含对多个 [Value] 的值变更的跟踪. 典型的实现为 [AbstractPluginData].
|
||||
*
|
||||
* [PluginData] 不涉及有关数据的存储, 而是只维护数据结构: [属性节点列表][valueNodes].
|
||||
* [AbstractPluginData] 不涉及有关数据的存储, 而是只维护数据结构: [属性节点列表][AbstractPluginData.valueNodes].
|
||||
*
|
||||
* 有关存储方案, 请查看 [PluginDataStorage].
|
||||
*
|
||||
@ -68,7 +75,7 @@ import kotlin.reflect.full.findAnnotation
|
||||
* val theList: MutableList<String> = AccountPluginData.list
|
||||
* ```
|
||||
*
|
||||
* 但也注意, 不要存储 `AccountPluginData.list`. 它可能受不到值跟踪. 若必要存储, 请使用 [PluginData.findBackingFieldValue]
|
||||
* 但也注意, 不要存储 `AccountPluginData.list`. 它可能受不到值跟踪. 若必要存储, 请使用 [AbstractPluginData.findBackingFieldValue]
|
||||
*
|
||||
* ### 使用 Java
|
||||
*
|
||||
@ -98,93 +105,36 @@ import kotlin.reflect.full.findAnnotation
|
||||
*
|
||||
* 要查看详细的解释,请查看 [docs/PluginData.md](https://github.com/mamoe/mirai-console/blob/master/docs/PluginData.md)
|
||||
*
|
||||
* @see JvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例.
|
||||
* @see AbstractJvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例.
|
||||
* @see PluginDataStorage [PluginData] 存储仓库
|
||||
* @see PluginDataExtensions 相关 [SerializerAwareValue] 映射函数
|
||||
*/
|
||||
public interface PluginData {
|
||||
/**
|
||||
* 添加了追踪的 [ValueNode] 列表 (即使用 `by value()` 委托的属性), 即通过 `by value` 初始化的属性列表.
|
||||
*
|
||||
* 他们的修改会被跟踪, 并触发 [onValueChanged].
|
||||
*
|
||||
* @see provideDelegate
|
||||
* @see track
|
||||
*/
|
||||
public val valueNodes: MutableList<ValueNode<*>>
|
||||
|
||||
/**
|
||||
* 这个 [PluginData] 保存时使用的名称. 默认通过 [ValueName] 获取, 否则使用 [类全名][KClass.qualifiedName] (即 [Class.getCanonicalName])
|
||||
* 这个 [PluginData] 保存时使用的名称.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public val saveName: String
|
||||
get() {
|
||||
val clazz = this::class
|
||||
return clazz.findAnnotation<ValueName>()?.value
|
||||
?: clazz.qualifiedName
|
||||
?: throw IllegalArgumentException("Cannot find a serial name for ${this::class}")
|
||||
}
|
||||
|
||||
/**
|
||||
* 由 [provideDelegate] 创建, 来自一个通过 `by value` 初始化的属性节点.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public data class ValueNode<T>(
|
||||
/**
|
||||
* 节点名称.
|
||||
*
|
||||
* 如果属性带有 [ValueName], 则使用 [ValueName.value],
|
||||
* 否则使用 [属性名称][KProperty.name]
|
||||
*/
|
||||
val valueName: String,
|
||||
/**
|
||||
* 属性值代理
|
||||
*/
|
||||
val value: Value<out T>,
|
||||
/**
|
||||
* 注解列表
|
||||
*/
|
||||
val annotations: List<Annotation>,
|
||||
/**
|
||||
* 属性值更新器
|
||||
*/
|
||||
val updaterSerializer: KSerializer<Unit>
|
||||
)
|
||||
|
||||
/**
|
||||
* 使用 `by value()` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪, 并创建 [ValueNode] 加入 [valueNodes]
|
||||
*/
|
||||
public operator fun <T : SerializerAwareValue<*>> T.provideDelegate(
|
||||
thisRef: Any?,
|
||||
property: KProperty<*>
|
||||
): T = track(property.valueName, property.getAnnotationListForValueSerialization())
|
||||
|
||||
/**
|
||||
* 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate]
|
||||
*/
|
||||
public fun <T : SerializerAwareValue<*>> T.track(
|
||||
/**
|
||||
* 值名称.
|
||||
*
|
||||
* 如果属性带有 [ValueName], 则使用 [ValueName.value],
|
||||
* 否则使用 [属性名称][KProperty.name]
|
||||
*
|
||||
* @see [ValueNode.value]
|
||||
*/
|
||||
valueName: String,
|
||||
annotations: List<Annotation>
|
||||
): T
|
||||
|
||||
/**
|
||||
* 所有 [valueNodes] 更新和保存序列化器. 仅供内部使用
|
||||
*/
|
||||
public val updaterSerializer: KSerializer<Unit>
|
||||
|
||||
/**
|
||||
* 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用.
|
||||
* 调用者为 [Value] 的实现.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public fun onValueChanged(value: Value<*>)
|
||||
|
||||
/**
|
||||
* 用于支持多态序列化.
|
||||
*
|
||||
* @see SerializersModule
|
||||
* @see serializersModuleOf
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public val serializersModule: SerializersModule
|
||||
|
||||
/**
|
||||
* 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用
|
||||
*/
|
||||
@ -192,62 +142,6 @@ public interface PluginData {
|
||||
public fun onInit(owner: PluginDataHolder, storage: PluginDataStorage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取这个 [KProperty] 委托的 [Value]
|
||||
*
|
||||
* 如, 对于
|
||||
* ```
|
||||
* object MyData : AutoSavePluginData(PluginMain) {
|
||||
* val list: List<String> by value()
|
||||
* }
|
||||
*
|
||||
* val value: Value<List<String>> = MyData.findBackingFieldValue(MyData::list)
|
||||
* ```
|
||||
*
|
||||
* @see PluginData
|
||||
*/
|
||||
public fun <T> PluginData.findBackingFieldValue(property: KProperty<T>): Value<out T>? =
|
||||
findBackingFieldValue(property.valueName)
|
||||
|
||||
/**
|
||||
* 获取这个 [KProperty] 委托的 [Value]
|
||||
*
|
||||
* 如, 对于
|
||||
* ```
|
||||
* object MyData : AutoSavePluginData(PluginMain) {
|
||||
* @ValueName("theList")
|
||||
* val list: List<String> by value()
|
||||
* val int: Int by value()
|
||||
* }
|
||||
*
|
||||
* val value: Value<List<String>> = MyData.findBackingFieldValue("theList") // 需使用 @ValueName 标注的名称
|
||||
* val intValue: Value<Int> = MyData.findBackingFieldValue("int")
|
||||
* ```
|
||||
*
|
||||
* @see PluginData
|
||||
*/
|
||||
public fun <T> PluginData.findBackingFieldValue(propertyValueName: String): Value<out T>? {
|
||||
return this.valueNodes.find { it.valueName == propertyValueName }?.value as Value<T>
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取这个 [KProperty] 委托的 [Value]
|
||||
*
|
||||
* 如, 对于
|
||||
* ```
|
||||
* object MyData : AutoSavePluginData(PluginMain) {
|
||||
* val list: List<String> by value()
|
||||
* }
|
||||
*
|
||||
* val value: PluginData.ValueNode<List<String>> = MyData.findBackingFieldValueNode(MyData::list)
|
||||
* ```
|
||||
*
|
||||
* @see PluginData
|
||||
*/
|
||||
public fun <T> PluginData.findBackingFieldValueNode(property: KProperty<T>): PluginData.ValueNode<out T>? {
|
||||
return this.valueNodes.find { it == property } as PluginData.ValueNode<out T>?
|
||||
}
|
||||
|
||||
// don't default = 0, cause ambiguity
|
||||
//// region PluginData_value_primitives CODEGEN ////
|
||||
@ -313,7 +207,7 @@ public fun PluginData.value(default: String): SerializerAwareValue<String> = val
|
||||
@LowPriorityInOverloadResolution
|
||||
public inline fun <reified T> PluginData.value(
|
||||
default: T,
|
||||
crossinline apply: T.() -> Unit = {}
|
||||
crossinline apply: T.() -> Unit = {},
|
||||
): SerializerAwareValue<T> =
|
||||
valueFromKType(typeOf0<T>(), default).also { it.value.apply() }
|
||||
|
||||
@ -321,9 +215,10 @@ public inline fun <reified T> PluginData.value(
|
||||
* 通过具体化类型创建一个 [SerializerAwareValue].
|
||||
* @see valueFromKType 查看更多实现信息
|
||||
*/
|
||||
@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR)
|
||||
@LowPriorityInOverloadResolution
|
||||
public inline fun <reified T> PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue<T> =
|
||||
valueImpl<T>(typeOf0<T>(), T::class).also { it.value.apply() }
|
||||
public inline fun <@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR) reified T>
|
||||
PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue<T> = valueImpl<T>(typeOf0<T>(), T::class).also { it.value.apply() }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@PublishedApi
|
||||
|
@ -41,7 +41,6 @@ public interface PluginDataHolder {
|
||||
* @see Companion.newPluginDataInstance
|
||||
* @see KClass.createType
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun <T : PluginData> newPluginDataInstance(type: KType): T =
|
||||
newPluginDataInstanceUsingReflection<PluginData>(type) as T
|
||||
|
||||
@ -64,7 +63,6 @@ public interface AutoSavePluginDataHolder : PluginDataHolder, CoroutineScope {
|
||||
/**
|
||||
* 仅支持确切的 [PluginData] 类型
|
||||
*/
|
||||
@JvmDefault
|
||||
public override fun <T : PluginData> newPluginDataInstance(type: KType): T {
|
||||
val classifier = type.classifier?.cast<KClass<PluginData>>()
|
||||
require(classifier != null && classifier.java == PluginData::class.java) {
|
||||
|
@ -135,6 +135,7 @@ public interface MultiFilePluginDataStorage : PluginDataStorage {
|
||||
}
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
@get:JvmSynthetic
|
||||
public inline val MultiFilePluginDataStorage.directory: File
|
||||
get() = this.directoryPath.toFile()
|
@ -14,7 +14,7 @@ package net.mamoe.mirai.console.data
|
||||
*
|
||||
* 例:
|
||||
* ```
|
||||
* object AccountPluginData : PluginData by ... {
|
||||
* object AccountPluginData : AutoSavePluginData() {
|
||||
* @ValueName("info")
|
||||
* val map: Map<String, String> by value("a" to "b")
|
||||
* }
|
||||
@ -26,6 +26,9 @@ package net.mamoe.mirai.console.data
|
||||
* map:
|
||||
* a: b
|
||||
* ```
|
||||
*
|
||||
* @see PluginData
|
||||
* @see Value
|
||||
*/
|
||||
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
|
@ -37,4 +37,10 @@ import net.mamoe.mirai.console.data.PluginData
|
||||
* @see JAutoSavePluginData
|
||||
* @see PluginConfig
|
||||
*/
|
||||
public abstract class JAutoSavePluginConfig : AutoSavePluginConfig(), PluginConfig
|
||||
public abstract class JAutoSavePluginConfig : AutoSavePluginConfig, PluginConfig {
|
||||
@Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("JAutoSavePluginConfig(\"把我改成保存名称\")"))
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
public constructor() : super()
|
||||
|
||||
public constructor(saveName: String) : super(saveName)
|
||||
}
|
||||
|
@ -66,7 +66,13 @@ import kotlin.reflect.full.createType
|
||||
*
|
||||
* @see PluginData
|
||||
*/
|
||||
public abstract class JAutoSavePluginData : AutoSavePluginData(), PluginConfig {
|
||||
public abstract class JAutoSavePluginData : AutoSavePluginData, PluginConfig {
|
||||
@Deprecated("请手动指定保存名称. 此构造器将在 1.0.0 删除", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("JAutoSavePluginData(\"把我改成保存名称\")"))
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
public constructor() : super()
|
||||
|
||||
public constructor(saveName: String) : super(saveName)
|
||||
|
||||
//// region JPluginData_value_primitives CODEGEN ////
|
||||
|
||||
/**
|
||||
|
@ -9,13 +9,14 @@
|
||||
|
||||
package net.mamoe.mirai.console.internal
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import java.time.Instant
|
||||
|
||||
internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants)
|
||||
@JvmStatic
|
||||
val buildDate: Instant = Instant.ofEpochSecond(1599934775)
|
||||
val buildDate: Instant = Instant.ofEpochSecond(1601134282)
|
||||
const val versionConst: String = "1.0-RC-dev-29"
|
||||
|
||||
@JvmStatic
|
||||
val version: Semver = Semver("1.0-M4", Semver.SemverType.LOOSE)
|
||||
val version: SemVersion = SemVersion(versionConst)
|
||||
}
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
package net.mamoe.mirai.console.internal
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
@ -23,7 +22,6 @@ import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation
|
||||
import net.mamoe.mirai.console.command.BuiltInCommands
|
||||
import net.mamoe.mirai.console.command.Command.Companion.primaryName
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
@ -48,6 +46,7 @@ import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInput
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
@ -67,7 +66,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
|
||||
private val instance: MiraiConsoleImplementation by MiraiConsoleImplementation.Companion::instance
|
||||
override val buildDate: Instant by MiraiConsoleBuildConstants::buildDate
|
||||
override val version: Semver by MiraiConsoleBuildConstants::version
|
||||
override val version: SemVersion by MiraiConsoleBuildConstants::version
|
||||
override val rootPath: Path by instance::rootPath
|
||||
override val frontEndDescription: MiraiConsoleFrontEndDescription by instance::frontEndDescription
|
||||
|
||||
@ -191,6 +190,10 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
|
||||
PluginManagerImpl.enableAllLoadedPlugins()
|
||||
|
||||
for (registeredCommand in CommandManager.allRegisteredCommands) {
|
||||
registeredCommand.permission // init
|
||||
}
|
||||
|
||||
mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) enabled." }
|
||||
}
|
||||
|
||||
@ -221,9 +224,9 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@DslMarker
|
||||
private annotation class ILoveOmaeKumikoForever
|
||||
internal annotation class ILoveOmaeKumikoForever
|
||||
|
||||
@ILoveOmaeKumikoForever
|
||||
private inline fun phase(block: () -> Unit) {
|
||||
|
@ -14,7 +14,7 @@ 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.primaryName
|
||||
import net.mamoe.mirai.console.command.Command.Companion.allNames
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
||||
import net.mamoe.mirai.event.Listener
|
||||
import net.mamoe.mirai.event.subscribeAlways
|
||||
@ -31,8 +31,9 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
||||
MiraiConsole.createLogger("command")
|
||||
}
|
||||
|
||||
@Suppress("ObjectPropertyName")
|
||||
@JvmField
|
||||
internal val registeredCommands: MutableList<Command> = mutableListOf()
|
||||
internal val _registeredCommands: MutableList<Command> = mutableListOf()
|
||||
|
||||
@JvmField
|
||||
internal val requiredPrefixCommandMap: MutableMap<String, Command> = mutableMapOf()
|
||||
@ -89,8 +90,8 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
||||
///// IMPL
|
||||
|
||||
|
||||
override val CommandOwner.registeredCommands: List<Command> get() = CommandManagerImpl.registeredCommands.filter { it.owner == this }
|
||||
override val allRegisteredCommands: List<Command> get() = registeredCommands.toList() // copy
|
||||
override val CommandOwner.registeredCommands: List<Command> get() = _registeredCommands.filter { it.owner == this }
|
||||
override val allRegisteredCommands: List<Command> get() = _registeredCommands.toList() // copy
|
||||
override val commandPrefix: String get() = "/"
|
||||
override fun CommandOwner.unregisterAllCommands() {
|
||||
for (registeredCommand in registeredCommands) {
|
||||
@ -100,24 +101,28 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
||||
|
||||
override fun Command.register(override: Boolean): Boolean {
|
||||
if (this is CompositeCommand) this.subCommands // init lazy
|
||||
this.permission // init lazy
|
||||
this.names // init lazy
|
||||
this.description // init lazy
|
||||
this.usage // init lazy
|
||||
kotlin.runCatching {
|
||||
this.permission // init lazy
|
||||
this.secondaryNames // init lazy
|
||||
this.description // init lazy
|
||||
this.usage // init lazy
|
||||
}.onFailure {
|
||||
throw IllegalStateException("Failed to init command ${this@register}.", it)
|
||||
}
|
||||
|
||||
modifyLock.withLock {
|
||||
if (!override) {
|
||||
if (findDuplicate() != null) return false
|
||||
}
|
||||
registeredCommands.add(this@register)
|
||||
_registeredCommands.add(this@register)
|
||||
if (this.prefixOptional) {
|
||||
for (name in this.names) {
|
||||
for (name in this.allNames) {
|
||||
val lowerCaseName = name.toLowerCase()
|
||||
optionalPrefixCommandMap[lowerCaseName] = this
|
||||
requiredPrefixCommandMap[lowerCaseName] = this
|
||||
}
|
||||
} else {
|
||||
for (name in this.names) {
|
||||
for (name in this.allNames) {
|
||||
val lowerCaseName = name.toLowerCase()
|
||||
optionalPrefixCommandMap.remove(lowerCaseName) // ensure resolution consistency
|
||||
requiredPrefixCommandMap[lowerCaseName] = this
|
||||
@ -128,21 +133,21 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
||||
}
|
||||
|
||||
override fun Command.findDuplicate(): Command? =
|
||||
registeredCommands.firstOrNull { it.names intersectsIgnoringCase this.names }
|
||||
_registeredCommands.firstOrNull { it.allNames intersectsIgnoringCase this.allNames }
|
||||
|
||||
override fun Command.unregister(): Boolean = modifyLock.withLock {
|
||||
if (this.prefixOptional) {
|
||||
this.names.forEach {
|
||||
this.allNames.forEach {
|
||||
optionalPrefixCommandMap.remove(it)
|
||||
}
|
||||
}
|
||||
this.names.forEach {
|
||||
this.allNames.forEach {
|
||||
requiredPrefixCommandMap.remove(it)
|
||||
}
|
||||
registeredCommands.remove(this)
|
||||
_registeredCommands.remove(this)
|
||||
}
|
||||
|
||||
override fun Command.isRegistered(): Boolean = this in registeredCommands
|
||||
override fun Command.isRegistered(): Boolean = this in _registeredCommands
|
||||
|
||||
override suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
|
@ -12,7 +12,6 @@
|
||||
package net.mamoe.mirai.console.internal.command
|
||||
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.Command.Companion.primaryName
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentContext
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentContextAware
|
||||
import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip
|
||||
@ -42,19 +41,21 @@ internal object SimpleCommandSubCommandAnnotationResolver :
|
||||
function.hasAnnotation<SimpleCommand.Handler>()
|
||||
|
||||
override fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String> =
|
||||
baseCommand.names
|
||||
baseCommand.secondaryNames
|
||||
}
|
||||
|
||||
internal abstract class AbstractReflectionCommand
|
||||
@JvmOverloads constructor(
|
||||
owner: CommandOwner,
|
||||
names: Array<out String>,
|
||||
primaryName: String,
|
||||
secondaryNames: Array<out String>,
|
||||
description: String = "<no description available>",
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
prefixOptional: Boolean = false,
|
||||
) : Command, AbstractCommand(
|
||||
owner,
|
||||
names = names,
|
||||
primaryName = primaryName,
|
||||
secondaryNames = secondaryNames,
|
||||
description = description,
|
||||
parentPermission = parentPermission,
|
||||
prefixOptional = prefixOptional
|
||||
@ -251,7 +252,7 @@ internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseComm
|
||||
|
||||
internal fun AbstractReflectionCommand.createSubCommand(
|
||||
function: KFunction<*>,
|
||||
context: CommandArgumentContext
|
||||
context: CommandArgumentContext,
|
||||
): AbstractReflectionCommand.SubCommandDescriptor {
|
||||
val notStatic = !function.hasAnnotation<JvmStatic>()
|
||||
//val overridePermission = null//function.findAnnotation<CompositeCommand.PermissionId>()//optional
|
||||
|
@ -10,7 +10,6 @@
|
||||
package net.mamoe.mirai.console.internal.command
|
||||
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.Command.Companion.primaryName
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.contact.Group
|
||||
|
@ -9,10 +9,9 @@
|
||||
|
||||
package net.mamoe.mirai.console.internal.data
|
||||
|
||||
import net.mamoe.mirai.console.data.MultiFilePluginDataStorage
|
||||
import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.data.PluginDataHolder
|
||||
import net.mamoe.mirai.console.data.PluginDataStorage
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.data.*
|
||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
@ -42,7 +41,7 @@ internal open class MultiFilePluginDataStorageImpl(
|
||||
} else {
|
||||
this.store(holder, instance) // save an initial copy
|
||||
}
|
||||
logger.debug { "Successfully loaded PluginData: ${instance.saveName} (containing ${instance.valueNodes.size} properties)" }
|
||||
logger.debug { "Successfully loaded PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
|
||||
}
|
||||
|
||||
protected open fun getPluginDataFile(holder: PluginDataHolder, instance: PluginData): File {
|
||||
@ -62,28 +61,33 @@ internal open class MultiFilePluginDataStorageImpl(
|
||||
return file.toFile().also { it.createNewFile() }
|
||||
}
|
||||
|
||||
private val json = Json {
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
allowStructuredMapKeys = true
|
||||
}
|
||||
|
||||
private val yaml = Yaml.default
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public override fun store(holder: PluginDataHolder, instance: PluginData) {
|
||||
val yaml =/* if (instance.saveName == "PermissionService") Json {
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
allowStructuredMapKeys = true
|
||||
} /*Yaml(
|
||||
configuration = YamlConfiguration(
|
||||
mapSerialization = YamlConfiguration.MapSerialization.FLOW_MAP,
|
||||
listSerialization = YamlConfiguration.ListSerialization.FLOW_SEQUENCE,
|
||||
classSerialization = YamlConfiguration.MapSerialization.FLOW_MAP
|
||||
)
|
||||
)*/ else */Yaml.default
|
||||
getPluginDataFile(holder, instance).writeText(
|
||||
kotlin.runCatching {
|
||||
yaml.encodeToString(instance.updaterSerializer, Unit)
|
||||
}.recoverCatching {
|
||||
// Just use mainLogger for convenience.
|
||||
MiraiConsole.mainLogger.warning(
|
||||
"Could not save ${instance.saveName} in YAML format due to exception in YAML encoder. " +
|
||||
"Please report this exception and relevant configurations to https://github.com/mamoe/mirai-console/issues/new",
|
||||
it
|
||||
)
|
||||
json.encodeToString(instance.updaterSerializer, Unit)
|
||||
}.getOrElse {
|
||||
throw IllegalStateException("Exception while saving $instance, saveName=${instance.saveName}", it)
|
||||
}
|
||||
)
|
||||
logger.debug { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.valueNodes.size} properties)" }
|
||||
logger.debug { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,9 +18,9 @@ import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.CompositeDecoder
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import net.mamoe.mirai.console.data.AbstractPluginData
|
||||
import net.mamoe.mirai.console.data.AbstractPluginData.ValueNode
|
||||
import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.data.PluginData.ValueNode
|
||||
import net.mamoe.mirai.console.data.Value
|
||||
import net.mamoe.mirai.console.data.ValueDescription
|
||||
import net.mamoe.mirai.console.data.ValueName
|
||||
import net.mamoe.yamlkt.Comment
|
||||
@ -34,12 +34,25 @@ import kotlin.reflect.KAnnotatedElement
|
||||
* - Auto-saving
|
||||
*/
|
||||
internal abstract class PluginDataImpl {
|
||||
internal fun findNodeInstance(name: String): ValueNode<*>? = valueNodes.firstOrNull { it.valueName == name }
|
||||
init {
|
||||
@Suppress("LeakingThis")
|
||||
check(this is AbstractPluginData)
|
||||
}
|
||||
|
||||
internal abstract val valueNodes: MutableList<ValueNode<*>>
|
||||
private fun findNodeInstance(name: String): ValueNode<*>? {
|
||||
check(this is AbstractPluginData)
|
||||
return valueNodes.firstOrNull { it.valueName == name }
|
||||
}
|
||||
|
||||
internal open val updaterSerializer: KSerializer<Unit> = object : KSerializer<Unit> {
|
||||
override val descriptor: SerialDescriptor get() = dataUpdaterSerializerDescriptor
|
||||
override val descriptor: SerialDescriptor by lazy {
|
||||
check(this@PluginDataImpl is AbstractPluginData)
|
||||
kotlinx.serialization.descriptors.buildClassSerialDescriptor((this@PluginDataImpl as PluginData).saveName) {
|
||||
for (valueNode in valueNodes) valueNode.run {
|
||||
element(valueName, updaterSerializer.descriptor, annotations = annotations, isOptional = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun deserialize(decoder: Decoder) {
|
||||
@ -84,6 +97,8 @@ internal abstract class PluginDataImpl {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun serialize(encoder: Encoder, value: Unit) {
|
||||
check(this@PluginDataImpl is AbstractPluginData)
|
||||
|
||||
val descriptor = descriptor
|
||||
with(encoder.beginStructure(descriptor)) {
|
||||
repeat(descriptor.elementsCount) { index ->
|
||||
@ -100,18 +115,6 @@ internal abstract class PluginDataImpl {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* flatten
|
||||
*/
|
||||
abstract fun onValueChanged(value: Value<*>)
|
||||
private val dataUpdaterSerializerDescriptor by lazy {
|
||||
kotlinx.serialization.descriptors.buildClassSerialDescriptor((this as PluginData).saveName) {
|
||||
for (valueNode in valueNodes) valueNode.run {
|
||||
element(valueName, updaterSerializer.descriptor, annotations = annotations, isOptional = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun KAnnotatedElement.getAnnotationListForValueSerialization(): List<Annotation> {
|
||||
|
@ -6,10 +6,7 @@ import net.mamoe.mirai.console.data.value
|
||||
import net.mamoe.mirai.console.internal.util.md5
|
||||
import net.mamoe.mirai.console.internal.util.toUHexString
|
||||
|
||||
internal object AutoLoginConfig : AutoSavePluginConfig() {
|
||||
override val saveName: String
|
||||
get() = "AutoLogin"
|
||||
|
||||
internal object AutoLoginConfig : AutoSavePluginConfig("AutoLogin") {
|
||||
@ValueDescription(
|
||||
"""
|
||||
账号和明文密码列表
|
||||
|
@ -143,7 +143,7 @@ internal fun KClass<*>.isPrimitiveOrBuiltInSerializableValue(): Boolean {
|
||||
Byte::class, Short::class, Int::class, Long::class,
|
||||
Boolean::class,
|
||||
Char::class, String::class,
|
||||
Pair::class, Triple::class // TODO: 2020/6/24 支持 PairValue, TripleValue
|
||||
//Pair::class, Triple::class // TODO: 2020/6/24 支持 PairValue, TripleValue
|
||||
-> return true
|
||||
}
|
||||
|
||||
|
@ -16,9 +16,7 @@ internal object BuiltInSingletonExtensionSelector : SingletonExtensionSelector {
|
||||
|
||||
internal val config: SaveData = SaveData()
|
||||
|
||||
internal class SaveData : AutoSavePluginConfig() {
|
||||
override val saveName: String get() = "ExtensionSelector"
|
||||
|
||||
internal class SaveData : AutoSavePluginConfig("ExtensionSelector") {
|
||||
val value: MutableMap<String, String> by value()
|
||||
}
|
||||
|
||||
|
@ -117,12 +117,12 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService<P
|
||||
|
||||
@Suppress("RedundantVisibilityModifier")
|
||||
internal class ConcurrentSaveData private constructor(
|
||||
public override val saveName: String,
|
||||
saveName: String,
|
||||
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?,
|
||||
) : AutoSavePluginConfig() {
|
||||
) : AutoSavePluginConfig(saveName) {
|
||||
public val grantedPermissionMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableSet<AbstractPermitteeId>>
|
||||
by value<MutableMap<PermissionId, MutableSet<AbstractPermitteeId>>>(ConcurrentHashMap())
|
||||
.withDefault { CopyOnWriteArraySet() }
|
||||
by value<MutableMap<PermissionId, MutableSet<AbstractPermitteeId>>>(ConcurrentHashMap())
|
||||
.withDefault { CopyOnWriteArraySet() }
|
||||
|
||||
public companion object {
|
||||
@JvmStatic
|
||||
|
@ -68,12 +68,12 @@ internal object BuiltInJvmPluginLoaderImpl :
|
||||
pluginClassLoader.declaredFilter = exportManagers[0]
|
||||
}
|
||||
}.map { (f, pluginClassLoader) ->
|
||||
f to (pluginClassLoader.findServices(
|
||||
f to pluginClassLoader.findServices(
|
||||
JvmPlugin::class,
|
||||
KotlinPlugin::class,
|
||||
AbstractJvmPlugin::class,
|
||||
JavaPlugin::class
|
||||
).loadAllServices())
|
||||
).loadAllServices()
|
||||
}.flatMap { (f, list) ->
|
||||
|
||||
list.associateBy { f }.asSequence()
|
||||
@ -121,7 +121,9 @@ internal object BuiltInJvmPluginLoaderImpl :
|
||||
|
||||
override fun disable(plugin: JvmPlugin) {
|
||||
if (!plugin.isEnabled) return
|
||||
ensureActive()
|
||||
|
||||
if (MiraiConsole.isActive)
|
||||
ensureActive()
|
||||
|
||||
if (plugin is JvmPluginInternal) {
|
||||
plugin.internalOnDisable()
|
||||
|
@ -7,24 +7,28 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.console.internal.plugin
|
||||
|
||||
import net.mamoe.mirai.console.plugin.jvm.ExportManager
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
internal class LoadingDeniedException(name: String) : ClassNotFoundException(name)
|
||||
|
||||
internal class JvmPluginClassLoader(
|
||||
val source: Any,
|
||||
urls: Array<URL>,
|
||||
val file: File,
|
||||
parent: ClassLoader?,
|
||||
val classLoaders: Collection<JvmPluginClassLoader>
|
||||
) : URLClassLoader(urls, parent) {
|
||||
val classLoaders: Collection<JvmPluginClassLoader>,
|
||||
) : URLClassLoader(arrayOf(file.toURI().toURL()), parent) {
|
||||
//// 只允许插件 getResource 时获取插件自身资源, #205
|
||||
override fun getResources(name: String?): Enumeration<URL> = findResources(name)
|
||||
override fun getResource(name: String?): URL? = findResource(name)
|
||||
// getResourceAsStream 在 URLClassLoader 中通过 getResource 确定资源
|
||||
// 因此无需 override getResourceAsStream
|
||||
|
||||
override fun toString(): String {
|
||||
return "JvmPluginClassLoader{source=$source}"
|
||||
}
|
||||
@ -118,3 +122,5 @@ internal class JvmPluginClassLoader(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class LoadingDeniedException(name: String) : ClassNotFoundException(name)
|
||||
|
@ -18,7 +18,6 @@ import net.mamoe.mirai.console.extension.PluginComponentStorage
|
||||
import net.mamoe.mirai.console.internal.data.mkdir
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.allocatePermissionIdForPlugin
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
|
||||
@ -26,7 +25,6 @@ import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceCont
|
||||
import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.util.NamedSupervisorJob
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.io.File
|
||||
@ -50,7 +48,7 @@ internal abstract class JvmPluginInternal(
|
||||
|
||||
final override val parentPermission: Permission by lazy {
|
||||
PermissionService.INSTANCE.register(
|
||||
PermissionService.INSTANCE.allocatePermissionIdForPlugin(name, "*"),
|
||||
PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*", PermissionService.PluginPermissionIdRequestType.ROOT_PERMISSION),
|
||||
"The base permission"
|
||||
)
|
||||
}
|
||||
@ -151,6 +149,7 @@ internal abstract class JvmPluginInternal(
|
||||
)
|
||||
)
|
||||
.also {
|
||||
if (!MiraiConsole.isActive) return@also
|
||||
BuiltInJvmPluginLoaderImpl.coroutineContext[Job]!!.invokeOnCompletion {
|
||||
this.cancel()
|
||||
}
|
||||
|
@ -27,6 +27,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.utils.info
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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 via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.console.internal.util.semver
|
||||
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
internal object RangeTokenReader {
|
||||
enum class TokenType {
|
||||
STRING,
|
||||
|
||||
/* 左括号 */
|
||||
LEFT,
|
||||
|
||||
/* 右括号 */
|
||||
RIGHT,
|
||||
|
||||
/* || */
|
||||
OR,
|
||||
|
||||
/* && */
|
||||
AND,
|
||||
GROUP
|
||||
}
|
||||
|
||||
sealed class Token {
|
||||
abstract val type: TokenType
|
||||
abstract val value: String
|
||||
abstract val position: Int
|
||||
|
||||
class LeftBracket(override val position: Int) : Token() {
|
||||
override val type: TokenType get() = TokenType.LEFT
|
||||
override val value: String get() = "{"
|
||||
|
||||
override fun toString(): String = "LB{"
|
||||
}
|
||||
|
||||
class RightBracket(override val position: Int) : Token() {
|
||||
override val type: TokenType get() = TokenType.RIGHT
|
||||
override val value: String get() = "}"
|
||||
|
||||
override fun toString(): String = "RB}"
|
||||
}
|
||||
|
||||
class Or(override val position: Int) : Token() {
|
||||
override val type: TokenType get() = TokenType.OR
|
||||
override val value: String get() = "||"
|
||||
override fun toString(): String = "OR||"
|
||||
}
|
||||
|
||||
class And(override val position: Int) : Token() {
|
||||
override val type: TokenType get() = TokenType.AND
|
||||
override val value: String get() = "&&"
|
||||
|
||||
override fun toString(): String = "AD&&"
|
||||
}
|
||||
|
||||
class Group(val values: List<Token>, override val position: Int) : Token() {
|
||||
override val type: TokenType get() = TokenType.GROUP
|
||||
override val value: String get() = ""
|
||||
}
|
||||
|
||||
class Raw(val source: String, val start: Int, val end: Int) : Token() {
|
||||
override val value: String get() = source.substring(start, end)
|
||||
override val position: Int
|
||||
get() = start
|
||||
override val type: TokenType get() = TokenType.STRING
|
||||
|
||||
override fun toString(): String = "R:$value"
|
||||
}
|
||||
}
|
||||
|
||||
fun parseToTokens(source: String): List<Token> = ArrayList<Token>(
|
||||
max(source.length / 3, 16)
|
||||
).apply {
|
||||
var index = 0
|
||||
var position = 0
|
||||
fun flushOld() {
|
||||
if (position > index) {
|
||||
val id = index
|
||||
index = position
|
||||
for (i in id until position) {
|
||||
if (!source[i].isWhitespace()) {
|
||||
add(Token.Raw(source, id, position))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val iterator = source.indices.iterator()
|
||||
for (i in iterator) {
|
||||
position = i
|
||||
when (source[i]) {
|
||||
'{' -> {
|
||||
flushOld()
|
||||
add(Token.LeftBracket(i))
|
||||
index = i + 1
|
||||
}
|
||||
'|' -> {
|
||||
if (source.getOrNull(i + 1) == '|') {
|
||||
flushOld()
|
||||
add(Token.Or(i))
|
||||
index = i + 2
|
||||
iterator.nextInt()
|
||||
}
|
||||
}
|
||||
'&' -> {
|
||||
if (source.getOrNull(i + 1) == '&') {
|
||||
flushOld()
|
||||
add(Token.And(i))
|
||||
index = i + 2
|
||||
iterator.nextInt()
|
||||
}
|
||||
}
|
||||
'}' -> {
|
||||
flushOld()
|
||||
add(Token.RightBracket(i))
|
||||
index = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
position = source.length
|
||||
flushOld()
|
||||
}
|
||||
|
||||
fun collect(source: String, tokens: Iterator<Token>, root: Boolean): List<Token> = ArrayList<Token>().apply {
|
||||
tokens.forEach { token ->
|
||||
if (token is Token.LeftBracket) {
|
||||
add(Token.Group(collect(source, tokens, false), token.position))
|
||||
} else if (token is Token.RightBracket) {
|
||||
if (root) {
|
||||
throw IllegalArgumentException("Syntax error: Unexpected }, ${buildMsg(source, token.position)}")
|
||||
} else {
|
||||
return@apply
|
||||
}
|
||||
} else add(token)
|
||||
}
|
||||
if (!root) {
|
||||
throw IllegalArgumentException("Syntax error: Excepted }, ${buildMsg(source, source.length)}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildMsg(source: String, position: Int): String {
|
||||
val ed = min(position + 10, source.length)
|
||||
val st = max(0, position - 10)
|
||||
return buildString {
|
||||
append('`')
|
||||
if (st != 0) append("...")
|
||||
append(source, st, ed)
|
||||
if (ed != source.length) append("...")
|
||||
append("` at ").append(position)
|
||||
}
|
||||
}
|
||||
|
||||
fun check(source: String, tokens: Iterator<Token>, group: Token.Group?) {
|
||||
if (!tokens.hasNext()) {
|
||||
throw IllegalArgumentException("Syntax error: empty rule, ${buildMsg(source, group?.position ?: 0)}")
|
||||
}
|
||||
var type = false
|
||||
do {
|
||||
val next = tokens.next()
|
||||
if (type) {
|
||||
if (next is Token.Group || next is Token.Raw) {
|
||||
throw IllegalArgumentException("Syntax error: Except logic but got expression, ${buildMsg(source, next.position)}")
|
||||
}
|
||||
} else {
|
||||
if (next is Token.Or || next is Token.And) {
|
||||
throw IllegalArgumentException("Syntax error: Except expression but got logic, ${buildMsg(source, next.position)}")
|
||||
}
|
||||
if (next is Token.Group) {
|
||||
check(source, next.values.iterator(), next)
|
||||
}
|
||||
}
|
||||
type = !type
|
||||
} while (tokens.hasNext())
|
||||
if (!type) {
|
||||
throw IllegalArgumentException("Syntax error: Except more expression, ${buildMsg(source, group?.values?.last()?.position ?: source.length)}")
|
||||
}
|
||||
}
|
||||
|
||||
fun parse(source: String, token: Token): SemVersion.Requirement {
|
||||
return when (token) {
|
||||
is Token.Group -> {
|
||||
if (token.values.size == 1) {
|
||||
parse(source, token.values.first())
|
||||
} else {
|
||||
val logic = token.values.asSequence().map { it.type }.filter {
|
||||
it == TokenType.OR || it == TokenType.AND
|
||||
}.toSet()
|
||||
if (logic.size == 2) {
|
||||
throw IllegalArgumentException("Syntax error: || and && cannot use in one group, ${buildMsg(source, token.position)}")
|
||||
}
|
||||
val rules = token.values.asSequence().filter {
|
||||
it is Token.Raw || it is Token.Group
|
||||
}.map { parse(source, it) }.toList()
|
||||
when (logic.first()) {
|
||||
TokenType.OR -> {
|
||||
return object : SemVersion.Requirement {
|
||||
override fun test(version: SemVersion): Boolean {
|
||||
rules.forEach { if (it.test(version)) return true }
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
TokenType.AND -> {
|
||||
return object : SemVersion.Requirement {
|
||||
override fun test(version: SemVersion): Boolean {
|
||||
rules.forEach { if (!it.test(version)) return false }
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
}
|
||||
}
|
||||
is Token.Raw -> SemVersionInternal.parseRule(token.value)
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
}
|
||||
|
||||
fun StringBuilder.dump(prefix: String, token: Token) {
|
||||
when (token) {
|
||||
is Token.LeftBracket -> append("${prefix}LF {\n")
|
||||
|
||||
is Token.RightBracket -> append("${prefix}LR }\n")
|
||||
|
||||
is Token.Or -> append("${prefix}OR ||\n")
|
||||
|
||||
is Token.And -> append("${prefix}AND &&\n")
|
||||
is Token.Group -> {
|
||||
append("${prefix}GROUP {\n")
|
||||
token.values.forEach { dump("$prefix ", it) }
|
||||
append("${prefix}}\n")
|
||||
}
|
||||
is Token.Raw -> append("${prefix}RAW ${token.value}\n")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,290 @@
|
||||
/*
|
||||
* 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 via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.internal.util.semver
|
||||
|
||||
import net.mamoe.mirai.console.internal.util.semver.RangeTokenReader.dump
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
@Suppress("RegExpRedundantEscape")
|
||||
internal object SemVersionInternal {
|
||||
private val directVersion = """^[0-9]+(\.[0-9]+)+(|[\-+].+)$""".toRegex()
|
||||
private val versionSelect = """^[0-9]+(\.[0-9]+)*\.x$""".toRegex()
|
||||
private val versionMathRange =
|
||||
"""([\[\(])([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))([\]\)])""".toRegex()
|
||||
private val versionRule = """^((\>\=)|(\<\=)|(\=)|(\!\=)|(\>)|(\<))\s*([0-9]+(\.[0-9]+)+(|[\-+].+))$""".toRegex()
|
||||
|
||||
private val SEM_VERSION_REGEX =
|
||||
"""^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$""".toRegex()
|
||||
|
||||
/** 解析核心版本号, eg: `1.0.0` -> IntArray[1, 0, 0] */
|
||||
@JvmStatic
|
||||
private fun String.parseMainVersion(): IntArray =
|
||||
split('.').map { it.toInt() }.toIntArray()
|
||||
|
||||
|
||||
fun parse(version: String): SemVersion {
|
||||
if (!SEM_VERSION_REGEX.matches(version)) {
|
||||
throw IllegalArgumentException("`$version` not a valid version")
|
||||
}
|
||||
var mainVersionEnd = 0
|
||||
kotlin.run {
|
||||
val iterator = version.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val next = iterator.next()
|
||||
if (next == '-' || next == '+') {
|
||||
break
|
||||
}
|
||||
mainVersionEnd++
|
||||
}
|
||||
}
|
||||
var identifier: String? = null
|
||||
var metadata: String? = null
|
||||
if (mainVersionEnd != version.length) {
|
||||
when (version[mainVersionEnd]) {
|
||||
'-' -> {
|
||||
val metadataSplitter = version.indexOf('+', startIndex = mainVersionEnd)
|
||||
if (metadataSplitter == -1) {
|
||||
identifier = version.substring(mainVersionEnd + 1)
|
||||
} else {
|
||||
identifier = version.substring(mainVersionEnd + 1, metadataSplitter)
|
||||
metadata = version.substring(metadataSplitter + 1)
|
||||
}
|
||||
}
|
||||
'+' -> {
|
||||
metadata = version.substring(mainVersionEnd + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
val mainVersion = version.substring(0, mainVersionEnd).parseMainVersion()
|
||||
return SemVersion(
|
||||
major = mainVersion[0],
|
||||
minor = mainVersion[1],
|
||||
patch = mainVersion.getOrNull(2),
|
||||
identifier = identifier,
|
||||
metadata = metadata
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
internal fun parseRule(rule: String): SemVersion.Requirement {
|
||||
val trimmed = rule.trim()
|
||||
if (directVersion.matches(trimmed)) {
|
||||
val parsed = SemVersion.invoke(trimmed)
|
||||
return object : SemVersion.Requirement {
|
||||
override fun test(version: SemVersion): Boolean = version.compareTo(parsed) == 0
|
||||
}
|
||||
}
|
||||
if (versionSelect.matches(trimmed)) {
|
||||
val regex = ("^" +
|
||||
trimmed.replace(".", "\\.")
|
||||
.replace("x", ".+") +
|
||||
"$"
|
||||
).toRegex()
|
||||
return object : SemVersion.Requirement {
|
||||
override fun test(version: SemVersion): Boolean = regex.matches(version.toString())
|
||||
}
|
||||
}
|
||||
versionMathRange.matchEntire(trimmed)?.let { range ->
|
||||
// 1 mode
|
||||
// 2 first
|
||||
// 5 sec
|
||||
// 8 type
|
||||
var typeStart = range.groupValues[1][0]
|
||||
var typeEnd = range.groupValues[8][0]
|
||||
var start = SemVersion.invoke(range.groupValues[2])
|
||||
var end = SemVersion.invoke(range.groupValues[5])
|
||||
if (start > end) {
|
||||
val c = end
|
||||
end = start
|
||||
start = c
|
||||
val x = typeEnd
|
||||
typeEnd = typeStart
|
||||
typeStart = x
|
||||
}
|
||||
val a: (SemVersion) -> Boolean = when (typeStart) {
|
||||
'[', ']' -> ({ start <= it })
|
||||
'(', ')' -> ({ start < it })
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
val b: (SemVersion) -> Boolean = when (typeEnd) {
|
||||
'[', ']' -> ({ it <= end })
|
||||
'(', ')' -> ({ it < end })
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
return object : SemVersion.Requirement {
|
||||
override fun test(version: SemVersion): Boolean = a(version) && b(version)
|
||||
}
|
||||
}
|
||||
versionRule.matchEntire(trimmed)?.let { result ->
|
||||
val operator = result.groupValues[1]
|
||||
val version1 = SemVersion.invoke(result.groupValues[8])
|
||||
return when (operator) {
|
||||
">=" -> {
|
||||
object : SemVersion.Requirement {
|
||||
override fun test(version: SemVersion): Boolean = version >= version1
|
||||
}
|
||||
}
|
||||
">" -> {
|
||||
object : SemVersion.Requirement {
|
||||
override fun test(version: SemVersion): Boolean = version > version1
|
||||
}
|
||||
}
|
||||
"<=" -> {
|
||||
object : SemVersion.Requirement {
|
||||
override fun test(version: SemVersion): Boolean = version <= version1
|
||||
}
|
||||
}
|
||||
"<" -> {
|
||||
object : SemVersion.Requirement {
|
||||
override fun test(version: SemVersion): Boolean = version < version1
|
||||
}
|
||||
}
|
||||
"=" -> {
|
||||
object : SemVersion.Requirement {
|
||||
override fun test(version: SemVersion): Boolean = version.compareTo(version1) == 0
|
||||
}
|
||||
}
|
||||
"!=" -> {
|
||||
object : SemVersion.Requirement {
|
||||
override fun test(version: SemVersion): Boolean = version.compareTo(version1) != 0
|
||||
}
|
||||
}
|
||||
else -> error("operator=$operator, version=$version1")
|
||||
}
|
||||
}
|
||||
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 {
|
||||
if (requirement.isBlank()) {
|
||||
throw IllegalArgumentException("Invalid requirement: Empty requirement rule.")
|
||||
}
|
||||
val tokens = RangeTokenReader.parseToTokens(requirement)
|
||||
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)
|
||||
}.onFailure { error ->
|
||||
throw IllegalArgumentException("Exception in parsing $requirement\n\n" + buildString {
|
||||
collected.forEach { dump("", it) }
|
||||
}, error)
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun compareInternal(source: SemVersion, other: SemVersion): Int {
|
||||
// ignored metadata in comparing
|
||||
|
||||
// If $this equals $other (without metadata),
|
||||
// return same.
|
||||
// Compare main-version
|
||||
|
||||
source.major.compareTo(other.major).takeUnless { it == 0 }?.let { return it }
|
||||
source.minor.compareTo(other.minor).takeUnless { it == 0 }?.let { return it }
|
||||
(source.patch ?: 0).compareTo(other.patch ?: 0).takeUnless { it == 0 }?.let { return it }
|
||||
|
||||
// If main-versions are same.
|
||||
var identifier0 = source.identifier
|
||||
var identifier1 = other.identifier
|
||||
// If anyone doesn't have the identifier...
|
||||
if (identifier0 == null || identifier1 == null) {
|
||||
return when (identifier0) {
|
||||
identifier1 -> { // null == null
|
||||
// Nobody has identifier
|
||||
0
|
||||
}
|
||||
null -> {
|
||||
// $other has identifier, but $this don't have identifier
|
||||
// E.g:
|
||||
// this = 1.0.0
|
||||
// other = 1.0.0-dev
|
||||
1
|
||||
}
|
||||
// It is the opposite of the above.
|
||||
else -> -1
|
||||
}
|
||||
}
|
||||
fun String.getSafe(index: Int) = getOrElse(index) { ' ' }
|
||||
|
||||
// ignored same prefix
|
||||
fun getSameSize(s1: String, s2: String): Int {
|
||||
val size = min(s1.length, s2.length)
|
||||
// 1.0-RC19 -> 19
|
||||
// 1.0-RC107 -> 107
|
||||
var realSameSize = 0
|
||||
for (index in 0 until size) {
|
||||
if (s1[index] != s2[index]) {
|
||||
return realSameSize
|
||||
} else {
|
||||
if (!s1[index].isDigit()) {
|
||||
realSameSize = index + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return realSameSize
|
||||
}
|
||||
|
||||
// We ignore the same parts. Because we only care about the differences.
|
||||
// E.g:
|
||||
// 1.0-RC1 -> 1
|
||||
// 1.0-RC2 -> 2
|
||||
val ignoredSize = getSameSize(identifier0, identifier1)
|
||||
identifier0 = identifier0.substring(ignoredSize)
|
||||
identifier1 = identifier1.substring(ignoredSize)
|
||||
// Multi-chunk comparing
|
||||
val chunks0 = identifier0.split('-', '.')
|
||||
val chunks1 = identifier1.split('-', '.')
|
||||
chunkLoop@ for (index in 0 until (max(chunks0.size, chunks1.size))) {
|
||||
val value0 = chunks0.getOrNull(index)
|
||||
val value1 = chunks1.getOrNull(index)
|
||||
// Any chunk is null
|
||||
if (value0 == null || value1 == null) {
|
||||
// value0 == null && value1 == null is impossible
|
||||
return if (value0 == null) {
|
||||
// E.g:
|
||||
// value0 = 1.0-RC-dev
|
||||
// value1 = 1.0-RC-dev-1
|
||||
-1
|
||||
} else {
|
||||
// E.g:
|
||||
// value0 = 1.0-RC-dev-1
|
||||
// value1 = 1.0-RC-dev
|
||||
1
|
||||
}
|
||||
}
|
||||
try {
|
||||
val result = value0.toInt().compareTo(value1.toInt())
|
||||
if (result != 0) {
|
||||
return result
|
||||
}
|
||||
continue@chunkLoop
|
||||
} catch (ignored: NumberFormatException) {
|
||||
}
|
||||
// compare chars
|
||||
for (index0 in 0 until (max(value0.length, value1.length))) {
|
||||
val result = value0.getSafe(index0).compareTo(value1.getSafe(index0))
|
||||
if (result != 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
@ -13,9 +13,9 @@ import net.mamoe.mirai.console.command.BuiltInCommands
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
|
||||
/**
|
||||
* 一个权限.
|
||||
* 一个抽象的「权限」. 由 [PermissionService] 实现不同, [Permission] 可能会有多种实例. 但一个权限总是拥有确定的 [id].
|
||||
*
|
||||
* 由 [PermissionService] 实现不同, [Permission] 可能会有多种实例. 但一个权限总是拥有确定的 [id].
|
||||
* 在匹配权限时, 应使用唯一的 [id] 作为依据. 而不应该使用 [Permission] 实例. 同时, [Permission] 也不适合存储.
|
||||
*
|
||||
* **注意**: 请不要手动实现这个接口. 总是从 [PermissionService.register] 获得实例.
|
||||
*
|
||||
@ -32,6 +32,7 @@ import net.mamoe.mirai.console.command.Command
|
||||
* #### 手动申请权限
|
||||
* [PermissionService.register]
|
||||
*/
|
||||
@PermissionImplementation
|
||||
public interface Permission {
|
||||
/**
|
||||
* 唯一识别 ID. 所有权限的 [id] 都互不相同.
|
||||
@ -49,6 +50,8 @@ public interface Permission {
|
||||
/**
|
||||
* 父权限.
|
||||
*
|
||||
* 在检查权限时, 若一个 [Permittee] 拥有父
|
||||
*
|
||||
* [RootPermission] 的 parent 为自身
|
||||
*/
|
||||
public val parent: Permission
|
||||
|
@ -12,6 +12,8 @@ package net.mamoe.mirai.console.permission
|
||||
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.*
|
||||
import net.mamoe.mirai.console.internal.data.map
|
||||
|
||||
|
||||
@ -25,10 +27,17 @@ import net.mamoe.mirai.console.internal.data.map
|
||||
*/
|
||||
@Serializable(with = PermissionId.PermissionIdAsStringSerializer::class)
|
||||
public data class PermissionId(
|
||||
public val namespace: String,
|
||||
public val name: String,
|
||||
@ResolveContext(PERMISSION_NAMESPACE) public val namespace: String,
|
||||
@ResolveContext(PERMISSION_NAME) public val name: String,
|
||||
) {
|
||||
init {
|
||||
require(!namespace.contains(' ')) {
|
||||
"' ' is not allowed in namespace"
|
||||
}
|
||||
require(!name.contains(' ')) {
|
||||
"' ' is not allowed in id"
|
||||
}
|
||||
|
||||
require(!namespace.contains(':')) {
|
||||
"':' is not allowed in namespace"
|
||||
}
|
||||
@ -54,13 +63,39 @@ public data class PermissionId(
|
||||
* @throws IllegalArgumentException 在解析失败时抛出.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun parseFromString(string: String): PermissionId {
|
||||
public fun parseFromString(@ResolveContext(PERMISSION_ID) string: String): PermissionId {
|
||||
return kotlin.runCatching {
|
||||
string.split(':').let { (namespace, id) -> PermissionId(namespace, id) }
|
||||
}.getOrElse {
|
||||
throw IllegalArgumentException("Could not parse PermissionId from '$string'", it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 [PermissionId.name] 的合法性. 在非法时抛出 [IllegalArgumentException]
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
public fun checkPermissionIdName(@ResolveContext(PERMISSION_NAME) value: String) {
|
||||
when {
|
||||
value.isBlank() -> throw IllegalArgumentException("PermissionId.name should not be blank.")
|
||||
value.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in PermissionId.name.")
|
||||
value.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.name.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 [PermissionId.namespace] 的合法性. 在非法时抛出 [IllegalArgumentException]
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
public fun checkPermissionIdNamespace(@ResolveContext(PERMISSION_NAME) value: String) {
|
||||
when {
|
||||
value.isBlank() -> throw IllegalArgumentException("PermissionId.namespace should not be blank.")
|
||||
value.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in PermissionId.namespace.")
|
||||
value.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.namespace.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,12 +9,18 @@
|
||||
|
||||
package net.mamoe.mirai.console.permission
|
||||
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAME
|
||||
|
||||
/**
|
||||
* [PermissionId] 的命名空间. 用于提供 [PermissionId.namespace].
|
||||
*/
|
||||
public interface PermissionIdNamespace {
|
||||
/**
|
||||
* 创建一个此命名空间下的 [PermitteeId]
|
||||
* 创建一个此命名空间下的 [PermitteeId].
|
||||
*
|
||||
* 在指令初始化时, 会申请对应权限. 此时 [name] 为 "command.$primaryName` 其中 [primaryName][Command.primaryName].
|
||||
*/
|
||||
public fun permissionId(name: String): PermissionId
|
||||
public fun permissionId(@ResolveContext(PERMISSION_NAME) name: String): PermissionId
|
||||
}
|
@ -12,10 +12,12 @@ package net.mamoe.mirai.console.permission
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
|
||||
/**
|
||||
* 表示一个应该由权限插件实现的类.
|
||||
* 表示一个应该由专有的权限插件 (提供 [PermissionService] 的插件) 实现的类.
|
||||
*
|
||||
* 这样的类不能被用户手动实现或者继承, 也不能使用属性委托或者类委托, 或者其他任意改变实现类的手段.
|
||||
* 用户仅应该使用从 [PermissionService] 或其他途径获取这些对象, 而不能自行实现它们.
|
||||
*
|
||||
* 这样的类不能被用户手动实现或者继承, 也不能使用属性委托或者类委托, 或者其他任意直接或间接实现他们的手段 (否则会导致 [PermissionService] 处理异常).
|
||||
*
|
||||
* 普通插件仅应该使用从 [PermissionService] 或其他途径获取这些对象.
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||
|
@ -11,9 +11,13 @@
|
||||
|
||||
package net.mamoe.mirai.console.permission
|
||||
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
|
||||
import net.mamoe.mirai.console.internal.permission.checkType
|
||||
import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
@ -77,6 +81,8 @@ public interface PermissionService<P : Permission> {
|
||||
* 申请并注册一个权限 [Permission].
|
||||
*
|
||||
* @throws PermissionRegistryConflictException 当已存在一个 [PermissionId] 时抛出.
|
||||
*
|
||||
* @return 申请到的 [Permission] 实例
|
||||
*/
|
||||
@Throws(PermissionRegistryConflictException::class)
|
||||
public fun register(
|
||||
@ -85,21 +91,29 @@ public interface PermissionService<P : Permission> {
|
||||
parent: Permission = RootPermission,
|
||||
): P
|
||||
|
||||
/** 为 [Plugin] 分配一个 [PermissionId] */
|
||||
public fun allocatePermissionIdForPlugin(
|
||||
plugin: Plugin,
|
||||
@ResolveContext(COMMAND_NAME) permissionName: String,
|
||||
reason: PluginPermissionIdRequestType
|
||||
): PermissionId = allocatePermissionIdForPluginDefaultImplement(plugin, permissionName, reason)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* 授予 [permitteeId] 以 [permission] 权限
|
||||
*
|
||||
* Console 内建的权限服务支持授予操作. 但插件扩展的权限服务可能不支持.
|
||||
* Console 内建的权限服务支持此操作. 但插件扩展的权限服务可能不支持.
|
||||
*
|
||||
* @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出.
|
||||
*/
|
||||
@Throws(UnsupportedOperationException::class)
|
||||
public fun permit(permitteeId: PermitteeId, permission: P)
|
||||
|
||||
/**
|
||||
* 撤销 [permitteeId] 的 [permission] 授权
|
||||
*
|
||||
* Console 内建的权限服务支持授予操作. 但插件扩展的权限服务可能不支持.
|
||||
* Console 内建的权限服务支持此操作. 但插件扩展的权限服务可能不支持.
|
||||
*
|
||||
* @param recursive `true` 时递归撤销所有子权限.
|
||||
* 例如, 若 [permission] 为 "*:*",
|
||||
@ -108,8 +122,18 @@ public interface PermissionService<P : Permission> {
|
||||
*
|
||||
* @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出.
|
||||
*/
|
||||
@Throws(UnsupportedOperationException::class)
|
||||
public fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean)
|
||||
|
||||
/** [Plugin] 尝试分配的 [PermissionId] 来源 */
|
||||
public enum class PluginPermissionIdRequestType {
|
||||
/** For [Plugin.parentPermission] */
|
||||
ROOT_PERMISSION,
|
||||
|
||||
/** For [Plugin.permissionId] */
|
||||
PERMISSION_ID
|
||||
}
|
||||
|
||||
public companion object {
|
||||
internal var instanceField: PermissionService<*>? = null
|
||||
|
||||
@ -118,11 +142,21 @@ public interface PermissionService<P : Permission> {
|
||||
public val INSTANCE: PermissionService<out Permission>
|
||||
get() = instanceField ?: error("PermissionService is not yet initialized therefore cannot be used.")
|
||||
|
||||
/**
|
||||
* 获取一个权限, 失败时抛出 [NoSuchElementException]
|
||||
*/
|
||||
@Throws(NoSuchElementException::class)
|
||||
public fun <P : Permission> PermissionService<P>.getOrFail(id: PermissionId): P =
|
||||
get(id) ?: throw NoSuchElementException("Permission not found: $id")
|
||||
|
||||
internal fun PermissionService<*>.allocatePermissionIdForPlugin(name: String, id: String) =
|
||||
PermissionId("plugin.${name.toLowerCase()}", id.toLowerCase())
|
||||
internal fun PermissionService<*>.allocatePermissionIdForPluginDefaultImplement(
|
||||
plugin: Plugin,
|
||||
@ResolveContext(COMMAND_NAME) permissionName: String,
|
||||
reason: PluginPermissionIdRequestType
|
||||
) = PermissionId(
|
||||
plugin.name.toLowerCase().replace(' ', '.'),
|
||||
permissionName.toLowerCase().replace(' ', '.')
|
||||
)
|
||||
|
||||
public fun PermissionId.findCorrespondingPermission(): Permission? = INSTANCE[this]
|
||||
|
||||
|
@ -14,6 +14,7 @@ package net.mamoe.mirai.console.permission
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import net.mamoe.mirai.console.command.BuiltInCommands
|
||||
import net.mamoe.mirai.console.internal.data.map
|
||||
import net.mamoe.mirai.console.internal.permission.parseFromStringImpl
|
||||
import net.mamoe.mirai.console.permission.AbstractPermitteeId.*
|
||||
@ -107,6 +108,33 @@ public interface PermitteeId {
|
||||
*
|
||||
* - 若指令 A 的权限被授予给 [AnyMember], 那么一个 [ExactMember] 可以执行这个指令.
|
||||
*
|
||||
* #### 字符串表示
|
||||
*
|
||||
* 当使用 [PermitteeId.asString] 时, 不同的类型的返回值如下表所示. 这些格式也适用于 [BuiltInCommands.PermissionCommand].
|
||||
*
|
||||
* (不区分大小写. 不区分 Bot).
|
||||
*
|
||||
*
|
||||
* | 被许可人类型 | 字符串表示示例 | 备注 |
|
||||
* |:----------------:|:-----------:|:-------------------------------------|
|
||||
* | 控制台 | console | |
|
||||
* | 精确群 | g123456 | 表示群, 而不表示群成员 |
|
||||
* | 精确好友 | f123456 | 必须通过好友消息 |
|
||||
* | 精确临时会话 | t123456.789 | 群 123456 内的成员 789. 必须通过临时会话 |
|
||||
* | 精确群成员 | m123456.789 | 群 123456 内的成员 789. 同时包含临时会话. |
|
||||
* | 精确用户 | u123456 | 同时包含群成员, 好友, 临时会话 |
|
||||
* | 任意群 | g* | |
|
||||
* | 任意群的任意群员 | m* | |
|
||||
* | 精确群的任意群员 | m123456.* | 群 123456 内的任意成员. 同时包含临时会话. |
|
||||
* | 任意群的任意临时会话 | t* | 必须通过临时会话 |
|
||||
* | 精确群的任意临时会话 | t123456.* | 群 123456 内的任意成员. 必须通过临时会话 |
|
||||
* | 任意好友 | f* | |
|
||||
* | 任意用户 | u* | 任何人在任何环境 |
|
||||
* | 任意对象 | * | 即任何人, 任何群, 控制台 |
|
||||
*
|
||||
*
|
||||
* #### 关系图
|
||||
*
|
||||
* ```
|
||||
* Console AnyContact
|
||||
* ↑
|
||||
@ -141,7 +169,7 @@ public interface PermitteeId {
|
||||
* ExactTemp
|
||||
* ```
|
||||
*/
|
||||
@Serializable(with = AbstractPermitteeId.AsStringSerializer::class)
|
||||
@Serializable(with = AsStringSerializer::class)
|
||||
public sealed class AbstractPermitteeId(
|
||||
public final override vararg val directParents: PermitteeId,
|
||||
) : PermitteeId {
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
package net.mamoe.mirai.console.plugin
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import net.mamoe.mirai.console.command.CommandOwner
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable
|
||||
@ -20,6 +19,7 @@ import net.mamoe.mirai.console.plugin.description.PluginDependency
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
|
||||
/**
|
||||
* 表示一个 mirai-console 插件.
|
||||
@ -62,7 +62,7 @@ public inline val Plugin.name: String get() = this.description.name
|
||||
/**
|
||||
* 获取 [PluginDescription.version]
|
||||
*/
|
||||
public inline val Plugin.version: Semver get() = this.description.version
|
||||
public inline val Plugin.version: SemVersion get() = this.description.version
|
||||
|
||||
/**
|
||||
* 获取 [PluginDescription.info]
|
||||
|
@ -41,28 +41,24 @@ public interface PluginFileExtensions {
|
||||
* 从数据目录获取一个文件.
|
||||
* @see dataFolderPath
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun resolveDataFile(relativePath: String): File = dataFolderPath.resolve(relativePath).toFile()
|
||||
|
||||
/**
|
||||
* 从数据目录获取一个文件.
|
||||
* @see dataFolderPath
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun resolveDataPath(relativePath: String): Path = dataFolderPath.resolve(relativePath)
|
||||
|
||||
/**
|
||||
* 从数据目录获取一个文件.
|
||||
* @see dataFolderPath
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun resolveDataFile(relativePath: Path): File = dataFolderPath.resolve(relativePath).toFile()
|
||||
|
||||
/**
|
||||
* 从数据目录获取一个文件路径.
|
||||
* @see dataFolderPath
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun resolveDataPath(relativePath: Path): Path = dataFolderPath.resolve(relativePath)
|
||||
|
||||
|
||||
@ -83,27 +79,23 @@ public interface PluginFileExtensions {
|
||||
* 从配置目录获取一个文件.
|
||||
* @see configFolderPath
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun resolveConfigFile(relativePath: String): File = configFolderPath.resolve(relativePath).toFile()
|
||||
|
||||
/**
|
||||
* 从配置目录获取一个文件.
|
||||
* @see configFolderPath
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun resolveConfigPath(relativePath: String): Path = configFolderPath.resolve(relativePath)
|
||||
|
||||
/**
|
||||
* 从配置目录获取一个文件.
|
||||
* @see configFolderPath
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun resolveConfigFile(relativePath: Path): File = configFolderPath.resolve(relativePath).toFile()
|
||||
|
||||
/**
|
||||
* 从配置目录获取一个文件路径.
|
||||
* @see configFolderPath
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun resolveConfigPath(relativePath: Path): Path = configFolderPath.resolve(relativePath)
|
||||
}
|
@ -37,7 +37,6 @@ public interface ResourceContainer {
|
||||
*
|
||||
* @return 资源文件内容. 在未找到文件时返回 `null`.
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun getResource(path: String): String? = getResource(path, Charsets.UTF_8)
|
||||
|
||||
/**
|
||||
@ -45,7 +44,6 @@ public interface ResourceContainer {
|
||||
*
|
||||
* @return 资源文件内容. 在未找到文件时返回 `null`.
|
||||
*/
|
||||
@JvmDefault
|
||||
public fun getResource(path: String, charset: Charset): String? =
|
||||
this.getResourceAsStream(path)?.use(InputStream::readBytes)?.let(::String)
|
||||
|
||||
|
@ -11,6 +11,10 @@
|
||||
|
||||
package net.mamoe.mirai.console.plugin.description
|
||||
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_ID
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
|
||||
/**
|
||||
* 插件的一个依赖的信息.
|
||||
*
|
||||
@ -20,16 +24,15 @@ public data class PluginDependency @JvmOverloads constructor(
|
||||
/**
|
||||
* 依赖插件 ID, [PluginDescription.id]
|
||||
*/
|
||||
public val id: String,
|
||||
@ResolveContext(PLUGIN_ID) public val id: String,
|
||||
/**
|
||||
* 依赖版本号. 为 null 时则为不限制版本.
|
||||
*
|
||||
* 版本遵循 [语义化版本 2.0 规范](https://semver.org/lang/zh-CN/),
|
||||
*
|
||||
* ### 示例
|
||||
* `Requirement.buildIvy("[1.0, 2.0)")`
|
||||
* @see SemVersion.Requirement
|
||||
*/
|
||||
public val versionRequirement: VersionRequirement? = null,
|
||||
public val versionRequirement: SemVersion.Requirement? = null,
|
||||
/**
|
||||
* 若为 `false`, 插件在找不到此依赖时也能正常加载.
|
||||
*/
|
||||
@ -46,7 +49,10 @@ public data class PluginDependency @JvmOverloads constructor(
|
||||
/**
|
||||
* @see PluginDependency
|
||||
*/
|
||||
public constructor(name: String, isOptional: Boolean = false) : this(
|
||||
name, null, isOptional
|
||||
public constructor(
|
||||
@ResolveContext(PLUGIN_ID) id: String,
|
||||
isOptional: Boolean = false,
|
||||
) : this(
|
||||
id, null, isOptional
|
||||
)
|
||||
}
|
@ -9,8 +9,10 @@
|
||||
|
||||
package net.mamoe.mirai.console.plugin.description
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.*
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
|
||||
|
||||
/**
|
||||
@ -46,6 +48,7 @@ public interface PluginDescription {
|
||||
* @see ID_REGEX
|
||||
* @see FORBIDDEN_ID_NAMES
|
||||
*/
|
||||
@ResolveContext(PLUGIN_ID)
|
||||
public val id: String
|
||||
|
||||
/**
|
||||
@ -60,6 +63,7 @@ public interface PluginDescription {
|
||||
*
|
||||
* @see FORBIDDEN_ID_NAMES
|
||||
*/
|
||||
@ResolveContext(PLUGIN_NAME)
|
||||
public val name: String
|
||||
|
||||
/**
|
||||
@ -88,7 +92,8 @@ public interface PluginDescription {
|
||||
*
|
||||
* @see Semver 语义化版本. 允许 [宽松][Semver.SemverType.LOOSE] 类型版本.
|
||||
*/
|
||||
public val version: Semver
|
||||
@ResolveContext(PLUGIN_VERSION)
|
||||
public val version: SemVersion
|
||||
|
||||
/**
|
||||
* 插件信息, 允许为空
|
||||
|
@ -1,242 +0,0 @@
|
||||
package net.mamoe.mirai.console.plugin.description
|
||||
|
||||
import com.vdurmont.semver4j.Requirement
|
||||
import com.vdurmont.semver4j.Semver
|
||||
|
||||
public sealed class VersionRequirement {
|
||||
public abstract operator fun contains(version: Semver): Boolean
|
||||
public fun contains(version: String): Boolean = contains(Semver(version, Semver.SemverType.LOOSE))
|
||||
|
||||
public class Exact
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
constructor(
|
||||
version: Semver,
|
||||
) : VersionRequirement() {
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
public val version: Semver = version.toStrict()
|
||||
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
public constructor(version: String) : this(Semver(version, Semver.SemverType.LOOSE))
|
||||
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
override fun contains(version: Semver): Boolean = this.version.isEquivalentTo(version.toStrict())
|
||||
}
|
||||
|
||||
public data class MatchesNpmPattern(
|
||||
val pattern: String,
|
||||
) : VersionRequirement() {
|
||||
private val requirement = Requirement.buildNPM(pattern)
|
||||
override fun contains(version: Semver): Boolean = requirement.isSatisfiedBy(version.toStrict())
|
||||
}
|
||||
|
||||
public data class MatchesIvyPattern(
|
||||
val pattern: String,
|
||||
) : VersionRequirement() {
|
||||
private val requirement = Requirement.buildIvy(pattern)
|
||||
override fun contains(version: Semver): Boolean = requirement.isSatisfiedBy(version.toStrict())
|
||||
}
|
||||
|
||||
|
||||
public data class MatchesCocoapodsPattern(
|
||||
val pattern: String,
|
||||
) : VersionRequirement() {
|
||||
private val requirement = Requirement.buildCocoapods(pattern)
|
||||
override fun contains(version: Semver): Boolean = requirement.isSatisfiedBy(version.toStrict())
|
||||
}
|
||||
|
||||
public abstract class Custom : VersionRequirement()
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
public class InRange(
|
||||
begin: Semver,
|
||||
public val beginInclusive: Boolean,
|
||||
end: Semver,
|
||||
public val endInclusive: Boolean,
|
||||
) : VersionRequirement() {
|
||||
public val end: Semver = end.toStrict()
|
||||
public val begin: Semver = begin.toStrict()
|
||||
|
||||
public constructor(
|
||||
begin: String,
|
||||
beginInclusive: Boolean,
|
||||
end: Semver,
|
||||
endInclusive: Boolean,
|
||||
) : this(Semver(begin, Semver.SemverType.LOOSE), beginInclusive, end, endInclusive)
|
||||
|
||||
public constructor(
|
||||
begin: String,
|
||||
beginInclusive: Boolean,
|
||||
end: String,
|
||||
endInclusive: Boolean,
|
||||
) : this(Semver(begin, Semver.SemverType.LOOSE),
|
||||
beginInclusive,
|
||||
Semver(end, Semver.SemverType.LOOSE),
|
||||
endInclusive)
|
||||
|
||||
public constructor(
|
||||
begin: Semver,
|
||||
beginInclusive: Boolean,
|
||||
end: String,
|
||||
endInclusive: Boolean,
|
||||
) : this(begin, beginInclusive, Semver(end, Semver.SemverType.LOOSE), endInclusive)
|
||||
|
||||
override fun contains(version: Semver): Boolean {
|
||||
val strict = version.toStrict()
|
||||
return if (beginInclusive) {
|
||||
strict.isGreaterThanOrEqualTo(begin)
|
||||
} else {
|
||||
strict.isGreaterThan(begin)
|
||||
} && if (endInclusive) {
|
||||
strict.isLowerThanOrEqualTo(end)
|
||||
} else {
|
||||
strict.isLowerThan(end)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return buildString {
|
||||
append(if (beginInclusive) "[" else "(")
|
||||
append(begin)
|
||||
append(",")
|
||||
append(end)
|
||||
append(if (endInclusive) "]" else ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("unused", "DeprecatedCallableAddReplaceWith")
|
||||
public class Builder {
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public fun exact(version: Semver): VersionRequirement = Exact(version)
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public fun exact(version: String): VersionRequirement = Exact(version)
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public fun custom(checker: (version: Semver) -> Boolean): VersionRequirement {
|
||||
return object : Custom() {
|
||||
override fun contains(version: Semver): Boolean = checker(version)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Semver.SemverType.NPM
|
||||
*/
|
||||
@ILoveKafuuChinoForever
|
||||
public fun npmPattern(versionPattern: String): VersionRequirement {
|
||||
return MatchesNpmPattern(versionPattern)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Semver.SemverType.IVY
|
||||
*/
|
||||
@ILoveKafuuChinoForever
|
||||
public fun ivyPattern(versionPattern: String): VersionRequirement {
|
||||
return MatchesIvyPattern(versionPattern)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Semver.SemverType.COCOAPODS
|
||||
*/
|
||||
@ILoveKafuuChinoForever
|
||||
public fun cocoapodsPattern(versionPattern: String): VersionRequirement {
|
||||
return MatchesCocoapodsPattern(versionPattern)
|
||||
}
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public fun range(
|
||||
begin: Semver,
|
||||
beginInclusive: Boolean,
|
||||
end: Semver,
|
||||
endInclusive: Boolean,
|
||||
): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive)
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public fun range(
|
||||
begin: String,
|
||||
beginInclusive: Boolean,
|
||||
end: Semver,
|
||||
endInclusive: Boolean,
|
||||
): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive)
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public fun range(
|
||||
begin: Semver,
|
||||
beginInclusive: Boolean,
|
||||
end: String,
|
||||
endInclusive: Boolean,
|
||||
): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive)
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public fun range(
|
||||
begin: String,
|
||||
beginInclusive: Boolean,
|
||||
end: String,
|
||||
endInclusive: Boolean,
|
||||
): VersionRequirement = InRange(begin, beginInclusive, end, endInclusive)
|
||||
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public operator fun Semver.rangeTo(endInclusive: Semver): VersionRequirement {
|
||||
return InRange(this, true, endInclusive, true)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public operator fun Semver.rangeTo(endInclusive: String): VersionRequirement {
|
||||
return InRange(this, true, Semver(endInclusive, Semver.SemverType.LOOSE), true)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public operator fun String.rangeTo(endInclusive: String): VersionRequirement {
|
||||
return InRange(Semver(this, Semver.SemverType.LOOSE),
|
||||
true,
|
||||
Semver(endInclusive, Semver.SemverType.LOOSE),
|
||||
true)
|
||||
}
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public operator fun String.rangeTo(endInclusive: Semver): VersionRequirement {
|
||||
return InRange(Semver(this, Semver.SemverType.LOOSE), true, endInclusive, true)
|
||||
}
|
||||
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public infix fun Semver.until(endExclusive: Semver): VersionRequirement {
|
||||
return InRange(this, true, endExclusive, false)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public infix fun Semver.until(endExclusive: String): VersionRequirement {
|
||||
return InRange(this, true, Semver(endExclusive, Semver.SemverType.LOOSE), false)
|
||||
}
|
||||
|
||||
@ILoveKafuuChinoForever
|
||||
public infix fun String.until(endExclusive: String): VersionRequirement {
|
||||
return InRange(Semver(this, Semver.SemverType.LOOSE),
|
||||
true,
|
||||
Semver(endExclusive, Semver.SemverType.LOOSE),
|
||||
false)
|
||||
}
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKafuuChinoForever
|
||||
public infix fun String.until(endExclusive: Semver): VersionRequirement {
|
||||
return InRange(Semver(this, Semver.SemverType.LOOSE), true, endExclusive, false)
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@DslMarker
|
||||
private annotation class ILoveKafuuChinoForever
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.console.data.PluginConfig
|
||||
import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.internal.plugin.JvmPluginInternal
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.utils.minutesToMillis
|
||||
import net.mamoe.mirai.utils.secondsToMillis
|
||||
@ -37,7 +38,8 @@ public abstract class AbstractJvmPlugin @JvmOverloads constructor(
|
||||
|
||||
public final override val loader: JvmPluginLoader get() = super<JvmPluginInternal>.loader
|
||||
|
||||
public final override fun permissionId(name: String): PermissionId = PermissionId(description.id, "command.$name")
|
||||
public final override fun permissionId(name: String): PermissionId =
|
||||
PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*", PermissionService.PluginPermissionIdRequestType.PERMISSION_ID)
|
||||
|
||||
/**
|
||||
* 重载 [PluginData]
|
||||
|
@ -18,6 +18,9 @@
|
||||
package net.mamoe.mirai.console.plugin.jvm
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.console.compiler.common.RestrictedScope
|
||||
import net.mamoe.mirai.console.compiler.common.RestrictedScope.Kind.COMMAND_REGISTER
|
||||
import net.mamoe.mirai.console.compiler.common.RestrictedScope.Kind.PERMISSION_REGISTER
|
||||
import net.mamoe.mirai.console.extension.PluginComponentStorage
|
||||
import net.mamoe.mirai.console.permission.PermissionIdNamespace
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
@ -59,6 +62,7 @@ public interface JvmPlugin : Plugin, CoroutineScope,
|
||||
*
|
||||
* @receiver 组件容器
|
||||
*/
|
||||
@RestrictedScope(COMMAND_REGISTER, PERMISSION_REGISTER)
|
||||
public fun PluginComponentStorage.onLoad() {}
|
||||
|
||||
/**
|
||||
|
@ -11,10 +11,11 @@
|
||||
|
||||
package net.mamoe.mirai.console.plugin.jvm
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.*
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDependency
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.plugin.description.VersionRequirement
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
|
||||
/**
|
||||
* JVM 插件的描述. 通常作为 `plugin.yml`
|
||||
@ -32,20 +33,21 @@ public interface JvmPluginDescription : PluginDescription {
|
||||
* 构建 [JvmPluginDescription]
|
||||
* @see JvmPluginDescriptionBuilder
|
||||
*/
|
||||
@JvmName("create")
|
||||
@JvmSynthetic
|
||||
public operator fun invoke(
|
||||
/**
|
||||
* @see [PluginDescription.id]
|
||||
*/
|
||||
id: String,
|
||||
@ResolveContext(PLUGIN_ID) id: String,
|
||||
/**
|
||||
* @see [PluginDescription.version]
|
||||
*/
|
||||
version: String,
|
||||
@ResolveContext(PLUGIN_VERSION) version: String,
|
||||
/**
|
||||
* @see [PluginDescription.name]
|
||||
*/
|
||||
name: String = id,
|
||||
@ResolveContext(PLUGIN_NAME) name: String = id,
|
||||
block: JvmPluginDescriptionBuilder.() -> Unit = {},
|
||||
): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build()
|
||||
|
||||
@ -53,22 +55,21 @@ public interface JvmPluginDescription : PluginDescription {
|
||||
* 构建 [JvmPluginDescription]
|
||||
* @see JvmPluginDescriptionBuilder
|
||||
*/
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@JvmName("create")
|
||||
@JvmSynthetic
|
||||
public operator fun invoke(
|
||||
/**
|
||||
* @see [PluginDescription.id]
|
||||
*/
|
||||
id: String,
|
||||
@ResolveContext(PLUGIN_ID) id: String,
|
||||
/**
|
||||
* @see [PluginDescription.version]
|
||||
*/
|
||||
version: Semver,
|
||||
@ResolveContext(PLUGIN_VERSION) version: SemVersion,
|
||||
/**
|
||||
* @see [PluginDescription.name]
|
||||
*/
|
||||
name: String = id,
|
||||
@ResolveContext(PLUGIN_NAME) name: String = id,
|
||||
block: JvmPluginDescriptionBuilder.() -> Unit = {},
|
||||
): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build()
|
||||
}
|
||||
@ -95,14 +96,14 @@ public interface JvmPluginDescription : PluginDescription {
|
||||
*
|
||||
* @see [JvmPluginDescription.invoke]
|
||||
*/
|
||||
public class JvmPluginDescriptionBuilder
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
constructor(
|
||||
public class JvmPluginDescriptionBuilder(
|
||||
private var id: String,
|
||||
private var version: Semver,
|
||||
private var version: SemVersion,
|
||||
) {
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
public constructor(name: String, version: String) : this(name, Semver(version, Semver.SemverType.LOOSE))
|
||||
public constructor(
|
||||
@ResolveContext(PLUGIN_ID) id: String,
|
||||
@ResolveContext(PLUGIN_VERSION) version: String,
|
||||
) : this(id, SemVersion(version))
|
||||
|
||||
private var name: String = id
|
||||
private var author: String = ""
|
||||
@ -110,19 +111,20 @@ constructor(
|
||||
private var dependencies: MutableSet<PluginDependency> = mutableSetOf()
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun name(value: String): JvmPluginDescriptionBuilder = apply { this.name = value.trim() }
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun version(value: String): JvmPluginDescriptionBuilder =
|
||||
apply { this.version = Semver(value, Semver.SemverType.LOOSE) }
|
||||
|
||||
@Deprecated("Semver 将会在 1.0-RC 被替换为 Console 自己实现的版本。请临时使用 String。", level = DeprecationLevel.ERROR)
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun version(value: Semver): JvmPluginDescriptionBuilder = apply { this.version = value }
|
||||
public fun name(@ResolveContext(PLUGIN_NAME) value: String): JvmPluginDescriptionBuilder =
|
||||
apply { this.name = value.trim() }
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun id(value: String): JvmPluginDescriptionBuilder = apply { this.id = value.trim() }
|
||||
public fun version(@ResolveContext(PLUGIN_VERSION) value: String): JvmPluginDescriptionBuilder =
|
||||
apply { this.version = SemVersion(value) }
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun version(@ResolveContext(PLUGIN_VERSION) value: SemVersion): JvmPluginDescriptionBuilder =
|
||||
apply { this.version = value }
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun id(@ResolveContext(PLUGIN_ID) value: String): JvmPluginDescriptionBuilder =
|
||||
apply { this.id = value.trim() }
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun author(value: String): JvmPluginDescriptionBuilder = apply { this.author = value.trim() }
|
||||
@ -151,9 +153,9 @@ constructor(
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
@ResolveContext(PLUGIN_ID) pluginId: String,
|
||||
isOptional: Boolean = false,
|
||||
versionRequirement: VersionRequirement,
|
||||
versionRequirement: SemVersion.Requirement,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
this.dependencies.add(PluginDependency(pluginId, versionRequirement, isOptional))
|
||||
}
|
||||
@ -165,8 +167,8 @@ constructor(
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
versionRequirement: VersionRequirement,
|
||||
@ResolveContext(PLUGIN_ID) pluginId: String,
|
||||
versionRequirement: SemVersion.Requirement,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
this.dependencies.add(PluginDependency(pluginId, versionRequirement, false))
|
||||
}
|
||||
@ -178,46 +180,24 @@ constructor(
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
@ResolveContext(PLUGIN_ID) pluginId: String,
|
||||
isOptional: Boolean = false,
|
||||
): JvmPluginDescriptionBuilder = apply {
|
||||
this.dependencies.add(PluginDependency(pluginId, null, isOptional))
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:
|
||||
*
|
||||
* ```
|
||||
* dependsOn("org.example.test-plugin") { "1.0.0".."1.2.0" }
|
||||
* dependsOn("org.example.test-plugin") { npmPattern("1.x || >=2.5.0 || 5.0.0 - 7.2.3") }
|
||||
* dependsOn("org.example.test-plugin") { ivyPattern("[1.0,2.0[") }
|
||||
* dependsOn("org.example.test-plugin") { custom { it.toString() == "1.0.0" } }
|
||||
* ```
|
||||
*
|
||||
* @see PluginDependency
|
||||
* @see VersionRequirement.Builder
|
||||
*/
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun dependsOn(
|
||||
pluginId: String,
|
||||
isOptional: Boolean = false,
|
||||
versionRequirement: VersionRequirement.Builder.() -> VersionRequirement,
|
||||
): JvmPluginDescriptionBuilder =
|
||||
apply {
|
||||
this.dependencies.add(PluginDependency(pluginId,
|
||||
VersionRequirement.Builder().run(versionRequirement),
|
||||
isOptional))
|
||||
}
|
||||
|
||||
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
public fun build(): JvmPluginDescription =
|
||||
SimpleJvmPluginDescription(name, version, id, author, info, dependencies)
|
||||
|
||||
/**
|
||||
* 标注一个 [JvmPluginDescription] DSL
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection")
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@DslMarker
|
||||
private annotation class ILoveKuriyamaMiraiForever // https://zh.moegirl.org.cn/zh-cn/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5
|
||||
internal annotation class ILoveKuriyamaMiraiForever // https://zh.moegirl.org.cn/zh-cn/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,56 +205,26 @@ constructor(
|
||||
*
|
||||
* @see JvmPluginDescription
|
||||
*/
|
||||
@Deprecated(
|
||||
"""
|
||||
将在 1.0-RC 删除. 请使用 JvmPluginDescription.
|
||||
""",
|
||||
replaceWith = ReplaceWith(
|
||||
"JvmPluginDescription",
|
||||
"net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription"
|
||||
),
|
||||
level = DeprecationLevel.ERROR
|
||||
)
|
||||
public data class SimpleJvmPluginDescription
|
||||
@Deprecated(
|
||||
"""
|
||||
构造器不稳定, 将在 1.0-RC 删除. 请使用 JvmPluginDescriptionBuilder.
|
||||
""",
|
||||
replaceWith = ReplaceWith(
|
||||
"JvmPluginDescription(name, version) {}",
|
||||
"net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription.Companion.invoke"
|
||||
),
|
||||
level = DeprecationLevel.ERROR
|
||||
)
|
||||
@JvmOverloads public constructor(
|
||||
public override val name: String,
|
||||
public override val version: Semver,
|
||||
public override val id: String = name,
|
||||
public override val author: String = "",
|
||||
public override val info: String = "",
|
||||
public override val dependencies: Set<PluginDependency> = setOf(),
|
||||
internal data class SimpleJvmPluginDescription
|
||||
@JvmOverloads constructor(
|
||||
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(),
|
||||
) : JvmPluginDescription {
|
||||
|
||||
@Deprecated(
|
||||
"""
|
||||
构造器不稳定, 将在 1.0-RC 删除. 请使用 JvmPluginDescriptionBuilder.
|
||||
""",
|
||||
replaceWith = ReplaceWith(
|
||||
"JvmPluginDescription.invoke(name, version) {}",
|
||||
"net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription.Companion.invoke"
|
||||
),
|
||||
level = DeprecationLevel.ERROR
|
||||
)
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
@JvmOverloads
|
||||
public constructor(
|
||||
constructor(
|
||||
name: String,
|
||||
version: String,
|
||||
id: String = name,
|
||||
author: String = "",
|
||||
info: String = "",
|
||||
dependencies: Set<PluginDependency> = setOf(),
|
||||
) : this(name, Semver(version, Semver.SemverType.LOOSE), id, author, info, dependencies)
|
||||
) : this(name, SemVersion(version), id, author, info, dependencies)
|
||||
|
||||
init {
|
||||
require(!name.contains(':')) { "':' is forbidden in plugin name" }
|
||||
|
@ -15,7 +15,7 @@ import net.mamoe.mirai.console.internal.plugin.BuiltInJvmPluginLoaderImpl
|
||||
import net.mamoe.mirai.console.plugin.loader.FilePluginLoader
|
||||
|
||||
/**
|
||||
* 内建的 Jar (JVM) 插件加载器
|
||||
* JVM 插件加载器
|
||||
*/
|
||||
public interface JvmPluginLoader : CoroutineScope, FilePluginLoader<JvmPlugin, JvmPluginDescription> {
|
||||
/**
|
||||
|
@ -61,10 +61,9 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等).
|
||||
*
|
||||
* @see PluginDescription 插件描述
|
||||
* @see getPluginDescription 无 receiver, 接受参数的版本.
|
||||
*/
|
||||
@Throws(PluginLoadException::class)
|
||||
public fun getPluginDescription(plugin: P): D // Java signature: `public D getDescription(P)`
|
||||
public fun getPluginDescription(plugin: P): D
|
||||
|
||||
/**
|
||||
* 主动加载一个插件 (实例), 但不 [启用][enable] 它. 返回加载成功的主类实例
|
||||
|
@ -96,7 +96,7 @@ import kotlin.internal.LowPriorityInOverloadResolution
|
||||
*/
|
||||
public interface MessageScope {
|
||||
/**
|
||||
* 如果此 [MessageScope], 仅包含一个消息对象, 则 [realTarget] 指向这个对象.
|
||||
* 如果此 [MessageScope] 仅包含一个消息对象, 则 [realTarget] 指向这个对象. 否则 [realTarget] 为 `null`.
|
||||
*
|
||||
* 对于 [CommandSender] 作为 [MessageScope], [realTarget] 总是指令执行者 [User], 即 [CommandSender.user]
|
||||
*
|
||||
@ -116,7 +116,6 @@ public interface MessageScope {
|
||||
/**
|
||||
* 立刻以此发送消息给所有在此 [MessageScope] 下的消息对象
|
||||
*/
|
||||
@JvmDefault
|
||||
@JvmBlockingBridge
|
||||
public suspend fun sendMessage(message: String)
|
||||
}
|
||||
|
@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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 via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.util
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_VERSION
|
||||
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
|
||||
|
||||
/**
|
||||
* [语义化版本](https://semver.org/lang/zh-CN/) 支持
|
||||
*
|
||||
* 解析示例:
|
||||
*
|
||||
* `1.0.0-M4+c25733b8` 将会解析出下面的内容,
|
||||
* [major] (主本号), [minor] (次版本号), [patch] (修订号), [identifier] (先行版本号) 和 [metadata] (元数据).
|
||||
* ```
|
||||
* SemVersion(
|
||||
* major = 1,
|
||||
* minor = 0,
|
||||
* patch = 0,
|
||||
* identifier = "M4"
|
||||
* metadata = "c25733b8"
|
||||
* )
|
||||
* ```
|
||||
* 其中 identifier 和 metadata 都是可选的.
|
||||
*
|
||||
* 对于核心版本号, 此实现稍微比 semver 宽松一些, 允许 x.y 的存在.
|
||||
*
|
||||
* @see Requirement
|
||||
* @see SemVersion.invoke
|
||||
*/
|
||||
@Serializable(with = SemVersion.SemVersionAsStringSerializer::class)
|
||||
public data class SemVersion
|
||||
/**
|
||||
* @see SemVersion.invoke 字符串解析
|
||||
*/
|
||||
internal constructor(
|
||||
/** 主版本号 */
|
||||
public val major: Int,
|
||||
/** 次版本号 */
|
||||
public val minor: Int,
|
||||
/** 修订号 */
|
||||
public val patch: Int?,
|
||||
/** 先行版本号识别符 */
|
||||
public val identifier: String? = null,
|
||||
/** 版本号元数据, 不参与版本号对比([compareTo]), 但是参与版本号严格对比([equals]) */
|
||||
public val metadata: String? = null,
|
||||
) : Comparable<SemVersion> {
|
||||
/**
|
||||
* 一条依赖规则
|
||||
* @see [parseRangeRequirement]
|
||||
*/
|
||||
public interface Requirement {
|
||||
/** 在 [version] 满足此要求时返回 true */
|
||||
public fun test(version: SemVersion): Boolean
|
||||
}
|
||||
|
||||
public object SemVersionAsStringSerializer : KSerializer<SemVersion> by String.serializer().map(
|
||||
serializer = { it.toString() },
|
||||
deserializer = { SemVersion(it) }
|
||||
)
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 解析一个版本号, 将会返回一个 [SemVersion],
|
||||
* 如果发生解析错误将会抛出一个 [IllegalArgumentException] 或者 [NumberFormatException]
|
||||
*
|
||||
* 对于版本号的组成, 有以下规定:
|
||||
* - 必须包含主版本号和次版本号
|
||||
* - 存在 先行版本号 的时候 先行版本号 不能为空
|
||||
* - 存在 元数据 的时候 元数据 不能为空
|
||||
* - 核心版本号只允许 `x.y` 和 `x.y.z` 的存在
|
||||
* - `1.0-RC` 是合法的
|
||||
* - `1.0.0-RC` 也是合法的, 与 `1.0-RC` 一样
|
||||
* - `1.0.0.0-RC` 是不合法的, 将会抛出一个 [IllegalArgumentException]
|
||||
*
|
||||
* 注意情况:
|
||||
* - 第一个 `+` 之后的所有内容全部识别为元数据
|
||||
* - `1.0+METADATA-M4`, metadata="METADATA-M4"
|
||||
* - 如果不确定版本号是否合法, 可以使用 [regex101.com](https://regex101.com/r/vkijKf/1/) 进行检查
|
||||
* - 此实现使用的正则表达式为 `^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class, NumberFormatException::class)
|
||||
@JvmStatic
|
||||
@JvmName("parse")
|
||||
public operator fun invoke(@ResolveContext(PLUGIN_VERSION) version: String): SemVersion = SemVersionInternal.parse(version)
|
||||
|
||||
/**
|
||||
* 解析一条依赖需求描述, 在无法解析的时候抛出 [IllegalArgumentException]
|
||||
*
|
||||
* 对于一条规则, 有以下方式可选
|
||||
*
|
||||
* - `1.0.0-M4` 要求 1.0.0-M4 版本, 且只能是 1.0.0-M4 版本
|
||||
* - `1.x` 要求 1.x 版本
|
||||
* - `> 1.0.0-RC` 要求 1.0.0-RC 之后的版本, 不能是 1.0.0-RC
|
||||
* - `>= 1.0.0-RC` 要求 1.0.0-RC 或之后的版本, 可以是 1.0.0-RC
|
||||
* - `< 1.0.0-RC` 要求 1.0.0-RC 之前的版本, 不能是 1.0.0-RC
|
||||
* - `<= 1.0.0-RC` 要求 1.0.0-RC 或之前的版本, 可以是 1.0.0-RC
|
||||
* - `!= 1.0.0-RC` 要求 除了1.0.0-RC 的任何版本
|
||||
* - `[1.0.0, 1.2.0]`
|
||||
* - `(1.0.0, 1.2.0]`
|
||||
* - `[1.0.0, 1.2.0)`
|
||||
* - `(1.0.0, 1.2.0)` [数学区间](https://baike.baidu.com/item/%E5%8C%BA%E9%97%B4/1273117)
|
||||
*
|
||||
* 对于多个规则, 允许使用逻辑符号 `{}`, `||`, `&&`
|
||||
* 例如:
|
||||
* - `1.x || 2.x || 3.0.0`
|
||||
* - `<= 0.5.3 || >= 1.0.0`
|
||||
* - `{> 1.0 && < 1.5} || {> 1.8}`
|
||||
* - `{> 1.0 && < 1.5} || {> 1.8}`
|
||||
* - `> 1.0.0 && != 1.2.0`
|
||||
*
|
||||
* 特别注意:
|
||||
* - 依赖规则版本号不需要携带版本号元数据, 元数据不参与依赖需求的检查
|
||||
* - 如果目标版本号携带有先行版本号, 请不要忘记先行版本号
|
||||
* - 因为 `()` 已经用于数学区间, 使用 `{}` 替代 `()`
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
@JvmStatic
|
||||
public fun parseRangeRequirement(requirement: String): Requirement =
|
||||
SemVersionInternal.parseRangeRequirement(requirement)
|
||||
|
||||
/** @see [Requirement.test] */
|
||||
@JvmStatic
|
||||
public fun Requirement.test(@ResolveContext(PLUGIN_VERSION) version: String): Boolean = test(invoke(version))
|
||||
|
||||
/**
|
||||
* 当满足 [requirement] 时返回 true, 否则返回 false
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun SemVersion.satisfies(requirement: Requirement): Boolean = requirement.test(this)
|
||||
|
||||
/** for Kotlin only */
|
||||
@JvmStatic
|
||||
@JvmSynthetic
|
||||
public operator fun Requirement.contains(version: SemVersion): Boolean = test(version)
|
||||
|
||||
/** for Kotlin only */
|
||||
@JvmStatic
|
||||
@JvmSynthetic
|
||||
public operator fun Requirement.contains(@ResolveContext(PLUGIN_VERSION) version: String): Boolean = test(version)
|
||||
}
|
||||
|
||||
@Transient
|
||||
private val toString: String by lazy(LazyThreadSafetyMode.NONE) {
|
||||
buildString {
|
||||
append(major)
|
||||
append('.').append(minor)
|
||||
patch?.let { append('.').append(it) }
|
||||
identifier?.let { identifier ->
|
||||
append('-').append(identifier)
|
||||
}
|
||||
metadata?.let { metadata ->
|
||||
append('+').append(metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = toString
|
||||
|
||||
/**
|
||||
* 将 [SemVersion] 转为 Kotlin data class 风格的 [String]
|
||||
*/
|
||||
public fun toStructuredString(): String {
|
||||
return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SemVersion
|
||||
|
||||
return compareTo(other) == 0 && other.identifier == identifier && other.metadata == metadata
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = major shl minor
|
||||
result *= (patch ?: 1)
|
||||
result = 31 * result + (identifier?.hashCode() ?: 0)
|
||||
result = 31 * result + (metadata?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this object with the specified object for order. Returns zero if this object is equal
|
||||
* to the specified [other] object, a negative number if it's less than [other], or a positive number
|
||||
* if it's greater than [other].
|
||||
*/
|
||||
public override operator fun compareTo(other: SemVersion): Int {
|
||||
return SemVersionInternal.run { compareInternal(this@SemVersion, other) }
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@
|
||||
|
||||
package net.mamoe.mirai.console
|
||||
|
||||
import com.vdurmont.semver4j.Semver
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
@ -20,6 +19,7 @@ import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
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
|
||||
@ -43,8 +43,8 @@ fun initTestEnvironment() {
|
||||
get() = "Test"
|
||||
override val vendor: String
|
||||
get() = "Test"
|
||||
override val version: Semver
|
||||
get() = Semver("1.0.0")
|
||||
override val version: SemVersion
|
||||
get() = SemVersion("1.0.0")
|
||||
|
||||
}
|
||||
override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader })
|
||||
|
@ -79,7 +79,7 @@ internal class TestCommand {
|
||||
|
||||
assertEquals(1, ConsoleCommandOwner.registeredCommands.size)
|
||||
|
||||
assertEquals(1, CommandManagerImpl.registeredCommands.size)
|
||||
assertEquals(1, CommandManagerImpl._registeredCommands.size)
|
||||
assertEquals(2, CommandManagerImpl.requiredPrefixCommandMap.size)
|
||||
} finally {
|
||||
TestCompositeCommand.unregister()
|
||||
|
@ -17,7 +17,7 @@ import kotlin.test.assertSame
|
||||
|
||||
@OptIn(ConsoleInternalApi::class)
|
||||
internal class PluginDataTest {
|
||||
class MyPluginData : AutoSavePluginData() {
|
||||
class MyPluginData : AutoSavePluginData("test") {
|
||||
var int by value(1)
|
||||
val map: MutableMap<String, String> by value()
|
||||
val map2: MutableMap<String, MutableMap<String, String>> by value()
|
||||
|
@ -0,0 +1,225 @@
|
||||
/*
|
||||
* 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 via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.util
|
||||
|
||||
import net.mamoe.mirai.console.util.SemVersion.Companion.test
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class TestSemVersion {
|
||||
@Test
|
||||
internal fun testCompare() {
|
||||
fun String.sem(): SemVersion = SemVersion.invoke(this)
|
||||
assert("1.0".sem() < "1.0.1".sem())
|
||||
assert("1.0.0".sem() == "1.0".sem())
|
||||
assert("1.1".sem() > "1.0.0".sem())
|
||||
assert("1.0-M4".sem() < "1.0-M5".sem())
|
||||
assert("1.0-M5-dev-7".sem() < "1.0-M5-dev-15".sem())
|
||||
assert("1.0-M5-dev-79".sem() < "1.0-M5-dev-7001".sem())
|
||||
assert("1.0-M6".sem() > "1.0-M5-dev-15".sem())
|
||||
assert("1.0-RC".sem() > "1.0-M5-dev-15".sem())
|
||||
assert("1.0-RC2".sem() > "1.0-RC".sem())
|
||||
// example on semver
|
||||
// 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0
|
||||
assert("1.0.0-alpha".sem() < "1.0.0-alpha.1".sem())
|
||||
assert("1.0.0-alpha.1".sem() < "1.0.0-alpha.beta".sem())
|
||||
assert("1.0.0-alpha.beta".sem() < "1.0.0-beta".sem())
|
||||
assert("1.0.0-beta".sem() < "1.0.0-beta.2".sem())
|
||||
assert("1.0.0-beta.2".sem() < "1.0.0-beta.11".sem())
|
||||
assert("1.0.0-beta.11".sem() < "1.0.0-rc.1".sem())
|
||||
assert("1.0.0-rc.1".sem() < "1.0.0".sem())
|
||||
}
|
||||
|
||||
@Test
|
||||
internal fun testRequirement() {
|
||||
fun SemVersion.Requirement.assert(version: String): SemVersion.Requirement {
|
||||
assert(test(version)) { version }
|
||||
return this
|
||||
}
|
||||
|
||||
fun assertInvalid(requirement: String) {
|
||||
kotlin.runCatching {
|
||||
SemVersion.parseRangeRequirement(requirement)
|
||||
}.onSuccess { assert(false) { requirement } }
|
||||
}
|
||||
|
||||
fun SemVersion.Requirement.assertFalse(version: String): SemVersion.Requirement {
|
||||
assert(!test(version)) { version }
|
||||
return this
|
||||
}
|
||||
SemVersion.parseRangeRequirement("1.0")
|
||||
.assert("1.0").assert("1.0.0")
|
||||
.assertFalse("1.1.0").assertFalse("2.0.0")
|
||||
SemVersion.parseRangeRequirement("1.x")
|
||||
.assert("1.0").assert("1.1")
|
||||
.assert("1.5").assert("1.14514")
|
||||
.assertFalse("2.33")
|
||||
SemVersion.parseRangeRequirement("2.0||1.2.x")
|
||||
SemVersion.parseRangeRequirement("{2.0||1.2.x} && 1.1.0 &&1.2.3")
|
||||
SemVersion.parseRangeRequirement("2.0 || 1.2.x")
|
||||
.assert("2.0").assert("2.0.0")
|
||||
.assertFalse("2.1")
|
||||
.assert("1.2.5").assert("1.2.0").assertFalse("1.2")
|
||||
.assertFalse("1.0.0")
|
||||
SemVersion.parseRangeRequirement("[1.0.0, 19190.0]")
|
||||
.assert("1.0.0").assertFalse("0.1.0")
|
||||
.assert("19190.0").assertFalse("19198.10")
|
||||
SemVersion.parseRangeRequirement("[1.0.0, 2.0.0)")
|
||||
.assert("1.0.0").assert("1.2.3").assertFalse("2.0.0")
|
||||
SemVersion.parseRangeRequirement("(2.0.0, 1.0.0]")
|
||||
.assert("1.0.0").assert("1.2.3").assertFalse("2.0.0")
|
||||
SemVersion.parseRangeRequirement("(2.0.0, 1.0.0)")
|
||||
.assertFalse("1.0.0").assert("1.2.3").assertFalse("2.0.0")
|
||||
SemVersion.parseRangeRequirement("(1.0.0, 2.0.0)")
|
||||
.assertFalse("1.0.0").assert("1.2.3").assertFalse("2.0.0")
|
||||
SemVersion.parseRangeRequirement(" >= 1.0.0")
|
||||
.assert("1.0.0")
|
||||
.assert("114.514.1919")
|
||||
.assertFalse("0.0.0")
|
||||
.assertFalse("0.98774587")
|
||||
SemVersion.parseRangeRequirement("> 1.0.0")
|
||||
.assertFalse("1.0.0")
|
||||
SemVersion.parseRangeRequirement("!= 1.0.0 && != 2.0.0")
|
||||
.assert("1.2.3").assert("2.1.1")
|
||||
.assertFalse("1.0").assertFalse("1.0.0")
|
||||
.assertFalse("2.0").assertFalse("2.0.0")
|
||||
.assert("2.0.1").assert("1.0.1")
|
||||
|
||||
SemVersion.parseRangeRequirement("> 1.0.0 || < 0.9.0")
|
||||
.assertFalse("1.0.0")
|
||||
.assert("0.8.0")
|
||||
.assertFalse("0.9.0")
|
||||
SemVersion.parseRangeRequirement("{>= 1.0.0 && <= 1.2.3} || {>= 2.0.0 && <= 2.2.3}")
|
||||
.assertFalse("1.3.0")
|
||||
.assert("1.0.0").assert("1.2.3")
|
||||
.assertFalse("0.9.0")
|
||||
.assert("2.0.0").assert("2.2.3").assertFalse("2.3.4")
|
||||
|
||||
assertInvalid("WPOXAXW")
|
||||
assertInvalid("1.0.0 || 1.0.0 && 1.0.0")
|
||||
assertInvalid("{")
|
||||
assertInvalid("}")
|
||||
assertInvalid("")
|
||||
assertInvalid("1.2.3 - 3.2.1")
|
||||
assertInvalid("1.5.78 &&")
|
||||
assertInvalid("|| 1.0.0")
|
||||
}
|
||||
|
||||
private fun String.check() {
|
||||
val sem = SemVersion.invoke(this)
|
||||
assert(this == sem.toString()) { "$this != $sem" }
|
||||
}
|
||||
|
||||
private fun String.checkInvalid() {
|
||||
kotlin.runCatching { SemVersion.invoke(this) }
|
||||
.onSuccess { assert(false) { "$this not a invalid sem-version" } }
|
||||
}
|
||||
|
||||
@Test
|
||||
internal fun testSemVersionParsing() {
|
||||
"0.0".check()
|
||||
"1.0.0".check()
|
||||
"1.2.3.4.5.6.7.8".checkInvalid()
|
||||
"5555.0-A".check()
|
||||
"5555.0-A+METADATA".check()
|
||||
"5555.0+METADATA".check()
|
||||
"987.0+wwwxx-wk".check()
|
||||
"NOT.NUMBER".checkInvalid()
|
||||
"0".checkInvalid()
|
||||
"".checkInvalid()
|
||||
"1.".checkInvalid()
|
||||
"0.1-".checkInvalid()
|
||||
"1.9+".checkInvalid()
|
||||
"5.1+68-7".check()
|
||||
"5.1+68-".check()
|
||||
}
|
||||
|
||||
@Test
|
||||
internal fun testSemVersionOfficial() {
|
||||
"""
|
||||
1.0-RC
|
||||
0.0.4
|
||||
1.2.3
|
||||
10.20.30
|
||||
1.1.2-prerelease+meta
|
||||
1.1.2+meta
|
||||
1.1.2+meta-valid
|
||||
1.0.0-alpha
|
||||
1.0.0-beta
|
||||
1.0.0-alpha.beta
|
||||
1.0.0-alpha.beta.1
|
||||
1.0.0-alpha.1
|
||||
1.0.0-alpha0.valid
|
||||
1.0.0-alpha.0valid
|
||||
1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay
|
||||
1.0.0-rc.1+build.1
|
||||
2.0.0-rc.1+build.123
|
||||
1.2.3-beta
|
||||
10.2.3-DEV-SNAPSHOT
|
||||
1.2.3-SNAPSHOT-123
|
||||
1.0.0
|
||||
2.0.0
|
||||
1.1.7
|
||||
2.0.0+build.1848
|
||||
2.0.1-alpha.1227
|
||||
1.0.0-alpha+beta
|
||||
1.2.3----RC-SNAPSHOT.12.9.1--.12+788
|
||||
1.2.3----R-S.12.9.1--.12+meta
|
||||
1.2.3----RC-SNAPSHOT.12.9.1--.12
|
||||
1.0.0+0.build.1-rc.10000aaa-kk-0.1
|
||||
1.0.0-0A.is.legal
|
||||
""".trimIndent().split('\n').asSequence()
|
||||
.filter { it.isNotBlank() }.map { it.trim() }.forEach { it.check() }
|
||||
"""
|
||||
1
|
||||
1.2.3-0123
|
||||
1.2.3-0123.0123
|
||||
1.1.2+.123
|
||||
+invalid
|
||||
-invalid
|
||||
-invalid+invalid
|
||||
-invalid.01
|
||||
alpha
|
||||
alpha.beta
|
||||
alpha.beta.1
|
||||
alpha.1
|
||||
alpha+beta
|
||||
alpha_beta
|
||||
alpha.
|
||||
alpha..
|
||||
beta
|
||||
1.0.0-alpha_beta
|
||||
-alpha.
|
||||
1.0.0-alpha..
|
||||
1.0.0-alpha..1
|
||||
1.0.0-alpha...1
|
||||
1.0.0-alpha....1
|
||||
1.0.0-alpha.....1
|
||||
1.0.0-alpha......1
|
||||
1.0.0-alpha.......1
|
||||
01.1.1
|
||||
1.01.1
|
||||
1.1.01
|
||||
1.2.3.DEV
|
||||
1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788
|
||||
1.2.31.2.3-RC
|
||||
-1.0.3-gamma+b7718
|
||||
+justmeta
|
||||
9.8.7+meta+meta
|
||||
9.8.7-whatever+meta+meta
|
||||
99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12
|
||||
""".trimIndent().split('\n').asSequence()
|
||||
.filter { it.isNotBlank() }.map { it.trim() }.forEach { it.checkInvalid() }
|
||||
}
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
@file:Suppress("UnstableApiUsage")
|
||||
plugins {
|
||||
id("com.jfrog.bintray") version Versions.bintray apply false
|
||||
id("net.mamoe.kotlin-jvm-blocking-bridge") version Versions.blockingBridge apply false
|
||||
kotlin("jvm") version Versions.kotlinCompiler
|
||||
kotlin("plugin.serialization") version Versions.kotlinCompiler
|
||||
id("com.jfrog.bintray") version Versions.bintray apply false
|
||||
id("net.mamoe.kotlin-jvm-blocking-bridge") version Versions.blockingBridge apply false
|
||||
id("com.gradle.plugin-publish") version "0.12.0" apply false
|
||||
//id("com.bmuschko.nexus") version "2.3.1" apply false
|
||||
//id("io.codearte.nexus-staging") version "0.11.0" apply false
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile::class.java) {
|
||||
|
@ -36,8 +36,8 @@ class MiraiConsoleBuildPlugin : Plugin<Project> {
|
||||
attributes(
|
||||
"Manifest-Version" to "1",
|
||||
"Implementation-Vendor" to "Mamoe Technologies",
|
||||
"Implementation-Title" to target.name.toString(),
|
||||
"Implementation-Version" to target.version.toString() + "-" + gitVersion
|
||||
"Implementation-Title" to target.name,
|
||||
"Implementation-Version" to target.version.toString() //+ "+" + gitVersion
|
||||
)
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ -120,15 +120,16 @@ fun Project.findLatestFile(): Pair<String, File> {
|
||||
} ?: error("cannot find any file to upload")*/
|
||||
}
|
||||
|
||||
/*
|
||||
val gitVersion: String by lazy {
|
||||
runCatching {
|
||||
val exec = Runtime.getRuntime().exec("git rev-parse HEAD")
|
||||
exec.waitFor()
|
||||
exec.inputStream.readBytes().toString(Charsets.UTF_8).trim().also {
|
||||
println("Git commit id: $it")
|
||||
}
|
||||
}.onFailure {
|
||||
} }.onFailure {
|
||||
it.printStackTrace()
|
||||
return@lazy "UNKNOWN"
|
||||
}.getOrThrow()
|
||||
}
|
||||
*/
|
@ -7,6 +7,9 @@ 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
|
||||
|
||||
@ -51,10 +54,48 @@ internal fun org.gradle.api.Project.`publishing`(configure: org.gradle.api.publi
|
||||
(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",
|
||||
bintrayPkgName: String = "mirai-console",
|
||||
bintrayPkgName: String = artifactId,
|
||||
vcs: String = "https://github.com/mamoe/mirai-console"
|
||||
) {
|
||||
|
||||
|
@ -8,11 +8,10 @@
|
||||
*/
|
||||
|
||||
object Versions {
|
||||
const val core = "1.2.3"
|
||||
const val console = "1.0-M4"
|
||||
const val core = "1.3.0"
|
||||
const val console = "1.0-RC-dev-30"
|
||||
const val consoleGraphical = "0.0.7"
|
||||
const val consoleTerminal = "0.1.0"
|
||||
const val consolePure = console
|
||||
const val consoleTerminal = console
|
||||
|
||||
const val kotlinCompiler = "1.4.10"
|
||||
const val kotlinStdlib = kotlinCompiler
|
||||
@ -27,6 +26,6 @@ object Versions {
|
||||
|
||||
const val bintray = "1.8.5"
|
||||
|
||||
const val blockingBridge = "1.0.5"
|
||||
const val yamlkt = "0.5.2"
|
||||
const val blockingBridge = "1.1.0"
|
||||
const val yamlkt = "0.5.3"
|
||||
}
|
@ -7,8 +7,10 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import org.gradle.api.artifacts.ExternalModuleDependency
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||
import org.gradle.kotlin.dsl.DependencyHandlerScope
|
||||
import org.gradle.kotlin.dsl.accessors.runtime.addDependencyTo
|
||||
|
||||
@Suppress("unused")
|
||||
fun DependencyHandlerScope.kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
|
||||
@ -17,7 +19,42 @@ fun DependencyHandlerScope.kotlinx(id: String, version: String) = "org.jetbrains
|
||||
fun DependencyHandlerScope.ktor(id: String, version: String = Versions.ktor) = "io.ktor:ktor-$id:$version"
|
||||
|
||||
@Suppress("unused")
|
||||
fun DependencyHandler.compileAndRuntime(any: Any) {
|
||||
fun DependencyHandler.compileAndTestRuntime(any: Any) {
|
||||
add("compileOnly", any)
|
||||
add("runtimeOnly", any)
|
||||
add("testRuntimeOnly", any)
|
||||
}
|
||||
|
||||
fun DependencyHandler.smartApi(
|
||||
dependencyNotation: String
|
||||
): ExternalModuleDependency {
|
||||
return smart("api", dependencyNotation)
|
||||
}
|
||||
|
||||
fun DependencyHandler.smartImplementation(
|
||||
dependencyNotation: String
|
||||
): ExternalModuleDependency {
|
||||
return smart("implementation", dependencyNotation)
|
||||
}
|
||||
|
||||
private fun DependencyHandler.smart(
|
||||
configuration: String,
|
||||
dependencyNotation: String
|
||||
): ExternalModuleDependency {
|
||||
return addDependencyTo(
|
||||
this, configuration, dependencyNotation
|
||||
) {
|
||||
fun exclude(group: String, module: String) {
|
||||
exclude(mapOf(
|
||||
"group" to group,
|
||||
"module" to module
|
||||
))
|
||||
}
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
|
||||
exclude("org.jetbrains.kotlinx", "kotlinx-coroutines-core-common")
|
||||
exclude("org.jetbrains.kotlinx", "kotlinx-coroutines-core")
|
||||
exclude("org.jetbrains.kotlinx", "kotlinx-serialization-common")
|
||||
exclude("org.jetbrains.kotlinx", "kotlinx-serialization-core")
|
||||
}
|
||||
}
|
||||
|
49
docs/Appendix.md
Normal file
49
docs/Appendix.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Mirai Console - Appendix
|
||||
|
||||
### Mirai Console 演进
|
||||
|
||||
Mirai Console 是不断前进的框架,将来必定会发生 API 弃用和重构。
|
||||
维护者会严谨地推进每一项修改,并提供迁移周期(至少 2 个次版本)。
|
||||
|
||||
#### 版本规范
|
||||
|
||||
Mirai Console 的版本号遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/#spec-item-9) 规范。
|
||||
|
||||
在日常开发中, Mirai Console 会以 `-dev-1`,`-dev-2` 等版本后缀发布开发预览版本。这些版本仅用于兼容性测试等目的,无稳定性保证。
|
||||
|
||||
在大版本开发过程中,Mirai Console 会以 `-M1`, `-M2` 等版本后缀发布里程碑预览版本。代表一系列功能的完成,但还不稳定。
|
||||
这些版本里新增的 API 仍可能还会在下一个 Milestone 版本变化,因此请按需使用。
|
||||
|
||||
在大版本即将发布前,Mirai Console 会以 `-RC` 版本后缀发布最终的预览版本。
|
||||
`RC` 表示新版本 API 已经确定,离稳定版发布只差最后的一些内部优化或 bug 修复。
|
||||
|
||||
#### 版本选择
|
||||
|
||||
**稳定性**:稳定 (`x.y.z`) > 发布预览 (`-RC`) > 里程碑预览 (`-M`) > 开发 (`-dev`)。
|
||||
|
||||
| 目的 | 推荐至少更新到版本 |
|
||||
|:--------------------------:|:--------------:|
|
||||
| 生产环境 | `x.y.z` |
|
||||
| 希望尽早体验稳定新特性的插件作者 | `-RC` |
|
||||
| 无论如何都想体验新特性的插件作者 | `-M` |
|
||||
| 前端实现者, 底层插件作者 | `-M` |
|
||||
| 为 Mirai Console 提交 PR | `-dev` |
|
||||
|
||||
其中,‘底层插件’ 表示提供扩展等的插件。如权限系统,其他语言插件加载器等。
|
||||
|
||||
#### 更新兼容性
|
||||
|
||||
对于 `x.y.z` 版本号:
|
||||
- 当 `z` 增加时,只会有 bug 修复,和必要的新函数添加(为了解决某一个问题),不会有破坏性变化。
|
||||
- 当 `y` 增加时,可能有新 API 的引入,和旧 API 的弃用。但这些弃用会经过一个弃用周期后才被删除(隐藏)。向下兼容得到保证。
|
||||
- 当 `x` 增加时,任何 API 都可能会有变化。无兼容性保证。
|
||||
|
||||
#### 弃用周期
|
||||
|
||||
一个计划被删除的 API,将会在下一个次版本开始经历弃用周期。
|
||||
|
||||
如一个 API 在 `1.1.0` 起被弃用,它首先会是 `WARNING` (使用时会得到一个编译警告)弃用级别。
|
||||
在 `1.2.0` 上升为 `ERROR`(使用时会得到一个编译错误);
|
||||
在 `1.3.0` 上升为 `HIDDEN`(使用者无法看到这些 API)。
|
||||
|
||||
`HIDDEN` 的 API 仍然会保留在代码中并正常编译,以提供二进制兼容性,直到下一个主版本更新。
|
80
docs/ConfiguringProjects.md
Normal file
80
docs/ConfiguringProjects.md
Normal file
@ -0,0 +1,80 @@
|
||||
# Mirai Console - Configuring Projects
|
||||
|
||||
配置 Mirai Console 项目。
|
||||
|
||||
## 模块说明
|
||||
|
||||
console 由后端和前端一起工作. 使用时必须选择一个前端.
|
||||
|
||||
- `mirai-console`: Mirai Console 后端。
|
||||
|
||||
- `mirai-console-terminal`: 终端前端,适用于 JVM。
|
||||
- [`MiraiAndroid`](https://github.com/mzdluo123/MiraiAndroid): Android 应用前端。
|
||||
|
||||
**注意:`mirai-console` 1.0-RC 发布之前, 前端请使用 `mirai-console-pure` 而不是 `mirai-console-terminal`**
|
||||
|
||||
## 选择版本
|
||||
|
||||
有关各类版本的区别,参考 [版本规范](Appendix.md#版本规范)
|
||||
|
||||
[Version]: https://api.bintray.com/packages/him188moe/mirai/mirai-console/images/download.svg?
|
||||
[Bintray Download]: https://bintray.com/him188moe/mirai/mirai-console/
|
||||
|
||||
| 版本类型 | 版本号 |
|
||||
|:------:|:------------:|
|
||||
| 稳定 | - |
|
||||
| 预览 | 1.0-M4 |
|
||||
| 开发 | [![Version]][Bintray Download] |
|
||||
|
||||
## 配置项目
|
||||
|
||||
### 使用模板项目
|
||||
|
||||
Mirai 鼓励插件开发者将自己的作品开源,并为此提供了模板项目。
|
||||
|
||||
注意,模板项目依赖的 Mirai Console 不一定是最新的。请检查
|
||||
|
||||
1. 访问 [mirai-console-plugin-template](https://github.com/project-mirai/mirai-console-plugin-template)
|
||||
2. 点击绿色按钮 "Use this template",创建项目
|
||||
3. 克隆项目,检查并修改生成的属性
|
||||
|
||||
### 使用 Gradle 插件配置项目
|
||||
|
||||
`VERSION`: [选择版本](#选择版本)
|
||||
|
||||
若使用 `build.gradle.kts`:
|
||||
```kotlin
|
||||
plugins {
|
||||
id("net.mamoe.mirai-console") version "VERSION"
|
||||
}
|
||||
```
|
||||
|
||||
若使用 `build.gradle`:
|
||||
```groovy
|
||||
plugins {
|
||||
id 'net.mamoe.mirai-console' version 'VERSION'
|
||||
}
|
||||
```
|
||||
|
||||
完成。Mirai Console Gradle 插件会为你配置依赖等所有编译环境。
|
||||
|
||||
### 手动配置项目
|
||||
|
||||
添加依赖:
|
||||
`build.gradle.kts`:
|
||||
```kotlin
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("net.mamoe:mirai-core:$CORE_VERSION") // mirai-core 的 API
|
||||
compileOnly("net.mamoe:mirai-console:$CONSOLE_VERSION") // 后端
|
||||
|
||||
testImplementation("net.mamoe:mirai-console-terminal:$CONSOLE_VERSION") // 前端, 用于启动测试
|
||||
testImplementation("net.mamoe:mirai-console-terminal:$CONSOLE_VERSION") // 前端, 用于启动测试
|
||||
}
|
||||
```
|
||||
|
||||
之后还需要配置 Kotlin `jvm-default` 编译参数,Kotlin 和 Java 的编译目标等。
|
||||
在打包插件时必须将依赖一并打包进插件 JAR,且排除 `mirai-core`,`mirai-console` 和它们的间接依赖,否则插件不会被加载。
|
32
docs/Contributing.md
Normal file
32
docs/Contributing.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Mirai Console - Contributing
|
||||
|
||||
感谢你来到这里,感谢你对 Mirai Console 做的一切贡献。
|
||||
|
||||
## 开发 Mirai Console
|
||||
|
||||
### 模块
|
||||
|
||||
Mirai Console 项目由四个模块组成:后端,前端,Gradle 插件,Intellij 插件。
|
||||
|
||||
```
|
||||
/
|
||||
|--- backend 后端
|
||||
| |--- codegen 后端代码生成工具
|
||||
| `--- mirai-console 后端主模块, 发布为 net.mamoe:mirai-console
|
||||
|--- buildSrc 项目构建
|
||||
|--- frontend 前端
|
||||
| `--- mirai-console-terminal 终端前端,发布为 net.mamoe:mirai-console-terminal
|
||||
`--- tools 开发工具
|
||||
|--- compiler-common 编译器通用模块
|
||||
|--- gradle-plugin Gradle 插件,发布为 net.mamoe.mirai-console
|
||||
`--- intellij-plugin IntelliJ 平台 IDE 插件,发布为 Mirai Console
|
||||
```
|
||||
|
||||
请前往各模块内的 README.md 查看详细说明。
|
||||
|
||||
### 构建
|
||||
```shell script
|
||||
gradlew build
|
||||
```
|
||||
|
||||
首次加载和构建 mirai-console 项目可能要花费数小时时间。
|
@ -108,6 +108,28 @@ interface PermitteeId {
|
||||
|
||||
在 [`AbstractPermitteeId`] 查看其子类。
|
||||
|
||||
#### 字符串表示
|
||||
|
||||
当使用 `PermitteeId.asString` 时, 不同的类型的返回值如下表所示. 这些格式也适用于 [权限指令](#使用内置权限服务指令).
|
||||
(不区分大小写. 不区分 Bot).
|
||||
|
||||
| 被许可人类型 | 字符串表示示例 | 备注 |
|
||||
|:----------------:|:-----------:|:-------------------------------------|
|
||||
| 控制台 | console | |
|
||||
| 精确群 | g123456 | 表示群, 而不表示群成员 |
|
||||
| 精确好友 | f123456 | 必须通过好友消息 |
|
||||
| 精确临时会话 | t123456.789 | 群 123456 内的成员 789. 必须通过临时会话 |
|
||||
| 精确群成员 | m123456.789 | 群 123456 内的成员 789. 同时包含临时会话. |
|
||||
| 精确用户 | u123456 | 同时包含群成员, 好友, 临时会话 |
|
||||
| 任意群 | g* | |
|
||||
| 任意群的任意群员 | m* | |
|
||||
| 精确群的任意群员 | m123456.* | 群 123456 内的任意成员. 同时包含临时会话. |
|
||||
| 任意群的任意临时会话 | t* | 必须通过临时会话 |
|
||||
| 精确群的任意临时会话 | t123456.* | 群 123456 内的任意成员. 必须通过临时会话 |
|
||||
| 任意好友 | f* | |
|
||||
| 任意用户 | u* | 任何人在任何环境 |
|
||||
| 任意对象 | * | 即任何人, 任何群, 控制台 |
|
||||
|
||||
### 获取被许可人
|
||||
|
||||
通常使用 `CommandSender.permitteeId`。
|
||||
@ -138,4 +160,18 @@ fun Permission.testPermission(PermitteeId): Boolean
|
||||
|
||||
如果希望手动注册一个其他用途的权限,使用 `PermissionService.register`。
|
||||
|
||||
**注意**:权限只能在插件 [启用](Plugins.md#启用) 之后才能注册。否则会得到一个异常。
|
||||
**注意**:权限只能在插件 [启用](Plugins.md#启用) 之后才能注册。否则会得到一个异常。
|
||||
|
||||
### 使用内置权限服务指令
|
||||
|
||||
**根指令**: "/permission", "/perm", "/权限"
|
||||
|
||||
```
|
||||
/permission cancel <被许可人 ID> <权限 ID> 取消授权一个权限
|
||||
/permission cancelall <被许可人 ID> <权限 ID> 取消授权一个权限及其所有子权限
|
||||
/permission listpermissions 查看所有权限列表
|
||||
/permission permit <被许可人 ID> <权限 ID> 授权一个权限
|
||||
/permission permittedpermissions <被许可人 ID> 查看被授权权限列表
|
||||
```
|
||||
|
||||
其中, 被许可人 ID 使用 [字符串表示](#字符串表示), 权限 ID 参见 [权限 ID](#权限-id)
|
||||
|
98
docs/Preparations.md
Normal file
98
docs/Preparations.md
Normal file
@ -0,0 +1,98 @@
|
||||
# Mirai Console - Preparations
|
||||
|
||||
***如果跳过本节内容,你很可能遇到无法解决的问题。***
|
||||
|
||||
***此文档假设你是 JVM 平台的开发者。若不是,请参考[其他语言 SDK](https://github.com/mamoe/mirai#%E5%BC%80%E5%8F%91%E8%80%85)***
|
||||
|
||||
### JVM 环境要求
|
||||
|
||||
- 桌面 JVM:最低 Java 8,但推荐 Java 11
|
||||
- Android:Android SDK 26+ (Android 8.0)
|
||||
|
||||
### 开发插件的准备工作
|
||||
|
||||
#### 安装 IDE 插件
|
||||
|
||||
推荐使用 [IntelliJ IDEA](https://www.jetbrains.com/idea/) 或 [Android Studio](https://developer.android.com/studio)。Mirai Console 提供 IntelliJ 插件来提升开发体验。
|
||||
|
||||
- [Kotlin Jvm Blocking Bridge](https://github.com/mamoe/kotlin-jvm-blocking-bridge) ([JetBrains 插件仓库](https://plugins.jetbrains.com/plugin/14816-kotlin-jvm-blocking-bridge), [一键安装](https://plugins.jetbrains.com/embeddable/install/14816)):帮助 Java 用户调用 Kotlin suspend 函数
|
||||
- [Mirai Console IntelliJ](../tools/intellij-plugin/) ([JetBrains 插件仓库](https://plugins.jetbrains.com/plugin/15094-mirai-console), [一键安装](https://plugins.jetbrains.com/embeddable/install/15094)):提供错误检查等功能
|
||||
|
||||
## 前置知识
|
||||
|
||||
要学习为 mirai-console 开发原生支持的插件, 需要:
|
||||
|
||||
- 掌握 Java 基础
|
||||
- 至少粗略了解 Kotlin 基础语法(30 分钟):
|
||||
- [基本类型](https://www.kotlincn.net/docs/reference/basic-types.html)
|
||||
- [类与继承](https://www.kotlincn.net/docs/reference/classes.html)
|
||||
- [属性与字段](https://www.kotlincn.net/docs/reference/properties.html)
|
||||
- [接口](https://www.kotlincn.net/docs/reference/interfaces.html)
|
||||
- [扩展](https://www.kotlincn.net/docs/reference/extensions.html)
|
||||
- [数据类](https://www.kotlincn.net/docs/reference/data-classes.html)
|
||||
- [对象](https://www.kotlincn.net/docs/reference/object-declarations.html)
|
||||
- [密封类](https://www.kotlincn.net/docs/reference/sealed-classes.html)
|
||||
- **[Java 中调用 Kotlin](https://www.kotlincn.net/docs/reference/java-to-kotlin-interop.html)**
|
||||
- 对于 Java 使用者,请阅读:
|
||||
- [Java 用户的使用指南](#kotlin-源码阅读帮助)
|
||||
- [在 Java 使用 Mirai Console 中的 Kotlin `suspend` 函数](#在-java-使用-mirai-console-中的-kotlin-suspend-函数)
|
||||
- 对于 Kotlin 使用者,请熟知 [Kotlin `1.4` 版本带来的新特性](#mirai-console-使用的-kotlin-14-版本的新特性)
|
||||
|
||||
|
||||
### Kotlin 源码阅读帮助
|
||||
|
||||
- Java 中的「方法」在 Kotlin 中均被称为「函数」。
|
||||
- Kotlin 默认的访问权限是 `public`。如 Kotlin `class Test` 相当于 Java 的 `public class Test {}`
|
||||
- Kotlin 的函数定义 `fun test(int: Int): String` 相当于 Java 的方法定义 `public String test(int int)`
|
||||
|
||||
### 在 Java 使用 Mirai Console 中的 Kotlin `suspend` 函数
|
||||
|
||||
#### 什么是 `suspend` 函数
|
||||
|
||||
`suspend` 函数中文是「挂起函数」,是 Kotlin 「[协程](https://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.html)」的一部分。
|
||||
|
||||
Kotlin 协程是语言级特性,函数的修饰符 `suspend` 会在编译阶段被处理。
|
||||
|
||||
对于一个挂起函数:
|
||||
```kotlin
|
||||
suspend fun test(): String
|
||||
```
|
||||
|
||||
它会被编译为 `public Object test(Continuation<String> $completion)`。
|
||||
|
||||
这是因为 Kotlin 对所有挂起函数都有这样的内部变化,并在编译时实现了协程的一些特性。
|
||||
|
||||
Java 用户无法调用这样的方法,因为 `Continuation` 的实现很复杂。
|
||||
|
||||
Mamoe 为此开发了 Kotlin 编译器插件 [Kotlin Jvm Blocking Bridge](https://github.com/mamoe/kotlin-jvm-blocking-bridge),通过 `@JvmBlockingBridge` 注解,在编译期额外生成一个供 Java 使用的方法,让 Java 用户可以使用拥有源码内相同的函数签名的方法。
|
||||
|
||||
要获取详细信息,参考 [Kotlin Jvm Blocking Bridge 编译器插件](https://github.com/mamoe/kotlin-jvm-blocking-bridge/blob/master/README-chs.md#%E7%BC%96%E8%AF%91%E5%99%A8%E6%8F%92%E4%BB%B6)
|
||||
|
||||
### Mirai Console 使用的 Kotlin `1.4` 版本的新特性
|
||||
|
||||
在官方文档的 [语言特性与改进](https://www.kotlincn.net/docs/reference/whatsnew14.html#%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7%E4%B8%8E%E6%94%B9%E8%BF%9B) 基础上,Mirai Console 的一些设计基于 Kotlin 1.4 的更多新特性。
|
||||
|
||||
#### `object` 内的扩展函数的自动引用
|
||||
对于如下定义:
|
||||
```kotlin
|
||||
package org.example
|
||||
object Obj {
|
||||
fun String.foo()
|
||||
}
|
||||
```
|
||||
在 Kotlin `1.3`,要调用 `foo`,必须使用:
|
||||
```kotlin
|
||||
Obj.run {
|
||||
"str".foo()
|
||||
}
|
||||
```
|
||||
因为 IDE 不会自动为 `String.foo` 添加 `import`。
|
||||
|
||||
Kotlin `1.4` 解决了这个问题。在使用 `"str".foo` 时 Kotlin 会自动添加 `org.example.Obj.foo` 的引用。
|
||||
|
||||
Mirai Console 很多单例对象都设计为 `interface + companion object INSTANCE` 的接口与实现模式,需要这样的新特性。例如:
|
||||
```kotlin
|
||||
interface MiraiConsole {
|
||||
companion object INSTANCE : MiraiConsole by MiraiConsoleImpl // MiraiConsoleImpl 是内部实现,不公开
|
||||
}
|
||||
```
|
164
docs/README.md
164
docs/README.md
@ -1,167 +1,3 @@
|
||||
# Mirai Console
|
||||
|
||||
欢迎来到 mirai-console 开发文档!
|
||||
|
||||
## 目录
|
||||
|
||||
- **[准备工作](#准备工作)**
|
||||
- **[启动 Console](Run.md)**
|
||||
|
||||
### 后端插件开发基础
|
||||
|
||||
- 插件 - [Plugin 模块](Plugins.md)
|
||||
- 指令 - [Command 模块](Commands.md)
|
||||
- 存储 - [PluginData 模块](PluginData.md)
|
||||
- 权限 - [Permission 模块](Permissions.md)
|
||||
|
||||
### 后端插件开发进阶
|
||||
|
||||
- 扩展 - [Extension 模块和扩展点](Extensions.md)
|
||||
|
||||
### 实现前端
|
||||
- [FrontEnd](FrontEnd.md)
|
||||
|
||||
[`Plugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
|
||||
[`Annotations`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/Annotations.kt
|
||||
[`PluginData`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt
|
||||
[`JavaPluginScheduler`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler.kt
|
||||
[`JvmPlugin`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
|
||||
[`PluginConfig`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt
|
||||
[`PluginLoader`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/loader/PluginLoader.kt
|
||||
[`ConsoleInput`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ConsoleInput.kt
|
||||
[`PluginDataStorage`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt
|
||||
[`Command`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt
|
||||
|
||||
## 准备工作
|
||||
***如果跳过本节内容,你很可能遇到无法解决的问题。***
|
||||
|
||||
### 环境要求
|
||||
|
||||
*不接受降低最低版本要求的建议*
|
||||
|
||||
- JDK 11
|
||||
- Android:Android SDK 26+ (Android 8.0)
|
||||
- Kotlin: 1.4
|
||||
|
||||
*Mirai Console 需要的 Kotlin 版本会与 Kotlin 最新稳定版本同步。*
|
||||
|
||||
### 开发插件的准备工作
|
||||
|
||||
- 安装并配置 JDK 11
|
||||
|
||||
若使用 Java,或要修改 Mirai Console:
|
||||
|
||||
- 使用 [IntelliJ IDEA](https://www.jetbrains.com/idea/) (或 `Android Studio`)。
|
||||
- IDE 需装有 [Kotlin Jvm Blocking Bridge](https://github.com/mamoe/kotlin-jvm-blocking-bridge) 插件 (先启动你的 IDE,再点击 [一键安装](https://plugins.jetbrains.com/embeddable/install/14816))
|
||||
|
||||
若使用 Kotlin,无特别要求。
|
||||
|
||||
### 前置知识
|
||||
要学习为 mirai-console 开发原生支持的插件, 需要:
|
||||
|
||||
- 掌握 Java 基础
|
||||
- 至少粗略了解 Kotlin 基础语法(30 分钟):
|
||||
- [基本类型](https://www.kotlincn.net/docs/reference/basic-types.html)
|
||||
- [类与继承](https://www.kotlincn.net/docs/reference/classes.html)
|
||||
- [属性与字段](https://www.kotlincn.net/docs/reference/properties.html)
|
||||
- [接口](https://www.kotlincn.net/docs/reference/interfaces.html)
|
||||
- [扩展](https://www.kotlincn.net/docs/reference/extensions.html)
|
||||
- [数据类](https://www.kotlincn.net/docs/reference/data-classes.html)
|
||||
- [对象](https://www.kotlincn.net/docs/reference/object-declarations.html)
|
||||
- [密封类](https://www.kotlincn.net/docs/reference/sealed-classes.html)
|
||||
- **[Java 中调用 Kotlin](https://www.kotlincn.net/docs/reference/java-to-kotlin-interop.html)**
|
||||
- 对于 Java 使用者,请阅读 [Java 用户的使用指南](#java-用户的使用指南),[在 Java 使用 Mirai Console 中的 Kotlin `suspend` 函数](#在-java-使用-mirai-console-中的-kotlin-suspend-函数)
|
||||
- 对于 Kotlin 使用者,请熟知 [Kotlin `1.4` 版本带来的新特性](#mirai-console-使用的-kotlin-14-版本的新特性)
|
||||
|
||||
|
||||
## 附录
|
||||
|
||||
### Java 用户的使用指南
|
||||
|
||||
- Java 中的「方法」在 Kotlin 中均被称为「函数」。
|
||||
- Kotlin 默认的访问权限是 `public`。如 Kotlin `class Test` 相当于 Java 的 `public class Test {}`
|
||||
- Kotlin 的函数定义 `fun test(int: Int): String` 相当于 Java 的方法定义 `public String test(int int)`
|
||||
|
||||
### 在 Java 使用 Mirai Console 中的 Kotlin `suspend` 函数
|
||||
|
||||
#### 什么是 `suspend` 函数
|
||||
|
||||
`suspend` 函数中文是「挂起函数」,是 Kotlin 「[协程](https://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.html)」的一部分。
|
||||
|
||||
Kotlin 协程是语言级特性,函数的修饰符 `suspend` 会在编译阶段被处理。
|
||||
|
||||
对于一个挂起函数:
|
||||
```kotlin
|
||||
suspend fun test(): String
|
||||
```
|
||||
|
||||
它会被编译为 `public Object test(Continuation<String> $completion)`。
|
||||
|
||||
这是因为 Kotlin 对所有挂起函数都有这样的内部变化,并在编译时实现了协程的一些特性。
|
||||
|
||||
Java 用户无法调用这样的方法,因为 `Continuation` 的实现很复杂。
|
||||
|
||||
Mamoe 为此开发了 Kotlin 编译器插件 [Kotlin Jvm Blocking Bridge](https://github.com/mamoe/kotlin-jvm-blocking-bridge),通过 `@JvmBlockingBridge` 注解,在编译期额外生成一个供 Java 使用的方法,让 Java 用户可以使用拥有源码内相同的函数签名的方法。
|
||||
|
||||
要获取详细信息,参考 [Kotlin Jvm Blocking Bridge 编译器插件](https://github.com/mamoe/kotlin-jvm-blocking-bridge/blob/master/README-chs.md#%E7%BC%96%E8%AF%91%E5%99%A8%E6%8F%92%E4%BB%B6)
|
||||
|
||||
### Mirai Console 使用的 Kotlin `1.4` 版本的新特性
|
||||
|
||||
在官方文档的 [语言特性与改进](https://www.kotlincn.net/docs/reference/whatsnew14.html#%E8%AF%AD%E8%A8%80%E7%89%B9%E6%80%A7%E4%B8%8E%E6%94%B9%E8%BF%9B) 基础上,Mirai Console 的一些设计基于 Kotlin 1.4 的更多新特性。
|
||||
|
||||
#### `object` 内的扩展函数的自动引用
|
||||
对于如下定义:
|
||||
```kotlin
|
||||
package org.example
|
||||
object Obj {
|
||||
fun String.foo()
|
||||
}
|
||||
```
|
||||
在 Kotlin `1.3`,要调用 `foo`,必须使用:
|
||||
```kotlin
|
||||
Obj.run {
|
||||
"str".foo()
|
||||
}
|
||||
```
|
||||
因为 IDE 不会自动为 `String.foo` 添加 `import`。
|
||||
|
||||
Kotlin `1.4` 解决了这个问题。在使用 `"str".foo` 时 Kotlin 会自动添加 `org.example.Obj.foo` 的引用。
|
||||
|
||||
Mirai Console 很多单例对象都设计为 `interface + companion object INSTANCE` 的接口与实现模式,需要这样的新特性。例如:
|
||||
```kotlin
|
||||
interface MiraiConsole {
|
||||
companion object INSTANCE : MiraiConsole by MiraiConsoleImpl // MiraiConsoleImpl 是内部实现,不公开
|
||||
}
|
||||
```
|
||||
|
||||
#### Mirai Console 演进
|
||||
|
||||
Mirai Console 是不断前进的框架,将来必定会发生 API 弃用和重构。
|
||||
维护者会严谨地推进每一项修改,并提供迁移周期(至少 2 个次版本)。
|
||||
|
||||
##### 版本规范
|
||||
|
||||
Mirai Console 的版本号遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/#spec-item-9) 规范。
|
||||
|
||||
在大版本开发过程中,Mirai Console 会以 `-M1`, `-M2` 等版本后缀发布里程碑预览版本。代表一些功能基本完成,但还不稳定。
|
||||
但这些版本里新增的 API 可能还会在下一个 Milestone 版本变化,因此请按需使用。
|
||||
|
||||
在大版本即将发布前,Mirai Console 会以 `-RC` 版本后缀发布最终的预览版本。
|
||||
`RC` 表示新版本 API 已经确定,离稳定版发布只差最后的一些内部优化或 bug 修复。
|
||||
|
||||
##### 更新兼容性
|
||||
|
||||
对于 `x.y.z` 版本号:
|
||||
- 当 `z` 增加时,只会有 bug 修复,和必要的新函数添加(为了解决某一个问题),不会有破坏性变化。
|
||||
- 当 `y` 增加时,可能有新 API 的引入,和旧 API 的弃用。但这些弃用会经过一个弃用周期后才被删除(隐藏)。向下兼容得到保证。
|
||||
- 当 `x` 增加时,任何 API 都可能会有变化。无兼容性保证。
|
||||
|
||||
##### 弃用周期
|
||||
|
||||
一个计划被删除的 API,将会在下一个次版本开始经历弃用周期。
|
||||
|
||||
如一个 API 在 `1.1.0` 起被弃用,它首先会是 `WARNING` (使用时会得到一个编译警告)弃用级别。
|
||||
在 `1.2.0` 上升为 `ERROR`(使用时会得到一个编译错误);
|
||||
在 `1.3.0` 上升为 `HIDDEN`(使用者无法看到这些 API)。
|
||||
|
||||
`HIDDEN` 的 API 仍然会保留在代码中并正常编译,以提供二进制兼容性,直到下一个主版本更新。
|
||||
|
30
docs/Run.md
30
docs/Run.md
@ -2,9 +2,12 @@
|
||||
|
||||
Mirai Console 可以独立启动,也可以被嵌入到某个应用中。
|
||||
|
||||
## 使用第三方工具自动启动
|
||||
## 使用工具自动独立启动
|
||||
|
||||
## 独立启动
|
||||
官方: https://github.com/iTXTech/mirai-console-loader
|
||||
第三方: https://github.com/LXY1226/MiraiOK
|
||||
|
||||
## 手动配置独立启动
|
||||
|
||||
### 环境
|
||||
- JRE 11+ / JDK 11+
|
||||
@ -17,16 +20,16 @@ Mirai Console 可以独立启动,也可以被嵌入到某个应用中。
|
||||
- mirai-console 任一前端
|
||||
- 相关依赖
|
||||
|
||||
只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 pure 前端可用。
|
||||
只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 terminal 前端可用。
|
||||
|
||||
### 启动 mirai-console-pure 前端
|
||||
### 启动 mirai-console-terminal 前端
|
||||
|
||||
mirai 在版本发布时会同时发布打包依赖的 Shadow JAR,存放在 [mirai-repo]。
|
||||
|
||||
1. 在 [mirai-repo] 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`):
|
||||
- mirai-core-qqandroid
|
||||
- mirai-console
|
||||
- mirai-console-pure
|
||||
- mirai-console-terminal
|
||||
|
||||
2. 创建一个新的文件, 名为 `start-mirai-console.bat`/`start-mirai-console.ps1`/`start-mirai-console.sh`
|
||||
|
||||
@ -34,26 +37,33 @@ Windows CMD:
|
||||
```shell script
|
||||
@echo off
|
||||
title Mirai Console
|
||||
java -cp "./libs/*" net.mamoe.mirai.console.pure.MiraiConsolePureLoader %*
|
||||
java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader %*
|
||||
pause
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
```shell script
|
||||
$Host.UI.RawUI.WindowTitle = "Mirai Console"
|
||||
java -cp "./libs/*" net.mamoe.mirai.console.pure.MiraiConsolePureLoader $args
|
||||
java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $args
|
||||
pause
|
||||
```
|
||||
|
||||
Linux:
|
||||
```shell script
|
||||
#!/usr/bin/env bash
|
||||
java -cp "./libs/*" net.mamoe.mirai.console.pure.MiraiConsolePureLoader $*
|
||||
java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $*
|
||||
```
|
||||
|
||||
然后就可以开始使用 mirai-console 了
|
||||
|
||||
### mirai-console-pure 前端参数
|
||||
使用 `./start-mirai-console --help` 查看 mirai-console-pure 支持的启动参数
|
||||
#### mirai-console-terminal 前端参数
|
||||
使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数
|
||||
|
||||
[mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow
|
||||
|
||||
|
||||
### 启动 mirai-console-pure 前端
|
||||
|
||||
与启动 `mirai-console-terminal` 前端大体相同
|
||||
- 下载 `mirai-console-terminal` 改成下载 `mirai-console-pure`
|
||||
- 启动入口从 `net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader` 改成 `net.mamoe.mirai.console.pure.MiraiConsolePureLoader`
|
||||
|
@ -36,40 +36,18 @@ dependencies {
|
||||
implementation("org.jline:jline:3.15.0")
|
||||
implementation("org.fusesource.jansi:jansi:1.18")
|
||||
|
||||
compileAndRuntime(project(":mirai-console"))
|
||||
compileAndRuntime("net.mamoe:mirai-core:${Versions.core}")
|
||||
compileAndRuntime(kotlin("stdlib", Versions.kotlinStdlib)) // embedded by core
|
||||
compileAndTestRuntime(project(":mirai-console"))
|
||||
compileAndTestRuntime("net.mamoe:mirai-core:${Versions.core}")
|
||||
compileAndTestRuntime(kotlin("stdlib-jdk8", Versions.kotlinStdlib)) // embedded by core
|
||||
|
||||
runtimeOnly("net.mamoe:mirai-core-qqandroid:${Versions.core}")
|
||||
testApi("net.mamoe:mirai-core-qqandroid:${Versions.core}")
|
||||
testApi(project(":mirai-console"))
|
||||
|
||||
|
||||
// val autoService = "1.0-rc7"
|
||||
// kapt("com.google.auto.service", "auto-service", autoService)
|
||||
// compileOnly("com.google.auto.service", "auto-service-annotations", autoService)
|
||||
// testCompileOnly("com.google.auto.service", "auto-service-annotations", autoService)
|
||||
}
|
||||
|
||||
ext.apply {
|
||||
// 傻逼 compileAndRuntime 没 exclude 掉
|
||||
// 傻逼 gradle 第二次配置 task 会覆盖掉第一次的配置
|
||||
val x: com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.() -> Unit = {
|
||||
dependencyFilter.include {
|
||||
when ("${it.moduleGroup}:${it.moduleName}") {
|
||||
"org.jline:jline" -> true
|
||||
"org.fusesource.jansi:jansi" -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
this.set("shadowJar", x)
|
||||
}
|
||||
version = Versions.consoleTerminal
|
||||
|
||||
version = Versions.consolePure
|
||||
description = "Console Terminal CLI frontend for mirai"
|
||||
|
||||
description = "Console Pure CLI frontend for mirai"
|
||||
|
||||
setupPublishing("mirai-console-pure", bintrayPkgName = "mirai-console-pure")
|
||||
setupPublishing("mirai-console-terminal", bintrayPkgName = "mirai-console-terminal")
|
||||
|
||||
// endregion
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.pure
|
||||
|
||||
import net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader
|
||||
|
||||
@Deprecated(
|
||||
message = "Please use MiraiConsoleTerminalLoader",
|
||||
level = DeprecationLevel.ERROR,
|
||||
replaceWith = ReplaceWith(
|
||||
"MiraiConsoleTerminalLoader",
|
||||
"net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader"
|
||||
)
|
||||
)
|
||||
object MiraiConsolePureLoader {
|
||||
@Deprecated(
|
||||
message = "for binary compatibility",
|
||||
level = DeprecationLevel.ERROR
|
||||
)
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
System.err.println("WARNING: Mirai Console Pure已经更名为 Mirai Console Terminal")
|
||||
System.err.println("请使用新的入口点 net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader")
|
||||
MiraiConsoleTerminalLoader.main(args)
|
||||
}
|
||||
}
|
@ -5,9 +5,10 @@
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.pure
|
||||
package net.mamoe.mirai.console.terminal
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.OutputStream
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.terminal
|
||||
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import net.mamoe.mirai.console.util.ConsoleInput
|
||||
import org.fusesource.jansi.Ansi
|
||||
import org.jline.reader.EndOfFileException
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
|
||||
internal object ConsoleInputImpl : ConsoleInput {
|
||||
private val format = DateTimeFormatter.ofPattern("HH:mm:ss")
|
||||
internal val thread = Executors.newSingleThreadExecutor { task ->
|
||||
Thread(task, "Mirai Console Input Thread").also {
|
||||
it.isDaemon = false
|
||||
}
|
||||
}
|
||||
internal var executingCoroutine: CancellableContinuation<String>? = null
|
||||
|
||||
|
||||
override suspend fun requestInput(hint: String): String {
|
||||
return suspendCancellableCoroutine { coroutine ->
|
||||
if (thread.isShutdown || thread.isTerminated) {
|
||||
coroutine.resumeWithException(EndOfFileException())
|
||||
return@suspendCancellableCoroutine
|
||||
}
|
||||
executingCoroutine = coroutine
|
||||
kotlin.runCatching {
|
||||
thread.submit {
|
||||
kotlin.runCatching {
|
||||
lineReader.readLine(
|
||||
if (hint.isNotEmpty()) {
|
||||
lineReader.printAbove(
|
||||
Ansi.ansi()
|
||||
.fgCyan()
|
||||
.a(
|
||||
LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault())
|
||||
.format(format)
|
||||
)
|
||||
.a(" ")
|
||||
.fgMagenta().a(hint)
|
||||
.reset()
|
||||
.toString()
|
||||
)
|
||||
"$hint > "
|
||||
} else "> "
|
||||
)
|
||||
}.let { result ->
|
||||
executingCoroutine = null
|
||||
coroutine.resumeWith(result)
|
||||
}
|
||||
}
|
||||
}.onFailure { error ->
|
||||
executingCoroutine = null
|
||||
kotlin.runCatching { coroutine.resumeWithException(EndOfFileException(error)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,12 +5,13 @@
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.pure
|
||||
package net.mamoe.mirai.console.terminal
|
||||
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||
@ -23,10 +24,10 @@ package net.mamoe.mirai.console.pure
|
||||
AnnotationTarget.CONSTRUCTOR
|
||||
)
|
||||
@MustBeDocumented
|
||||
annotation class ConsolePureExperimentalApi
|
||||
annotation class ConsoleTerminalExperimentalApi
|
||||
|
||||
@ConsolePureExperimentalApi
|
||||
public object ConsolePureSettings {
|
||||
@ConsoleTerminalExperimentalApi
|
||||
public object ConsoleTerminalSettings {
|
||||
@JvmField
|
||||
var setupAnsi: Boolean = System.getProperty("os.name")
|
||||
.toLowerCase()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user