/* * 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.alsoLogin 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.ConfigurationKey.protocol 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.data.castOrNull 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.jvm.AbstractJvmPlugin 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>> 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 fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver = instance.createLoginSolver(requesterBot, configuration) override val loggerController: LoggerController by instance::loggerController init { DefaultLogger = 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.botInstances.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 `collect extensions`@{ for (resolvedPlugin in PluginManagerImpl.resolvedPlugins) { resolvedPlugin.castOrNull()?.let { GlobalComponentStorage.mergeWith(it.componentStorage) } } } 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 { 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 val config = BotConfiguration() for ((key, value) in account.configuration) { runCatching { when (key) { protocol -> config.protocol = BotConfiguration.MiraiProtocol.valueOf(value.toString()) } }.onFailure { error("Bad auto-login config value for $key for account $id") } } when (account.password.kind) { PLAIN -> { mainLogger.info { "Auto-login ${account.account}" } MiraiConsole.addBot(id, account.password.value).alsoLogin() } 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).alsoLogin() } } } } } 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() } }