mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-13 23:00:14 +08:00
433 lines
18 KiB
Kotlin
433 lines
18 KiB
Kotlin
/*
|
|
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
|
*
|
|
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
|
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found through the following link.
|
|
*
|
|
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
|
*/
|
|
|
|
package net.mamoe.mirai.console.command
|
|
|
|
import kotlinx.coroutines.*
|
|
import kotlinx.coroutines.sync.Mutex
|
|
import kotlinx.coroutines.sync.withLock
|
|
import net.mamoe.mirai.alsoLogin
|
|
import net.mamoe.mirai.console.MiraiConsole
|
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
|
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
|
|
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.map
|
|
import net.mamoe.mirai.console.command.descriptor.PermissionIdValueArgumentParser
|
|
import net.mamoe.mirai.console.command.descriptor.PermitteeIdValueArgumentParser
|
|
import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext
|
|
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
|
|
import net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants
|
|
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
|
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
|
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands
|
|
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig
|
|
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.*
|
|
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN
|
|
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
|
|
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
|
|
import net.mamoe.mirai.console.internal.util.runIgnoreException
|
|
import net.mamoe.mirai.console.permission.Permission
|
|
import net.mamoe.mirai.console.permission.PermissionService
|
|
import net.mamoe.mirai.console.permission.PermissionService.Companion.cancel
|
|
import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermissionOrFail
|
|
import net.mamoe.mirai.console.permission.PermissionService.Companion.getPermittedPermissions
|
|
import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
|
|
import net.mamoe.mirai.console.permission.PermitteeId
|
|
import net.mamoe.mirai.console.plugin.name
|
|
import net.mamoe.mirai.console.plugin.version
|
|
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
|
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
|
import net.mamoe.mirai.event.events.EventCancelledException
|
|
import net.mamoe.mirai.message.nextMessageOrNull
|
|
import net.mamoe.mirai.utils.secondsToMillis
|
|
import java.lang.management.ManagementFactory
|
|
import java.lang.management.MemoryUsage
|
|
import java.time.ZoneId
|
|
import java.time.format.DateTimeFormatter
|
|
import kotlin.concurrent.thread
|
|
import kotlin.math.floor
|
|
import kotlin.system.exitProcess
|
|
|
|
|
|
@ConsoleExperimentalApi
|
|
@Suppress("EXPOSED_SUPER_INTERFACE")
|
|
public interface BuiltInCommand : Command
|
|
|
|
// for identification
|
|
internal interface BuiltInCommandInternal : Command, BuiltInCommand
|
|
|
|
/**
|
|
* 内建指令列表
|
|
*/
|
|
@Suppress("unused")
|
|
public object BuiltInCommands {
|
|
@ConsoleExperimentalApi
|
|
public val parentPermission: Permission by lazy {
|
|
PermissionService.INSTANCE.register(
|
|
ConsoleCommandOwner.permissionId("*"),
|
|
"The parent of any built-in commands"
|
|
)
|
|
}
|
|
|
|
internal val all: Array<out Command> by lazy {
|
|
this::class.nestedClasses.mapNotNull { it.objectInstance as? Command }.toTypedArray()
|
|
}
|
|
|
|
internal fun registerAll() {
|
|
BuiltInCommands::class.nestedClasses.forEach {
|
|
(it.objectInstance as? Command)?.register()
|
|
}
|
|
}
|
|
|
|
public object HelpCommand : SimpleCommand(
|
|
ConsoleCommandOwner, "help",
|
|
description = "查看指令帮助",
|
|
), BuiltInCommandInternal {
|
|
@Handler
|
|
public suspend fun CommandSender.handle() {
|
|
sendMessage(
|
|
allRegisteredCommands
|
|
.joinToString("\n\n") { command ->
|
|
val lines = command.usage.lines()
|
|
if (lines.isEmpty()) "/${command.primaryName} ${command.description}"
|
|
else
|
|
"◆ " + lines.first() + "\n" + lines.drop(1).joinToString("\n") { " $it" }
|
|
}.lines().filterNot(String::isBlank).joinToString("\n")
|
|
)
|
|
}
|
|
}
|
|
|
|
init {
|
|
Runtime.getRuntime().addShutdownHook(thread(false) {
|
|
MiraiConsole.cancel()
|
|
})
|
|
}
|
|
|
|
public object StopCommand : SimpleCommand(
|
|
ConsoleCommandOwner, "stop", "shutdown", "exit",
|
|
description = "关闭 Mirai Console",
|
|
), BuiltInCommandInternal {
|
|
|
|
private val closingLock = Mutex()
|
|
|
|
@Handler
|
|
public suspend fun CommandSender.handle() {
|
|
GlobalScope.launch {
|
|
kotlin.runCatching {
|
|
closingLock.withLock {
|
|
if (!MiraiConsole.isActive) return@withLock
|
|
sendMessage("Stopping mirai-console")
|
|
kotlin.runCatching {
|
|
MiraiConsole.job.cancelAndJoin()
|
|
}.fold(
|
|
onSuccess = {
|
|
runIgnoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
|
|
},
|
|
onFailure = {
|
|
@OptIn(ConsoleInternalApi::class)
|
|
MiraiConsole.mainLogger.error("Exception in stop", it)
|
|
runIgnoreException<EventCancelledException> {
|
|
sendMessage(
|
|
it.localizedMessage ?: it.message ?: it.toString()
|
|
)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
}.exceptionOrNull()?.let(MiraiConsole.mainLogger::error)
|
|
exitProcess(0)
|
|
}
|
|
}
|
|
}
|
|
|
|
public object LoginCommand : SimpleCommand(
|
|
ConsoleCommandOwner, "login", "登录",
|
|
description = "登录一个账号",
|
|
), BuiltInCommandInternal {
|
|
@Handler
|
|
public suspend fun CommandSender.handle(@Name("qq") id: Long, password: String) {
|
|
kotlin.runCatching {
|
|
MiraiConsole.addBot(id, password).alsoLogin()
|
|
}.fold(
|
|
onSuccess = { sendMessage("${it.nick} ($id) Login successful") },
|
|
onFailure = { throwable ->
|
|
sendMessage(
|
|
"Login failed: ${throwable.localizedMessage ?: throwable.message ?: throwable.toString()}" +
|
|
if (this is CommandSenderOnMessage<*>) {
|
|
CommandManagerImpl.launch(CoroutineName("stacktrace delayer from Login")) {
|
|
fromEvent.nextMessageOrNull(60.secondsToMillis) { it.message.contentEquals("stacktrace") }
|
|
}
|
|
"\n 1 分钟内发送 stacktrace 以获取堆栈信息"
|
|
} else ""
|
|
)
|
|
|
|
throw throwable
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
public object PermissionCommand : CompositeCommand(
|
|
ConsoleCommandOwner, "permission", "权限", "perm",
|
|
description = "管理权限",
|
|
overrideContext = buildCommandArgumentContext {
|
|
PermitteeId::class with PermitteeIdValueArgumentParser
|
|
Permission::class with PermissionIdValueArgumentParser.map { id ->
|
|
kotlin.runCatching {
|
|
id.findCorrespondingPermissionOrFail()
|
|
}.getOrElse { throw CommandArgumentParserException("指令不存在: $id", it) }
|
|
}
|
|
},
|
|
), BuiltInCommandInternal {
|
|
// TODO: 2020/9/10 improve Permission command
|
|
|
|
@Description("授权一个权限")
|
|
@SubCommand("permit", "grant", "add")
|
|
public suspend fun CommandSender.permit(
|
|
@Name("被许可人 ID") target: PermitteeId,
|
|
@Name("权限 ID") permission: Permission,
|
|
) {
|
|
target.permit(permission)
|
|
sendMessage("OK")
|
|
}
|
|
|
|
@Description("取消授权一个权限")
|
|
@SubCommand("cancel", "deny", "remove")
|
|
public suspend fun CommandSender.cancel(
|
|
@Name("被许可人 ID") target: PermitteeId,
|
|
@Name("权限 ID") permission: Permission,
|
|
) {
|
|
target.cancel(permission, false)
|
|
sendMessage("OK")
|
|
}
|
|
|
|
@Description("取消授权一个权限及其所有子权限")
|
|
@SubCommand("cancelAll", "denyAll", "removeAll")
|
|
public suspend fun CommandSender.cancelAll(
|
|
@Name("被许可人 ID") target: PermitteeId,
|
|
@Name("权限 ID") permission: Permission,
|
|
) {
|
|
target.cancel(permission, true)
|
|
sendMessage("OK")
|
|
}
|
|
|
|
@Description("查看被授权权限列表")
|
|
@SubCommand("permittedPermissions", "pp", "grantedPermissions", "gp")
|
|
public suspend fun CommandSender.permittedPermissions(
|
|
@Name("被许可人 ID") target: PermitteeId,
|
|
) {
|
|
val grantedPermissions = target.getPermittedPermissions()
|
|
sendMessage(grantedPermissions.joinToString("\n") { it.id.toString() })
|
|
}
|
|
|
|
@Description("查看所有权限列表")
|
|
@SubCommand("listPermissions", "lp")
|
|
public suspend fun CommandSender.listPermissions() {
|
|
sendMessage(PermissionService.INSTANCE.getRegisteredPermissions().joinToString("\n") { it.id.toString() })
|
|
}
|
|
}
|
|
|
|
|
|
public object AutoLoginCommand : CompositeCommand(
|
|
ConsoleCommandOwner, "autoLogin", "自动登录",
|
|
description = "自动登录设置",
|
|
overrideContext = buildCommandArgumentContext {
|
|
ConfigurationKey::class with ConfigurationKey.Parser
|
|
}
|
|
), BuiltInCommandInternal {
|
|
@Description("查看自动登录账号列表")
|
|
@SubCommand
|
|
public suspend fun CommandSender.list() {
|
|
sendMessage(buildString {
|
|
for (account in AutoLoginConfig.accounts) {
|
|
if (account.account == "123456") continue
|
|
append("- ")
|
|
append("账号: ")
|
|
append(account.account)
|
|
appendLine()
|
|
append(" 密码: ")
|
|
append(account.password.value)
|
|
appendLine()
|
|
|
|
if (account.configuration.isNotEmpty()) {
|
|
appendLine(" 配置:")
|
|
for ((key, value) in account.configuration) {
|
|
append(" $key = $value")
|
|
}
|
|
appendLine()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
@Description("添加自动登录")
|
|
@SubCommand
|
|
public suspend fun CommandSender.add(account: Long, password: String, passwordKind: PasswordKind = PLAIN) {
|
|
val accountStr = account.toString()
|
|
if (AutoLoginConfig.accounts.any { it.account == accountStr }) {
|
|
sendMessage("已有相同账号在自动登录配置中. 请先删除该账号.")
|
|
return
|
|
}
|
|
AutoLoginConfig.accounts.add(AutoLoginConfig.Account(accountStr, Password(passwordKind, password)))
|
|
sendMessage("已成功添加 '$account'.")
|
|
}
|
|
|
|
@Description("清除所有配置")
|
|
@SubCommand
|
|
public suspend fun CommandSender.clear() {
|
|
AutoLoginConfig.accounts.clear()
|
|
sendMessage("已清除所有自动登录配置.")
|
|
}
|
|
|
|
@Description("删除一个账号")
|
|
@SubCommand
|
|
public suspend fun CommandSender.remove(account: Long) {
|
|
val accountStr = account.toString()
|
|
if (AutoLoginConfig.accounts.removeIf { it.account == accountStr }) {
|
|
sendMessage("已成功删除 '$account'.")
|
|
return
|
|
}
|
|
sendMessage("账号 '$account' 未配置自动登录.")
|
|
}
|
|
|
|
@Description("设置一个账号的一个配置项")
|
|
@SubCommand
|
|
public suspend fun CommandSender.setConfig(account: Long, configKey: ConfigurationKey, value: String) {
|
|
val accountStr = account.toString()
|
|
|
|
val oldAccount = AutoLoginConfig.accounts.find { it.account == accountStr } ?: kotlin.run {
|
|
sendMessage("未找到账号 $account.")
|
|
return
|
|
}
|
|
|
|
if (value.isEmpty()) return removeConfig(account, configKey)
|
|
|
|
val newAccount = oldAccount.copy(configuration = oldAccount.configuration.toMutableMap().apply {
|
|
put(configKey, value)
|
|
})
|
|
|
|
AutoLoginConfig.accounts.remove(oldAccount)
|
|
AutoLoginConfig.accounts.add(newAccount)
|
|
|
|
sendMessage("成功修改 '$account' 的配置 '$configKey' 为 '$value'")
|
|
}
|
|
|
|
@Description("删除一个账号的一个配置项")
|
|
@SubCommand
|
|
public suspend fun CommandSender.removeConfig(account: Long, configKey: ConfigurationKey) {
|
|
val accountStr = account.toString()
|
|
|
|
val oldAccount = AutoLoginConfig.accounts.find { it.account == accountStr } ?: kotlin.run {
|
|
sendMessage("未找到账号 $account.")
|
|
return
|
|
}
|
|
|
|
val newAccount = oldAccount.copy(configuration = oldAccount.configuration.toMutableMap().apply {
|
|
remove(configKey)
|
|
})
|
|
|
|
AutoLoginConfig.accounts.remove(oldAccount)
|
|
AutoLoginConfig.accounts.add(newAccount)
|
|
|
|
sendMessage("成功删除 '$account' 的配置 '$configKey'.")
|
|
}
|
|
}
|
|
|
|
public object StatusCommand : SimpleCommand(
|
|
ConsoleCommandOwner, "status", "states", "状态",
|
|
description = "获取 Mirai Console 运行状态"
|
|
), BuiltInCommandInternal {
|
|
@Handler
|
|
public suspend fun CommandSender.handle() {
|
|
sendMessage(buildString {
|
|
val buildDateFormatted =
|
|
MiraiConsoleBuildConstants.buildDate.atZone(ZoneId.systemDefault())
|
|
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
|
|
|
append("Running MiraiConsole v${MiraiConsoleBuildConstants.versionConst}, built on ").append(buildDateFormatted)
|
|
.append(".\n")
|
|
append(MiraiConsoleImplementationBridge.frontEndDescription.render()).append("\n\n")
|
|
append("Permission Service: ").append(
|
|
if (PermissionService.INSTANCE is BuiltInPermissionService) {
|
|
"Built In Permission Service"
|
|
} else {
|
|
val plugin = PermissionServiceProvider.providerPlugin
|
|
if (plugin == null) {
|
|
PermissionService.INSTANCE.toString()
|
|
} else {
|
|
"${plugin.name} v${plugin.version}"
|
|
}
|
|
}
|
|
)
|
|
append("\n\n")
|
|
|
|
append("Plugins: ")
|
|
if (PluginManagerImpl.resolvedPlugins.isEmpty()) {
|
|
append("<none>")
|
|
} else {
|
|
PluginManagerImpl.resolvedPlugins.joinTo(this) { plugin ->
|
|
"${plugin.name} v${plugin.version}"
|
|
}
|
|
}
|
|
append("\n\n")
|
|
val memoryMXBean = ManagementFactory.getMemoryMXBean()
|
|
|
|
append("Object Pending Finalization Count: ")
|
|
.append(memoryMXBean.objectPendingFinalizationCount)
|
|
.append("\n")
|
|
|
|
append(" Heap Memory: ")
|
|
renderMemoryUsage(memoryMXBean.heapMemoryUsage)
|
|
append("\nNon-Heap Memory: ")
|
|
renderMemoryUsage(memoryMXBean.nonHeapMemoryUsage)
|
|
})
|
|
}
|
|
|
|
private const val MEM_B = 1024L
|
|
private const val MEM_KB = 1024L shl 10
|
|
private const val MEM_MB = 1024L shl 20
|
|
private const val MEM_GB = 1024L shl 30
|
|
|
|
@Suppress("NOTHING_TO_INLINE")
|
|
private inline fun StringBuilder.appendDouble(number: Double): StringBuilder =
|
|
append(floor(number * 100) / 100)
|
|
|
|
private fun StringBuilder.renderMemoryUsageNumber(num: Long) {
|
|
when {
|
|
num == -1L -> {
|
|
append(num)
|
|
}
|
|
num < MEM_B -> {
|
|
append(num).append("B")
|
|
}
|
|
num < MEM_KB -> {
|
|
appendDouble(num / 1024.0).append("KB")
|
|
}
|
|
num < MEM_MB -> {
|
|
appendDouble((num ushr 10) / 1024.0).append("MB")
|
|
}
|
|
else -> {
|
|
appendDouble((num ushr 20) / 1024.0).append("GB")
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private fun StringBuilder.renderMemoryUsage(usage: MemoryUsage) {
|
|
append("(committed / init / used / max) [")
|
|
renderMemoryUsageNumber(usage.committed)
|
|
append(", ")
|
|
renderMemoryUsageNumber(usage.init)
|
|
append(", ")
|
|
renderMemoryUsageNumber(usage.used)
|
|
append(", ")
|
|
renderMemoryUsageNumber(usage.max)
|
|
append("]")
|
|
}
|
|
}
|
|
} |