mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-11 02:50:15 +08:00
165 lines
6.7 KiB
Kotlin
165 lines
6.7 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:Suppress(
|
|
"INVISIBLE_MEMBER",
|
|
"INVISIBLE_REFERENCE",
|
|
"CANNOT_OVERRIDE_INVISIBLE_MEMBER",
|
|
"INVISIBLE_SETTER",
|
|
"INVISIBLE_GETTER",
|
|
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER",
|
|
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING",
|
|
"EXPOSED_SUPER_CLASS"
|
|
)
|
|
@file:OptIn(ConsoleInternalApi::class, ConsoleFrontEndImplementation::class, ConsoleTerminalExperimentalApi::class)
|
|
|
|
package net.mamoe.mirai.console.terminal
|
|
|
|
|
|
import kotlinx.coroutines.CancellationException
|
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
|
import kotlinx.coroutines.CoroutineName
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import net.mamoe.mirai.console.ConsoleFrontEndImplementation
|
|
import net.mamoe.mirai.console.MiraiConsole
|
|
import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
|
|
import net.mamoe.mirai.console.MiraiConsoleImplementation
|
|
import net.mamoe.mirai.console.data.MultiFilePluginDataStorage
|
|
import net.mamoe.mirai.console.data.PluginDataStorage
|
|
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
|
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
|
import net.mamoe.mirai.console.terminal.ConsoleInputImpl.requestInput
|
|
import net.mamoe.mirai.console.terminal.noconsole.AllEmptyLineReader
|
|
import net.mamoe.mirai.console.terminal.noconsole.NoConsole
|
|
import net.mamoe.mirai.console.util.*
|
|
import net.mamoe.mirai.utils.*
|
|
import org.fusesource.jansi.Ansi
|
|
import org.jline.reader.LineReader
|
|
import org.jline.reader.LineReaderBuilder
|
|
import org.jline.reader.impl.completer.NullCompleter
|
|
import org.jline.terminal.Terminal
|
|
import org.jline.terminal.TerminalBuilder
|
|
import org.jline.terminal.impl.AbstractWindowsTerminal
|
|
import java.nio.file.Path
|
|
import java.nio.file.Paths
|
|
|
|
/**
|
|
* mirai-console-terminal 后端实现
|
|
*
|
|
* @see MiraiConsoleTerminalLoader CLI 入口点
|
|
*/
|
|
@ConsoleExperimentalApi
|
|
class MiraiConsoleImplementationTerminal
|
|
@JvmOverloads constructor(
|
|
override val rootPath: Path = Paths.get(System.getProperty("user.dir", ".")).toAbsolutePath(),
|
|
override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader }),
|
|
override val frontEndDescription: MiraiConsoleFrontEndDescription = ConsoleFrontEndDescImpl,
|
|
override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl = ConsoleCommandSenderImplTerminal,
|
|
override val dataStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
|
|
override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
|
|
override val configStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
|
|
override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
|
|
) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(
|
|
NamedSupervisorJob("MiraiConsoleImplementationTerminal") +
|
|
CoroutineExceptionHandler { coroutineContext, throwable ->
|
|
if (throwable is CancellationException) {
|
|
return@CoroutineExceptionHandler
|
|
}
|
|
val coroutineName = coroutineContext[CoroutineName]?.name ?: "<unnamed>"
|
|
MiraiConsole.mainLogger.error("Exception in coroutine $coroutineName", throwable)
|
|
}) {
|
|
override val consoleInput: ConsoleInput get() = ConsoleInputImpl
|
|
|
|
override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver {
|
|
return DefaultLoginSolver(input = { requestInput("LOGIN> ") })
|
|
}
|
|
|
|
override fun createLogger(identity: String?): MiraiLogger = LoggerCreator(identity)
|
|
|
|
init {
|
|
with(rootPath.toFile()) {
|
|
mkdir()
|
|
require(isDirectory) { "rootDir $absolutePath is not a directory" }
|
|
}
|
|
}
|
|
}
|
|
|
|
val lineReader: LineReader by lazy {
|
|
val terminal = terminal
|
|
if (terminal is NoConsole) return@lazy AllEmptyLineReader
|
|
|
|
LineReaderBuilder.builder()
|
|
.terminal(terminal)
|
|
.completer(NullCompleter())
|
|
.build()
|
|
}
|
|
|
|
val terminal: Terminal = run {
|
|
if (ConsoleTerminalSettings.noConsole) return@run NoConsole
|
|
|
|
val dumb = System.getProperty("java.class.path")
|
|
.contains("idea_rt.jar") || System.getProperty("mirai.idea") !== null || System.getenv("mirai.idea") !== null
|
|
|
|
runCatching {
|
|
TerminalBuilder.builder()
|
|
.dumb(dumb)
|
|
.paused(true)
|
|
.build()
|
|
.let { terminal ->
|
|
if (terminal is AbstractWindowsTerminal) {
|
|
val pumpField = runCatching {
|
|
AbstractWindowsTerminal::class.java.getDeclaredField("pump").also {
|
|
it.isAccessible = true
|
|
}
|
|
}.onFailure { err ->
|
|
err.printStackTrace()
|
|
return@let terminal.also { it.resume() }
|
|
}.getOrThrow()
|
|
var response = terminal
|
|
terminal.setOnClose {
|
|
response = NoConsole
|
|
}
|
|
terminal.resume()
|
|
val pumpThread = pumpField[terminal] as? Thread ?: return@let NoConsole
|
|
@Suppress("ControlFlowWithEmptyBody")
|
|
while (pumpThread.state == Thread.State.NEW);
|
|
Thread.sleep(1000)
|
|
terminal.setOnClose(null)
|
|
return@let response
|
|
}
|
|
terminal.resume()
|
|
terminal
|
|
}
|
|
}.recoverCatching {
|
|
TerminalBuilder.builder()
|
|
.jansi(true)
|
|
.build()
|
|
}.recoverCatching {
|
|
TerminalBuilder.builder()
|
|
.system(true)
|
|
.build()
|
|
}.getOrThrow()
|
|
}
|
|
|
|
private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription {
|
|
override val name: String get() = "Terminal"
|
|
override val vendor: String get() = "Mamoe Technologies"
|
|
// net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
|
|
// is console's version not frontend's version
|
|
override val version: SemVersion = SemVersion(net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.versionConst)
|
|
}
|
|
|
|
private val ANSI_RESET = Ansi().reset().toString()
|
|
|
|
internal val LoggerCreator: (identity: String?) -> MiraiLogger = {
|
|
PlatformLogger(identity = it, output = { line ->
|
|
lineReader.printAbove(line + ANSI_RESET)
|
|
})
|
|
}
|