Merge remote-tracking branch 'origin/master' into dependencies

This commit is contained in:
Karlatemp 2020-09-10 22:34:33 +08:00
commit 95e1486e3f
No known key found for this signature in database
GPG Key ID: 21FBDDF664FF06F8
38 changed files with 544 additions and 649 deletions

View File

@ -84,46 +84,46 @@ internal object MessageScopeCodegen {
)
appendLine()
}
//
// for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
// appendKCode(
// """
// @LowPriorityInOverloadResolution
// public fun ${a}.scopeWith(vararg others: ${b}): MessageScope {
// return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) }
// }
// """
// )
// appendLine()
// }
for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
appendKCode(
"""
@LowPriorityInOverloadResolution
public fun ${a}.scopeWith(vararg others: ${b}): MessageScope {
return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) }
public fun ${a}?.scopeWith(vararg others: ${b}?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) }
}
"""
)
appendLine()
}
//
// for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
// appendKCode(
// """
// public fun ${a}.scopeWith(other: ${b}): MessageScope {
// return CombinedScope(asMessageScope(), other.asMessageScope())
// }
// """
// )
// appendLine()
// }
for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
appendKCode(
"""
@LowPriorityInOverloadResolution
public fun ${a}?.scopeWithNotNull(vararg others: ${b}?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWithNotNull(other?.asMessageScope()) }
}
"""
)
appendLine()
}
for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
appendKCode(
"""
public fun ${a}.scopeWith(other: ${b}): MessageScope {
return CombinedScope(asMessageScope(), other.asMessageScope())
}
"""
)
appendLine()
}
for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
appendKCode(
"""
public fun ${a}?.scopeWithNotNull(other: ${b}?): MessageScope {
public fun ${a}?.scopeWith(other: ${b}?): MessageScope {
@Suppress("DuplicatedCode")
return when {
this == null && other == null -> NoopMessageScope
@ -137,11 +137,22 @@ internal object MessageScopeCodegen {
)
appendLine()
}
//
// for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
// appendKCode(
// """
// public inline fun <R> ${a}.scopeWith(vararg others: ${b}, action: MessageScope.() -> R): R {
// return scopeWith(*others).invoke(action)
// }
// """
// )
// appendLine()
// }
for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
appendKCode(
"""
public inline fun <R> ${a}.scopeWith(vararg others: ${b}, action: MessageScope.() -> R): R {
public inline fun <R> ${a}?.scopeWith(vararg others: ${b}?, action: MessageScope.() -> R): R {
return scopeWith(*others).invoke(action)
}
"""
@ -149,17 +160,6 @@ internal object MessageScopeCodegen {
appendLine()
}
for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) {
appendKCode(
"""
public inline fun <R> ${a}?.scopeWithNotNull(vararg others: ${b}?, action: MessageScope.() -> R): R {
return scopeWithNotNull(*others).invoke(action)
}
"""
)
appendLine()
}
for (a in (TypeCandidatesForMessageScope + KtMessageScope)) {
appendKCode(
"""

View File

@ -1,193 +0,0 @@
# mirai-console backend
欢迎来到 mirai-console 后端开发文档。
[`Plugin`]: src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
[`PluginDescription`]: src/main/kotlin/net/mamoe/mirai/console/plugin/description/PluginDescription.kt
[`PluginLoader`]: src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
[`PluginManager`]: src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt
[`JarPluginLoader`]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt
[`JvmPlugin`]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
[`JvmPluginDescription`]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription.kt
[`AbstractJvmPlugin`]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt
[`KotlinPlugin`]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/KotlinPlugin.kt
[`JavaPlugin`]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPlugin.kt
[`PluginData`]: src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt
[`PluginConfig`]: src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt
[`PluginDataStorage`]: src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt
[`MiraiConsole`]: src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt
[`MiraiConsoleImplementation`]: src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt
<!--[MiraiConsoleFrontEnd]: src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt-->
[`Command`]: src/main/kotlin/net/mamoe/mirai/console/command/Command.kt
[`CompositeCommand`]: src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt
[`SimpleCommand`]: src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt
[`RawCommand`]: src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt
[`CommandManager`]: src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt
[`BotManager`]: src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt
[`Annotations`]: src/main/kotlin/net/mamoe/mirai/console/util/Annotations.kt
[`ConsoleInput`]: src/main/kotlin/net/mamoe/mirai/console/util/ConsoleInput.kt
[`JavaPluginScheduler`]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler.kt
[`ResourceContainer`]: src/main/kotlin/net/mamoe/mirai/console/plugin/ResourceContainer.kt
## 基础
### `Plugin` 模块
Console 支持拥有强扩展性的插件加载器。内建 JVM 插件支持 ([`JarPluginLoader`])。
#### [插件加载器 `PluginLoader`][`PluginLoader`] 和 [插件管理器][`PluginManager`]
Console 本身是一套高扩展性的「框架」,拥有通用的 [插件加载器][`PluginLoader`]。
Console 内置 [`JarPluginLoader`],支持加载使用 Kotlin、 Java或其他 JVM 平台编程语言并打包为 jar 的插件 (详见下文 `JvmPlugin`)。
扩展的 [插件加载器][`PluginLoader`] 可以由一个特别的 [JVM 插件][`JvmPlugin`] 提供。
##### 服务器启动过程中的插件加载流程
在服务器启动过程中, Console 首先加载那些提供扩展 [插件加载器][`PluginLoader`] 的插件。
随后对插件按依赖顺序调用 `onLoad()`, 告知插件主类加载完毕, 相关依赖解决完毕.
当所有插件的 `onLoad()` 都被调用后, [`PluginManager`] 按依赖顺序依次调用 `onEnable()`
如果 A 依赖 B, B 依赖 C. 那么启动时的调用顺序为:
`C.onLoad()` -> `B.onLoad()` -> `A.onLoad()` -> `C.onEnable` -> `B.onEnable()` -> `A.onEnable()`
#### [`Plugin`]
所有 Console 插件都必须实现 [`Plugin`] 接口。
`Plugin` 很通用,它只拥有很少的成员:
```kotlin
interface Plugin : CommandOwner {
val isEnabled: Boolean
val loader: PluginLoader<*, *> // 能处理这个 Plugin 的 PluginLoader
}
```
[`Plugin`] 可在相应 [插件加载器 `PluginLoader`][`PluginLoader`] 的帮助下,成为任何语言实现的插件与 Console 建立联系的桥梁。
#### [JVM 插件][`JvmPlugin`]
##### [`JvmPlugin`]
```kotlin
interface JvmPlugin : Plugin, CoroutineScope, PluginFileExtensions, ResourceContainer, AutoSavePluginDataHolder {
val logger: MiraiLogger
val description: JvmPluginDescription
val loader: JarPluginLoader
fun <T : PluginData> loadPluginData(clazz: Class<T>): T
fun <T : PluginConfig> loadPluginConfig(clazz: Class<T>): T
fun onLoad() {}
fun onEnable() {}
fun onDisable() {}
}
```
##### 提供插件信息
JVM 插件, 通常需要打包为 `jar` 后才能被加载. Console 使用类似 Java ServiceLoader 的方式加载插件.
- 方法 A. (推荐) 自动创建 service 文件 (使用 Google auto-service)
`build.gradle.kts` 添加:
```kotlin
plugins {
kotlin("kapt")
}
dependencies {
val autoService = "1.0-rc7"
kapt("com.google.auto.service", "auto-service", autoService)
compileOnly("com.google.auto.service", "auto-service-annotations", autoService)
}
```
*对于 `build.gradle` 用户, 请自行按照 Groovy DSL 语法翻译*
- 方法 B. 手动创建 service 文件
`jar``META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin` 文件内存放插件主类全名.
**注意**:
- 插件自身的版本要求遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/) 规范, 合格的版本例如: `1.0.0`, `1.0`, `1.0-M1`, `1.0-pre-1`
- 插件依赖的版本遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/) 规范, 同时支持 [Apache Ivy 风格表示方法](http://ant.apache.org/ivy/history/latest-milestone/settings/version-matchers.html).
#### 实现 Kotlin 插件
一个 Kotlin 插件的主类通常需:
- 继承 [`KotlinPlugin`]
- 访问权限为 `public` 或默认 (不指定)
```kotlin
@AutoService(JvmPlugin::class) // 让 Console 知道这个 object 是一个插件主类.
object SchedulePlugin : KotlinPlugin(
SimpleJvmPluginDescription( // 插件的描述, name 和 version 是必须的
name = "Schedule",
version = "1.0.0",
// author, description, ...
)
) {
// ...
}
```
#### 实现 Java 插件
一个 Java 插件的主类通常需:
- 继承 [`KotlinPlugin`]
- 访问权限为 `public` 或默认 (不指定)
(推荐) 静态初始化:
```java
@AutoService(JvmPlugin.class)
public final class JExample extends JavaPlugin {
public static final JExample INSTANCE = new JExample(); // 可以像 Kotlin 一样静态初始化单例
private JExample() {
super(new SimpleJvmPluginDescription(
"JExample", // name
"1.0.0" // version
));
}
}
```
由 Console 初始化:
```java
@AutoService(JvmPlugin.class)
public final class JExample extends JavaPlugin {
private static final JExample instance;
public static JExample getInstance() {
return instance;
}
public JExample() { // 此时必须 public
super(new SimpleJvmPluginDescription(
"JExample", // name
"1.0.0" // version
));
instance = this;
}
}
```
#### 获取资源文件 [`ResourceContainer`]
[`JvmPlugin`] 实现接口 [`ResourceContainer`], 可在 `jar` 包内搜索资源文件.
提供三个获取方法:
```kotlin
interface ResourceContainer {
fun getResourceAsStream(path: String): InputStream?
fun getResource(path: String): String?
fun getResource(path: String, charset: Charset): String?
}
```
### [`PluginData`] 模块
[`PluginData`]
... 待续

View File

@ -16,17 +16,13 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.command.description.PermissibleIdentifierArgumentParser
import net.mamoe.mirai.console.command.description.PermissionIdArgumentParser
import net.mamoe.mirai.console.command.description.buildCommandArgumentContext
import net.mamoe.mirai.console.command.description.*
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.PermissibleIdentifier
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.permission.*
import net.mamoe.mirai.console.permission.PermissionService.Companion.denyPermission
import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermissionOrFail
import net.mamoe.mirai.console.permission.PermissionService.Companion.getGrantedPermissions
import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
@ -51,6 +47,7 @@ internal interface BuiltInCommandInternal : Command
*/
@ConsoleExperimentalAPI
@Suppress("unused")
@OptIn(ExperimentalPermission::class)
public object BuiltInCommands {
public val all: Array<out Command> by lazy {
@ -63,9 +60,10 @@ public object BuiltInCommands {
}
}
public object Help : SimpleCommand(
public object HelpCommand : SimpleCommand(
ConsoleCommandOwner, "help",
description = "Command list"
description = "Command list",
parentPermission = RootConsoleBuiltInPermission,
), BuiltInCommand {
@Handler
public suspend fun CommandSender.handle() {
@ -82,9 +80,10 @@ public object BuiltInCommands {
})
}
public object Stop : SimpleCommand(
public object StopCommand : SimpleCommand(
ConsoleCommandOwner, "stop", "shutdown", "exit",
description = "Stop the whole world."
description = "Stop the whole world.",
parentPermission = RootConsoleBuiltInPermission,
), BuiltInCommand {
private val closingLock = Mutex()
@ -117,9 +116,10 @@ public object BuiltInCommands {
}
}
public object Login : SimpleCommand(
public object LoginCommand : SimpleCommand(
ConsoleCommandOwner, "login", "登录",
description = "Log in a bot account."
description = "Log in a bot account.",
parentPermission = RootConsoleBuiltInPermission,
), BuiltInCommand {
@Handler
public suspend fun CommandSender.handle(id: Long, password: String) {
@ -145,23 +145,28 @@ public object BuiltInCommands {
}
@OptIn(ExperimentalPermission::class)
public object Permission : CompositeCommand(
ConsoleCommandOwner, "permission", "权限",
public object PermissionCommand : CompositeCommand(
ConsoleCommandOwner, "permission", "权限", "perm",
description = "Manage permissions",
overrideContext = buildCommandArgumentContext {
PermissibleIdentifier::class with PermissibleIdentifierArgumentParser
PermissionId::class with PermissionIdArgumentParser
Permission::class with PermissionIdArgumentParser.map { id ->
kotlin.runCatching {
id.findCorrespondingPermissionOrFail()
}.getOrElse { illegalArgument("指令不存在: $id", it) }
}
},
parentPermission = RootConsoleBuiltInPermission,
), BuiltInCommand {
// TODO: 2020/9/10 improve Permission command
@SubCommand
public suspend fun CommandSender.grant(target: PermissibleIdentifier, permission: PermissionId) {
public suspend fun CommandSender.grant(target: PermissibleIdentifier, permission: Permission) {
target.grantPermission(permission)
sendMessage("OK")
}
@SubCommand
public suspend fun CommandSender.deny(target: PermissibleIdentifier, permission: PermissionId) {
public suspend fun CommandSender.deny(target: PermissibleIdentifier, permission: Permission) {
target.denyPermission(permission)
sendMessage("OK")
}

View File

@ -15,11 +15,10 @@ 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.createCommandPermission
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
import net.mamoe.mirai.console.internal.command.isValidSubName
import net.mamoe.mirai.console.permission.ExperimentalPermission
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
@ -107,7 +106,7 @@ public abstract class AbstractCommand
public override val owner: CommandOwner,
vararg names: String,
description: String = "<no description available>",
parentPermission: PermissionId = owner.basePermission,
parentPermission: Permission = owner.parentPermission,
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
public override val prefixOptional: Boolean = false
) : Command {
@ -118,5 +117,5 @@ public abstract class AbstractCommand
}.toTypedArray()
@OptIn(ExperimentalPermission::class)
public override val permission: Permission by lazy { createCommandPermission(parentPermission) }
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
}

View File

@ -10,10 +10,7 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.PermissionIdNamespace
import net.mamoe.mirai.console.permission.RootPermission
import net.mamoe.mirai.console.permission.*
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
/**
@ -27,12 +24,12 @@ import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
@OptIn(ExperimentalPermission::class)
public interface CommandOwner : PermissionIdNamespace {
/**
* [PermissionIdNamespace] 拥有的指令都默认将 [basePermission] 作为父权限.
* [PermissionIdNamespace] 拥有的指令都默认将 [parentPermission] 作为父权限.
*
* TODO document
*/
@ExperimentalPermission
public val basePermission: PermissionId
public val parentPermission: Permission
}
/**
@ -40,8 +37,8 @@ public interface CommandOwner : PermissionIdNamespace {
*/
internal object ConsoleCommandOwner : CommandOwner {
@ExperimentalPermission
override val basePermission: PermissionId
get() = RootPermission.id
override val parentPermission: Permission
get() = RootPermission
@ExperimentalPermission
override fun permissionId(id: String): PermissionId = PermissionId("console", id)

View File

@ -21,7 +21,7 @@ import net.mamoe.mirai.console.command.description.*
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.PermissionId
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
@ -86,7 +86,7 @@ public abstract class CompositeCommand @OptIn(ExperimentalPermission::class) con
owner: CommandOwner,
vararg names: String,
description: String = "no description available",
parentPermission: PermissionId = owner.basePermission,
parentPermission: Permission = owner.parentPermission,
prefixOptional: Boolean = false,
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext
) : Command, AbstractReflectionCommand(owner, names, description, parentPermission, prefixOptional),
@ -110,12 +110,6 @@ public abstract class CompositeCommand @OptIn(ExperimentalPermission::class) con
@Target(FUNCTION)
protected annotation class SubCommand(vararg val value: String)
/** 指定子指令要求的权限 */
@Retention(RUNTIME)
@Target(FUNCTION)
@ExperimentalPermission
protected annotation class Permission(val value: String)
/** 指令描述 */
@Retention(RUNTIME)
@Target(FUNCTION)

View File

@ -14,10 +14,9 @@ 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.internal.command.createCommandPermission
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.message.data.MessageChain
/**
@ -44,12 +43,12 @@ public abstract class RawCommand @OptIn(ExperimentalPermission::class) construct
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
public override val description: String = "<no descriptions given>",
/** 指令父权限 */
parentPermission: PermissionId = owner.basePermission,
parentPermission: Permission = owner.parentPermission,
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
public override val prefixOptional: Boolean = false
) : Command {
@OptIn(ExperimentalPermission::class)
public override val permission: Permission by lazy { createCommandPermission(parentPermission) }
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
/**
* 在指令被执行时调用.

View File

@ -23,7 +23,7 @@ import net.mamoe.mirai.console.command.java.JSimpleCommand
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.message.data.MessageChain
/**
@ -53,10 +53,10 @@ public abstract class SimpleCommand @OptIn(ExperimentalPermission::class) constr
owner: CommandOwner,
vararg names: String,
description: String = "no description available",
basePermission: PermissionId = owner.basePermission,
parentPermission: Permission = owner.parentPermission,
prefixOptional: Boolean = false,
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext
) : Command, AbstractReflectionCommand(owner, names, description, basePermission, prefixOptional),
) : Command, AbstractReflectionCommand(owner, names, description, parentPermission, prefixOptional),
CommandArgumentContextAware {
/**

View File

@ -81,6 +81,21 @@ public interface CommandArgumentParser<out T : Any> {
public fun parse(raw: MessageContent, sender: CommandSender): T = parse(raw.content, sender)
}
/**
* 使用原 [this] 解析, 成功后使用 [mapper] 映射为另一个类型.
*/
public fun <T : Any, R : Any> CommandArgumentParser<T>.map(
mapper: CommandArgumentParser<R>.(T) -> R
): CommandArgumentParser<R> = MappingCommandArgumentParser(this, mapper)
private class MappingCommandArgumentParser<T : Any, R : Any>(
private val original: CommandArgumentParser<T>,
private val mapper: CommandArgumentParser<R>.(T) -> R
) : CommandArgumentParser<R> {
override fun parse(raw: String, sender: CommandSender): R = mapper(original.parse(raw, sender))
override fun parse(raw: MessageContent, sender: CommandSender): R = mapper(original.parse(raw, sender))
}
/**
* 解析一个字符串或 [SingleMessage] [T] 类型参数
*

View File

@ -321,7 +321,7 @@ public object PermissionIdArgumentParser : CommandArgumentParser<PermissionId> {
public object PermissibleIdentifierArgumentParser : CommandArgumentParser<PermissibleIdentifier> {
override fun parse(raw: String, sender: CommandSender): PermissibleIdentifier {
return kotlin.runCatching { AbstractPermissibleIdentifier.parseFromString(raw) }.getOrElse {
illegalArgument("无法解析 $raw 为 PermissionId")
illegalArgument("无法解析 $raw 为 PermissibleIdentifier")
}
}

View File

@ -15,7 +15,7 @@ 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.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
/**
@ -73,14 +73,14 @@ public abstract class JCompositeCommand @OptIn(ExperimentalPermission::class)
@JvmOverloads constructor(
owner: CommandOwner,
vararg names: String,
parentPermission: PermissionId = owner.basePermission,
parentPermission: Permission = owner.parentPermission,
) : CompositeCommand(owner, *names, parentPermission = parentPermission) {
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
public final override var description: String = "<no descriptions given>"
protected set
@OptIn(ExperimentalPermission::class)
public final override var permission: net.mamoe.mirai.console.permission.Permission = super.permission
public final override var permission: Permission = super.permission
protected set
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */

View File

@ -13,10 +13,9 @@ 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.internal.command.createCommandPermission
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
import net.mamoe.mirai.console.permission.ExperimentalPermission
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
@ -55,7 +54,7 @@ public abstract class JRawCommand @OptIn(ExperimentalPermission::class)
public override val owner: CommandOwner,
/** 指令名. 需要至少有一个元素. 所有元素都不能带有空格 */
public override vararg val names: String,
parentPermission: PermissionId = owner.basePermission,
parentPermission: Permission = owner.parentPermission,
) : Command {
/** 用法说明, 用于发送给用户 */
public override var usage: String = "<no usages given>"
@ -67,7 +66,7 @@ public abstract class JRawCommand @OptIn(ExperimentalPermission::class)
/** 指令权限 */
@ExperimentalPermission
public final override var permission: Permission = createCommandPermission(parentPermission)
public final override var permission: Permission = createOrFindCommandPermission(parentPermission)
protected set
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */

View File

@ -16,7 +16,6 @@ import net.mamoe.mirai.console.command.SimpleCommand
import net.mamoe.mirai.console.command.description.CommandArgumentContext
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionId
/**
* Java 实现:
@ -44,8 +43,8 @@ import net.mamoe.mirai.console.permission.PermissionId
public abstract class JSimpleCommand @OptIn(ExperimentalPermission::class) constructor(
owner: CommandOwner,
vararg names: String,
basePermission: PermissionId,
) : SimpleCommand(owner, *names, basePermission = basePermission) {
basePermission: Permission,
) : SimpleCommand(owner, *names, parentPermission = basePermission) {
public override var description: String = super.description
protected set

View File

@ -11,6 +11,7 @@ package net.mamoe.mirai.console.extension
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
import net.mamoe.mirai.console.extensions.PluginLoaderProvider
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
/**
@ -28,6 +29,8 @@ public interface FunctionExtension : Extension
/**
* 为某单例服务注册的 [Extension].
*
* 若同时有多个实例可用, 将会使用 [SingletonExtensionSelector.selectSingleton] 选择
*
* @see PermissionServiceProvider
*/
@ConsoleExperimentalAPI

View File

@ -5,12 +5,14 @@ import net.mamoe.mirai.console.extension.SingletonExtension
import net.mamoe.mirai.console.extension.SingletonExtensionPoint
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.plugin.description.PluginKind
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
/**
* [权限服务][PermissionService] 提供器.
*
* 此扩展可由 [PluginKind.LOADER] [PluginKind.HIGH_PRIORITY_EXTENSIONS] 插件提供
* 当插件注册 [PermissionService] , 默认会使用插件的 [PermissionService].
*
* 此扩展可由 [PluginLoadPriority.BEFORE_EXTENSIONS] [PluginLoadPriority.ON_EXTENSIONS] 插件提供
*/
@ExperimentalPermission
public interface PermissionServiceProvider : SingletonExtension<PermissionService<*>> {

View File

@ -3,12 +3,12 @@ package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
import net.mamoe.mirai.console.extension.InstanceExtension
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.description.PluginKind
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
/**
* 提供扩展 [PluginLoader]
*
* 此扩展可由 [PluginKind.LOADER] 插件提供
* 此扩展可由 [PluginLoadPriority.BEFORE_EXTENSIONS] 插件提供
*/
public interface PluginLoaderProvider : InstanceExtension<PluginLoader<*, *>> {
public companion object ExtensionPoint : AbstractExtensionPoint<PluginLoaderProvider>(PluginLoaderProvider::class)

View File

@ -12,7 +12,7 @@ package net.mamoe.mirai.console.extensions
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.extension.*
import net.mamoe.mirai.console.internal.extensions.BuiltInSingletonExtensionSelector
import net.mamoe.mirai.console.plugin.description.PluginKind
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.utils.info
import kotlin.reflect.KClass
@ -22,7 +22,7 @@ import kotlin.reflect.KClass
*
* 如有多个 [SingletonExtensionSelector] 注册, 将会停止服务器.
*
* 此扩展可由 [PluginKind.LOADER] [PluginKind.HIGH_PRIORITY_EXTENSIONS] 插件提供
* 此扩展可由 [PluginLoadPriority.BEFORE_EXTENSIONS] 插件提供
*/
public interface SingletonExtensionSelector : FunctionExtension {

View File

@ -33,7 +33,9 @@ import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig
import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope
import net.mamoe.mirai.console.internal.extensions.BuiltInSingletonExtensionSelector
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.internal.util.autoHexToBytes
import net.mamoe.mirai.console.permission.BuiltInPermissionService
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.PermissionService
@ -124,13 +126,22 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
val pluginLoadSession: PluginManagerImpl.PluginLoadSession
phase `load plugins`@{
phase `load BEFORE_EXTENSIONS plugins`@{
PluginManager // init
mainLogger.verbose { "Loading PluginLoader provider plugins..." }
PluginManagerImpl.loadEnablePluginProviderPlugins()
mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." }
}
phase `load SingletonExtensionSelector`@{
val instance = SingletonExtensionSelector.instance
if (instance is BuiltInSingletonExtensionSelector) {
ConsoleDataScope.addAndReloadConfig(instance.config)
}
}
phase `load ON_EXTENSIONS plugins`@{
mainLogger.verbose { "Scanning high-priority extension and normal plugins..." }
pluginLoadSession = PluginManagerImpl.scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider()
mainLogger.verbose { "${pluginLoadSession.allKindsOfPlugins.size} plugin(s) found." }
@ -140,8 +151,6 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." }
}
SingletonExtensionSelector.instance // init
phase `load PermissionService`@{
mainLogger.verbose { "Loading PermissionService..." }
PermissionService.INSTANCE.let { ps ->
@ -162,7 +171,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
CommandManagerImpl.commandListener // start
}
phase `load normal plugins`@{
phase `load AFTER_EXTENSION plugins`@{
mainLogger.verbose { "Loading normal plugins..." }
val count = PluginManagerImpl.loadEnableNormalPlugins(pluginLoadSession)
mainLogger.verbose { "$count normal plugin(s) loaded." }
@ -172,14 +181,19 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
phase `auto-login bots`@{
runBlocking {
for ((id, password) in AutoLoginConfig.plainPasswords) {
for ((id, password) in AutoLoginConfig.plainPasswords.filterNot { it.key == 123456654321L }) {
mainLogger.info { "Auto-login $id" }
MiraiConsole.addBot(id, password).alsoLogin()
}
for ((id, password) in AutoLoginConfig.md5Passwords) {
for ((id, password) in AutoLoginConfig.md5Passwords.filterNot { it.key == 123456654321L }) {
mainLogger.info { "Auto-login $id" }
MiraiConsole.addBot(id, password).alsoLogin()
val x = runCatching {
password.autoHexToBytes()
}.getOrElse {
error("Bad auto-login md5: '$password'")
}
MiraiConsole.addBot(id, x).alsoLogin()
}
}
}

View File

@ -18,8 +18,6 @@ import net.mamoe.mirai.console.command.description.CommandArgumentContextAware
import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
import net.mamoe.mirai.message.data.*
import kotlin.reflect.KAnnotatedElement
@ -53,7 +51,7 @@ internal abstract class AbstractReflectionCommand @OptIn(ExperimentalPermission:
owner: CommandOwner,
names: Array<out String>,
description: String = "<no description available>",
parentPermission: PermissionId = owner.basePermission,
parentPermission: Permission = owner.parentPermission,
prefixOptional: Boolean = false
) : Command, AbstractCommand(
owner,
@ -79,7 +77,7 @@ internal abstract class AbstractReflectionCommand @OptIn(ExperimentalPermission:
internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy {
DefaultSubCommandDescriptor(
"",
createCommandPermission(parentPermission),
createOrFindCommandPermission(parentPermission),
onCommand = { sender: CommandSender, args: MessageChain ->
sender.onDefault(args)
}
@ -144,7 +142,7 @@ internal abstract class AbstractReflectionCommand @OptIn(ExperimentalPermission:
argsWithSubCommandNameNotRemoved: MessageChain,
removeSubName: Boolean
) {
val args = parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) names.size else 0)
val args = parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) 1 else 0)
if (!this.permission.testPermission(sender)) {
sender.sendMessage(usage) // TODO: 2020/8/26 #127
return
@ -259,7 +257,7 @@ internal fun AbstractReflectionCommand.createSubCommand(
context: CommandArgumentContext
): AbstractReflectionCommand.SubCommandDescriptor {
val notStatic = !function.hasAnnotation<JvmStatic>()
val overridePermission = function.findAnnotation<CompositeCommand.Permission>()//optional
//val overridePermission = null//function.findAnnotation<CompositeCommand.PermissionId>()//optional
val subDescription =
function.findAnnotation<CompositeCommand.Description>()?.value ?: ""
@ -331,7 +329,7 @@ internal fun AbstractReflectionCommand.createSubCommand(
commandName,
params,
subDescription, // overridePermission?.value
overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission,
permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission,
onCommand = { sender: CommandSender, args: Array<out Any> ->
val result = if (notStatic) {
if (hasSenderParam) {

View File

@ -13,7 +13,6 @@ import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
import net.mamoe.mirai.contact.Group
@ -143,8 +142,9 @@ internal fun Group.fuzzySearchMember(
}
@OptIn(ExperimentalPermission::class)
internal fun Command.createCommandPermission(parent: PermissionId): Permission {
return PermissionService.INSTANCE.register(owner.permissionId(primaryName), description, parent)
internal fun Command.createOrFindCommandPermission(parent: Permission): Permission {
val id = owner.permissionId(primaryName)
return PermissionService.INSTANCE[id] ?: PermissionService.INSTANCE.register(id, description, parent)
}
//// internal

View File

@ -3,6 +3,8 @@ package net.mamoe.mirai.console.internal.data.builtins
import net.mamoe.mirai.console.data.AutoSavePluginConfig
import net.mamoe.mirai.console.data.ValueDescription
import net.mamoe.mirai.console.data.value
import net.mamoe.mirai.console.internal.util.md5
import net.mamoe.mirai.console.internal.util.toUHexString
internal object AutoLoginConfig : AutoSavePluginConfig() {
override val saveName: String
@ -13,7 +15,7 @@ internal object AutoLoginConfig : AutoSavePluginConfig() {
账号和明文密码列表
"""
)
val plainPasswords: MutableMap<Long, String> by value(mutableMapOf())
val plainPasswords: MutableMap<Long, String> by value(mutableMapOf(123456654321L to "example"))
@ValueDescription(
@ -21,5 +23,9 @@ internal object AutoLoginConfig : AutoSavePluginConfig() {
账号和 MD5 密码列表
"""
)
val md5Passwords: MutableMap<Long, String> by value(mutableMapOf())
val md5Passwords: MutableMap<Long, String> by value(
mutableMapOf(
123456654321L to "example".toByteArray().md5().toUHexString()
)
)
}

View File

@ -2,7 +2,7 @@ package net.mamoe.mirai.console.internal.extensions
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.AutoSavePluginData
import net.mamoe.mirai.console.data.AutoSavePluginConfig
import net.mamoe.mirai.console.data.value
import net.mamoe.mirai.console.extension.Extension
import net.mamoe.mirai.console.extension.ExtensionRegistry
@ -15,9 +15,9 @@ import kotlin.reflect.KClass
internal object BuiltInSingletonExtensionSelector : SingletonExtensionSelector {
private val config: SaveData = SaveData()
internal val config: SaveData = SaveData()
private class SaveData : AutoSavePluginData() {
internal class SaveData : AutoSavePluginConfig() {
override val saveName: String get() = "ExtensionSelector"
val value: MutableMap<String, String> by value()

View File

@ -16,6 +16,7 @@ import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.data.runCatchingLog
import net.mamoe.mirai.console.internal.data.mkdir
import net.mamoe.mirai.console.permission.ExperimentalPermission
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.permission.PermissionService.Companion.allocatePermissionIdForPlugin
@ -44,11 +45,11 @@ internal abstract class JvmPluginInternal(
) : JvmPlugin, CoroutineScope {
@OptIn(ExperimentalPermission::class)
final override val basePermission: PermissionId by lazy {
final override val parentPermission: Permission by lazy {
PermissionService.INSTANCE.register(
PermissionService.INSTANCE.allocatePermissionIdForPlugin(name, "*"),
"The base permission"
).id
)
}
final override var isEnabled: Boolean = false
@ -110,7 +111,7 @@ internal abstract class JvmPluginInternal(
}
internal fun internalOnEnable(): Boolean {
basePermission
parentPermission
if (!firstRun) refreshCoroutineContext()
kotlin.runCatching {
onEnable()

View File

@ -22,7 +22,7 @@ import net.mamoe.mirai.console.internal.data.mkdir
import net.mamoe.mirai.console.plugin.*
import net.mamoe.mirai.console.plugin.description.PluginDependency
import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.description.PluginKind
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.utils.info
@ -131,7 +131,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
internal fun loadEnableHighPriorityExtensionPlugins(session: PluginLoadSession): Int {
loadersLock.withLock {
session.allKindsOfPlugins.flatMap { it.second }
.filter { it.kind == PluginKind.HIGH_PRIORITY_EXTENSIONS }
.filter { it.loadPriority == PluginLoadPriority.ON_EXTENSIONS }
.sortByDependencies()
.also { it.loadAndEnableAllInOrder() }
.let { return it.size }
@ -142,7 +142,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
internal fun loadEnableNormalPlugins(session: PluginLoadSession): Int {
loadersLock.withLock {
session.allKindsOfPlugins.flatMap { it.second }
.filter { it.kind == PluginKind.NORMAL }
.filter { it.loadPriority == PluginLoadPriority.AFTER_EXTENSIONS }
.sortByDependencies()
.also { it.loadAndEnableAllInOrder() }
.let { return it.size }
@ -191,7 +191,8 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
loader as PluginLoader<Plugin, PluginDescription>
descriptions.forEach(PluginManagerImpl::checkPluginDescription)
descriptions.filter { it.kind == PluginKind.LOADER }.sortByDependencies().loadAndEnableAllInOrder()
descriptions.filter { it.loadPriority == PluginLoadPriority.BEFORE_EXTENSIONS }.sortByDependencies()
.loadAndEnableAllInOrder()
}
.flatMap { it.second.asSequence() }

View File

@ -0,0 +1,54 @@
/*
* 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.internal.util
import java.security.MessageDigest
@Suppress("DuplicatedCode") // false positive. `this` is not the same for `List<Byte>` and `ByteArray`
internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) {
require(offset >= 0) { "offset shouldn't be negative: $offset" }
require(length >= 0) { "length shouldn't be negative: $length" }
require(offset + length <= this.size) { "offset ($offset) + length ($length) > array.size (${this.size})" }
}
internal fun String.autoHexToBytes(): ByteArray =
this.trim(Char::isWhitespace).asSequence().chunked(2).map {
(it[0].toString() + it[1]).toUByte(16).toByte()
}.toList().toByteArray()
internal fun ByteArray.md5(offset: Int = 0, length: Int = this.size - offset): ByteArray {
this.checkOffsetAndLength(offset, length)
return MessageDigest.getInstance("MD5").apply { update(this@md5, offset, length) }.digest()
}
@JvmOverloads
@Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray
internal fun ByteArray.toUHexString(
separator: String = " ",
offset: Int = 0,
length: Int = this.size - offset
): String {
this.checkOffsetAndLength(offset, length)
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)
}
}
}
}

View File

@ -9,28 +9,27 @@
package net.mamoe.mirai.console.permission
import net.mamoe.mirai.console.data.PluginDataExtensions
import net.mamoe.mirai.console.permission.PermissibleIdentifier.Companion.grantedWith
import java.util.concurrent.CopyOnWriteArrayList
/**
*
*/
@ExperimentalPermission
public abstract class AbstractConcurrentPermissionService<P : Permission> : PermissionService<P> {
internal abstract class AbstractConcurrentPermissionService<P : Permission> : PermissionService<P> {
protected abstract val permissions: MutableMap<PermissionId, P>
protected abstract val grantedPermissionsMap: MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
protected abstract val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
protected abstract fun createPermission(
id: PermissionId,
description: String,
base: PermissionId = RootPermission.id
parent: Permission
): P
override fun get(id: PermissionId): P? = permissions[id]
override fun register(id: PermissionId, description: String, base: PermissionId): P {
grantedPermissionsMap[id] = CopyOnWriteArrayList() // mutations are not quite often performed
val instance = createPermission(id, description, base)
override fun register(id: PermissionId, description: String, parent: Permission): P {
val instance = createPermission(id, description, parent)
val old = permissions.putIfAbsent(id, instance)
if (old != null) throw DuplicatedPermissionRegistrationException(instance, old)
return instance
@ -38,16 +37,15 @@ public abstract class AbstractConcurrentPermissionService<P : Permission> : Perm
override fun grant(permissibleIdentifier: PermissibleIdentifier, permission: P) {
val id = permission.id
grantedPermissionsMap[id]?.add(permissibleIdentifier)
?: error("Bad PermissionService implementation: grantedPermissionsMap[id] is null.")
grantedPermissionsMap[id].add(permissibleIdentifier)
}
override fun deny(permissibleIdentifier: PermissibleIdentifier, permission: P) {
grantedPermissionsMap[permission.id]?.remove(permissibleIdentifier)
grantedPermissionsMap[permission.id].remove(permissibleIdentifier)
}
override fun getRegisteredPermissions(): Sequence<P> = permissions.values.asSequence()
public override fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence<P> = sequence<P> {
override fun getGrantedPermissions(permissibleIdentifier: PermissibleIdentifier): Sequence<P> = sequence<P> {
for ((permissionIdentifier, permissibleIdentifiers) in grantedPermissionsMap) {
val granted =

View File

@ -9,28 +9,28 @@
package net.mamoe.mirai.console.permission
import kotlinx.serialization.Serializable
import net.mamoe.mirai.console.data.AutoSavePluginConfig
import net.mamoe.mirai.console.data.PluginDataExtensions
import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault
import net.mamoe.mirai.console.data.value
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.CopyOnWriteArraySet
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.createType
@ExperimentalPermission
public object AllGrantPermissionService : PermissionService<PermissionImpl> {
internal object AllGrantPermissionService : PermissionService<PermissionImpl> {
private val all = ConcurrentHashMap<PermissionId, PermissionImpl>()
override val permissionType: KClass<PermissionImpl>
get() = PermissionImpl::class
override val permissionType: KClass<PermissionImpl> get() = PermissionImpl::class
override val rootPermission: PermissionImpl get() = RootPermissionImpl.also { all[it.id] = it }
override fun register(
id: PermissionId,
description: String,
base: PermissionId
parent: Permission
): PermissionImpl {
val new = PermissionImpl(id, description, base)
val new = PermissionImpl(id, description, parent)
val old = all.putIfAbsent(id, new)
if (old != null) throw DuplicatedPermissionRegistrationException(new, old)
return new
@ -51,18 +51,23 @@ public object AllGrantPermissionService : PermissionService<PermissionImpl> {
}
}
@Suppress("DEPRECATION")
@OptIn(ExperimentalPermission::class)
private val RootPermissionImpl = PermissionImpl(PermissionId("*", "*"), "The root permission").also { it.parent = it }
@ExperimentalPermission
public object AllDenyPermissionService : PermissionService<PermissionImpl> {
internal object AllDenyPermissionService : PermissionService<PermissionImpl> {
private val all = ConcurrentHashMap<PermissionId, PermissionImpl>()
override val permissionType: KClass<PermissionImpl>
get() = PermissionImpl::class
override val rootPermission: PermissionImpl = RootPermissionImpl.also { all[it.id] = it }
override fun register(
id: PermissionId,
description: String,
base: PermissionId
parent: Permission
): PermissionImpl {
val new = PermissionImpl(id, description, base)
val new = PermissionImpl(id, description, parent)
val old = all.putIfAbsent(id, new)
if (old != null) throw DuplicatedPermissionRegistrationException(new, old)
return new
@ -90,41 +95,75 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService<P
@ExperimentalPermission
override val permissionType: KClass<PermissionImpl>
get() = PermissionImpl::class
override val permissions: MutableMap<PermissionId, PermissionImpl> = ConcurrentHashMap()
override val permissions: ConcurrentHashMap<PermissionId, PermissionImpl> = ConcurrentHashMap()
override val rootPermission: PermissionImpl = RootPermissionImpl.also { permissions[it.id] = it }
@Suppress("UNCHECKED_CAST")
override val grantedPermissionsMap: MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
get() = config.grantedPermissionMap as MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
override val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
get() = config.grantedPermissionMap as PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
override fun createPermission(id: PermissionId, description: String, base: PermissionId): PermissionImpl =
PermissionImpl(id, description, base)
override fun createPermission(id: PermissionId, description: String, parent: Permission): PermissionImpl =
PermissionImpl(id, description, parent)
internal val config: ConcurrentSaveData<PermissionImpl> =
ConcurrentSaveData(
PermissionImpl::class.createType(),
"PermissionService",
)
internal val config: ConcurrentSaveData =
ConcurrentSaveData("PermissionService")
@Suppress("RedundantVisibilityModifier")
@ExperimentalPermission
internal class ConcurrentSaveData<P : Permission> private constructor(
permissionType: KType,
internal class ConcurrentSaveData private constructor(
public override val saveName: String,
// delegate: PluginConfig,
@Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?
) : AutoSavePluginConfig() {
public val grantedPermissionMap: MutableMap<PermissionId, MutableList<AbstractPermissibleIdentifier>>
by value<MutableMap<PermissionId, MutableList<AbstractPermissibleIdentifier>>>(ConcurrentHashMap())
.withDefault { CopyOnWriteArrayList() }
public val grantedPermissionMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableSet<AbstractPermissibleIdentifier>>
by value<MutableMap<PermissionId, MutableSet<AbstractPermissibleIdentifier>>>(ConcurrentHashMap())
.withDefault { CopyOnWriteArraySet() }
public companion object {
@JvmStatic
public operator fun <P : Permission> invoke(
permissionType: KType,
public operator fun invoke(
saveName: String,
// delegate: PluginConfig,
): ConcurrentSaveData<P> = ConcurrentSaveData(permissionType, saveName, null)
): ConcurrentSaveData = ConcurrentSaveData(saveName, null)
}
}
}
/**
* [Permission] 的简单实现
*/
@Serializable
@ExperimentalPermission
internal data class PermissionImpl @Deprecated("Only for Root") constructor(
override val id: PermissionId,
override val description: String,
) : Permission {
override lateinit var parent: Permission
@Suppress("DEPRECATION")
constructor(id: PermissionId, description: String, parent: Permission) : this(id, description) {
this.parent = parent
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as PermissionImpl
if (id != other.id) return false
if (description != other.description) return false
if (parent !== other.parent) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + description.hashCode()
result = 31 * result + if (parent == this) 1 else parent.hashCode()
return result
}
override fun toString(): String =
"PermissionImpl(id=$id, description='$description', parent=${if (parent === this) "<self>" else parent.toString()})"
}

View File

@ -46,36 +46,67 @@ public sealed class AbstractPermissibleIdentifier(
public companion object {
@JvmStatic
public fun parseFromString(string: String): AbstractPermissibleIdentifier {
val str = string.trim()
objects.find { it.toString() == str }?.let { return it as AbstractPermissibleIdentifier }
for ((regex, block) in regexes) {
val result = regex.find(str) ?: continue
if (result.range.last != str.lastIndex) continue
if (result.range.first != 0) continue
return result.destructured.run(block)
val str = string.trim { it.isWhitespace() }.toLowerCase()
if (str == "console") return Console
if (str.isNotEmpty()) {
when (str[0]) {
'g' -> {
val arg = str.substring(1)
if (arg == "*") return AnyGroup
else arg.toLongOrNull()?.let(::ExactGroup)?.let { return it }
}
'f' -> {
val arg = str.substring(1)
if (arg == "*") return AnyFriend
else arg.toLongOrNull()?.let(::ExactFriend)?.let { return it }
}
'u' -> {
val arg = str.substring(1)
if (arg == "*") return AnyUser
else arg.toLongOrNull()?.let(::ExactUser)?.let { return it }
}
'c' -> {
val arg = str.substring(1)
if (arg == "*") return AnyContact
}
'm' -> kotlin.run {
val arg = str.substring(1)
if (arg == "*") return AnyMemberFromAnyGroup
else {
val components = arg.split('.')
if (components.size == 2) {
val groupId = components[0].toLongOrNull() ?: return@run
if (components[1] == "*") return AnyMember(groupId)
else {
val memberId = components[1].toLongOrNull() ?: return@run
return ExactMember(groupId, memberId)
}
}
}
}
't' -> kotlin.run {
val arg = str.substring(1)
if (arg == "*") return AnyTempFromAnyGroup
else {
val components = arg.split('.')
if (components.size == 2) {
val groupId = components[0].toLongOrNull() ?: return@run
if (components[1] == "*") return AnyTemp(groupId)
else {
val memberId = components[1].toLongOrNull() ?: return@run
return ExactTemp(groupId, memberId)
}
}
}
}
}
}
error("Cannot deserialize '$str' as AbstractPermissibleIdentifier")
}
internal val objects by lazy {
// https://youtrack.jetbrains.com/issue/KT-41782
AbstractPermissibleIdentifier::class.nestedClasses.mapNotNull { it.objectInstance }
}
internal val regexes: List<Pair<Regex, (matchGroup: MatchResult.Destructured) -> AbstractPermissibleIdentifier>> =
listOf(
Regex("""ExactGroup\(\s*([0-9]+)\s*\)""") to { (id) -> ExactGroup(id.toLong()) },
Regex("""ExactFriend\(\s*([0-9]+)\s*\)""") to { (id) -> ExactFriend(id.toLong()) },
Regex("""ExactUser\(\s*([0-9]+)\s*\)""") to { (id) -> ExactUser(id.toLong()) },
Regex("""AnyMember\(\s*([0-9]+)\s*\)""") to { (id) -> AnyMember(id.toLong()) },
Regex("""ExactMember\(\s*([0-9]+)\s*([0-9]+)\s*\)""") to { (a, b) ->
ExactMember(
a.toLong(),
b.toLong()
)
},
Regex("""ExactTemp\(\s*([0-9]+)\s*([0-9]+)\s*\)""") to { (a, b) -> ExactTemp(a.toLong(), b.toLong()) },
)
}
@ConsoleExperimentalAPI
@ -86,54 +117,70 @@ public sealed class AbstractPermissibleIdentifier(
)
public object AnyGroup : AbstractPermissibleIdentifier(AnyContact) {
override fun toString(): String = "AnyGroup"
override fun toString(): String = "g*"
}
public data class ExactGroup(public val groupId: Long) : AbstractPermissibleIdentifier(AnyGroup)
public data class ExactGroup(public val groupId: Long) : AbstractPermissibleIdentifier(AnyGroup) {
override fun toString(): String = "g$groupId"
}
public data class AnyMember(public val groupId: Long) : AbstractPermissibleIdentifier(AnyMemberFromAnyGroup)
public data class AnyMember(public val groupId: Long) : AbstractPermissibleIdentifier(AnyMemberFromAnyGroup) {
override fun toString(): String = "m$groupId.*"
}
public object AnyMemberFromAnyGroup : AbstractPermissibleIdentifier(AnyUser) {
override fun toString(): String = "AnyMemberFromAnyGroup"
override fun toString(): String = "m*"
}
public object AnyTempFromAnyGroup : AbstractPermissibleIdentifier(AnyUser) {
override fun toString(): String = "t*"
}
public data class ExactMember(
public val groupId: Long,
public val memberId: Long
) : AbstractPermissibleIdentifier(AnyMember(groupId), ExactUser(memberId))
) : AbstractPermissibleIdentifier(AnyMember(groupId), ExactUser(memberId)) {
override fun toString(): String = "m$groupId.$memberId"
}
public object AnyFriend : AbstractPermissibleIdentifier(AnyUser) {
override fun toString(): String = "AnyFriend"
override fun toString(): String = "f*"
}
public data class ExactFriend(
public val id: Long
) : AbstractPermissibleIdentifier(ExactUser(id)) {
override fun toString(): String = "ExactFriend"
override fun toString(): String = "f$id"
}
public object AnyTemp : AbstractPermissibleIdentifier(AnyUser) {
override fun toString(): String = "AnyTemp"
public data class AnyTemp(
public val groupId: Long,
) : AbstractPermissibleIdentifier(AnyUser, AnyMember(groupId)) {
override fun toString(): String = "t$groupId.*"
}
public data class ExactTemp(
public val groupId: Long,
public val id: Long
) : AbstractPermissibleIdentifier(ExactUser(groupId)) // TODO: 2020/9/8 ExactMember ?
public val memberId: Long
) : AbstractPermissibleIdentifier(ExactUser(groupId), ExactMember(groupId, memberId)) {
override fun toString(): String = "t$groupId.$memberId"
}
public object AnyUser : AbstractPermissibleIdentifier(AnyContact) {
override fun toString(): String = "AnyUser"
override fun toString(): String = "u*"
}
public data class ExactUser(
public val id: Long
) : AbstractPermissibleIdentifier(AnyUser)
) : AbstractPermissibleIdentifier(AnyUser) {
override fun toString(): String = "u$id"
}
public object AnyContact : AbstractPermissibleIdentifier() {
override fun toString(): String = "AnyContact"
override fun toString(): String = "*"
}
public object Console : AbstractPermissibleIdentifier() {
override fun toString(): String = "Console"
override fun toString(): String = "console"
}
}

View File

@ -9,8 +9,6 @@
package net.mamoe.mirai.console.permission
import kotlinx.serialization.Serializable
import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermission
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
@ -25,22 +23,29 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
public interface Permission {
public val id: PermissionId
public val description: String
public val parentId: PermissionId
}
@OptIn(ExperimentalPermission::class)
private val ROOT_PERMISSION_ID = PermissionId("*", "*")
/**
* [RootPermission] parent 为自身
*/
public val parent: Permission
}
/**
* 所有权限的父权限.
*/
@get:JvmName("getRootPermission")
@ExperimentalPermission
public val RootPermission: Permission by lazy {
public val RootPermission: Permission
get() = PermissionService.INSTANCE.rootPermission
/**
* 所有内建指令的权限
*/
@ExperimentalPermission
public val RootConsoleBuiltInPermission: Permission by lazy {
PermissionService.INSTANCE.register(
ROOT_PERMISSION_ID,
"The parent of any permission",
ROOT_PERMISSION_ID
PermissionId("console", "*"),
"The parent of any built-in commands"
)
}
@ -48,16 +53,5 @@ public val RootPermission: Permission by lazy {
@ExperimentalPermission
public fun Permission.parentsWithSelfSequence(): Sequence<Permission> =
generateSequence(this) { p ->
p.parentId.findCorrespondingPermission()?.takeIf { parent -> parent != p }
p.parent.takeIf { parent -> parent != p }
}
/**
* [Permission] 的简单实现
*/
@Serializable
@ExperimentalPermission
public class PermissionImpl(
override val id: PermissionId,
override val description: String,
override val parentId: PermissionId = RootPermission.id
) : Permission

View File

@ -23,6 +23,7 @@ import kotlin.reflect.full.isSuperclassOf
public interface PermissionService<P : Permission> {
@ExperimentalPermission
public val permissionType: KClass<P>
public val rootPermission: P
///////////////////////////////////////////////////////////////////////////
@ -46,7 +47,7 @@ public interface PermissionService<P : Permission> {
public fun register(
id: PermissionId,
description: String,
base: PermissionId = RootPermission.id
parent: Permission = RootPermission
): P
///////////////////////////////////////////////////////////////////////////

View File

@ -18,7 +18,7 @@ import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
import net.mamoe.mirai.console.plugin.description.PluginDependency
import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.description.PluginKind
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
/**
@ -65,9 +65,9 @@ public inline val Plugin.name: String get() = this.description.name
public inline val Plugin.version: Semver get() = this.description.version
/**
* 获取 [PluginDescription.kind]
* 获取 [PluginDescription.loadPriority]
*/
public inline val Plugin.kind: PluginKind get() = this.description.kind
public inline val Plugin.loadPriority: PluginLoadPriority get() = this.description.loadPriority
/**
* 获取 [PluginDescription.info]

View File

@ -23,9 +23,9 @@ public interface PluginDescription {
/**
* 插件类型. 将会决定加载顺序
*
* @see PluginKind
* @see PluginLoadPriority
*/
public val kind: PluginKind
public val loadPriority: PluginLoadPriority
/**
* 插件 ID, 必须全英文, 仅允许英文字母, '-', '_', '.'.

View File

@ -14,39 +14,46 @@ import kotlinx.serialization.builtins.serializer
import net.mamoe.mirai.console.extension.Extension
import net.mamoe.mirai.console.extensions.BotConfigurationAlterer
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
import net.mamoe.mirai.console.extensions.PluginLoaderProvider
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
import net.mamoe.mirai.console.internal.data.map
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.description.PluginKind.*
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority.*
/**
* 插件类型.
*
* 插件类型将影响加载顺序: [LOADER] -> [HIGH_PRIORITY_EXTENSIONS] -> [NORMAL].
* 插件类型将影响加载顺序: [BEFORE_EXTENSIONS] -> [ON_EXTENSIONS] -> [AFTER_EXTENSIONS].
*
* 依赖解决过程与插件类型有很大关联. 在一个较早的阶段, 只会解决在此阶段加载的插件. 意味着 [LOADER] 不允许依赖一个 [NORMAL] 类型的插件.
* 依赖解决过程与插件类型有很大关联. 在一个较早的阶段, 只会解决在此阶段加载的插件. 意味着 [BEFORE_EXTENSIONS] 不允许依赖一个 [AFTER_EXTENSIONS] 类型的插件.
*/
public enum class PluginKind {
/** 表示此插件提供一个 [PluginLoader], 也可以同时提供其他 [Extension] 应最早被加载 */
LOADER,
public enum class PluginLoadPriority {
/**
* 表示此插件最早被加载. Console 启动时的第一初始化阶段就会加载这些插件.
*
* 一般只有提供 [PluginLoaderProvider] [SingletonExtensionSelector] 的插件才需要在此阶段加载.
*/
BEFORE_EXTENSIONS,
/**
* 表示此插件提供一些高优先级的 [Extension], 应在加载其他 [NORMAL] 类型插件前加载
* 表示此插件提供一些高优先级的 [Extension], 应在加载其他 [AFTER_EXTENSIONS] 类型插件前加载
*
* 高优先级的 [Extension] 通常是覆盖 Console 内置的部分服务的扩展. [PermissionServiceProvider].
*
* 一些普通的 [Extension], [BotConfigurationAlterer], 也可以使用 [NORMAL] 类型插件注册.
* 一些普通的 [Extension], [BotConfigurationAlterer], 也可以使用 [AFTER_EXTENSIONS] 类型插件注册.
*/
HIGH_PRIORITY_EXTENSIONS,
ON_EXTENSIONS,
/** 表示此插件为一个通常的插件, 按照正常的依赖关系加载. */
NORMAL;
/**
* 表示此插件为一个通常的插件, 在扩展处理完毕后加载.
*/
AFTER_EXTENSIONS;
public object AsStringSerializer : KSerializer<PluginKind> by String.serializer().map(
public object AsStringSerializer : KSerializer<PluginLoadPriority> by String.serializer().map(
serializer = { it.name },
deserializer = { str ->
values().firstOrNull {
it.name.equals(str, ignoreCase = true)
} ?: NORMAL
} ?: AFTER_EXTENSIONS
}
)
}

View File

@ -14,7 +14,7 @@ package net.mamoe.mirai.console.plugin.jvm
import com.vdurmont.semver4j.Semver
import net.mamoe.mirai.console.plugin.description.PluginDependency
import net.mamoe.mirai.console.plugin.description.PluginDescription
import net.mamoe.mirai.console.plugin.description.PluginKind
import net.mamoe.mirai.console.plugin.description.PluginLoadPriority
import kotlin.internal.LowPriorityInOverloadResolution
/**
@ -82,7 +82,7 @@ public class JvmPluginDescriptionBuilder(
private var author: String = ""
private var info: String = ""
private var dependencies: MutableSet<PluginDependency> = mutableSetOf()
private var kind: PluginKind = PluginKind.NORMAL
private var loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS
@ILoveKuriyamaMiraiForever
public fun name(value: String): JvmPluginDescriptionBuilder = apply { this.name = value.trim() }
@ -104,17 +104,19 @@ public class JvmPluginDescriptionBuilder(
public fun info(value: String): JvmPluginDescriptionBuilder = apply { this.info = value.trimIndent() }
@ILoveKuriyamaMiraiForever
public fun kind(value: PluginKind): JvmPluginDescriptionBuilder = apply { this.kind = value }
public fun kind(value: PluginLoadPriority): JvmPluginDescriptionBuilder = apply { this.loadPriority = value }
@ILoveKuriyamaMiraiForever
public fun normalPlugin(): JvmPluginDescriptionBuilder = apply { this.kind = PluginKind.NORMAL }
public fun normalPlugin(): JvmPluginDescriptionBuilder =
apply { this.loadPriority = PluginLoadPriority.AFTER_EXTENSIONS }
@ILoveKuriyamaMiraiForever
public fun loaderProviderPlugin(): JvmPluginDescriptionBuilder = apply { this.kind = PluginKind.LOADER }
public fun loaderProviderPlugin(): JvmPluginDescriptionBuilder =
apply { this.loadPriority = PluginLoadPriority.BEFORE_EXTENSIONS }
@ILoveKuriyamaMiraiForever
public fun highPriorityExtensionsPlugin(): JvmPluginDescriptionBuilder =
apply { this.kind = PluginKind.HIGH_PRIORITY_EXTENSIONS }
apply { this.loadPriority = PluginLoadPriority.ON_EXTENSIONS }
@ILoveKuriyamaMiraiForever
public fun dependsOn(
@ -152,7 +154,7 @@ public class JvmPluginDescriptionBuilder(
@Suppress("DEPRECATION_ERROR")
public fun build(): JvmPluginDescription =
SimpleJvmPluginDescription(name, version, id, author, info, dependencies, kind)
SimpleJvmPluginDescription(name, version, id, author, info, dependencies, loadPriority)
@Retention(AnnotationRetention.SOURCE)
@DslMarker
@ -192,7 +194,7 @@ public data class SimpleJvmPluginDescription
public override val author: String = "",
public override val info: String = "",
public override val dependencies: Set<PluginDependency> = setOf(),
public override val kind: PluginKind = PluginKind.NORMAL,
public override val loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS,
) : JvmPluginDescription {
@Deprecated(
@ -214,8 +216,8 @@ public data class SimpleJvmPluginDescription
author: String = "",
info: String = "",
dependencies: Set<PluginDependency> = setOf(),
kind: PluginKind = PluginKind.NORMAL,
) : this(name, Semver(version, Semver.SemverType.LOOSE), id, author, info, dependencies, kind)
loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS,
) : this(name, Semver(version, Semver.SemverType.LOOSE), id, author, info, dependencies, loadPriority)
init {
require(!name.contains(':')) { "':' is forbidden in plugin name" }
@ -240,8 +242,8 @@ public fun JvmPluginDescription(
author: String = "",
info: String = "",
dependencies: Set<PluginDependency> = setOf(),
kind: PluginKind = PluginKind.NORMAL
): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies, kind)
loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS
): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies, loadPriority)
@Deprecated(
"JvmPluginDescription 没有构造器. 请使用 SimpleJvmPluginDescription.",
@ -260,5 +262,5 @@ public fun JvmPluginDescription(
author: String = "",
info: String = "",
dependencies: Set<PluginDependency> = setOf(),
kind: PluginKind = PluginKind.NORMAL
): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies, kind)
loadPriority: PluginLoadPriority = PluginLoadPriority.AFTER_EXTENSIONS
): JvmPluginDescription = SimpleJvmPluginDescription(name, version, id, author, info, dependencies, loadPriority)

View File

@ -22,7 +22,6 @@ import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.message.data.Message
import kotlin.internal.InlineOnly
import kotlin.internal.LowPriorityInOverloadResolution
/**
@ -38,10 +37,10 @@ import kotlin.internal.LowPriorityInOverloadResolution
* - `C<A>.toMessageScope()`. 其中 `C` 表示 `Iterable`, `Sequence`, `Flow`, `Array` 其中任一.
*
* ## 连接 [MessageScope]
* - `A.scopeWith(vararg B)`.
* - `A.scopeWith(vararg A)`.
* - `A.scopeWithNotNull(vararg B?)`. 类似 [listOfNotNull].
* - `A.scopeWithNotNull(vararg A?)`. 类似 [listOfNotNull].
* - `A?.scopeWith(vararg B?)`.
* - `A?.scopeWith(vararg A?)`.
*
* `null` 项将会被过滤.
*
* ## 自动去重
* 在连接时, [MessageScope] 会自动根据真实的 [收信对象][CommandSender.subject] 去重.
@ -83,12 +82,12 @@ import kotlin.internal.LowPriorityInOverloadResolution
*
* // 使用 MessageScope, 清晰逻辑
* // 表示至少发送给 `this`, 当 `this` 的真实发信对象与 `target.group` 不同时, 还额外发送给 `target.group`
* this.scopeWithNotNull(target.group) {
* this.scopeWith(target.group) {
* sendMessage("${name} 禁言了 ${target.nameCardOrNick} $duration")
* }
*
* // 同样地, 可以扩展用法, 同时私聊指令执行者:
* // this.scopeWithNotNull(
* // this.scopeWith(
* // target,
* // target.group
* // ) { ... }
@ -143,132 +142,51 @@ public fun Contact.asMessageScope(): MessageScope = createScopeDelegate(this)
public fun CommandSender.asMessageScope(): MessageScope = createScopeDelegate(this)
@LowPriorityInOverloadResolution
public fun Contact.scopeWith(vararg others: Contact): MessageScope {
return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) }
public fun Contact?.scopeWith(vararg others: Contact?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun Contact.scopeWith(vararg others: CommandSender): MessageScope {
return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) }
public fun Contact?.scopeWith(vararg others: CommandSender?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun Contact.scopeWith(vararg others: MessageScope): MessageScope {
return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) }
public fun Contact?.scopeWith(vararg others: MessageScope?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun CommandSender.scopeWith(vararg others: Contact): MessageScope {
return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) }
public fun CommandSender?.scopeWith(vararg others: Contact?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun CommandSender.scopeWith(vararg others: CommandSender): MessageScope {
return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) }
public fun CommandSender?.scopeWith(vararg others: CommandSender?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun CommandSender.scopeWith(vararg others: MessageScope): MessageScope {
return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) }
public fun CommandSender?.scopeWith(vararg others: MessageScope?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun MessageScope.scopeWith(vararg others: Contact): MessageScope {
return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) }
public fun MessageScope?.scopeWith(vararg others: Contact?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun MessageScope.scopeWith(vararg others: CommandSender): MessageScope {
return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) }
public fun MessageScope?.scopeWith(vararg others: CommandSender?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun MessageScope.scopeWith(vararg others: MessageScope): MessageScope {
return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) }
public fun MessageScope?.scopeWith(vararg others: MessageScope?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun Contact?.scopeWithNotNull(vararg others: Contact?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWithNotNull(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun Contact?.scopeWithNotNull(vararg others: CommandSender?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWithNotNull(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun Contact?.scopeWithNotNull(vararg others: MessageScope?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWithNotNull(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun CommandSender?.scopeWithNotNull(vararg others: Contact?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWithNotNull(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun CommandSender?.scopeWithNotNull(vararg others: CommandSender?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWithNotNull(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun CommandSender?.scopeWithNotNull(vararg others: MessageScope?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWithNotNull(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun MessageScope?.scopeWithNotNull(vararg others: Contact?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWithNotNull(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun MessageScope?.scopeWithNotNull(vararg others: CommandSender?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWithNotNull(other?.asMessageScope()) }
}
@LowPriorityInOverloadResolution
public fun MessageScope?.scopeWithNotNull(vararg others: MessageScope?): MessageScope {
return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWithNotNull(other?.asMessageScope()) }
}
public fun Contact.scopeWith(other: Contact): MessageScope {
return CombinedScope(asMessageScope(), other.asMessageScope())
}
public fun Contact.scopeWith(other: CommandSender): MessageScope {
return CombinedScope(asMessageScope(), other.asMessageScope())
}
public fun Contact.scopeWith(other: MessageScope): MessageScope {
return CombinedScope(asMessageScope(), other.asMessageScope())
}
public fun CommandSender.scopeWith(other: Contact): MessageScope {
return CombinedScope(asMessageScope(), other.asMessageScope())
}
public fun CommandSender.scopeWith(other: CommandSender): MessageScope {
return CombinedScope(asMessageScope(), other.asMessageScope())
}
public fun CommandSender.scopeWith(other: MessageScope): MessageScope {
return CombinedScope(asMessageScope(), other.asMessageScope())
}
public fun MessageScope.scopeWith(other: Contact): MessageScope {
return CombinedScope(asMessageScope(), other.asMessageScope())
}
public fun MessageScope.scopeWith(other: CommandSender): MessageScope {
return CombinedScope(asMessageScope(), other.asMessageScope())
}
public fun MessageScope.scopeWith(other: MessageScope): MessageScope {
return CombinedScope(asMessageScope(), other.asMessageScope())
}
public fun Contact?.scopeWithNotNull(other: Contact?): MessageScope {
public fun Contact?.scopeWith(other: Contact?): MessageScope {
@Suppress("DuplicatedCode")
return when {
this == null && other == null -> NoopMessageScope
@ -279,7 +197,7 @@ public fun Contact?.scopeWithNotNull(other: Contact?): MessageScope {
}
}
public fun Contact?.scopeWithNotNull(other: CommandSender?): MessageScope {
public fun Contact?.scopeWith(other: CommandSender?): MessageScope {
@Suppress("DuplicatedCode")
return when {
this == null && other == null -> NoopMessageScope
@ -290,7 +208,7 @@ public fun Contact?.scopeWithNotNull(other: CommandSender?): MessageScope {
}
}
public fun Contact?.scopeWithNotNull(other: MessageScope?): MessageScope {
public fun Contact?.scopeWith(other: MessageScope?): MessageScope {
@Suppress("DuplicatedCode")
return when {
this == null && other == null -> NoopMessageScope
@ -301,7 +219,7 @@ public fun Contact?.scopeWithNotNull(other: MessageScope?): MessageScope {
}
}
public fun CommandSender?.scopeWithNotNull(other: Contact?): MessageScope {
public fun CommandSender?.scopeWith(other: Contact?): MessageScope {
@Suppress("DuplicatedCode")
return when {
this == null && other == null -> NoopMessageScope
@ -312,7 +230,7 @@ public fun CommandSender?.scopeWithNotNull(other: Contact?): MessageScope {
}
}
public fun CommandSender?.scopeWithNotNull(other: CommandSender?): MessageScope {
public fun CommandSender?.scopeWith(other: CommandSender?): MessageScope {
@Suppress("DuplicatedCode")
return when {
this == null && other == null -> NoopMessageScope
@ -323,7 +241,7 @@ public fun CommandSender?.scopeWithNotNull(other: CommandSender?): MessageScope
}
}
public fun CommandSender?.scopeWithNotNull(other: MessageScope?): MessageScope {
public fun CommandSender?.scopeWith(other: MessageScope?): MessageScope {
@Suppress("DuplicatedCode")
return when {
this == null && other == null -> NoopMessageScope
@ -334,7 +252,7 @@ public fun CommandSender?.scopeWithNotNull(other: MessageScope?): MessageScope {
}
}
public fun MessageScope?.scopeWithNotNull(other: Contact?): MessageScope {
public fun MessageScope?.scopeWith(other: Contact?): MessageScope {
@Suppress("DuplicatedCode")
return when {
this == null && other == null -> NoopMessageScope
@ -345,7 +263,7 @@ public fun MessageScope?.scopeWithNotNull(other: Contact?): MessageScope {
}
}
public fun MessageScope?.scopeWithNotNull(other: CommandSender?): MessageScope {
public fun MessageScope?.scopeWith(other: CommandSender?): MessageScope {
@Suppress("DuplicatedCode")
return when {
this == null && other == null -> NoopMessageScope
@ -356,7 +274,7 @@ public fun MessageScope?.scopeWithNotNull(other: CommandSender?): MessageScope {
}
}
public fun MessageScope?.scopeWithNotNull(other: MessageScope?): MessageScope {
public fun MessageScope?.scopeWith(other: MessageScope?): MessageScope {
@Suppress("DuplicatedCode")
return when {
this == null && other == null -> NoopMessageScope
@ -367,78 +285,42 @@ public fun MessageScope?.scopeWithNotNull(other: MessageScope?): MessageScope {
}
}
public inline fun <R> Contact.scopeWith(vararg others: Contact, action: MessageScope.() -> R): R {
public inline fun <R> Contact?.scopeWith(vararg others: Contact?, action: MessageScope.() -> R): R {
return scopeWith(*others).invoke(action)
}
public inline fun <R> Contact.scopeWith(vararg others: CommandSender, action: MessageScope.() -> R): R {
public inline fun <R> Contact?.scopeWith(vararg others: CommandSender?, action: MessageScope.() -> R): R {
return scopeWith(*others).invoke(action)
}
public inline fun <R> Contact.scopeWith(vararg others: MessageScope, action: MessageScope.() -> R): R {
public inline fun <R> Contact?.scopeWith(vararg others: MessageScope?, action: MessageScope.() -> R): R {
return scopeWith(*others).invoke(action)
}
public inline fun <R> CommandSender.scopeWith(vararg others: Contact, action: MessageScope.() -> R): R {
public inline fun <R> CommandSender?.scopeWith(vararg others: Contact?, action: MessageScope.() -> R): R {
return scopeWith(*others).invoke(action)
}
public inline fun <R> CommandSender.scopeWith(vararg others: CommandSender, action: MessageScope.() -> R): R {
public inline fun <R> CommandSender?.scopeWith(vararg others: CommandSender?, action: MessageScope.() -> R): R {
return scopeWith(*others).invoke(action)
}
public inline fun <R> CommandSender.scopeWith(vararg others: MessageScope, action: MessageScope.() -> R): R {
public inline fun <R> CommandSender?.scopeWith(vararg others: MessageScope?, action: MessageScope.() -> R): R {
return scopeWith(*others).invoke(action)
}
public inline fun <R> MessageScope.scopeWith(vararg others: Contact, action: MessageScope.() -> R): R {
public inline fun <R> MessageScope?.scopeWith(vararg others: Contact?, action: MessageScope.() -> R): R {
return scopeWith(*others).invoke(action)
}
public inline fun <R> MessageScope.scopeWith(vararg others: CommandSender, action: MessageScope.() -> R): R {
public inline fun <R> MessageScope?.scopeWith(vararg others: CommandSender?, action: MessageScope.() -> R): R {
return scopeWith(*others).invoke(action)
}
public inline fun <R> MessageScope.scopeWith(vararg others: MessageScope, action: MessageScope.() -> R): R {
public inline fun <R> MessageScope?.scopeWith(vararg others: MessageScope?, action: MessageScope.() -> R): R {
return scopeWith(*others).invoke(action)
}
public inline fun <R> Contact?.scopeWithNotNull(vararg others: Contact?, action: MessageScope.() -> R): R {
return scopeWithNotNull(*others).invoke(action)
}
public inline fun <R> Contact?.scopeWithNotNull(vararg others: CommandSender?, action: MessageScope.() -> R): R {
return scopeWithNotNull(*others).invoke(action)
}
public inline fun <R> Contact?.scopeWithNotNull(vararg others: MessageScope?, action: MessageScope.() -> R): R {
return scopeWithNotNull(*others).invoke(action)
}
public inline fun <R> CommandSender?.scopeWithNotNull(vararg others: Contact?, action: MessageScope.() -> R): R {
return scopeWithNotNull(*others).invoke(action)
}
public inline fun <R> CommandSender?.scopeWithNotNull(vararg others: CommandSender?, action: MessageScope.() -> R): R {
return scopeWithNotNull(*others).invoke(action)
}
public inline fun <R> CommandSender?.scopeWithNotNull(vararg others: MessageScope?, action: MessageScope.() -> R): R {
return scopeWithNotNull(*others).invoke(action)
}
public inline fun <R> MessageScope?.scopeWithNotNull(vararg others: Contact?, action: MessageScope.() -> R): R {
return scopeWithNotNull(*others).invoke(action)
}
public inline fun <R> MessageScope?.scopeWithNotNull(vararg others: CommandSender?, action: MessageScope.() -> R): R {
return scopeWithNotNull(*others).invoke(action)
}
public inline fun <R> MessageScope?.scopeWithNotNull(vararg others: MessageScope?, action: MessageScope.() -> R): R {
return scopeWithNotNull(*others).invoke(action)
}
@Deprecated(
"Senseless scopeWith. Use asMessageScope.",
ReplaceWith("this.asMessageScope()", "net.mamoe.mirai.console.util.asMessageScope")
@ -623,27 +505,23 @@ public suspend fun Flow<MessageScope>.toMessageScope(): MessageScope { // Flow<A
// [MessageScope] 实现
@PublishedApi
@InlineOnly
internal inline fun MessageScope.asMessageScope(): MessageScope = this
@InlineOnly
private inline fun MessageScope?.asMessageScopeOrNoop(): MessageScope = this?.asMessageScope() ?: NoopMessageScope
@InlineOnly
private inline fun Contact?.asMessageScopeOrNoop(): MessageScope = this?.asMessageScope() ?: NoopMessageScope
@InlineOnly
private inline fun CommandSender?.asMessageScopeOrNoop(): MessageScope = this?.asMessageScope() ?: NoopMessageScope
@InlineOnly
private inline fun createScopeDelegate(o: CommandSender) = CommandSenderAsMessageScope(o)
@InlineOnly
private inline fun createScopeDelegate(o: Contact) = ContactAsMessageScope(o)
private fun MessageScope.asSequence(): Sequence<MessageScope> {
internal fun MessageScope.asSequence(): Sequence<MessageScope> {
return if (this is CombinedScope) {
sequenceOf(this.first.asSequence(), this.second.asSequence()).flatten()
val a = this.first.asSequence()
val b = this.second.asSequence() // don't inline. fuck compilers
sequenceOf(a, b).flatten()
} else sequenceOf(this)
}

View File

@ -23,8 +23,8 @@ object Versions {
const val consoleTerminal = "0.1.0"
const val consolePure = console
const val kotlinCompiler = "1.4.0"
const val kotlinStdlib = "1.4.0"
const val kotlinCompiler = "1.4.10"
const val kotlinStdlib = kotlinCompiler
const val coroutines = "1.3.9"
const val collectionsImmutable = "0.3.2"
@ -37,5 +37,5 @@ object Versions {
const val bintray = "1.8.5"
const val blockingBridge = "1.0.5"
const val yamlkt = "0.5.1"
const val yamlkt = "0.5.2"
}

View File

@ -26,7 +26,6 @@
[`RawCommand`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt
[`CommandManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt
[`BotManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt
[`Annotations`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/Annotations.kt
[`ConsoleInput`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/ConsoleInput.kt
[`JavaPluginScheduler`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler.kt
@ -75,9 +74,9 @@ Mirai Console 提供一些基础的实现,即 [`AbstractJvmPlugin`],并将 [
#### 描述
插件描述需要在主类构造器传递给 `super`。因此插件不需要 `plugin.yml`, `plugin.xml` 等配置文件来指示信息。
Mirai Console 使用 `ServiceLoader` 加载插件。
Mirai Console 使用类似 `ServiceLoader` 的机制加载插件。
在 Kotlin[使用 AutoService])自动配置 service 信息。
在 Kotlin 或其他语言,手动创建 service 文件: 在 `jar``META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin` 文件内存放插件主类全名(以纯文本 UTF-8 存储,文件内容只包含一行插件主类全名).
在 Kotlin 或其他语言,手动创建 service 文件: 在 `jar``META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin` 文件内存放插件主类全名(以纯文本 UTF-8 存储,文件内容只包含一行插件主类全名).
有关插件版本号的限制:
@ -94,13 +93,16 @@ Mirai Console 使用 `ServiceLoader` 加载插件。
- 访问权限为 `public` 或默认 (不指定)
```kotlin
@AutoService(JvmPlugin::class) // 如果选用上述自动配置的方法
object SchedulePlugin : KotlinPlugin(
SimpleJvmPluginDescription( // 插件的描述, name 和 version 是必须的
name = "Schedule",
JvmPluginDescription(
id = "org.example.my-schedule-plugin",
version = "1.0.0",
// author, description, ...
)
) {
name("Schedule")
// author("...")
// dependsOn("...")
}
) {
// ...
}
@ -117,10 +119,14 @@ object SchedulePlugin : KotlinPlugin(
public final class JExample extends JavaPlugin {
public static final JExample INSTANCE = new JExample(); // 可以像 Kotlin 一样静态初始化单例
private JExample() {
super(new SimpleJvmPluginDescription(
super(new JvmPluginDescriptionBuilder(
"JExample", // name
"1.0.0" // version
));
)
// .author("...")
// .info("...")
.build()
);
}
}
```
@ -133,10 +139,14 @@ public final class JExample extends JavaPlugin {
return instance;
}
public JExample() { // 此时必须 public
super(new SimpleJvmPluginDescription(
super(new JvmPluginDescriptionBuilder(
"JExample", // name
"1.0.0" // version
));
)
// .author("...")
// .info("...")
.build()
);
instance = this;
}
}
@ -248,5 +258,31 @@ Java
**仅可在插件 onEnable() 时及其之后才能使用这些方法。**
**在插件 onDisable() 之后不能使用这些方法。**
#### 使用示例
```kotlin
object SchedulePlugin : KotlinPlugin(
JvmPluginDescription(
id = "org.example.my-schedule-plugin",
version = "1.0.0",
) {
name("Schedule")
// author("...")
// dependsOn("...")
}
) {
// ...
override fun onEnable() {
MyData.reload() // 仅需此行,保证启动时更新数据,在之后自动存储数据。
}
}
object MyData : AutoSavePluginData() {
val value: Map<String, String> by value()
}
```
### 附录Java 插件的多线程调度器 - [`JavaPluginScheduler`]
拥有生命周期管理的简单 Java 线程池。
拥有生命周期管理的简单 Java 线程池。其中所有的任务都会在插件被关闭时自动停止。

View File

@ -35,7 +35,7 @@ internal fun startupConsoleThread() {
val next = MiraiConsole.requestInput("").let {
when {
it.startsWith(CommandManager.commandPrefix) -> it
it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName
it == "?" -> CommandManager.commandPrefix + BuiltInCommands.HelpCommand.primaryName
else -> CommandManager.commandPrefix + it
}
}