mirai/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt
2021-01-12 17:37:56 +08:00

283 lines
12 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
*/
@file:OptIn(ConsoleExperimentalApi::class)
package net.mamoe.mirai.console.internal
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MalformedMiraiConsoleImplementationError
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
import net.mamoe.mirai.console.MiraiConsoleImplementation
import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
import net.mamoe.mirai.console.extensions.PostStartupExtension
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
import net.mamoe.mirai.console.internal.command.CommandConfig
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.MD5
import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN
import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScope
import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig
import net.mamoe.mirai.console.internal.extension.BuiltInSingletonExtensionSelector
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
import net.mamoe.mirai.console.internal.logging.MiraiConsoleLogger
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
import net.mamoe.mirai.console.internal.util.autoHexToBytes
import net.mamoe.mirai.console.internal.util.runIgnoreException
import net.mamoe.mirai.console.logging.LoggerController
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
import net.mamoe.mirai.console.permission.RootPermission
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.center.PluginCenter
import net.mamoe.mirai.console.plugin.loader.PluginLoader
import net.mamoe.mirai.console.plugin.name
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.console.util.SemVersion
import net.mamoe.mirai.utils.*
import java.nio.file.Path
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
/**
* [MiraiConsole] 公开 API 与前端实现的连接桥.
*/
@Suppress("SpellCheckingInspection")
internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleImplementation,
MiraiConsole {
override val pluginCenter: PluginCenter get() = throw UnsupportedOperationException("PluginCenter is not supported yet")
private val instance: MiraiConsoleImplementation by MiraiConsoleImplementation.Companion::instance
override val buildDate: Instant by MiraiConsoleBuildConstants::buildDate
override val version: SemVersion by MiraiConsoleBuildConstants::version
override val rootPath: Path by instance::rootPath
override val frontEndDescription: MiraiConsoleFrontEndDescription by instance::frontEndDescription
override val mainLogger: MiraiLogger by lazy {
createLogger("main")
}
override val coroutineContext: CoroutineContext by instance::coroutineContext
override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> by instance::builtInPluginLoaders
override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl by instance::consoleCommandSender
override val dataStorageForJvmPluginLoader: PluginDataStorage by instance::dataStorageForJvmPluginLoader
override val configStorageForJvmPluginLoader: PluginDataStorage by instance::configStorageForJvmPluginLoader
override val dataStorageForBuiltIns: PluginDataStorage by instance::dataStorageForBuiltIns
override val configStorageForBuiltIns: PluginDataStorage by instance::configStorageForBuiltIns
override val consoleInput: ConsoleInput by instance::consoleInput
override val isAnsiSupported: Boolean by instance::isAnsiSupported
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver =
instance.createLoginSolver(requesterBot, configuration)
override val loggerController: LoggerController by instance::loggerController
init {
MiraiLogger.setDefaultLoggerCreator(this::createLogger)
}
override fun createLogger(identity: String?): MiraiLogger {
val controller = loggerController
return MiraiConsoleLogger(controller, instance.createLogger(identity))
}
@Suppress("RemoveRedundantBackticks")
internal fun doStart() {
phase `setup logger controller`@{
if (loggerController === LoggerControllerImpl) {
// Reload LoggerConfig.
ConsoleDataScope.addAndReloadConfig(LoggerConfig)
LoggerControllerImpl.initialized = true
}
}
phase `greeting`@{
val buildDateFormatted =
buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
mainLogger.info { "Starting mirai-console..." }
mainLogger.info { "Backend: version $version, built on $buildDateFormatted." }
mainLogger.info { frontEndDescription.render() }
}
phase `check coroutineContext`@{
if (coroutineContext[Job] == null) {
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
}
if (coroutineContext[CoroutineExceptionHandler] == null) {
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a CoroutineExceptionHandler in it.")
}
MiraiConsole.job.invokeOnCompletion {
Bot.instances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
}
}
ConsoleInput
// start
phase `load configurations`@{
mainLogger.verbose { "Loading configurations..." }
ConsoleDataScope.addAndReloadConfig(CommandConfig)
ConsoleDataScope.reloadAll()
}
phase `initialize all plugins`@{
PluginManager // init
mainLogger.verbose { "Loading JVM plugins..." }
PluginManagerImpl.loadAllPluginsUsingBuiltInLoaders()
PluginManagerImpl.initExternalPluginLoaders().let { count ->
mainLogger.verbose { "$count external PluginLoader(s) found. " }
if (count != 0) {
mainLogger.verbose { "Loading external plugins..." }
}
}
}
phase `load all plugins`@{
PluginManagerImpl.loadPlugins(PluginManagerImpl.scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider())
mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." }
}
phase `load SingletonExtensionSelector`@{
SingletonExtensionSelector.init()
val instance = SingletonExtensionSelector.instance
if (instance is BuiltInSingletonExtensionSelector) {
ConsoleDataScope.addAndReloadConfig(instance.config)
}
}
phase `load PermissionService`@{
mainLogger.verbose { "Loading PermissionService..." }
PermissionService.INSTANCE.let { ps ->
if (ps is BuiltInPermissionService) {
ConsoleDataScope.addAndReloadConfig(ps.config)
mainLogger.verbose { "Reloaded PermissionService settings." }
} else {
mainLogger.info { "Loaded PermissionService from plugin ${PermissionServiceProvider.providerPlugin?.name}" }
}
}
runIgnoreException<UnsupportedOperationException> { ConsoleCommandSender.permit(RootPermission) }
}
phase `prepare commands`@{
mainLogger.verbose { "Loading built-in commands..." }
BuiltInCommands.registerAll()
mainLogger.info { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
CommandManager
// CommandManagerImpl.commandListener // start
}
phase `enable plugins`@{
mainLogger.verbose { "Enabling plugins..." }
PluginManagerImpl.enableAllLoadedPlugins()
for (registeredCommand in CommandManager.allRegisteredCommands) {
registeredCommand.permission // init
}
mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) enabled." }
}
phase `auto-login bots`@{
runBlocking {
val accounts = AutoLoginConfig.accounts.toList()
for (account in accounts) {
val id = kotlin.runCatching {
account.account.toLong()
}.getOrElse {
error("Bad auto-login account: '${account.account}'")
}
if (id == 123456L) continue
fun BotConfiguration.configBot() {
mainLogger.info { "Auto-login ${account.account}" }
account.configuration[AutoLoginConfig.Account.ConfigurationKey.protocol]
?.let { protocol ->
this.protocol = runCatching {
BotConfiguration.MiraiProtocol.valueOf(protocol.toString())
}.getOrElse {
throw IllegalArgumentException(
"Bad auto-login config value for `protocol` for account $id",
it
)
}
}
}
val bot = when (account.password.kind) {
PLAIN -> {
MiraiConsole.addBot(id, account.password.value, BotConfiguration::configBot)
}
MD5 -> {
val md5 = kotlin.runCatching {
account.password.value.autoHexToBytes()
}.getOrElse {
error("Bad auto-login md5: '${account.password.value}' for account $id")
}
MiraiConsole.addBot(id, md5, BotConfiguration::configBot)
}
}
runCatching { bot.login() }.getOrElse {
mainLogger.error(it)
}
}
}
}
GlobalComponentStorage.run {
PostStartupExtension.useExtensions { it() } // exceptions thrown will be caught by caller of `doStart`.
}
mainLogger.info { "mirai-console started successfully." }
}
@Suppress("SpellCheckingInspection")
@Retention(AnnotationRetention.BINARY)
@DslMarker
internal annotation class ILoveOmaeKumikoForever
/**
* 表示一个初始化阶段, 无实际作用.
*/
@ILoveOmaeKumikoForever
private inline fun phase(block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
}