mirai/backend/mirai-console/src/MiraiConsole.kt

222 lines
7.9 KiB
Kotlin

/*
* Copyright 2019-2021 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("WRONG_MODIFIER_CONTAINING_DECLARATION", "unused")
@file:OptIn(ConsoleInternalApi::class)
package net.mamoe.mirai.console
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotFactory
import net.mamoe.mirai.console.MiraiConsole.INSTANCE
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.extensions.BotConfigurationAlterer
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.util.AnsiMessageBuilder
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInternalApi
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext
import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.verbose
import java.io.File
import java.nio.file.Path
import java.time.Instant
/**
* mirai-console 实例
*
* @see INSTANCE
* @see MiraiConsoleImplementation
*/
public interface MiraiConsole : CoroutineScope {
/**
* Console 运行根目录, 由前端决定确切路径.
*
* 所有子模块都会在这个目录之下创建子目录.
*
* @see PluginManager.pluginsPath
* @see PluginManager.pluginsDataPath
* @see PluginManager.pluginsConfigPath
*/
public val rootPath: Path
/**
* Console 主日志.
*
* **实现细节**: 这个 [MiraiLogger] 的 [MiraiLogger.identity] 通常为 `main`
*
* **注意**: 插件不应该在任何时刻使用它.
*/
@ConsoleInternalApi
public val mainLogger: MiraiLogger
/**
* 内建加载器列表, 一般需要包含 [JvmPluginLoader].
*
* @return 不可变 [List] ([java.util.Collections.unmodifiableList])
*/
public val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>>
/**
* 此 Console 后端构建时间
*/
public val buildDate: Instant
/**
* 此 Console 后端版本号
*/
public val version: SemVersion
@ConsoleExperimentalApi
public val pluginCenter: PluginCenter
/**
* 创建一个 logger
*/
@ConsoleExperimentalApi
public fun createLogger(identity: String?): MiraiLogger
/**
* 是否支持使用 Ansi 输出彩色信息
*
* 注: 不是每个前端都可能提供 `org.fusesource.jansi:jansi` 库支持,
* 请不要直接使用 `org.fusesource.jansi:jansi`
*
* @see [AnsiMessageBuilder]
*/
@ConsoleExperimentalApi
public val isAnsiSupported: Boolean
public companion object INSTANCE : MiraiConsole by MiraiConsoleImplementationBridge {
/**
* 获取 [MiraiConsole] 的 [Job]
*/ // MiraiConsole.INSTANCE.getJob()
public val job: Job
get() = MiraiConsole.coroutineContext[Job]
?: throw MalformedMiraiConsoleImplementationError("Internal error: Job not found in MiraiConsole.coroutineContext")
/**
* 添加一个 [Bot] 实例到全局 Bot 列表, 但不登录.
*
* 调用 [Bot.login] 可登录.
*
* @see Bot.botInstances 获取现有 [Bot] 实例列表
* @see BotConfigurationAlterer ExtensionPoint
*/
// don't static
@ConsoleExperimentalApi("This is a low-level API and might be removed in the future.")
public fun addBot(id: Long, password: String, configuration: BotConfiguration.() -> Unit = {}): Bot =
addBotImpl(id, password, configuration)
/**
* 添加一个 [Bot] 实例到全局 Bot 列表, 但不登录.
*
* 调用 [Bot.login] 可登录.
*
* @see Bot.instances 获取现有 [Bot] 实例列表
* @see BotConfigurationAlterer ExtensionPoint
*/
@ConsoleExperimentalApi("This is a low-level API and might be removed in the future.")
public fun addBot(id: Long, password: ByteArray, configuration: BotConfiguration.() -> Unit = {}): Bot =
addBotImpl(id, password, configuration)
@Suppress("UNREACHABLE_CODE")
private fun addBotImpl(id: Long, password: Any, configuration: BotConfiguration.() -> Unit = {}): Bot {
var config = BotConfiguration().apply {
workingDir = MiraiConsole.rootDir
.resolve("bots")
.resolve(id.toString())
.also { it.mkdirs() }
mainLogger.verbose { "Bot $id working in $workingDir" }
val deviceInRoot = MiraiConsole.rootDir.resolve("device.json")
val deviceInWorkingDir = workingDir.resolve("device.json")
val deviceInfoInWorkingDir = workingDir.resolve("deviceInfo.json")
if (!deviceInWorkingDir.exists()) {
when {
deviceInfoInWorkingDir.exists() -> {
// rename bots/id/deviceInfo.json to bots/id/device.json
mainLogger.verbose { "Renaming $deviceInfoInWorkingDir to $deviceInWorkingDir" }
deviceInfoInWorkingDir.renameTo(deviceInWorkingDir)
}
deviceInRoot.exists() -> {
// copy root/device.json to bots/id/device.json
mainLogger.verbose { "Coping $deviceInRoot to $deviceInWorkingDir" }
deviceInRoot.copyTo(deviceInWorkingDir)
}
}
}
fileBasedDeviceInfo("device.json")
redirectNetworkLogToDirectory()
this.botLoggerSupplier = {
MiraiLogger.create("Bot.${it.id}")
}
parentCoroutineContext = MiraiConsole.childScopeContext("Bot $id")
autoReconnectOnForceOffline()
this.loginSolver = MiraiConsoleImplementationBridge.createLoginSolver(id, this)
configuration()
}
config = GlobalComponentStorage.run {
BotConfigurationAlterer.foldExtensions(config) { acc, extension ->
extension.alterConfiguration(id, acc)
}
}
return when (password) {
is ByteArray -> BotFactory.newBot(id, password, config)
is String -> BotFactory.newBot(id, password, config)
else -> throw IllegalArgumentException("Bad password type: `${password.javaClass.name}`. Require ByteArray or String")
}
}
@ConsoleExperimentalApi("This is a low-level API and might be removed in the future.")
public val isActive: Boolean
get() = job.isActive
}
}
/**
* @see MiraiConsole.rootPath
*/
public val MiraiConsole.rootDir: File get() = rootPath.toFile()
/**
* [MiraiConsoleImplementation] 实现有误时抛出.
*
* @see MiraiConsoleImplementation.start
*/
public class MalformedMiraiConsoleImplementationError : Error {
public constructor() : super()
public constructor(message: String?) : super(message)
public constructor(message: String?, cause: Throwable?) : super(message, cause)
public constructor(cause: Throwable?) : super(cause)
}