Public API stabilization

This commit is contained in:
Him188 2020-08-16 23:21:11 +08:00
parent 30e359c282
commit 0f5d46d017
76 changed files with 966 additions and 1023 deletions

View File

@ -7,7 +7,7 @@ import net.mamoe.mirai.console.plugin.Config;
import net.mamoe.mirai.console.plugin.ConfigSection;
import net.mamoe.mirai.console.plugin.ConfigSectionFactory;
import net.mamoe.mirai.console.plugin.PluginBase;
import net.mamoe.mirai.console.utils.Utils;
import net.mamoe.mirai.console.util.Utils;
import net.mamoe.mirai.message.GroupMessage;
import org.jetbrains.annotations.NotNull;
import org.jsoup.Jsoup;

View File

@ -1,6 +1,6 @@
plugins {
kotlin("jvm") version "1.4-M2"
kotlin("plugin.serialization") version "1.4-M2"
kotlin("jvm")
kotlin("plugin.serialization")
id("java")
}
@ -10,8 +10,6 @@ kotlin {
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
languageSettings.progressiveMode = true
languageSettings.languageVersion = "1.4"
languageSettings.apiVersion = "1.4"
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")

View File

@ -6,11 +6,12 @@ import java.util.Date
import java.util.TimeZone
plugins {
kotlin("jvm") version Versions.kotlinCompiler
kotlin("plugin.serialization") version Versions.kotlinCompiler
kotlin("jvm")
kotlin("plugin.serialization")
id("java")
`maven-publish`
id("com.jfrog.bintray")
id("net.mamoe.kotlin-jvm-blocking-bridge")
}
version = Versions.console
@ -31,8 +32,9 @@ kotlin {
sourceSets.all {
target.compilations.all {
kotlinOptions {
freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=enable"
jvmTarget = "1.8"
freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all"
//useIR = true
}
}
languageSettings.apply {
@ -45,7 +47,7 @@ kotlin {
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
useExperimentalAnnotation("net.mamoe.mirai.console.utils.ConsoleExperimentalAPI")
useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleExperimentalAPI")
useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")

View File

@ -1,189 +0,0 @@
package net.mamoe.mirai.console.command;
import kotlin.NotImplementedError;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.EmptyCoroutineContext;
import kotlinx.coroutines.BuildersKt;
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.CoroutineStart;
import kotlinx.coroutines.future.FutureKt;
import net.mamoe.mirai.console.plugin.jvm.JavaPlugin;
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI;
import net.mamoe.mirai.message.data.Message;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
/**
* Java 适配的 {@link CommandManagerKt}
*/
@SuppressWarnings({"unused", "RedundantSuppression"})
public final class JCommandManager {
private JCommandManager() {
throw new NotImplementedError();
}
/**
* 获取指令前缀
*
* @return 指令前缀
*/
@NotNull
public static String getCommandPrefix() {
return CommandManagerKt.getCommandPrefix();
}
/**
* 获取一个指令所有者已经注册了的指令列表.
*
* @param owner 指令所有者
* @return 指令列表
*/
@NotNull
public static List<@NotNull Command> getRegisteredCommands(final @NotNull CommandOwner owner) {
return CommandManagerKt.getRegisteredCommands(Objects.requireNonNull(owner, "owner"));
}
/**
* 注册一个指令.
*
* @param command 指令实例
* @param override 是否覆盖重名指令.
* <p>
* 若原有指令 P, {@link Command#getNames()} 'a', 'b', 'c'. <br>
* 新指令 Q, {@link Command#getNames()} 'b', 将会覆盖原指令 A 注册的 'b'.
* <p>
* 即注册完成后, 'a' 'c' 将会解析到指令 P, 'b' 会解析到指令 Q.
* @return 若已有重名指令, <code>override</code> <code>false</code>, 返回 <code>false</code>; <br>
* 若已有重名指令, <code>override</code> <code>true</code>, 覆盖原有指令并返回 <code>true</code>.
*/
public static boolean register(final @NotNull Command command, final boolean override) {
Objects.requireNonNull(command, "command");
return CommandManagerKt.register(command, override);
}
/**
* 注册一个指令, 已有重复名称的指令时返回 <code>false</code>
*
* @param command 指令实例
* @return 若已有重名指令, 返回 <code>false</code>, 否则返回 <code>true</code>.
*/
public static boolean register(final @NotNull Command command) {
Objects.requireNonNull(command, "command");
return register(command, false);
}
/**
* 查找并返回重名的指令. 返回重名指令.
*/
@Nullable
public static Command findDuplicate(final @NotNull Command command) {
Objects.requireNonNull(command, "command");
return CommandManagerKt.findDuplicate(command);
}
/**
* 取消注册这个指令. 若指令未注册, 返回 <code>false</code>.
*/
public static boolean unregister(final @NotNull Command command) {
Objects.requireNonNull(command, "command");
return CommandManagerKt.unregister(command);
}
/**
* 取消注册所有属于 <code>owner</code> 的指令
*
* @param owner 指令所有者
*/
public static void unregisterAllCommands(final @NotNull CommandOwner owner) {
Objects.requireNonNull(owner, "owner");
CommandManagerKt.unregisterAllCommands(owner);
}
/**
* 解析并执行一个指令
*
* @param args 接受 {@link String} {@link Message} , 其他对象将会被 {@link Object#toString()}
* @return 成功执行的指令, 在无匹配指令时返回 <code>null</code>
* @throws CommandExecutionException {@link Command#onCommand(CommandSender, Object[], Continuation)} 抛出异常时包装并附带相关指令信息抛出
* @see #executeCommandAsync(CoroutineScope, CommandSender, Object...)
*/
@Nullable
public static Command executeCommand(final @NotNull CommandSender sender, final @NotNull Object... args) throws CommandExecutionException, InterruptedException {
Objects.requireNonNull(sender, "sender");
Objects.requireNonNull(args, "args");
for (Object arg : args) {
Objects.requireNonNull(arg, "element of args");
}
return BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE, (scope, completion) -> CommandManagerKt.executeCommand(sender, args, completion));
}
/**
* 异步 ( Kotlin 协程线程池) 解析并执行一个指令
*
* @param scope 协程作用域 (用于管理协程生命周期). 一般填入 {@link JavaPlugin} 实例.
* @param args 接受 {@link String} {@link Message} , 其他对象将会被 {@link Object#toString()}
* @return 成功执行的指令, 在无匹配指令时返回 <code>null</code>
* @see #executeCommand(CommandSender, Object...)
*/
@NotNull
public static CompletableFuture<@Nullable Command> executeCommandAsync(final @NotNull CoroutineScope scope, final @NotNull CommandSender sender, final @NotNull Object... args) {
Objects.requireNonNull(sender, "sender");
Objects.requireNonNull(args, "args");
Objects.requireNonNull(scope, "scope");
for (Object arg : args) {
Objects.requireNonNull(arg, "element of args");
}
return FutureKt.future(scope, EmptyCoroutineContext.INSTANCE, CoroutineStart.DEFAULT, (sc, completion) -> CommandManagerKt.executeCommand(sender, args, completion));
}
/**
* 解析并执行一个指令, 获取详细的指令参数等信息.
* <br />
* 执行过程中产生的异常将不会直接抛出, 而会包装为 {@link CommandExecuteResult.ExecutionException}
*
* @param args 接受 {@link String} {@link Message} , 其他对象将会被 {@link Object#toString()}
* @return 执行结果
* @see #executeCommandDetailedAsync(CoroutineScope, CommandSender, Object...)
*/
@ConsoleExperimentalAPI
@NotNull
public static CommandExecuteResult executeCommandDetailed(final @NotNull CommandSender sender, final @NotNull Object... args) throws InterruptedException {
Objects.requireNonNull(sender, "sender");
Objects.requireNonNull(args, "args");
for (Object arg : args) {
Objects.requireNonNull(arg, "element of args");
}
return BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE, (scope, completion) -> CommandManagerKt.executeCommandDetailed(sender, args, completion));
}
/**
* 异步 ( Kotlin 协程线程池) 解析并执行一个指令, 获取详细的指令参数等信息
*
* @param scope 协程作用域 (用于管理协程生命周期). 一般填入 {@link JavaPlugin} 实例.
* @param args 接受 {@link String} {@link Message} , 其他对象将会被 {@link Object#toString()}
* @return 执行结果
* @see #executeCommandDetailed(CommandSender, Object...)
*/
@ConsoleExperimentalAPI
@NotNull
public static CompletableFuture<@NotNull CommandExecuteResult>
executeCommandDetailedAsync(final @NotNull CoroutineScope scope, final @NotNull CommandSender sender, final @NotNull Object... args) {
Objects.requireNonNull(sender, "sender");
Objects.requireNonNull(args, "args");
Objects.requireNonNull(scope, "scope");
for (Object arg : args) {
Objects.requireNonNull(arg, "element of args");
}
return FutureKt.future(scope, EmptyCoroutineContext.INSTANCE, CoroutineStart.DEFAULT, (sc, completion) -> CommandManagerKt.executeCommandDetailed(sender, args, completion));
}
}

View File

@ -1,39 +0,0 @@
/*
* 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.utils;
import net.mamoe.mirai.Bot;
import java.util.List;
/**
* 获取 Bot Manager
* Java 友好 API
*/
public class BotManager {
public static List<Long> getManagers(long botAccount) {
Bot bot = Bot.getInstance(botAccount);
return getManagers(bot);
}
public static List<Long> getManagers(Bot bot) {
return BotManagers.getManagers(bot);
}
public static boolean isManager(Bot bot, long target) {
return getManagers(bot).contains(target);
}
public static boolean isManager(long botAccount, long target) {
return getManagers(botAccount).contains(target);
}
}

View File

@ -14,38 +14,23 @@ package net.mamoe.mirai.console
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.io.charsets.Charset
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole.INSTANCE
import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.command.internal.InternalCommandManager
import net.mamoe.mirai.console.command.primaryName
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.CuiPluginCenter
import net.mamoe.mirai.console.plugin.center.PluginCenter
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.plugin.jvm.PluginManagerImpl
import net.mamoe.mirai.console.setting.SettingStorage
import net.mamoe.mirai.console.utils.ConsoleBuiltInSettingStorage
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.info
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
import java.text.SimpleDateFormat
import java.util.*
import kotlin.coroutines.CoroutineContext
/**
* mirai-console 实例
*
* @see INSTANCE
* @see MiraiConsoleImplementation
*/
public interface MiraiConsole : CoroutineScope {
/**
@ -56,6 +41,7 @@ public interface MiraiConsole : CoroutineScope {
/**
* Console 前端接口
*/
@ConsoleExperimentalAPI
public val frontEnd: MiraiConsoleFrontEnd
/**
@ -74,6 +60,7 @@ public interface MiraiConsole : CoroutineScope {
public val version: String
@ConsoleExperimentalAPI
public val pluginCenter: PluginCenter
@ConsoleExperimentalAPI
@ -94,80 +81,3 @@ public class IllegalMiraiConsoleImplementationError @JvmOverloads constructor(
public override val cause: Throwable? = null
) : Error()
internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants)
@JvmStatic
val buildDate: Date = Date(1595136353901L) // 2020-07-19 13:25:53
const val version: String = "1.0-dev-4"
}
/**
* [MiraiConsole] 公开 API 与前端实现的连接桥.
*/
internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleImplementation,
MiraiConsole {
override val pluginCenter: PluginCenter get() = CuiPluginCenter
private val instance: MiraiConsoleImplementation get() = MiraiConsoleImplementation.instance
override val buildDate: Date get() = MiraiConsoleBuildConstants.buildDate
override val version: String get() = MiraiConsoleBuildConstants.version
override val rootDir: File get() = instance.rootDir
override val frontEnd: MiraiConsoleFrontEnd get() = instance.frontEnd
@ConsoleExperimentalAPI
override val mainLogger: MiraiLogger
get() = instance.mainLogger
override val coroutineContext: CoroutineContext get() = instance.coroutineContext
override val builtInPluginLoaders: List<PluginLoader<*, *>> get() = instance.builtInPluginLoaders
override val consoleCommandSender: ConsoleCommandSender get() = instance.consoleCommandSender
override val settingStorageForJarPluginLoader: SettingStorage get() = instance.settingStorageForJarPluginLoader
override val settingStorageForBuiltIns: SettingStorage get() = instance.settingStorageForBuiltIns
init {
DefaultLogger = { identity -> this.newLogger(identity) }
}
@ConsoleExperimentalAPI
override fun newLogger(identity: String?): MiraiLogger = frontEnd.loggerFor(identity)
internal fun doStart() {
val buildDateFormatted = SimpleDateFormat("yyyy-MM-dd").format(buildDate)
mainLogger.info { "Starting mirai-console..." }
mainLogger.info { "Backend: version $version, built on $buildDateFormatted." }
mainLogger.info { "Frontend ${frontEnd.name}: version $version." }
if (coroutineContext[Job] == null) {
throw IllegalMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
}
MiraiConsole.job.invokeOnCompletion {
Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
}
BuiltInCommands.registerAll()
mainLogger.info { "Preparing built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
InternalCommandManager.commandListener // start
mainLogger.info { "Loading plugins..." }
PluginManagerImpl.loadEnablePlugins()
mainLogger.info { "${PluginManager.plugins.size} plugin(s) loaded." }
mainLogger.info { "mirai-console started successfully." }
ConsoleBuiltInSettingStorage // init
// Only for initialize
}
}
/**
* Included in kotlin stdlib 1.4
*/
internal val Throwable.stacktraceString: String
get() =
ByteArrayOutputStream().apply {
printStackTrace(PrintStream(this))
}.use { it.toByteArray().encodeToString() }
@Suppress("NOTHING_TO_INLINE")
internal inline fun ByteArray.encodeToString(charset: Charset = Charsets.UTF_8): String =
kotlinx.io.core.String(this, charset = charset)

View File

@ -10,7 +10,7 @@
package net.mamoe.mirai.console
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiLogger

View File

@ -13,10 +13,13 @@ package net.mamoe.mirai.console
import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.setting.SettingStorage
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.utils.MiraiLogger
import java.io.File
import java.util.concurrent.locks.ReentrantLock
@ -35,7 +38,9 @@ import kotlin.annotation.AnnotationTarget.*
public annotation class ConsoleFrontEndImplementation
/**
* [MiraiConsole] 前端实现, 需低啊用
* 由前端实现这个接口
*
* @see MiraiConsoleImplementation.start
*/
@ConsoleFrontEndImplementation
public interface MiraiConsoleImplementation : CoroutineScope {
@ -47,6 +52,7 @@ public interface MiraiConsoleImplementation : CoroutineScope {
/**
* Console 前端接口
*/
@ConsoleExperimentalAPI
public val frontEnd: MiraiConsoleFrontEnd
/**
@ -70,8 +76,9 @@ public interface MiraiConsoleImplementation : CoroutineScope {
internal lateinit var instance: MiraiConsoleImplementation
private val initLock = ReentrantLock()
/** 由前端调用, 初始化 [MiraiConsole] 实例, 并 */
/** 由前端调用, 初始化 [MiraiConsole] 实例, 并启动 */
@JvmStatic
@ConsoleFrontEndImplementation
public fun MiraiConsoleImplementation.start(): Unit = initLock.withLock {
this@Companion.instance = this
MiraiConsoleImplementationBridge.doStart()

View File

@ -16,7 +16,9 @@ import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.stacktraceString
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.command.CommandManagerImpl.allRegisteredCommands
import net.mamoe.mirai.console.command.CommandManagerImpl.register
import net.mamoe.mirai.event.selectMessagesUnit
import net.mamoe.mirai.utils.DirectoryLogger
import net.mamoe.mirai.utils.weeksToMillis
@ -138,7 +140,7 @@ public object BuiltInCommands {
if (this is MessageEventContextAware<*>) {
this.fromEvent.selectMessagesUnit {
"stacktrace" reply {
throwable.stacktraceString
throwable.stackTraceToString()
}
}
"test"

View File

@ -11,16 +11,20 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.internal.isValidSubName
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.internal.command.isValidSubName
import net.mamoe.mirai.message.data.SingleMessage
/**
* 指令
* 通常情况下, 你的指令应继承 @see CompositeCommand/SimpleCommand
* @see register 注册这个指令
*
* @see CommandManager.register 注册这个指令
*
* @see RawCommand
* @see CompositeCommand
* @see SimpleCommand
*/
public interface Command {
/**
@ -28,8 +32,14 @@ public interface Command {
*/
public val names: Array<out String>
/**
* 用法说明, 用于发送给用户
*/
public val usage: String
/**
* 指令描述, 用于显示在 [BuiltInCommands.Help]
*/
public val description: String
/**
@ -38,20 +48,37 @@ public interface Command {
public val permission: CommandPermission
/**
* `true` 时表示 [指令前缀][CommandPrefix] 可选
* `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选
*/
public val prefixOptional: Boolean
/**
* 指令拥有者, 对于插件的指令通常是 [PluginCommandOwner]
*/
public val owner: CommandOwner
/**
* @param args 指令参数. 可能是 [SingleMessage] [String]. 且已经以 ' ' 分割.
* @param args 指令参数. 数组元素类型可能是 [SingleMessage] [String]. 且已经以 ' ' 分割.
*
* @see Command.execute
*/ // TODO: 2020/6/28 Java-friendly bridges
* @see CommandManager.execute
*/
@JvmBlockingBridge
public suspend fun CommandSender.onCommand(args: Array<out Any>)
public companion object {
/**
* 主要指令名. [Command.names] 的第一个元素.
*/
@JvmStatic
public val Command.primaryName: String
get() = names[0]
}
}
@JvmSynthetic
public suspend inline fun Command.onCommand(sender: CommandSender, args: Array<out Any>): Unit =
sender.run { onCommand(args) }
/**
* [Command] 的基础实现
*/
@ -68,12 +95,4 @@ public abstract class AbstractCommand @JvmOverloads constructor(
list.firstOrNull { !it.isValidSubName() }?.let { error("Invalid name: $it") }
}.toTypedArray()
}
public suspend inline fun Command.onCommand(sender: CommandSender, args: Array<out Any>): Unit =
sender.run { onCommand(args) }
/**
* 主要指令名. [Command.names] 的第一个元素.
*/
public val Command.primaryName: String get() = names[0]
}

View File

@ -11,6 +11,8 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.Command.Companion.primaryName
/**
* [executeCommand] , [Command.onCommand] 抛出异常时包装的异常.
*/

View File

@ -15,15 +15,163 @@
package net.mamoe.mirai.console.command
import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.console.command.internal.*
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.console.command.CommandManagerImpl.unregisterAllCommands
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
public interface CommandManager {
/**
* 获取已经注册了的属于这个 [CommandOwner] 的指令列表.
*/
public val CommandOwner.registeredCommands: List<Command>
/**
* 获取所有已经注册了指令列表.
*/
public val allRegisteredCommands: List<Command>
/**
* 指令前缀, '/'
*/
public val commandPrefix: String
/**
* 取消注册所有属于 [this] 的指令
*/
public fun CommandOwner.unregisterAllCommands()
/**
* 注册一个指令.
*
* @param override 是否覆盖重名指令.
*
* 若原有指令 P, [Command.names] 'a', 'b', 'c'.
* 新指令 Q, [Command.names] 'b', 将会覆盖原指令 A 注册的 'b'.
*
* 即注册完成后, 'a' 'c' 将会解析到指令 P, 'b' 会解析到指令 Q.
*
* @return
* 若已有重名指令, [override] `false`, 返回 `false`;
* 若已有重名指令, [override] `true`, 覆盖原有指令并返回 `true`.
*/
public fun Command.register(override: Boolean = false): Boolean
/**
* 查找并返回重名的指令. 返回重名指令.
*/
public fun Command.findDuplicate(): Command?
/**
* 取消注册这个指令. 若指令未注册, 返回 `false`.
*/
public fun Command.unregister(): Boolean
/**
* [this] 已经 [注册][register] 后返回 `true`
*/
public fun Command.isRegistered(): Boolean
/**
* 解析并执行一个指令. 将会检查指令权限, 在无权限时抛出
*
* @param messages 接受 [String] [Message], 其他对象将会被 [Any.toString]
*
* @return 成功执行的指令, 在无匹配指令时返回 `null`
* @throws CommandExecutionException [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
*/
@JvmBlockingBridge
public suspend fun CommandSender.executeCommand(vararg messages: Any): Command?
/**
* 解析并执行一个指令
*
* @return 成功执行的指令, 在无匹配指令时返回 `null`
* @throws CommandExecutionException [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
*/
@Throws(CommandExecutionException::class)
@JvmBlockingBridge
public suspend fun CommandSender.executeCommand(message: MessageChain): Command?
/**
* 执行一个指令
*
* @return 成功执行的指令, 在无匹配指令时返回 `null`
* @throws CommandExecutionException [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
*/
@JvmBlockingBridge
@Throws(CommandExecutionException::class)
public suspend fun Command.execute(sender: CommandSender, args: MessageChain, checkPermission: Boolean = true)
/**
* 执行一个指令
*
* @return 成功执行的指令, 在无匹配指令时返回 `null`
* @throws CommandExecutionException [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
*/
@JvmBlockingBridge
@Throws(CommandExecutionException::class)
public suspend fun Command.execute(sender: CommandSender, vararg args: Any, checkPermission: Boolean = true)
/**
* 解析并执行一个指令, 获取详细的指令参数等信息
*
* @param messages 接受 [String] [Message], 其他对象将会被 [Any.toString]
*
* @return 执行结果
*/
@ConsoleExperimentalAPI
@JvmBlockingBridge
public suspend fun CommandSender.executeCommandDetailed(vararg messages: Any): CommandExecuteResult
/**
* 解析并执行一个指令, 获取详细的指令参数等信息
*
* 执行过程中产生的异常将不会直接抛出, 而会包装为 [CommandExecuteResult.ExecutionException]
*
* @return 执行结果
*/
@ConsoleExperimentalAPI
@JvmBlockingBridge
public suspend fun CommandSender.executeCommandDetailed(messages: MessageChain): CommandExecuteResult
public companion object INSTANCE : CommandManager by CommandManagerImpl {
override val CommandOwner.registeredCommands: List<Command> get() = CommandManagerImpl.run { 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() }
override fun Command.unregister(): Boolean = CommandManagerImpl.run { unregister() }
override fun Command.isRegistered(): Boolean = CommandManagerImpl.run { isRegistered() }
override suspend fun CommandSender.executeCommand(vararg messages: Any): Command? =
CommandManagerImpl.run { executeCommand(*messages) }
override suspend fun CommandSender.executeCommand(message: MessageChain): Command? =
CommandManagerImpl.run { executeCommand(message) }
override suspend fun Command.execute(
sender: CommandSender,
args: MessageChain,
checkPermission: Boolean
): Unit =
CommandManagerImpl.run { execute(sender, args = args, checkPermission = checkPermission) }
override suspend fun Command.execute(sender: CommandSender, vararg args: Any, checkPermission: Boolean): Unit =
CommandManagerImpl.run { execute(sender, args = args, checkPermission = checkPermission) }
@ConsoleExperimentalAPI
override suspend fun CommandSender.executeCommandDetailed(vararg messages: Any): CommandExecuteResult =
CommandManagerImpl.run { executeCommandDetailed(*messages) }
@ConsoleExperimentalAPI
override suspend fun CommandSender.executeCommandDetailed(messages: MessageChain): CommandExecuteResult =
CommandManagerImpl.run { executeCommandDetailed(messages) }
}
}
/**
* 指令的所有者.
* @see PluginCommandOwner
@ -33,7 +181,9 @@ public sealed class CommandOwner
/**
* 插件指令所有者. 插件只能通过 [PluginCommandOwner] 管理指令.
*/
public abstract class PluginCommandOwner(public val plugin: Plugin) : CommandOwner() {
public abstract class PluginCommandOwner(
public val plugin: Plugin
) : CommandOwner() {
init {
if (plugin is CoroutineScope) { // JVM Plugin
plugin.coroutineContext[Job]?.invokeOnCompletion {
@ -47,239 +197,3 @@ public abstract class PluginCommandOwner(public val plugin: Plugin) : CommandOwn
* 代表控制台所有者. 所有的 mirai-console 内建的指令都属于 [ConsoleCommandOwner].
*/
public object ConsoleCommandOwner : CommandOwner()
/**
* 获取已经注册了的属于这个 [CommandOwner] 的指令列表.
* @see JCommandManager.getRegisteredCommands Java 方法
*/
public val CommandOwner.registeredCommands: List<Command> get() = InternalCommandManager.registeredCommands.filter { it.owner == this }
/**
* 获取所有已经注册了指令列表.
* @see JCommandManager.getRegisteredCommands Java 方法
*/
public val allRegisteredCommands: List<Command> get() = InternalCommandManager.registeredCommands.toList() // copy
/**
* 指令前缀, '/'
* @see JCommandManager.getCommandPrefix Java 方法
*/
@get:JvmName("getCommandPrefix")
public val CommandPrefix: String
get() = InternalCommandManager.COMMAND_PREFIX
/**
* 取消注册所有属于 [this] 的指令
* @see JCommandManager.unregisterAllCommands Java 方法
*/
public fun CommandOwner.unregisterAllCommands() {
for (registeredCommand in registeredCommands) {
registeredCommand.unregister()
}
}
/**
* 注册一个指令.
*
* @param override 是否覆盖重名指令.
*
* 若原有指令 P, [Command.names] 'a', 'b', 'c'.
* 新指令 Q, [Command.names] 'b', 将会覆盖原指令 A 注册的 'b'.
*
* 即注册完成后, 'a' 'c' 将会解析到指令 P, 'b' 会解析到指令 Q.
*
* @return
* 若已有重名指令, [override] `false`, 返回 `false`;
* 若已有重名指令, [override] `true`, 覆盖原有指令并返回 `true`.
*
* @see JCommandManager.register Java 方法
*/
@JvmOverloads
public fun Command.register(override: Boolean = false): Boolean {
if (this is CompositeCommand) this.subCommands // init
InternalCommandManager.modifyLock.withLock {
if (!override) {
if (findDuplicate() != null) return false
}
InternalCommandManager.registeredCommands.add(this@register)
if (this.prefixOptional) {
for (name in this.names) {
val lowerCaseName = name.toLowerCase()
InternalCommandManager.optionalPrefixCommandMap[lowerCaseName] = this
InternalCommandManager.requiredPrefixCommandMap[lowerCaseName] = this
}
} else {
for (name in this.names) {
val lowerCaseName = name.toLowerCase()
InternalCommandManager.optionalPrefixCommandMap.remove(lowerCaseName) // ensure resolution consistency
InternalCommandManager.requiredPrefixCommandMap[lowerCaseName] = this
}
}
return true
}
}
/**
* 查找并返回重名的指令. 返回重名指令.
*
* @see JCommandManager.findDuplicate Java 方法
*/
public fun Command.findDuplicate(): Command? =
InternalCommandManager.registeredCommands.firstOrNull { it.names intersectsIgnoringCase this.names }
/**
* 取消注册这个指令. 若指令未注册, 返回 `false`.
*
* @see JCommandManager.unregister Java 方法
*/
public fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
if (this.prefixOptional) {
this.names.forEach {
InternalCommandManager.optionalPrefixCommandMap.remove(it)
}
}
this.names.forEach {
InternalCommandManager.requiredPrefixCommandMap.remove(it)
}
InternalCommandManager.registeredCommands.remove(this)
}
/**
* [this] 已经 [注册][register] 后返回 `true`
*/
public fun Command.isRegistered(): Boolean = this in InternalCommandManager.registeredCommands
//// executing without detailed result (faster)
/**
* 解析并执行一个指令. 将会检查指令权限, 在无权限时抛出
*
* @param messages 接受 [String] [Message], 其他对象将会被 [Any.toString]
*
* @return 成功执行的指令, 在无匹配指令时返回 `null`
* @throws CommandExecutionException [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
*
* @see JCommandManager.executeCommand Java 方法
*/
public suspend fun CommandSender.executeCommand(vararg messages: Any): Command? {
if (messages.isEmpty()) return null
return matchAndExecuteCommandInternal(messages, messages[0].toString().substringBefore(' '))
}
/**
* 解析并执行一个指令
*
* @return 成功执行的指令, 在无匹配指令时返回 `null`
* @throws CommandExecutionException [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
*
* @see JCommandManager.executeCommand Java 方法
*/
@Throws(CommandExecutionException::class)
public suspend fun CommandSender.executeCommand(message: MessageChain): Command? {
if (message.isEmpty()) return null
return matchAndExecuteCommandInternal(message, message[0].toString().substringBefore(' '))
}
/**
* 执行一个指令
*
* @return 成功执行的指令, 在无匹配指令时返回 `null`
* @throws CommandExecutionException [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
*
* @see JCommandManager.executeCommand Java 方法
*/
@JvmOverloads
@Throws(CommandExecutionException::class)
public suspend fun Command.execute(sender: CommandSender, args: MessageChain, checkPermission: Boolean = true) {
sender.executeCommandInternal(
this,
args.flattenCommandComponents().toTypedArray(),
this.primaryName,
checkPermission
)
}
/**
* 执行一个指令
*
* @return 成功执行的指令, 在无匹配指令时返回 `null`
* @throws CommandExecutionException [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
*
* @see JCommandManager.executeCommand Java 方法
*/
@JvmOverloads
@Throws(CommandExecutionException::class)
public suspend fun Command.execute(sender: CommandSender, vararg args: Any, checkPermission: Boolean = true) {
sender.executeCommandInternal(
this,
args.flattenCommandComponents().toTypedArray(),
this.primaryName,
checkPermission
)
}
//// execution with detailed result
/**
* 解析并执行一个指令, 获取详细的指令参数等信息
*
* @param messages 接受 [String] [Message], 其他对象将会被 [Any.toString]
*
* @return 执行结果
*
* @see JCommandManager.executeCommandDetailed Java 方法
*/
@ConsoleExperimentalAPI
public suspend fun CommandSender.executeCommandDetailed(vararg messages: Any): CommandExecuteResult {
if (messages.isEmpty()) return CommandExecuteResult.CommandNotFound("")
return executeCommandDetailedInternal(messages, messages[0].toString().substringBefore(' '))
}
/**
* 解析并执行一个指令, 获取详细的指令参数等信息
*
* 执行过程中产生的异常将不会直接抛出, 而会包装为 [CommandExecuteResult.ExecutionException]
*
* @return 执行结果
*
* @see JCommandManager.executeCommandDetailed Java 方法
*/
@ConsoleExperimentalAPI
public suspend fun CommandSender.executeCommandDetailed(messages: MessageChain): CommandExecuteResult {
if (messages.isEmpty()) return CommandExecuteResult.CommandNotFound("")
return executeCommandDetailedInternal(messages, messages[0].toString())
}
@JvmSynthetic
internal suspend inline fun CommandSender.executeCommandDetailedInternal(
messages: Any,
commandName: String
): CommandExecuteResult {
val command =
InternalCommandManager.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName)
val args = messages.flattenCommandComponents().dropToTypedArray(1)
if (!command.testPermission(this)) {
return CommandExecuteResult.PermissionDenied(command, commandName)
}
kotlin.runCatching {
command.onCommand(this, args)
}.fold(
onSuccess = {
return CommandExecuteResult.Success(
commandName = commandName,
command = command,
args = args
)
},
onFailure = {
return CommandExecuteResult.ExecutionException(
commandName = commandName,
command = command,
exception = it,
args = args
)
}
)
}

View File

@ -0,0 +1,156 @@
package net.mamoe.mirai.console.command
import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.internal.command.*
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.data.MessageChain
import java.util.concurrent.locks.ReentrantLock
internal object CommandManagerImpl : CommandManager, CoroutineScope by CoroutineScope(MiraiConsole.job) {
@JvmField
internal val registeredCommands: MutableList<Command> = mutableListOf()
/**
* 全部注册的指令
* /mute -> MuteCommand
* /jinyan -> MuteCommand
*/
@JvmField
internal val requiredPrefixCommandMap: MutableMap<String, Command> = mutableMapOf()
/**
* Command name of commands that are prefix optional
* mute -> MuteCommand
*/
@JvmField
internal val optionalPrefixCommandMap: MutableMap<String, Command> = mutableMapOf()
@JvmField
internal val modifyLock = ReentrantLock()
/**
* 从原始的 command 中解析出 Command 对象
*/
internal fun matchCommand(rawCommand: String): Command? {
if (rawCommand.startsWith(commandPrefix)) {
return requiredPrefixCommandMap[rawCommand.substringAfter(commandPrefix).toLowerCase()]
}
return optionalPrefixCommandMap[rawCommand.toLowerCase()]
}
internal val commandListener: Listener<MessageEvent> by lazy {
@Suppress("RemoveExplicitTypeArguments")
subscribeAlways<MessageEvent>(
concurrency = Listener.ConcurrencyKind.CONCURRENT,
priority = Listener.EventPriority.HIGH
) {
if (this.sender.asCommandSender().executeCommand(message) != null) {
intercept()
}
}
}
///// IMPL
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) {
registeredCommand.unregister()
}
}
override fun Command.register(override: Boolean): Boolean {
if (this is CompositeCommand) this.subCommands // init
modifyLock.withLock {
if (!override) {
if (findDuplicate() != null) return false
}
registeredCommands.add(this@register)
if (this.prefixOptional) {
for (name in this.names) {
val lowerCaseName = name.toLowerCase()
optionalPrefixCommandMap[lowerCaseName] = this
requiredPrefixCommandMap[lowerCaseName] = this
}
} else {
for (name in this.names) {
val lowerCaseName = name.toLowerCase()
optionalPrefixCommandMap.remove(lowerCaseName) // ensure resolution consistency
requiredPrefixCommandMap[lowerCaseName] = this
}
}
return true
}
}
override fun Command.findDuplicate(): Command? =
registeredCommands.firstOrNull { it.names intersectsIgnoringCase this.names }
override fun Command.unregister(): Boolean = modifyLock.withLock {
if (this.prefixOptional) {
this.names.forEach {
optionalPrefixCommandMap.remove(it)
}
}
this.names.forEach {
requiredPrefixCommandMap.remove(it)
}
registeredCommands.remove(this)
}
override fun Command.isRegistered(): Boolean = this in registeredCommands
//// executing without detailed result (faster)
override suspend fun CommandSender.executeCommand(vararg messages: Any): Command? {
if (messages.isEmpty()) return null
return matchAndExecuteCommandInternal(messages, messages[0].toString().substringBefore(' '))
}
override suspend fun CommandSender.executeCommand(message: MessageChain): Command? {
if (message.isEmpty()) return null
return matchAndExecuteCommandInternal(message, message[0].toString().substringBefore(' '))
}
override suspend fun Command.execute(sender: CommandSender, args: MessageChain, checkPermission: Boolean) {
sender.executeCommandInternal(
this,
args.flattenCommandComponents().toTypedArray(),
this.primaryName,
checkPermission
)
}
override suspend fun Command.execute(sender: CommandSender, vararg args: Any, checkPermission: Boolean) {
sender.executeCommandInternal(
this,
args.flattenCommandComponents().toTypedArray(),
this.primaryName,
checkPermission
)
}
//// execution with detailed result
@ConsoleExperimentalAPI
override suspend fun CommandSender.executeCommandDetailed(vararg messages: Any): CommandExecuteResult {
if (messages.isEmpty()) return CommandExecuteResult.CommandNotFound("")
return executeCommandDetailedInternal(messages, messages[0].toString().substringBefore(' '))
}
@ConsoleExperimentalAPI
override suspend fun CommandSender.executeCommandDetailed(messages: MessageChain): CommandExecuteResult {
if (messages.isEmpty()) return CommandExecuteResult.CommandNotFound("")
return executeCommandDetailedInternal(messages, messages[0].toString())
}
}

View File

@ -12,7 +12,7 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.utils.isManager
import net.mamoe.mirai.console.util.BotManager.INSTANCE.isManager
import net.mamoe.mirai.contact.isAdministrator
import net.mamoe.mirai.contact.isOperator
import net.mamoe.mirai.contact.isOwner
@ -35,13 +35,11 @@ public interface CommandPermission {
/**
* 满足两个权限其中一个即可使用指令
*/ // no extension for Java
@JvmDefault
public infix fun or(another: CommandPermission): CommandPermission = OrCommandPermission(this, another)
/**
* 同时拥有两个权限才能使用指令
*/ // no extension for Java
@JvmDefault
public infix fun and(another: CommandPermission): CommandPermission = AndCommandPermission(this, another)

View File

@ -9,6 +9,8 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.Command.Companion.primaryName
/**
* [executeCommand] , [CommandSender] 未拥有 [Command.permission] 所要求的权限时抛出的异常.
*

View File

@ -11,11 +11,10 @@
package net.mamoe.mirai.console.command
import kotlinx.coroutines.runBlocking
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.utils.JavaFriendlyAPI
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.*
import net.mamoe.mirai.message.data.Message
@ -38,17 +37,8 @@ public interface CommandSender {
* 立刻发送一条消息
*/
@JvmSynthetic
@JvmBlockingBridge
public suspend fun sendMessage(message: Message)
@JvmDefault
@JavaFriendlyAPI
@JvmName("sendMessage")
public fun __sendMessageBlocking(messageChain: Message): Unit = runBlocking { sendMessage(messageChain) }
@JvmDefault
@JavaFriendlyAPI
@JvmName("sendMessage")
public fun __sendMessageBlocking(message: String): Unit = runBlocking { sendMessage(message) }
}
/**
@ -58,6 +48,7 @@ public interface BotAwareCommandSender : CommandSender {
public override val bot: Bot
}
@JvmSynthetic
public suspend inline fun CommandSender.sendMessage(message: String): Unit = sendMessage(PlainText(message))
/**

View File

@ -18,9 +18,9 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.description.*
import net.mamoe.mirai.console.command.internal.AbstractReflectionCommand
import net.mamoe.mirai.console.command.internal.CompositeCommandSubCommandAnnotationResolver
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.FUNCTION
import kotlin.reflect.KClass
@ -35,13 +35,13 @@ public abstract class CompositeCommand @JvmOverloads constructor(
description: String = "no description available",
permission: CommandPermission = CommandPermission.Default,
prefixOptional: Boolean = false,
overrideContext: CommandParserContext = EmptyCommandParserContext
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext
) : Command, AbstractReflectionCommand(owner, names, description, permission, prefixOptional),
CommandParserContextAware {
CommandArgumentContextAware {
/**
* [CommandArgParser] 的环境
* [CommandArgumentParser] 的环境
*/
public final override val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
public final override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
/**
* 标记一个函数为子指令, [value] 为空时使用函数名.

View File

@ -1,5 +1,8 @@
package net.mamoe.mirai.console.command
/**
* 无参数解析, 接收原生参数的指令.
*/
public abstract class RawCommand(
public override val owner: CommandOwner,
public override vararg val names: String,

View File

@ -17,12 +17,12 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.description.CommandParserContext
import net.mamoe.mirai.console.command.description.CommandParserContextAware
import net.mamoe.mirai.console.command.description.EmptyCommandParserContext
import net.mamoe.mirai.console.command.description.CommandArgumentContext
import net.mamoe.mirai.console.command.description.CommandArgumentContextAware
import net.mamoe.mirai.console.command.description.EmptyCommandArgumentContext
import net.mamoe.mirai.console.command.description.plus
import net.mamoe.mirai.console.command.internal.AbstractReflectionCommand
import net.mamoe.mirai.console.command.internal.SimpleCommandSubCommandAnnotationResolver
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
public abstract class SimpleCommand @JvmOverloads constructor(
owner: CommandOwner,
@ -30,9 +30,9 @@ public abstract class SimpleCommand @JvmOverloads constructor(
description: String = "no description available",
permission: CommandPermission = CommandPermission.Default,
prefixOptional: Boolean = false,
overrideContext: CommandParserContext = EmptyCommandParserContext
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext
) : Command, AbstractReflectionCommand(owner, names, description, permission, prefixOptional),
CommandParserContextAware {
CommandArgumentContextAware {
public override val usage: String
get() = super.usage
@ -42,7 +42,11 @@ public abstract class SimpleCommand @JvmOverloads constructor(
*/
protected annotation class Handler
public final override val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
public final override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
public final override suspend fun CommandSender.onCommand(args: Array<out Any>) {
subCommands.single().parseAndExecute(this, args, false)
}
internal override fun checkSubCommand(subCommands: Array<SubCommandDescriptor>) {
super.checkSubCommand(subCommands)
@ -52,10 +56,6 @@ public abstract class SimpleCommand @JvmOverloads constructor(
@Deprecated("prohibited", level = DeprecationLevel.HIDDEN)
internal override suspend fun CommandSender.onDefault(rawArgs: Array<out Any>) = sendMessage(usage)
public final override suspend fun CommandSender.onCommand(args: Array<out Any>) {
subCommands.single().parseAndExecute(this, args, false)
}
internal final override val subCommandAnnotationResolver: SubCommandAnnotationResolver
get() = SimpleCommandSubCommandAnnotationResolver
}

View File

@ -14,7 +14,7 @@ import net.mamoe.mirai.console.command.BotAwareCommandSender
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.MemberCommandSender
import net.mamoe.mirai.console.command.UserCommandSender
import net.mamoe.mirai.console.command.internal.fuzzySearchMember
import net.mamoe.mirai.console.internal.command.fuzzySearchMember
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
@ -23,43 +23,43 @@ import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.content
public object IntArgParser : CommandArgParser<Int> {
public object IntArgumentParser : CommandArgumentParser<Int> {
public override fun parse(raw: String, sender: CommandSender): Int =
raw.toIntOrNull() ?: illegalArgument("无法解析 $raw 为整数")
}
public object LongArgParser : CommandArgParser<Long> {
public object LongArgumentParser : CommandArgumentParser<Long> {
public override fun parse(raw: String, sender: CommandSender): Long =
raw.toLongOrNull() ?: illegalArgument("无法解析 $raw 为长整数")
}
public object ShortArgParser : CommandArgParser<Short> {
public object ShortArgumentParser : CommandArgumentParser<Short> {
public override fun parse(raw: String, sender: CommandSender): Short =
raw.toShortOrNull() ?: illegalArgument("无法解析 $raw 为短整数")
}
public object ByteArgParser : CommandArgParser<Byte> {
public object ByteArgumentParser : CommandArgumentParser<Byte> {
public override fun parse(raw: String, sender: CommandSender): Byte =
raw.toByteOrNull() ?: illegalArgument("无法解析 $raw 为字节")
}
public object DoubleArgParser : CommandArgParser<Double> {
public object DoubleArgumentParser : CommandArgumentParser<Double> {
public override fun parse(raw: String, sender: CommandSender): Double =
raw.toDoubleOrNull() ?: illegalArgument("无法解析 $raw 为小数")
}
public object FloatArgParser : CommandArgParser<Float> {
public object FloatArgumentParser : CommandArgumentParser<Float> {
public override fun parse(raw: String, sender: CommandSender): Float =
raw.toFloatOrNull() ?: illegalArgument("无法解析 $raw 为小数")
}
public object StringArgParser : CommandArgParser<String> {
public object StringArgumentParser : CommandArgumentParser<String> {
public override fun parse(raw: String, sender: CommandSender): String {
return raw
}
}
public object BooleanArgParser : CommandArgParser<Boolean> {
public object BooleanArgumentParser : CommandArgumentParser<Boolean> {
public override fun parse(raw: String, sender: CommandSender): Boolean = raw.trim().let { str ->
str.equals("true", ignoreCase = true)
|| str.equals("yes", ignoreCase = true)
@ -73,14 +73,14 @@ public object BooleanArgParser : CommandArgParser<Boolean> {
* output: Bot
* errors: String->Int convert, Bot Not Exist
*/
public object ExistBotArgParser : CommandArgParser<Bot> {
public object ExistBotArgumentParser : CommandArgumentParser<Bot> {
public override fun parse(raw: String, sender: CommandSender): Bot {
val uin = raw.toLongOrNull() ?: illegalArgument("无法识别 QQ ID: $raw")
return Bot.getInstanceOrNull(uin) ?: illegalArgument("无法找到 Bot $uin")
}
}
public object ExistFriendArgParser : CommandArgParser<Friend> {
public object ExistFriendArgumentParser : CommandArgumentParser<Friend> {
//Bot.friend
//friend
//~ = self
@ -125,7 +125,7 @@ public object ExistFriendArgParser : CommandArgParser<Friend> {
}
}
public object ExistGroupArgParser : CommandArgParser<Group> {
public object ExistGroupArgumentParser : CommandArgumentParser<Group> {
public override fun parse(raw: String, sender: CommandSender): Group {
//by default
if ((raw == "" || raw == "~") && sender is MemberCommandSender) {
@ -160,7 +160,7 @@ public object ExistGroupArgParser : CommandArgParser<Group> {
}
}
public object ExistMemberArgParser : CommandArgParser<Member> {
public object ExistMemberArgumentParser : CommandArgumentParser<Member> {
//后台: Bot.Group.Member[QQ/名片]
//私聊: Group.Member[QQ/名片]
//群内: Q号

View File

@ -0,0 +1,228 @@
/*
* 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("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command.description
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.CompositeCommand
import net.mamoe.mirai.console.command.SimpleCommand
import net.mamoe.mirai.console.command.description.CommandArgumentContext.ParserPair
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
/**
* [CommandArgumentParser] 的集合, 用于 [CompositeCommand] [SimpleCommand].
*
* @see SimpleCommandArgumentContext 简单实现
* @see EmptyCommandArgumentContext 空实现, 类似 [emptyList]
* @see CommandArgumentContext.EMPTY 空实现的另一种获取方式.
*
* @see CommandArgumentContext.Builtins 内建 [CommandArgumentParser]
*
* @see CommandArgumentContext DSL
*/
public interface CommandArgumentContext {
/**
* [KClass] [CommandArgumentParser] 的匹配
*/
public data class ParserPair<T : Any>(
val klass: KClass<T>,
val parser: CommandArgumentParser<T>
)
public operator fun <T : Any> get(klass: KClass<out T>): CommandArgumentParser<T>?
public fun toList(): List<ParserPair<*>>
public companion object {
/**
* For Java callers.
*
* @see [EmptyCommandArgumentContext]
*/
@JvmStatic
public val EMPTY: CommandArgumentContext = EmptyCommandArgumentContext
}
/**
* 内建的默认 [CommandArgumentParser]
*/
public object Builtins : CommandArgumentContext by (CommandArgumentContext {
Int::class with IntArgumentParser
Byte::class with ByteArgumentParser
Short::class with ShortArgumentParser
Boolean::class with BooleanArgumentParser
String::class with StringArgumentParser
Long::class with LongArgumentParser
Double::class with DoubleArgumentParser
Float::class with FloatArgumentParser
Member::class with ExistMemberArgumentParser
Group::class with ExistGroupArgumentParser
Bot::class with ExistBotArgumentParser
Friend::class with ExistFriendArgumentParser
})
}
/**
* 拥有 [CommandArgumentContext] 的类
*
* @see SimpleCommand
* @see CompositeCommand
*/
public interface CommandArgumentContextAware {
/**
* [CommandArgumentParser] 的集合
*/
public val context: CommandArgumentContext
}
public object EmptyCommandArgumentContext : CommandArgumentContext by SimpleCommandArgumentContext(listOf())
/**
* 合并两个 [CommandArgumentContext], [replacer] 将会替换 [this] 中重复的 parser.
*/
public operator fun CommandArgumentContext.plus(replacer: CommandArgumentContext): CommandArgumentContext {
if (replacer == EmptyCommandArgumentContext) return this
if (this == EmptyCommandArgumentContext) return replacer
return object : CommandArgumentContext {
override fun <T : Any> get(klass: KClass<out T>): CommandArgumentParser<T>? =
replacer[klass] ?: this@plus[klass]
override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList()
}
}
/**
* 合并 [this] [replacer], [replacer] 将会替换 [this] 中重复的 parser.
*/
public operator fun CommandArgumentContext.plus(replacer: List<ParserPair<*>>): CommandArgumentContext {
if (replacer.isEmpty()) return this
if (this == EmptyCommandArgumentContext) return SimpleCommandArgumentContext(replacer)
return object : CommandArgumentContext {
@Suppress("UNCHECKED_CAST")
override fun <T : Any> get(klass: KClass<out T>): CommandArgumentParser<T>? =
replacer.firstOrNull { klass.isSubclassOf(it.klass) }?.parser as CommandArgumentParser<T>?
?: this@plus[klass]
override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList()
}
}
/**
* 自定义 [CommandArgumentContext]
*
* @see CommandArgumentContext
*/
@Suppress("UNCHECKED_CAST")
public class SimpleCommandArgumentContext(
public val list: List<ParserPair<*>>
) : CommandArgumentContext {
override fun <T : Any> get(klass: KClass<out T>): CommandArgumentParser<T>? =
this.list.firstOrNull { klass.isSubclassOf(it.klass) }?.parser as CommandArgumentParser<T>?
override fun toList(): List<ParserPair<*>> = list
}
/**
* 构建一个 [CommandArgumentContext].
*
* ```
* CommandArgumentContext {
* Int::class with IntArgParser
* Member::class with ExistMemberArgParser
* Group::class with { s: String, sender: CommandSender ->
* Bot.getInstance(s.toLong()).getGroup(s.toLong())
* }
* Bot::class with { s: String ->
* Bot.getInstance(s.toLong())
* }
* }
* ```
*
* @see CommandArgumentContextBuilder
* @see CommandArgumentContext
*/
@Suppress("FunctionName")
@JvmSynthetic
public inline fun CommandArgumentContext(block: CommandArgumentContextBuilder.() -> Unit): CommandArgumentContext {
return SimpleCommandArgumentContext(CommandArgumentContextBuilder().apply(block).distinctByReversed { it.klass })
}
/**
* @see CommandArgumentContext
*/
public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutableListOf() {
@JvmName("add")
public inline infix fun <T : Any> KClass<T>.with(parser: CommandArgumentParser<T>): ParserPair<*> =
ParserPair(this, parser).also { add(it) }
/**
* 添加一个指令解析器
*/
@JvmSynthetic
@LowPriorityInOverloadResolution
public inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandArgumentParser<T>.(s: String, sender: CommandSender) -> T
): ParserPair<*> = ParserPair(this, CommandArgParser(parser)).also { add(it) }
/**
* 添加一个指令解析器
*/
@JvmSynthetic
public inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandArgumentParser<T>.(s: String) -> T
): ParserPair<*> = ParserPair(this, CommandArgParser { s: String, _: CommandSender -> parser(s) }).also { add(it) }
@JvmSynthetic
public inline fun <reified T : Any> add(parser: CommandArgumentParser<T>): ParserPair<*> =
ParserPair(T::class, parser).also { add(it) }
/**
* 添加一个指令解析器
*/
@ConsoleExperimentalAPI
@JvmSynthetic
public inline infix fun <reified T : Any> add(
crossinline parser: CommandArgumentParser<*>.(s: String) -> T
): ParserPair<*> = T::class with CommandArgParser { s: String, _: CommandSender -> parser(s) }
/**
* 添加一个指令解析器
*/
@ConsoleExperimentalAPI
@JvmSynthetic
@LowPriorityInOverloadResolution
public inline infix fun <reified T : Any> add(
crossinline parser: CommandArgumentParser<*>.(s: String, sender: CommandSender) -> T
): ParserPair<*> = T::class with CommandArgParser(parser)
}
@PublishedApi
internal inline fun <T, K> List<T>.distinctByReversed(selector: (T) -> K): List<T> {
val set = HashSet<K>()
val list = ArrayList<T>()
for (i in this.indices.reversed()) {
val element = this[i]
if (set.add(element.let(selector))) {
list.add(element)
}
}
return list
}

View File

@ -17,17 +17,18 @@ import net.mamoe.mirai.message.data.content
import kotlin.contracts.contract
/**
* this output type of that arg
* input is always String
* 指令参数解析器.
*
* @see CommandArgumentContext
*/
public interface CommandArgParser<out T : Any> {
public interface CommandArgumentParser<out T : Any> {
public fun parse(raw: String, sender: CommandSender): T
@JvmDefault
public fun parse(raw: SingleMessage, sender: CommandSender): T = parse(raw.content, sender)
}
public fun <T : Any> CommandArgParser<T>.parse(raw: Any, sender: CommandSender): T {
public fun <T : Any> CommandArgumentParser<T>.parse(raw: Any, sender: CommandSender): T {
contract {
returns() implies (raw is String || raw is SingleMessage)
}
@ -41,12 +42,12 @@ public fun <T : Any> CommandArgParser<T>.parse(raw: Any, sender: CommandSender):
@Suppress("unused")
@JvmSynthetic
public inline fun CommandArgParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing {
public inline fun CommandArgumentParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing {
throw ParserException(message, cause)
}
@JvmSynthetic
public inline fun CommandArgParser<*>.checkArgument(
public inline fun CommandArgumentParser<*>.checkArgument(
condition: Boolean,
crossinline message: () -> String = { "Check failed." }
) {
@ -57,25 +58,25 @@ public inline fun CommandArgParser<*>.checkArgument(
}
/**
* 创建匿名 [CommandArgParser]
* 创建匿名 [CommandArgumentParser]
*/
@Suppress("FunctionName")
@JvmSynthetic
public inline fun <T : Any> CommandArgParser(
crossinline stringParser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T
): CommandArgParser<T> = object : CommandArgParser<T> {
crossinline stringParser: CommandArgumentParser<T>.(s: String, sender: CommandSender) -> T
): CommandArgumentParser<T> = object : CommandArgumentParser<T> {
override fun parse(raw: String, sender: CommandSender): T = stringParser(raw, sender)
}
/**
* 创建匿名 [CommandArgParser]
* 创建匿名 [CommandArgumentParser]
*/
@Suppress("FunctionName")
@JvmSynthetic
public inline fun <T : Any> CommandArgParser(
crossinline stringParser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T,
crossinline messageParser: CommandArgParser<T>.(m: SingleMessage, sender: CommandSender) -> T
): CommandArgParser<T> = object : CommandArgParser<T> {
crossinline stringParser: CommandArgumentParser<T>.(s: String, sender: CommandSender) -> T,
crossinline messageParser: CommandArgumentParser<T>.(m: SingleMessage, sender: CommandSender) -> T
): CommandArgumentParser<T> = object : CommandArgumentParser<T> {
override fun parse(raw: String, sender: CommandSender): T = stringParser(raw, sender)
override fun parse(raw: SingleMessage, sender: CommandSender): T = messageParser(raw, sender)
}

View File

@ -1,193 +0,0 @@
/*
* 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("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command.description
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.description.CommandParserContext.ParserPair
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
/**
* [KClass] [CommandArgParser] 的匹配
* @see CustomCommandParserContext 自定义
*/
public interface CommandParserContext {
public data class ParserPair<T : Any>(
val klass: KClass<T>,
val parser: CommandArgParser<T>
)
public operator fun <T : Any> get(klass: KClass<out T>): CommandArgParser<T>?
public fun toList(): List<ParserPair<*>>
/**
* 内建的默认 [CommandArgParser]
*/
public object Builtins : CommandParserContext by (CommandParserContext {
Int::class with IntArgParser
Byte::class with ByteArgParser
Short::class with ShortArgParser
Boolean::class with BooleanArgParser
String::class with StringArgParser
Long::class with LongArgParser
Double::class with DoubleArgParser
Float::class with FloatArgParser
Member::class with ExistMemberArgParser
Group::class with ExistGroupArgParser
Bot::class with ExistBotArgParser
Friend::class with ExistFriendArgParser
})
}
/**
* 拥有 [CommandParserContext] 的类
*/
public interface CommandParserContextAware {
/**
* [CommandArgParser] 的环境
*/
public val context: CommandParserContext
}
public object EmptyCommandParserContext : CommandParserContext by CustomCommandParserContext(listOf())
/**
* 合并两个 [CommandParserContext], [replacer] 将会替换 [this] 中重复的 parser.
*/
public operator fun CommandParserContext.plus(replacer: CommandParserContext): CommandParserContext {
if (replacer == EmptyCommandParserContext) return this
if (this == EmptyCommandParserContext) return replacer
return object : CommandParserContext {
override fun <T : Any> get(klass: KClass<out T>): CommandArgParser<T>? = replacer[klass] ?: this@plus[klass]
override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList()
}
}
/**
* 合并 [this] [replacer], [replacer] 将会替换 [this] 中重复的 parser.
*/
public operator fun CommandParserContext.plus(replacer: List<ParserPair<*>>): CommandParserContext {
if (replacer.isEmpty()) return this
if (this == EmptyCommandParserContext) return CustomCommandParserContext(replacer)
return object : CommandParserContext {
@Suppress("UNCHECKED_CAST")
override fun <T : Any> get(klass: KClass<out T>): CommandArgParser<T>? =
replacer.firstOrNull { klass.isSubclassOf(it.klass) }?.parser as CommandArgParser<T>? ?: this@plus[klass]
override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList()
}
}
@Suppress("UNCHECKED_CAST")
public open class CustomCommandParserContext(public val list: List<ParserPair<*>>) : CommandParserContext {
override fun <T : Any> get(klass: KClass<out T>): CommandArgParser<T>? =
this.list.firstOrNull { klass.isSubclassOf(it.klass) }?.parser as CommandArgParser<T>?
override fun toList(): List<ParserPair<*>> {
return list
}
}
/**
* 构建一个 [CommandParserContext].
*
* ```
* CommandParserContext {
* Int::class with IntArgParser
* Member::class with ExistMemberArgParser
* Group::class with { s: String, sender: CommandSender ->
* Bot.getInstance(s.toLong()).getGroup(s.toLong())
* }
* Bot::class with { s: String ->
* Bot.getInstance(s.toLong())
* }
* }
* ```
*/
@Suppress("FunctionName")
@JvmSynthetic
public inline fun CommandParserContext(block: CommandParserContextBuilder.() -> Unit): CommandParserContext {
return CustomCommandParserContext(CommandParserContextBuilder().apply(block).distinctByReversed { it.klass })
}
/**
* @see CommandParserContext
*/
public class CommandParserContextBuilder : MutableList<ParserPair<*>> by mutableListOf() {
@JvmName("add")
public inline infix fun <T : Any> KClass<T>.with(parser: CommandArgParser<T>): ParserPair<*> =
ParserPair(this, parser).also { add(it) }
/**
* 添加一个指令解析器
*/
@JvmSynthetic
@LowPriorityInOverloadResolution
public inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T
): ParserPair<*> = ParserPair(this, CommandArgParser(parser)).also { add(it) }
/**
* 添加一个指令解析器
*/
@JvmSynthetic
public inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandArgParser<T>.(s: String) -> T
): ParserPair<*> = ParserPair(this, CommandArgParser { s: String, _: CommandSender -> parser(s) }).also { add(it) }
@JvmSynthetic
public inline fun <reified T : Any> add(parser: CommandArgParser<T>): ParserPair<*> =
ParserPair(T::class, parser).also { add(it) }
/**
* 添加一个指令解析器
*/
@ConsoleExperimentalAPI
@JvmSynthetic
public inline infix fun <reified T : Any> add(
crossinline parser: CommandArgParser<*>.(s: String) -> T
): ParserPair<*> = T::class with CommandArgParser { s: String, _: CommandSender -> parser(s) }
/**
* 添加一个指令解析器
*/
@ConsoleExperimentalAPI
@JvmSynthetic
@LowPriorityInOverloadResolution
public inline infix fun <reified T : Any> add(
crossinline parser: CommandArgParser<*>.(s: String, sender: CommandSender) -> T
): ParserPair<*> = T::class with CommandArgParser(parser)
}
@PublishedApi
internal inline fun <T, K> List<T>.distinctByReversed(selector: (T) -> K): List<T> {
val set = HashSet<K>()
val list = ArrayList<T>()
for (i in this.indices.reversed()) {
val element = this[i]
if (set.add(element.let(selector))) {
list.add(element)
}
}
return list
}

View File

@ -34,24 +34,24 @@ internal data class CommandParam<T : Any>(
*/
val name: String,
/**
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandArgParser] 解析.
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandArgumentParser] 解析.
*/
val type: KClass<T> // exact type
) {
constructor(name: String, type: KClass<T>, parser: CommandArgParser<T>) : this(name, type) {
constructor(name: String, type: KClass<T>, parser: CommandArgumentParser<T>) : this(name, type) {
this._overrideParser = parser
}
@Suppress("PropertyName")
@JvmField
internal var _overrideParser: CommandArgParser<T>? = null
internal var _overrideParser: CommandArgumentParser<T>? = null
/**
* 覆盖的 [CommandArgParser].
* 覆盖的 [CommandArgumentParser].
*
* 如果非 `null`, 将不会从 [CommandParserContext] 寻找 [CommandArgParser]
* 如果非 `null`, 将不会从 [CommandArgumentContext] 寻找 [CommandArgumentParser]
*/
val overrideParser: CommandArgParser<T>? get() = _overrideParser
val overrideParser: CommandArgumentParser<T>? get() = _overrideParser
}

View File

@ -0,0 +1,9 @@
package net.mamoe.mirai.console.internal
import java.util.*
internal object MiraiConsoleBuildConstants { // auto-filled on build (task :mirai-console:fillBuildConstants)
@JvmStatic
val buildDate: Date = Date(1596350800177L) // 2020-08-02 14:46:40
const val version: String = "1.0-M1"
}

View File

@ -0,0 +1,88 @@
@file:OptIn(ConsoleExperimentalAPI::class)
package net.mamoe.mirai.console.internal
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.IllegalMiraiConsoleImplementationError
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleFrontEnd
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.CommandManagerImpl
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.internal.plugin.CuiPluginCenter
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.internal.utils.ConsoleBuiltInSettingStorage
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter
import net.mamoe.mirai.console.setting.SettingStorage
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.info
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import kotlin.coroutines.CoroutineContext
/**
* [MiraiConsole] 公开 API 与前端实现的连接桥.
*/
internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleImplementation,
MiraiConsole {
override val pluginCenter: PluginCenter get() = CuiPluginCenter
private val instance: MiraiConsoleImplementation get() = MiraiConsoleImplementation.instance
override val buildDate: Date get() = MiraiConsoleBuildConstants.buildDate
override val version: String get() = MiraiConsoleBuildConstants.version
override val rootDir: File get() = instance.rootDir
override val frontEnd: MiraiConsoleFrontEnd get() = instance.frontEnd
@ConsoleExperimentalAPI
override val mainLogger: MiraiLogger
get() = instance.mainLogger
override val coroutineContext: CoroutineContext get() = instance.coroutineContext
override val builtInPluginLoaders: List<PluginLoader<*, *>> get() = instance.builtInPluginLoaders
override val consoleCommandSender: ConsoleCommandSender get() = instance.consoleCommandSender
override val settingStorageForJarPluginLoader: SettingStorage get() = instance.settingStorageForJarPluginLoader
override val settingStorageForBuiltIns: SettingStorage get() = instance.settingStorageForBuiltIns
init {
DefaultLogger = { identity -> this.newLogger(identity) }
}
@ConsoleExperimentalAPI
override fun newLogger(identity: String?): MiraiLogger = frontEnd.loggerFor(identity)
@OptIn(ConsoleExperimentalAPI::class)
internal fun doStart() {
val buildDateFormatted = SimpleDateFormat("yyyy-MM-dd").format(buildDate)
mainLogger.info { "Starting mirai-console..." }
mainLogger.info { "Backend: version $version, built on $buildDateFormatted." }
mainLogger.info { "Frontend ${frontEnd.name}: version $version." }
if (coroutineContext[Job] == null) {
throw IllegalMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
}
MiraiConsole.job.invokeOnCompletion {
Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
}
BuiltInCommands.registerAll()
mainLogger.info { "Preparing built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
CommandManagerImpl.commandListener // start
mainLogger.info { "Loading plugins..." }
PluginManagerImpl.loadEnablePlugins()
mainLogger.info { "${PluginManager.plugins.size} plugin(s) loaded." }
mainLogger.info { "mirai-console started successfully." }
ConsoleBuiltInSettingStorage // init
// Only for initialize
}
}

View File

@ -9,12 +9,13 @@
@file:Suppress("NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package net.mamoe.mirai.console.command.internal
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.command.description.CommandParam
import net.mamoe.mirai.console.command.description.CommandParserContext
import net.mamoe.mirai.console.command.description.CommandParserContextAware
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.SingleMessage
import kotlin.reflect.KAnnotatedElement
@ -43,11 +44,11 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
prefixOptional: Boolean = false
) : Command, AbstractCommand(
owner,
names = *names,
names = names,
description = description,
permission = permission,
prefixOptional = prefixOptional
), CommandParserContextAware {
), CommandArgumentContextAware {
internal abstract val subCommandAnnotationResolver: SubCommandAnnotationResolver
@JvmField
@ -101,7 +102,7 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
map[name] = descriptor
}
}
map.toSortedMap(Comparator { o1, o2 -> o1!!.contentHashCode() - o2!!.contentHashCode() })
map.toSortedMap { o1, o2 -> o1!!.contentHashCode() - o2!!.contentHashCode() }
}
}
@ -117,7 +118,7 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
val description: String,
val permission: CommandPermission,
val onCommand: suspend (sender: CommandSender, parsedArgs: Array<out Any>) -> Boolean,
val context: CommandParserContext,
val context: CommandArgumentContext,
val usage: String
) {
internal suspend inline fun parseAndExecute(
@ -210,7 +211,7 @@ internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?:
internal fun AbstractReflectionCommand.createSubCommand(
function: KFunction<*>,
context: CommandParserContext
context: CommandArgumentContext
): AbstractReflectionCommand.SubCommandDescriptor {
val notStatic = !function.hasAnnotation<JvmStatic>()
val overridePermission = function.findAnnotation<CompositeCommand.Permission>()//optional

View File

@ -7,17 +7,11 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.command.internal
package net.mamoe.mirai.console.internal.command
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.message.MessageEvent
import java.util.concurrent.locks.ReentrantLock
internal infix fun Array<String>.matchesBeginning(list: List<Any>): Boolean {
@ -27,54 +21,6 @@ internal infix fun Array<String>.matchesBeginning(list: List<Any>): Boolean {
return true
}
internal object InternalCommandManager : CoroutineScope by CoroutineScope(MiraiConsole.job) {
const val COMMAND_PREFIX = "/"
@JvmField
internal val registeredCommands: MutableList<Command> = mutableListOf()
/**
* 全部注册的指令
* /mute -> MuteCommand
* /jinyan -> MuteCommand
*/
@JvmField
internal val requiredPrefixCommandMap: MutableMap<String, Command> = mutableMapOf()
/**
* Command name of commands that are prefix optional
* mute -> MuteCommand
*/
@JvmField
internal val optionalPrefixCommandMap: MutableMap<String, Command> = mutableMapOf()
@JvmField
internal val modifyLock = ReentrantLock()
/**
* 从原始的 command 中解析出 Command 对象
*/
internal fun matchCommand(rawCommand: String): Command? {
if (rawCommand.startsWith(COMMAND_PREFIX)) {
return requiredPrefixCommandMap[rawCommand.substringAfter(COMMAND_PREFIX).toLowerCase()]
}
return optionalPrefixCommandMap[rawCommand.toLowerCase()]
}
internal val commandListener: Listener<MessageEvent> by lazy {
@Suppress("RemoveExplicitTypeArguments")
subscribeAlways<MessageEvent>(
concurrency = Listener.ConcurrencyKind.CONCURRENT,
priority = Listener.EventPriority.HIGH
) {
if (this.sender.asCommandSender().executeCommand(message) != null) {
intercept()
}
}
}
}
internal infix fun Array<out String>.intersectsIgnoringCase(other: Array<out String>): Boolean {
val max = this.size.coerceAtMost(other.size)
for (i in 0 until max) {
@ -186,7 +132,7 @@ internal suspend inline fun CommandSender.matchAndExecuteCommandInternal(
messages: Any,
commandName: String
): Command? {
val command = InternalCommandManager.matchCommand(
val command = CommandManagerImpl.matchCommand(
commandName
) ?: return null
@ -215,4 +161,38 @@ internal suspend inline fun CommandSender.executeCommandInternal(
}.onFailure {
throw CommandExecutionException(command, commandName, it)
}
}
}
@JvmSynthetic
internal suspend inline fun CommandSender.executeCommandDetailedInternal(
messages: Any,
commandName: String
): CommandExecuteResult {
val command =
CommandManagerImpl.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName)
val args = messages.flattenCommandComponents().dropToTypedArray(1)
if (!command.testPermission(this)) {
return CommandExecuteResult.PermissionDenied(command, commandName)
}
kotlin.runCatching {
command.onCommand(this, args)
}.fold(
onSuccess = {
return CommandExecuteResult.Success(
commandName = commandName,
command = command,
args = args
)
},
onFailure = {
return CommandExecuteResult.ExecutionException(
commandName = commandName,
command = command,
exception = it,
args = args
)
}
)
}

View File

@ -9,7 +9,7 @@
@file:OptIn(ConsoleExperimentalAPI::class)
package net.mamoe.mirai.console.plugin.center
package net.mamoe.mirai.console.internal.plugin
import io.ktor.client.*
import io.ktor.client.engine.cio.*
@ -17,8 +17,9 @@ import io.ktor.client.request.*
import io.ktor.util.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.utils.retryCatching
import net.mamoe.mirai.console.plugin.center.PluginCenter
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.retryCatching
import java.io.File
internal val json = Json {

View File

@ -7,18 +7,18 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.plugin.internal
package net.mamoe.mirai.console.internal.plugin
import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader
import net.mamoe.mirai.console.plugin.PluginLoadException
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.console.setting.SettingStorage
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.yamlkt.Yaml
import java.io.File

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.plugin.internal
package net.mamoe.mirai.console.internal.plugin
import kotlinx.atomicfu.AtomicLong
import kotlinx.atomicfu.locks.withLock
@ -20,7 +20,7 @@ import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.console.utils.ResourceContainer.Companion.asResourceContainer
import net.mamoe.mirai.console.util.ResourceContainer.Companion.asResourceContainer
import net.mamoe.mirai.utils.MiraiLogger
import java.io.File
import java.io.InputStream

View File

@ -9,12 +9,12 @@
@file:Suppress("NOTHING_TO_INLINE", "unused")
package net.mamoe.mirai.console.plugin.jvm
package net.mamoe.mirai.console.internal.plugin
import kotlinx.atomicfu.locks.withLock
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.internal.setting.cast
import net.mamoe.mirai.console.plugin.*
import net.mamoe.mirai.console.setting.internal.cast
import net.mamoe.mirai.utils.info
import java.io.File
import java.util.concurrent.locks.ReentrantLock
@ -120,7 +120,7 @@ internal object PluginManagerImpl : PluginManager {
@Throws(PluginMissingDependencyException::class)
private fun loadAndEnableLoaderProviders(): List<PluginDescriptionWithLoader> {
val allDescriptions =
this.builtInLoaders.listAllPlugins()
builtInLoaders.listAllPlugins()
.asSequence()
.onEach { (loader, descriptions) ->
loader as PluginLoader<Plugin, PluginDescription>

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.plugin.internal
package net.mamoe.mirai.console.internal.plugin
import net.mamoe.mirai.console.MiraiConsole
import java.io.File

View File

@ -9,7 +9,7 @@
@file:Suppress("unused")
package net.mamoe.mirai.console.setting.internal
package net.mamoe.mirai.console.internal.setting
import net.mamoe.mirai.console.setting.*

View File

@ -7,13 +7,12 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.utils
package net.mamoe.mirai.console.internal.setting
import com.vdurmont.semver4j.Semver
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.builtins.serializer
import net.mamoe.mirai.console.setting.internal.map
@Serializer(forClass = Semver::class)
internal object SemverAsStringSerializerLoose : KSerializer<Semver> by String.serializer().map(

View File

@ -9,7 +9,7 @@
@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package net.mamoe.mirai.console.setting.internal
package net.mamoe.mirai.console.internal.setting
import kotlinx.serialization.KSerializer
import net.mamoe.mirai.console.setting.SerializableValue.Companion.serializableValueWith

View File

@ -9,7 +9,7 @@
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_SUPER_CLASS")
package net.mamoe.mirai.console.setting.internal
package net.mamoe.mirai.console.internal.setting
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName

View File

@ -7,19 +7,19 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.setting.internal
package net.mamoe.mirai.console.internal.setting
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.mamoe.mirai.console.command.internal.qualifiedNameOrTip
import net.mamoe.mirai.console.plugin.internal.updateWhen
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
import net.mamoe.mirai.console.internal.plugin.updateWhen
import net.mamoe.mirai.console.plugin.jvm.loadSetting
import net.mamoe.mirai.console.setting.*
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.utils.currentTimeMillis
import net.mamoe.yamlkt.Yaml
import java.io.File

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.setting.internal
package net.mamoe.mirai.console.internal.setting
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.setting.internal
package net.mamoe.mirai.console.internal.setting
import kotlinx.serialization.builtins.serializer
import net.mamoe.mirai.console.setting.SerializerAwareValue

View File

@ -7,9 +7,9 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.setting.internal
package net.mamoe.mirai.console.internal.setting
import net.mamoe.mirai.console.command.internal.qualifiedNameOrTip
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
import net.mamoe.mirai.console.setting.Setting
import kotlin.reflect.KClass
import kotlin.reflect.KType

View File

@ -9,7 +9,7 @@
@file:Suppress("DuplicatedCode")
package net.mamoe.mirai.console.setting.internal
package net.mamoe.mirai.console.internal.setting
import kotlinx.serialization.UnsafeSerializationApi
import kotlinx.serialization.serializer

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.setting.internal
package net.mamoe.mirai.console.internal.setting
import kotlinx.serialization.KSerializer
import kotlinx.serialization.UnsafeSerializationApi

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.setting.internal
package net.mamoe.mirai.console.internal.setting
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName

View File

@ -6,41 +6,42 @@
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:JvmName("BotManagers")
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.utils
package net.mamoe.mirai.console.internal.utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.setting.*
import net.mamoe.mirai.console.setting.SettingStorage.Companion.load
import net.mamoe.mirai.console.util.BotManager
import net.mamoe.mirai.contact.User
import net.mamoe.mirai.utils.minutesToMillis
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
internal object BotManagerImpl : BotManager {
/**
* 判断此用户是否为 console 管理员
*/
override val User.isManager: Boolean get() = this.id in this.bot.managers
/**
* 判断此用户是否为 console 管理员
*/
public val User.isManager: Boolean get() = this.id in this.bot.managers
override fun Bot.removeManager(id: Long): Boolean {
return ManagersConfig[this].remove(id)
}
public fun Bot.removeManager(id: Long): Boolean {
return ManagersConfig[this].remove(id)
override val Bot.managers: List<Long>
get() = ManagersConfig[this].toList()
override fun Bot.addManager(id: Long): Boolean {
return ManagersConfig[this].add(id)
}
}
public val Bot.managers: List<Long>
get() = ManagersConfig[this].toList()
internal fun Bot.addManager(id: Long): Boolean {
return ManagersConfig[this].add(id)
}
internal object ManagersConfig : Setting by ConsoleBuiltInSettingStorage.load() {
private val managers: MutableMap<Long, MutableSet<Long>> by value()

View File

@ -12,7 +12,7 @@
package net.mamoe.mirai.console.plugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import java.io.File
/**

View File

@ -12,7 +12,7 @@
package net.mamoe.mirai.console.plugin
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugin.jvm.PluginManagerImpl
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import java.io.File
/**
@ -55,7 +55,9 @@ public interface PluginManager {
*/
public val Plugin.description: PluginDescription
public companion object INSTANCE : PluginManager by PluginManagerImpl
public companion object INSTANCE : PluginManager by PluginManagerImpl {
override val Plugin.description: PluginDescription get() = PluginManagerImpl.run { description }
}
}
@JvmSynthetic

View File

@ -11,7 +11,7 @@ package net.mamoe.mirai.console.plugin.center
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import java.io.File
@ConsoleExperimentalAPI

View File

@ -13,8 +13,8 @@ import com.vdurmont.semver4j.Semver
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.serializer
import net.mamoe.mirai.console.setting.internal.map
import net.mamoe.mirai.console.utils.SemverAsStringSerializerIvy
import net.mamoe.mirai.console.internal.setting.SemverAsStringSerializerIvy
import net.mamoe.mirai.console.internal.setting.map
import net.mamoe.yamlkt.Yaml
import net.mamoe.yamlkt.YamlDynamicSerializer
import java.io.File

View File

@ -11,7 +11,7 @@
package net.mamoe.mirai.console.plugin.jvm
import net.mamoe.mirai.console.plugin.internal.JvmPluginInternal
import net.mamoe.mirai.console.internal.plugin.JvmPluginInternal
import net.mamoe.mirai.utils.minutesToSeconds
import net.mamoe.mirai.utils.secondsToMillis
import kotlin.coroutines.CoroutineContext

View File

@ -10,10 +10,10 @@
package net.mamoe.mirai.console.plugin.jvm
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.internal.plugin.JarPluginLoaderImpl
import net.mamoe.mirai.console.plugin.FilePluginLoader
import net.mamoe.mirai.console.plugin.internal.JarPluginLoaderImpl
import net.mamoe.mirai.console.setting.SettingStorage
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
/**
* 内建的 Jar (JVM) 插件加载器
@ -25,5 +25,9 @@ public interface JarPluginLoader : CoroutineScope, FilePluginLoader<JvmPlugin, J
@ConsoleExperimentalAPI
public val settingStorage: SettingStorage
public companion object INSTANCE : JarPluginLoader by JarPluginLoaderImpl
public companion object INSTANCE : JarPluginLoader by JarPluginLoaderImpl {
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
override val JvmPlugin.description: JvmPluginDescription
get() = JarPluginLoaderImpl.run { description }
}
}

View File

@ -11,7 +11,7 @@
package net.mamoe.mirai.console.plugin.jvm
import net.mamoe.mirai.console.utils.JavaPluginScheduler
import net.mamoe.mirai.console.util.JavaPluginScheduler
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

View File

@ -16,7 +16,7 @@ import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.PluginFileExtensions
import net.mamoe.mirai.console.setting.AutoSaveSettingHolder
import net.mamoe.mirai.console.setting.Setting
import net.mamoe.mirai.console.utils.ResourceContainer
import net.mamoe.mirai.console.util.ResourceContainer
import net.mamoe.mirai.utils.MiraiLogger
import kotlin.reflect.KClass

View File

@ -13,11 +13,11 @@ import com.vdurmont.semver4j.Semver
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.console.internal.setting.SemverAsStringSerializerLoose
import net.mamoe.mirai.console.plugin.FilePluginDescription
import net.mamoe.mirai.console.plugin.PluginDependency
import net.mamoe.mirai.console.plugin.PluginDescription
import net.mamoe.mirai.console.plugin.PluginKind
import net.mamoe.mirai.console.utils.SemverAsStringSerializerLoose
import java.io.File
@Serializable

View File

@ -11,7 +11,7 @@
package net.mamoe.mirai.console.plugin.jvm
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

View File

@ -12,11 +12,11 @@
package net.mamoe.mirai.console.setting
import kotlinx.serialization.KSerializer
import net.mamoe.mirai.console.internal.setting.*
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.loadSetting
import net.mamoe.mirai.console.setting.internal.*
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI
import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KProperty
import kotlin.reflect.KType

View File

@ -13,10 +13,10 @@ package net.mamoe.mirai.console.setting
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.console.internal.setting.*
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.setting.SettingStorage.Companion.load
import net.mamoe.mirai.console.setting.internal.*
import java.io.File
import kotlin.reflect.KClass
import kotlin.reflect.KType

View File

@ -14,9 +14,9 @@ package net.mamoe.mirai.console.setting
import kotlinx.serialization.BinaryFormat
import kotlinx.serialization.KSerializer
import kotlinx.serialization.StringFormat
import net.mamoe.mirai.console.setting.internal.map
import net.mamoe.mirai.console.setting.internal.setValueBySerializer
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
import net.mamoe.mirai.console.internal.setting.map
import net.mamoe.mirai.console.internal.setting.setValueBySerializer
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import kotlin.reflect.KProperty
/**

View File

@ -0,0 +1,25 @@
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.console.util
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.internal.utils.BotManagerImpl
import net.mamoe.mirai.contact.User
public interface BotManager {
/**
* 判断此用户是否为 console 管理员
*/
public val User.isManager: Boolean
public val Bot.managers: List<Long>
public fun Bot.removeManager(id: Long): Boolean
public fun Bot.addManager(id: Long): Boolean
public companion object INSTANCE : BotManager { // kotlin import handler doesn't recognize delegation.
override fun Bot.addManager(id: Long): Boolean = BotManagerImpl.run { addManager(id) }
override fun Bot.removeManager(id: Long): Boolean = BotManagerImpl.run { removeManager(id) }
override val User.isManager: Boolean get() = BotManagerImpl.run { isManager }
override val Bot.managers: List<Long> get() = BotManagerImpl.run { managers }
}
}

View File

@ -9,7 +9,7 @@
@file:Suppress("INAPPLICABLE_JVM_NAME", "unused")
package net.mamoe.mirai.console.utils
package net.mamoe.mirai.console.util
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.utils
package net.mamoe.mirai.console.util
import kotlin.annotation.AnnotationTarget.*

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.utils
package net.mamoe.mirai.console.util
import kotlinx.coroutines.*
import kotlinx.coroutines.future.future

View File

@ -9,11 +9,10 @@
@file:Suppress("unused")
package net.mamoe.mirai.console.utils
package net.mamoe.mirai.console.util
import net.mamoe.mirai.console.encodeToString
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.utils.ResourceContainer.Companion.asResourceContainer
import net.mamoe.mirai.console.util.ResourceContainer.Companion.asResourceContainer
import java.io.InputStream
import java.nio.charset.Charset
import kotlin.reflect.KClass
@ -42,7 +41,7 @@ public interface ResourceContainer {
*/
@JvmDefault
public fun getResource(name: String, charset: Charset): String =
this.getResourceAsStream(name).use { it.readBytes() }.encodeToString(charset)
String(this.getResourceAsStream(name).use { it.readBytes() })
public companion object {
/**

View File

@ -9,7 +9,7 @@
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
package net.mamoe.mirai.console.utils
package net.mamoe.mirai.console.util
import org.jetbrains.annotations.Range
import kotlin.contracts.InvocationKind

View File

@ -21,7 +21,7 @@ import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.setting.MemorySettingStorage
import net.mamoe.mirai.console.setting.SettingStorage
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.LoginSolver

View File

@ -16,11 +16,16 @@ import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.Testing
import net.mamoe.mirai.console.Testing.withTesting
import net.mamoe.mirai.console.command.description.CommandArgParser
import net.mamoe.mirai.console.command.description.CommandParserContext
import net.mamoe.mirai.console.command.internal.InternalCommandManager
import net.mamoe.mirai.console.command.internal.flattenCommandComponents
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registeredCommands
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregister
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands
import net.mamoe.mirai.console.command.description.CommandArgumentContext
import net.mamoe.mirai.console.command.description.CommandArgumentParser
import net.mamoe.mirai.console.initTestEnvironment
import net.mamoe.mirai.console.internal.command.flattenCommandComponents
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.toMessage
@ -75,8 +80,8 @@ internal class TestCommand {
assertEquals(1, ConsoleCommandOwner.registeredCommands.size)
assertEquals(1, InternalCommandManager.registeredCommands.size)
assertEquals(2, InternalCommandManager.requiredPrefixCommandMap.size)
assertEquals(1, CommandManagerImpl.registeredCommands.size)
assertEquals(2, CommandManagerImpl.requiredPrefixCommandMap.size)
} finally {
TestCompositeCommand.unregister()
}
@ -180,8 +185,8 @@ internal class TestCommand {
val composite = object : CompositeCommand(
ConsoleCommandOwner,
"test",
overrideContext = CommandParserContext {
add(object : CommandArgParser<MyClass> {
overrideContext = CommandArgumentContext {
add(object : CommandArgumentParser<MyClass> {
override fun parse(raw: String, sender: CommandSender): MyClass {
return MyClass(raw.toInt())
}

View File

@ -9,6 +9,9 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregister
inline fun <T : Command, R> T.withRegistration(block: T.() -> R): R {
this.register()
try {

View File

@ -10,7 +10,7 @@
package net.mamoe.mirai.console.setting
import kotlinx.serialization.json.Json
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertSame

View File

@ -1,6 +1,9 @@
@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
}
tasks.withType(JavaCompile::class.java) {

View File

@ -29,9 +29,7 @@ dependencies {
api(ktor("client-json", "1.3.2"))
compileOnly(gradleApi())
//compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72")
compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72")
//runtimeOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72")
compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0-rc")
compileOnly("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5")
api("com.github.jengelman.gradle.plugins:shadow:6.0.0")
}

View File

@ -30,7 +30,7 @@ class MiraiConsoleBuildPlugin : Plugin<Project> {
tasks.getByName("shadowJar") {
with(this as ShadowJar) {
archiveFileName.set(
"${target.name}-${target.version}.jar"
"${target.name}-${target.version}-all.jar"
)
manifest {
attributes(
@ -101,7 +101,7 @@ fun Project.findLatestFile(): Map.Entry<String, File> {
return File(projectDir, "build/libs").walk()
.filter { it.isFile }
.onEach { println("all files=$it") }
.filter { it.name.matches(Regex("""${project.name}-[0-9][0-9]*(\.[0-9]*)*.*\.jar""")) }
.filter { it.name.matches(Regex("""${project.name}-[0-9][0-9]*(\.[0-9]*)*.*\-all\.jar""")) }
.onEach { println("matched file: ${it.name}") }
.associateBy { it.nameWithoutExtension.substringAfterLast('-') }
.onEach { println("versions: $it") }

View File

@ -34,4 +34,6 @@ object Versions {
const val androidGradle = "3.6.2"
const val bintray = "1.8.5"
const val blockingBridge = "0.9.0"
}

View File

@ -1,12 +1,19 @@
plugins {
kotlin("jvm") version Versions.kotlinCompiler
kotlin("plugin.serialization") version Versions.kotlinCompiler
kotlin("jvm")
kotlin("plugin.serialization")
id("java")
`maven-publish`
id("com.jfrog.bintray")
}
kotlin {
target.compilations.all {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all"
}
}
sourceSets {
all {
languageSettings.enableLanguageFeature("InlineClasses")
@ -16,7 +23,7 @@ kotlin {
languageSettings.progressiveMode = true
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.utils.ConsoleExperimentalAPI")
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.util.ConsoleExperimentalAPI")
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.ConsoleFrontEndImplementation")
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")

View File

@ -25,9 +25,8 @@ package net.mamoe.mirai.console.pure
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsoleBuildConstants
import net.mamoe.mirai.console.MiraiConsoleFrontEnd
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.utils.DefaultLoginSolver
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.MiraiLogger
@ -72,13 +71,13 @@ object MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd {
const val COLOR_RESET = "\u001b[39;49m"
// }
val sdf by lazy {
private val sdf by lazy {
SimpleDateFormat("HH:mm:ss")
}
override val name: String
get() = "Pure"
override val version: String
get() = MiraiConsoleBuildConstants.version
get() = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
override fun loggerFor(identity: String?): MiraiLogger {
identity?.apply {

View File

@ -34,7 +34,7 @@ import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.setting.MultiFileSettingStorage
import net.mamoe.mirai.console.setting.SettingStorage
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.utils.MiraiLogger
import java.io.File
import java.util.*

View File

@ -23,8 +23,13 @@ package net.mamoe.mirai.console.pure
import kotlinx.coroutines.isActive
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.utils.ConsoleInternalAPI
import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.command.CommandExecuteStatus
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommandDetailed
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.content
import net.mamoe.mirai.utils.DefaultLogger
@ -74,11 +79,11 @@ internal fun startConsoleThread() {
while (isActive) {
val next = MiraiConsoleFrontEndPure.requestInput("").let {
when {
it.startsWith(CommandPrefix) -> {
it.startsWith(CommandManager.commandPrefix) -> {
it
}
it == "?" -> CommandPrefix + BuiltInCommands.Help.primaryName
else -> CommandPrefix + it
it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName
else -> CommandManager.commandPrefix + it
}
}
if (next.isBlank()) {