Commit eb866066 authored by Him188's avatar Him188

Merge remote-tracking branch 'origin/master'

parents dcddcc92 bd381b80
......@@ -120,7 +120,7 @@ JVM 上启动需 80M 内存, 每多一个机器人实例需要 30M 内存.
现在体验低付出高效率的 Mirai
```kotlin
val bot = TIMPC.Bot(qqId, password).alsoLogin()
val bot = Bot(qqId, password).alsoLogin()
bot.subscribeMessages {
"你好" reply "你好!"
"profile" reply { sender.queryProfile() }
......
### Mirai Console Graphical
支持windows/mac
有正式UI界面实现的CONSOLE
优点: 适合新手/完全不懂编程的/界面美丽
缺点: 不能在linux服务器运行
所使用插件系统与terminal版本一致 可以来回切换
\ No newline at end of file
plugins {
id("kotlinx-serialization")
id("kotlin")
id("java")
}
apply(plugin = "com.github.johnrengelman.shadow")
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext
val klockVersion: String by rootProject.ext
val ktorVersion: String by rootProject.ext
val serializationVersion: String by rootProject.ext
fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
dependencies {
api(project(":mirai-core"))
api(project(":mirai-core-qqandroid"))
api(project(":mirai-api-http"))
api(project(":mirai-console"))
runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main"))
api(group = "no.tornado", name = "tornadofx", version = "1.7.19")
api("org.bouncycastle:bcprov-jdk15on:1.64")
// classpath is not set correctly by IDE
}
\ No newline at end of file
### Mirai Console Terminal
支持windows/mac/linux
在terminal环境下的Console, 由控制台富文本实现简易UI
优点: 可以在linux环境下运行/简洁使用效率高
缺点: 需要有略微的terminal知识
所使用插件系统与graphical版本一致 可以来回切换
\ No newline at end of file
plugins {
id("kotlinx-serialization")
id("kotlin")
id("java")
}
apply(plugin = "com.github.johnrengelman.shadow")
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() {
manifest {
attributes["Main-Class"] = "net.mamoe.mirai.console.MiraiConsoleTerminalLoader"
}
}
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext
val klockVersion: String by rootProject.ext
val ktorVersion: String by rootProject.ext
val serializationVersion: String by rootProject.ext
fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
dependencies {
api(project(":mirai-core"))
api(project(":mirai-core-qqandroid"))
api(project(":mirai-api-http"))
api(project(":mirai-console"))
runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main"))
runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main"))
api(group = "com.googlecode.lanterna", name = "lanterna", version = "3.0.2")
api("org.bouncycastle:bcprov-jdk15on:1.64")
// classpath is not set correctly by IDE
}
\ No newline at end of file
package net.mamoe.mirai.console
import kotlin.concurrent.thread
class MiraiConsoleTerminalLoader {
companion object {
@JvmStatic
fun main(args: Array<String>) {
MiraiConsoleTerminalUI.start()
thread {
MiraiConsole.start(
MiraiConsoleTerminalUI
)
}
Runtime.getRuntime().addShutdownHook(thread(start = false) {
MiraiConsole.stop()
})
}
}
}
\ No newline at end of file
package net.mamoe.mirai.console
import com.googlecode.lanterna.SGR
import com.googlecode.lanterna.TerminalSize
import com.googlecode.lanterna.TextColor
import com.googlecode.lanterna.graphics.TextGraphics
import com.googlecode.lanterna.input.KeyStroke
import com.googlecode.lanterna.input.KeyType
import com.googlecode.lanterna.terminal.DefaultTerminalFactory
import com.googlecode.lanterna.terminal.Terminal
import com.googlecode.lanterna.terminal.TerminalResizeListener
import com.googlecode.lanterna.terminal.swing.SwingTerminal
import com.googlecode.lanterna.terminal.swing.SwingTerminalFontConfiguration
import com.googlecode.lanterna.terminal.swing.SwingTerminalFrame
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsoleTerminalUI.LoggerDrawer.cleanPage
import net.mamoe.mirai.console.MiraiConsoleTerminalUI.LoggerDrawer.drawLog
import net.mamoe.mirai.console.MiraiConsoleTerminalUI.LoggerDrawer.redrawLogs
import java.awt.Font
import java.io.OutputStream
import java.io.PrintStream
import java.nio.charset.Charset
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedDeque
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.concurrent.thread
import kotlin.system.exitProcess
/**
* 此文件不推荐任何人看
* 可能导致
* 1:心肌梗死
* 2:呼吸困难
* 3:想要重写但是发现改任何一个看似不合理的地方都会崩
*
* @author NaturalHG
*
*/
object MiraiConsoleTerminalUI : MiraiConsoleUI {
val cacheLogSize = 50
var mainTitle = "Mirai Console v0.01 Core v0.15"
override fun pushVersion(consoleVersion: String, consoleBuild: String, coreVersion: String) {
mainTitle = "Mirai Console(Terminal) $consoleVersion $consoleBuild Core $coreVersion"
}
override fun pushLog(identity: Long, message: String) {
log[identity]!!.push(message)
if (identity == screens[currentScreenId]) {
drawLog(message)
}
}
override fun prePushBot(identity: Long) {
log[identity] = LimitLinkedQueue(cacheLogSize)
}
override fun pushBot(bot: Bot) {
botAdminCount[bot.uin] = 0
screens.add(bot.uin)
drawFrame(this.getScreenName(currentScreenId))
if (terminal is SwingTerminalFrame) {
terminal.flush()
}
}
var requesting = false
var requestResult: String? = null
override suspend fun requestInput(question: String): String {
requesting = true
while (requesting) {
delay(100)//不然会卡死 迷惑吧
}
return requestResult!!
}
fun provideInput(input: String) {
if (requesting) {
requestResult = input
requesting = false
} else {
MiraiConsole.CommandListener.commandChannel.offer(
commandBuilder.toString()
)
}
}
override fun pushBotAdminStatus(identity: Long, admins: List<Long>) {
botAdminCount[identity] = admins.size
}
val log = ConcurrentHashMap<Long, LimitLinkedQueue<String>>().also {
it[0L] = LimitLinkedQueue(cacheLogSize)
}
val botAdminCount = ConcurrentHashMap<Long, Int>()
private val screens = mutableListOf(0L)
private var currentScreenId = 0
lateinit var terminal: Terminal
lateinit var textGraphics: TextGraphics
var hasStart = false
private lateinit var internalPrinter: PrintStream
fun start() {
if (hasStart) {
return
}
internalPrinter = System.out
hasStart = true
val defaultTerminalFactory = DefaultTerminalFactory(internalPrinter, System.`in`, Charset.defaultCharset())
val fontSize = 12
defaultTerminalFactory
.setInitialTerminalSize(
TerminalSize(
101, 60
)
)
.setTerminalEmulatorFontConfiguration(
SwingTerminalFontConfiguration.newInstance(
Font("Monospaced", Font.PLAIN, fontSize)
)
)
try {
terminal = defaultTerminalFactory.createTerminal()
terminal.enterPrivateMode()
terminal.clearScreen()
terminal.setCursorVisible(false)
} catch (e: Exception) {
try {
terminal = SwingTerminalFrame("Mirai Console")
terminal.enterPrivateMode()
terminal.clearScreen()
terminal.setCursorVisible(false)
} catch (e: Exception) {
error("can not create terminal")
}
}
textGraphics = terminal.newTextGraphics()
/*
var lastRedrawTime = 0L
var lastNewWidth = 0
var lastNewHeight = 0
terminal.addResizeListener(TerminalResizeListener { terminal1: Terminal, newSize: TerminalSize ->
try {
if (lastNewHeight == newSize.rows
&&
lastNewWidth == newSize.columns
) {
return@TerminalResizeListener
}
lastNewHeight = newSize.rows
lastNewWidth = newSize.columns
terminal.clearScreen()
if(terminal !is SwingTerminalFrame) {
Thread.sleep(300)
}
update()
redrawCommand()
redrawLogs(log[screens[currentScreenId]]!!)
}catch (ignored:Exception){
}
})
*/
var lastJob: Job? = null
terminal.addResizeListener(TerminalResizeListener { terminal1: Terminal, newSize: TerminalSize ->
lastJob = GlobalScope.launch {
delay(300)
if (lastJob == coroutineContext[Job]) {
terminal.clearScreen()
//inited = false
update()
redrawCommand()
redrawLogs(log[screens[currentScreenId]]!!)
}
}
})
if (terminal !is SwingTerminalFrame) {
System.setOut(PrintStream(object : OutputStream() {
var builder = java.lang.StringBuilder()
override fun write(b: Int) {
with(b.toChar()) {
if (this == '\n') {
pushLog(0, builder.toString())
builder = java.lang.StringBuilder()
} else {
builder.append(this)
}
}
}
}))
}
System.setErr(System.out)
update()
val charList = listOf(',', '.', '/', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '=', '+', '!', ' ')
thread {
while (true) {
var keyStroke: KeyStroke = terminal.readInput()
when (keyStroke.keyType) {
KeyType.ArrowLeft -> {
currentScreenId =
getLeftScreenId()
clearRows(2)
cleanPage()
update()
}
KeyType.ArrowRight -> {
currentScreenId =
getRightScreenId()
clearRows(2)
cleanPage()
update()
}
KeyType.Enter -> {
provideInput(commandBuilder.toString())
emptyCommand()
}
KeyType.Escape -> {
exitProcess(0)
}
else -> {
if (keyStroke.character != null) {
if (keyStroke.character.toInt() == 8) {
deleteCommandChar()
}
if (keyStroke.character.isLetterOrDigit() || charList.contains(keyStroke.character)) {
addCommandChar(keyStroke.character)
}
}
}
}
}
}
}
private fun getLeftScreenId(): Int {
var newId = currentScreenId - 1
if (newId < 0) {
newId = screens.size - 1
}
return newId
}
private fun getRightScreenId(): Int {
var newId = 1 + currentScreenId
if (newId >= screens.size) {
newId = 0
}
return newId
}
private fun getScreenName(id: Int): String {
return when (screens[id]) {
0L -> {
"Console Screen"
}
else -> {
"Bot: ${screens[id]}"
}
}
}
fun clearRows(row: Int) {
textGraphics.putString(
0, row, " ".repeat(
terminal.terminalSize.columns
)
)
}
fun drawFrame(
title: String
) {
val width = terminal.terminalSize.columns
val height = terminal.terminalSize.rows
terminal.setBackgroundColor(TextColor.ANSI.DEFAULT)
textGraphics.foregroundColor = TextColor.ANSI.WHITE
textGraphics.backgroundColor = TextColor.ANSI.GREEN
textGraphics.putString((width - mainTitle.length) / 2, 1, mainTitle, SGR.BOLD)
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
textGraphics.putString(2, 3, "-".repeat(width - 4))
textGraphics.putString(2, 5, "-".repeat(width - 4))
textGraphics.putString(2, height - 4, "-".repeat(width - 4))
textGraphics.putString(2, height - 3, "|>>>")
textGraphics.putString(width - 3, height - 3, "|")
textGraphics.putString(2, height - 2, "-".repeat(width - 4))
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
val leftName =
getScreenName(getLeftScreenId())
// clearRows(2)
textGraphics.putString((width - title.length) / 2 - "$leftName << ".length, 2, "$leftName << ")
textGraphics.foregroundColor = TextColor.ANSI.WHITE
textGraphics.backgroundColor = TextColor.ANSI.YELLOW
textGraphics.putString((width - title.length) / 2, 2, title, SGR.BOLD)
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
val rightName =
getScreenName(getRightScreenId())
textGraphics.putString((width + title.length) / 2 + 1, 2, ">> $rightName")
}
fun drawMainFrame(
onlineBotCount: Number
) {
drawFrame("Console Screen")
val width = terminal.terminalSize.columns
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
clearRows(4)
textGraphics.putString(2, 4, "|Online Bots: $onlineBotCount")
textGraphics.putString(
width - 2 - "Powered By Mamoe Technologies|".length,
4,
"Powered By Mamoe Technologies|"
)
}
fun drawBotFrame(
qq: Long,
adminCount: Number
) {
drawFrame("Bot: $qq")
val width = terminal.terminalSize.columns
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
clearRows(4)
textGraphics.putString(2, 4, "|Admins: $adminCount")
textGraphics.putString(width - 2 - "Add admins via commands|".length, 4, "Add admins via commands|")
}
object LoggerDrawer {
var currentHeight = 6
fun drawLog(string: String, flush: Boolean = true) {
val maxHeight = terminal.terminalSize.rows - 4
val heightNeed = (string.length / (terminal.terminalSize.columns - 6)) + 1
if (heightNeed - 1 > maxHeight) {
return//拒绝打印
}
if (currentHeight + heightNeed > maxHeight) {
cleanPage()
}
val width = terminal.terminalSize.columns - 7
var x = string
while (true) {
if (x == "") break
val toWrite = if (x.length > width) {
x.substring(0, width).also {
x = x.substring(width)
}
} else {
x.also {
x = ""
}
}
try {
textGraphics.foregroundColor = TextColor.ANSI.GREEN
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
textGraphics.putString(
3,
currentHeight, toWrite, SGR.ITALIC
)
} catch (ignored: Exception) {
//
}
++currentHeight
}
if (flush && terminal is SwingTerminalFrame) {
terminal.flush()
}
}
fun cleanPage() {
for (index in 6 until terminal.terminalSize.rows - 4) {
clearRows(index)
}
currentHeight = 6
}
fun redrawLogs(toDraw: Queue<String>) {
//this.cleanPage()
currentHeight = 6
var logsToDraw = 0
var vara = 0
val toPrint = mutableListOf<String>()
toDraw.forEach {
val heightNeed = (it.length / (terminal.terminalSize.columns - 6)) + 1
vara += heightNeed
if (currentHeight + vara < terminal.terminalSize.rows - 4) {
logsToDraw++
toPrint.add(it)
} else {
return@forEach
}
}
toPrint.reversed().forEach {
drawLog(it, false)
}
if (terminal is SwingTerminalFrame) {
terminal.flush()
}
}
}
var commandBuilder = StringBuilder()
fun redrawCommand() {
val height = terminal.terminalSize.rows
val width = terminal.terminalSize.columns
clearRows(height - 3)
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.putString(2, height - 3, "|>>>")
textGraphics.putString(width - 3, height - 3, "|")
textGraphics.foregroundColor = TextColor.ANSI.WHITE
textGraphics.backgroundColor = TextColor.ANSI.BLACK
textGraphics.putString(7, height - 3, commandBuilder.toString())
if (terminal is SwingTerminalFrame) {
terminal.flush()
}
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
}
private fun addCommandChar(
c: Char
) {
if (!requesting && commandBuilder.isEmpty() && c != '/') {
addCommandChar('/')
}
textGraphics.foregroundColor = TextColor.ANSI.WHITE
textGraphics.backgroundColor = TextColor.ANSI.BLACK
val height = terminal.terminalSize.rows
commandBuilder.append(c)
if (terminal is SwingTerminalFrame) {
redrawCommand()
} else {
textGraphics.putString(6 + commandBuilder.length, height - 3, c.toString())
}
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
}
private fun deleteCommandChar() {
if (!commandBuilder.isEmpty()) {
commandBuilder = StringBuilder(commandBuilder.toString().substring(0, commandBuilder.length - 1))
}
val height = terminal.terminalSize.rows
if (terminal is SwingTerminalFrame) {
redrawCommand()
} else {
textGraphics.putString(7 + commandBuilder.length, height - 3, " ")
}
}
var lastEmpty: Job? = null
private fun emptyCommand() {
commandBuilder = StringBuilder()
if (terminal is SwingTerminal) {
redrawCommand()
terminal.flush()
} else {
lastEmpty = GlobalScope.launch {
delay(100)
if (lastEmpty == coroutineContext[Job]) {
terminal.clearScreen()
//inited = false
update()
redrawCommand()
redrawLogs(log[screens[currentScreenId]]!!)
}
}
}
}
fun update() {
when (screens[currentScreenId]) {
0L -> {
drawMainFrame(screens.size - 1)
}
else -> {
drawBotFrame(
screens[currentScreenId],
0
)
}
}
redrawLogs(log[screens[currentScreenId]]!!)
}
}
class LimitLinkedQueue<T>(
val limit: Int = 50
) : ConcurrentLinkedDeque<T>() {
override fun push(e: T) {
if (size >= limit) {
this.pollLast()
}
return super.push(e)
}
}
......@@ -8,12 +8,6 @@ plugins {
apply(plugin = "com.github.johnrengelman.shadow")
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() {
manifest {
attributes["Main-Class"] = "net.mamoe.mirai.console.MiraiConsoleLoader"
}
}
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
......
......@@ -11,7 +11,6 @@ package net.mamoe.mirai.console
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.api.http.MiraiHttpAPIServer
import net.mamoe.mirai.api.http.generateSessionKey
import net.mamoe.mirai.console.plugins.PluginManager
......@@ -19,7 +18,7 @@ import net.mamoe.mirai.console.plugins.loadAsConfig
import net.mamoe.mirai.console.plugins.withDefaultWrite
import net.mamoe.mirai.console.plugins.withDefaultWriteSave
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.utils.SimpleLogger
import net.mamoe.mirai.utils.*
import java.io.File
import java.util.*
import java.util.concurrent.LinkedBlockingQueue
......@@ -46,12 +45,19 @@ object MiraiConsole {
var path: String = System.getProperty("user.dir")
val version = "0.01"
var coreVersion = "0.15"
val version = "v0.01"
var coreVersion = "v0.15.1"
val build = "Beta"
fun start() {
logger("Mirai-console [v$version $build | core version v$coreVersion] is still in testing stage, majority feature is available")
lateinit var frontEnd: MiraiConsoleUI
fun start(
frontEnd: MiraiConsoleUI
) {
this.frontEnd = frontEnd
frontEnd.pushVersion(
version, build, coreVersion
)
logger("Mirai-console [$version $build | core version $coreVersion] is still in testing stage, majority feature is available")
logger(
"Mirai-console now running under " + System.getProperty(
"user.dir"
......@@ -115,12 +121,47 @@ object MiraiConsole {
logger("[Bot Login]", 0, "login...")
try {
runBlocking {
Bot(qqNumber, qqPassword).alsoLogin()
frontEnd.prePushBot(qqNumber)
val bot = Bot(qqNumber, qqPassword) {
this.loginSolver = DefaultLoginSolver(object : LoginSolverInputReader {
override suspend fun read(question: String): String? {
return frontEnd.requestInput(question)
}
},
SimpleLogger("Login Helper") { _, message, e ->
logger("[Login Helper]", qqNumber, message)
if (e != null) {
logger("[NETWORK ERROR]", qqNumber, e.toString())//因为在一页 所以可以不打QQ
e.printStackTrace()
}
}
)
this.botLoggerSupplier = {
SimpleLogger("BOT $qqNumber]") { _, message, e ->
logger("[BOT $qqNumber]", qqNumber, message)
if (e != null) {
logger("[NETWORK ERROR]", qqNumber, e.toString())//因为在一页 所以可以不打QQ
e.printStackTrace()
}
}
}
this.networkLoggerSupplier = {
SimpleLogger("BOT $qqNumber") { _, message, e ->
logger("[NETWORK]", qqNumber, message)//因为在一页 所以可以不打QQ
if (e != null) {
logger("[NETWORK ERROR]", qqNumber, e.toString())//因为在一页 所以可以不打QQ
e.printStackTrace()
}
}
}
}
bot.login()
logger(
"[Bot Login]",
0,
"$qqNumber login successes"
)
frontEnd.pushBot(bot)
}
} catch (e: Exception) {
logger(
......@@ -128,7 +169,6 @@ object MiraiConsole {
0,
"$qqNumber login failed -> " + e.message
)
e.printStackTrace()
}
true
}
......@@ -283,7 +323,7 @@ object MiraiConsole {
operator fun invoke(identityStr: String, identity: Long, any: Any? = null) {
if (any != null) {
MiraiConsoleUI.pushLog(identity, "$identityStr: $any")
frontEnd.pushLog(identity, "$identityStr: $any")
}
}
}
......@@ -301,18 +341,5 @@ object MiraiConsole {
}
class MiraiConsoleLoader {
companion object {
@JvmStatic
fun main(args: Array<String>) {
MiraiConsoleUI.start()
MiraiConsole.start()
Runtime.getRuntime().addShutdownHook(thread(start = false) {
MiraiConsole.stop()
})
}
}
}
package net.mamoe.mirai.console
import com.googlecode.lanterna.SGR
import com.googlecode.lanterna.TerminalSize
import com.googlecode.lanterna.TextColor
import com.googlecode.lanterna.graphics.TextGraphics
import com.googlecode.lanterna.input.KeyStroke
import com.googlecode.lanterna.input.KeyType
import com.googlecode.lanterna.terminal.DefaultTerminalFactory
import com.googlecode.lanterna.terminal.Terminal
import com.googlecode.lanterna.terminal.TerminalResizeListener
import com.googlecode.lanterna.terminal.swing.SwingTerminal
import com.googlecode.lanterna.terminal.swing.SwingTerminalFontConfiguration
import com.googlecode.lanterna.terminal.swing.SwingTerminalFrame
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.mamoe.mirai.console.MiraiConsoleUI.LoggerDrawer.cleanPage
import net.mamoe.mirai.console.MiraiConsoleUI.LoggerDrawer.drawLog
import net.mamoe.mirai.console.MiraiConsoleUI.LoggerDrawer.redrawLogs
import java.awt.Font
import java.io.OutputStream
import java.io.PrintStream
import java.nio.charset.Charset
import java.util.*
import kotlin.concurrent.thread
import net.mamoe.mirai.Bot
/**
* 此文件不推荐任何人看
* 可能导致
* 1:心肌梗死
* 2:呼吸困难
* 3:想要重写但是发现改任何一个看似不合理的地方都会崩
*
* @author NaturalHG
*
* 只需要实现一个这个 传入MiraiConsole 就可以绑定UI层与Console层
* 注意线程
*/
@SuppressWarnings("UNCHECKED")
object MiraiConsoleUI {
val cacheLogSize = 50
interface MiraiConsoleUI {
/**
* 让UI层展示一条log
*
* identity:log所属的screen, Main=0; Bot=Bot.uin
*/
fun pushLog(
identity: Long,
message: String
)
/**
* 让UI层准备接受新增的一个BOT
*/
fun prePushBot(
identity: Long
)
/**
* 让UI层接受一个新的bot
* */
fun pushBot(
bot: Bot
)
fun pushVersion(
consoleVersion: String,
consoleBuild: String,
coreVersion: String
)
/**
* 让UI层提供一个Input
* 这个Input 不 等于 Command
*
*/
suspend fun requestInput(
question: String
): String
/**
* 让UI层更新BOT管理员的数据
*/
fun pushBotAdminStatus(
identity: Long,
admins: List<Long>
)
val log = mutableMapOf<Long, LimitLinkedQueue<String>>().also {
it[0L] =
LimitLinkedQueue(cacheLogSize)
it[2821869985L] =
LimitLinkedQueue(cacheLogSize)
}
val botAdminCount = mutableMapOf<Long, Long>()
private val screens = mutableListOf(0L, 2821869985L)
private var currentScreenId = 0
fun addBotScreen(uin: Long) {
screens.add(uin)
log[uin] =
LimitLinkedQueue(cacheLogSize)
botAdminCount[uin] = 0
}
lateinit var terminal: Terminal
lateinit var textGraphics: TextGraphics
var hasStart = false
private lateinit var internalPrinter: PrintStream
fun start() {
if (hasStart) {
return
}
internalPrinter = System.out
hasStart = true
val defaultTerminalFactory = DefaultTerminalFactory(internalPrinter, System.`in`, Charset.defaultCharset())
val fontSize = 12
defaultTerminalFactory
.setInitialTerminalSize(
TerminalSize(
101, 60
)
)
.setTerminalEmulatorFontConfiguration(
SwingTerminalFontConfiguration.newInstance(
Font("Monospaced", Font.PLAIN, fontSize)
)
)
try {
terminal = defaultTerminalFactory.createTerminal()
terminal.enterPrivateMode()
terminal.clearScreen()
terminal.setCursorVisible(false)
} catch (e: Exception) {
try {
terminal = SwingTerminalFrame("Mirai Console")
terminal.enterPrivateMode()
terminal.clearScreen()
terminal.setCursorVisible(false)
} catch (e: Exception) {
error("can not create terminal")
}
}
textGraphics = terminal.newTextGraphics()
/*
var lastRedrawTime = 0L
var lastNewWidth = 0
var lastNewHeight = 0
terminal.addResizeListener(TerminalResizeListener { terminal1: Terminal, newSize: TerminalSize ->
try {
if (lastNewHeight == newSize.rows
&&
lastNewWidth == newSize.columns
) {
return@TerminalResizeListener
}
lastNewHeight = newSize.rows
lastNewWidth = newSize.columns
terminal.clearScreen()
if(terminal !is SwingTerminalFrame) {
Thread.sleep(300)
}
update()
redrawCommand()
redrawLogs(log[screens[currentScreenId]]!!)
}catch (ignored:Exception){
}
})
*/
var lastJob: Job? = null
terminal.addResizeListener(TerminalResizeListener { terminal1: Terminal, newSize: TerminalSize ->
lastJob = GlobalScope.launch {
delay(300)
if (lastJob == coroutineContext[Job]) {
terminal.clearScreen()
//inited = false
update()
redrawCommand()
redrawLogs(log[screens[currentScreenId]]!!)
}
}
})
if (terminal !is SwingTerminalFrame) {
System.setOut(PrintStream(object : OutputStream() {
var builder = java.lang.StringBuilder()
override fun write(b: Int) {
with(b.toChar()) {
if (this == '\n') {
pushLog(0, builder.toString())
builder = java.lang.StringBuilder()
} else {
builder.append(this)
}
}
}
}))
}
System.setErr(System.out)
update()
val charList = listOf(',', '.', '/', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '=', '+', '!', ' ')
thread {
while (true) {
var keyStroke: KeyStroke = terminal.readInput()
when (keyStroke.keyType) {
KeyType.ArrowLeft -> {
currentScreenId =
getLeftScreenId()
clearRows(2)
cleanPage()
update()
}
KeyType.ArrowRight -> {
currentScreenId =
getRightScreenId()
clearRows(2)
cleanPage()
update()
}
KeyType.Enter -> {
MiraiConsole.CommandListener.commandChannel.offer(
commandBuilder.toString()
)
emptyCommand()
}
else -> {
if (keyStroke.character != null) {
if (keyStroke.character.toInt() == 8) {
deleteCommandChar()
}
if (keyStroke.character.isLetterOrDigit() || charList.contains(keyStroke.character)) {
addCommandChar(keyStroke.character)
}
}
}
}
}
}
}
private fun getLeftScreenId(): Int {
var newId = currentScreenId - 1
if (newId < 0) {
newId = screens.size - 1
}
return newId
}
private fun getRightScreenId(): Int {
var newId = 1 + currentScreenId
if (newId >= screens.size) {
newId = 0
}
return newId
}
private fun getScreenName(id: Int): String {
return when (screens[id]) {
0L -> {
"Console Screen"
}
else -> {
"Bot: ${screens[id]}"
}
}
}
fun clearRows(row: Int) {
textGraphics.putString(
0, row, " ".repeat(
terminal.terminalSize.columns
)
)
}
fun drawFrame(
title: String
) {
val width = terminal.terminalSize.columns
val height = terminal.terminalSize.rows
terminal.setBackgroundColor(TextColor.ANSI.DEFAULT)
val mainTitle = "Mirai Console v0.01 Core v0.15"
textGraphics.foregroundColor = TextColor.ANSI.WHITE
textGraphics.backgroundColor = TextColor.ANSI.GREEN
textGraphics.putString((width - mainTitle.length) / 2, 1, mainTitle, SGR.BOLD)
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
textGraphics.putString(2, 3, "-".repeat(width - 4))
textGraphics.putString(2, 5, "-".repeat(width - 4))
textGraphics.putString(2, height - 4, "-".repeat(width - 4))
textGraphics.putString(2, height - 3, "|>>>")
textGraphics.putString(width - 3, height - 3, "|")
textGraphics.putString(2, height - 2, "-".repeat(width - 4))
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
val leftName =
getScreenName(getLeftScreenId())
// clearRows(2)
textGraphics.putString((width - title.length) / 2 - "$leftName << ".length, 2, "$leftName << ")
textGraphics.foregroundColor = TextColor.ANSI.WHITE
textGraphics.backgroundColor = TextColor.ANSI.YELLOW
textGraphics.putString((width - title.length) / 2, 2, title, SGR.BOLD)
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
val rightName =
getScreenName(getRightScreenId())
textGraphics.putString((width + title.length) / 2 + 1, 2, ">> $rightName")
}
fun drawMainFrame(
onlineBotCount: Number
) {
drawFrame("Console Screen")
val width = terminal.terminalSize.columns
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
clearRows(4)
textGraphics.putString(2, 4, "|Online Bots: $onlineBotCount")
textGraphics.putString(
width - 2 - "Powered By Mamoe Technologies|".length,
4,
"Powered By Mamoe Technologies|"
)
}
fun drawBotFrame(
qq: Long,
adminCount: Number
) {
drawFrame("Bot: $qq")
val width = terminal.terminalSize.columns
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
clearRows(4)
textGraphics.putString(2, 4, "|Admins: $adminCount")
textGraphics.putString(width - 2 - "Add admins via commands|".length, 4, "Add admins via commands|")
}
object LoggerDrawer {
var currentHeight = 6
fun drawLog(string: String, flush: Boolean = true) {
val maxHeight = terminal.terminalSize.rows - 4
val heightNeed = (string.length / (terminal.terminalSize.columns - 6)) + 1
if (currentHeight + heightNeed > maxHeight) {
cleanPage()
}
val width = terminal.terminalSize.columns - 7
var x = string
while (true) {
if (x == "") break
val toWrite = if (x.length > width) {
x.substring(0, width).also {
x = x.substring(width)
}
} else {
x.also {
x = ""
}
}
try {
textGraphics.foregroundColor = TextColor.ANSI.GREEN
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
textGraphics.putString(
3,
currentHeight, toWrite, SGR.ITALIC
)
} catch (ignored: Exception) {
//
}
++currentHeight
}
if (flush && terminal is SwingTerminalFrame) {
terminal.flush()
}
}
fun cleanPage() {
for (index in 6 until terminal.terminalSize.rows - 4) {
clearRows(index)
}
currentHeight = 6
}
fun redrawLogs(toDraw: List<String>) {
//this.cleanPage()
currentHeight = 6
var logsToDraw = 0
var vara = 0
toDraw.forEach {
val heightNeed = (it.length / (terminal.terminalSize.columns - 6)) + 1
vara += heightNeed
if (currentHeight + vara < terminal.terminalSize.rows - 4) {
logsToDraw++
} else {
return@forEach
}
}
for (index in 0 until logsToDraw) {
drawLog(toDraw[logsToDraw - index - 1], false)
}
if (terminal is SwingTerminalFrame) {
terminal.flush()
}
}
}
fun pushLog(uin: Long, str: String) {
log[uin]!!.push(str)
if (uin == screens[currentScreenId]) {
drawLog(str)
}
}
var commandBuilder = StringBuilder()
fun redrawCommand() {
val height = terminal.terminalSize.rows
val width = terminal.terminalSize.columns
clearRows(height - 3)
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.putString(2, height - 3, "|>>>")
textGraphics.putString(width - 3, height - 3, "|")
textGraphics.foregroundColor = TextColor.ANSI.WHITE
textGraphics.backgroundColor = TextColor.ANSI.BLACK
textGraphics.putString(7, height - 3, commandBuilder.toString())
if (terminal is SwingTerminalFrame) {
terminal.flush()
}
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
}
private fun addCommandChar(
c: Char
) {
if (commandBuilder.isEmpty() && c != '/') {
addCommandChar('/')
}
textGraphics.foregroundColor = TextColor.ANSI.WHITE
textGraphics.backgroundColor = TextColor.ANSI.BLACK
val height = terminal.terminalSize.rows
commandBuilder.append(c)
if (terminal is SwingTerminalFrame) {
redrawCommand()
} else {
textGraphics.putString(6 + commandBuilder.length, height - 3, c.toString())
}
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
}
private fun deleteCommandChar() {
if (!commandBuilder.isEmpty()) {
commandBuilder = StringBuilder(commandBuilder.toString().substring(0, commandBuilder.length - 1))
}
val height = terminal.terminalSize.rows
if (terminal is SwingTerminalFrame) {
redrawCommand()
} else {
textGraphics.putString(7 + commandBuilder.length, height - 3, " ")
}
}
var lastEmpty: Job? = null
private fun emptyCommand() {
commandBuilder = StringBuilder()
if (terminal is SwingTerminal) {
redrawCommand()
terminal.flush()
} else {
lastEmpty = GlobalScope.launch {
delay(100)
if (lastEmpty == coroutineContext[Job]) {
terminal.clearScreen()
//inited = false
update()
redrawCommand()
redrawLogs(log[screens[currentScreenId]]!!)
}
}
}
}
fun update() {
when (screens[currentScreenId]) {
0L -> {
drawMainFrame(screens.size - 1)
}
else -> {
drawBotFrame(
screens[currentScreenId],
0
)
}
}
redrawLogs(log[screens[currentScreenId]]!!)
}
}
class LimitLinkedQueue<T>(
val limit: Int = 50
) : LinkedList<T>(), List<T> {
override fun push(e: T) {
if (size >= limit) {
pollLast()
}
super.push(e)
}
}
\ No newline at end of file
......@@ -39,53 +39,79 @@ import kotlin.coroutines.EmptyCoroutineContext
actual var defaultLoginSolver: LoginSolver = DefaultLoginSolver()
internal class DefaultLoginSolver : LoginSolver() {
interface LoginSolverInputReader{
suspend fun read(question:String):String?
suspend operator fun invoke(question: String):String?{
return read(question)
}
}
class DefaultLoginSolverInputReader: LoginSolverInputReader{
override suspend fun read(question: String): String? {
return readLine()
}
}
class DefaultLoginSolver(
val reader: LoginSolverInputReader = DefaultLoginSolverInputReader(),
val overrideLogger:MiraiLogger? = null
) : LoginSolver() {
fun getLogger(bot: Bot):MiraiLogger{
if(overrideLogger!=null){
return overrideLogger
}
return bot.logger
}
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? = loginSolverLock.withLock {
val logger = getLogger(bot)
val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
withContext(Dispatchers.IO) {
tempFile.createNewFile()
bot.logger.info("需要图片验证码登录, 验证码为 4 字母")
logger.info("需要图片验证码登录, 验证码为 4 字母")
try {
tempFile.writeChannel().apply { writeFully(data); close() }
bot.logger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}")
logger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}")
} catch (e: Exception) {
bot.logger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片")
logger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片")
}
tempFile.inputStream().use {
val img = ImageIO.read(it)
if (img == null) {
bot.logger.info("无法创建字符图片. 请查看文件")
logger.info("无法创建字符图片. 请查看文件")
} else {
bot.logger.info(img.createCharImg())
logger.info(img.createCharImg())
}
}
}
bot.logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车")
return readLine()!!.takeUnless { it.isEmpty() || it.length != 4 }.also {
bot.logger.info("正在提交[$it]中...")
logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车")
return reader("请输入 4 位字母验证码. 若要更换验证码, 请直接回车")!!.takeUnless { it.isEmpty() || it.length != 4 }.also {
logger.info("正在提交[$it]中...")
}
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? = loginSolverLock.withLock {
bot.logger.info("需要滑动验证码")
bot.logger.info("请在任意浏览器中打开以下链接并完成验证码. ")
bot.logger.info("完成后请输入任意字符 ")
bot.logger.info(url)
return readLine().also {
bot.logger.info("正在提交中...")
val logger = getLogger(bot)
logger.info("需要滑动验证码")
logger.info("请在任意浏览器中打开以下链接并完成验证码. ")
logger.info("完成后请输入任意字符 ")
logger.info(url)
return reader("完成后请输入任意字符").also {
logger.info("正在提交中...")
}
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? = loginSolverLock.withLock {
bot.logger.info("需要进行账户安全认证")
bot.logger.info("该账户有[设备锁]/[不常用登陆地点]/[不常用设备登陆]的问题")
bot.logger.info("完成以下账号认证即可成功登陆|理论本认证在mirai每个账户中最多出现1次")
bot.logger.info("请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符")
bot.logger.info("这步操作将在后续的版本中优化")
bot.logger.info(url)
return readLine().also {
bot.logger.info("正在提交中...")
val logger = getLogger(bot)
logger.info("需要进行账户安全认证")
logger.info("该账户有[设备锁]/[不常用登陆地点]/[不常用设备登陆]的问题")
logger.info("完成以下账号认证即可成功登陆|理论本认证在mirai每个账户中最多出现1次")
logger.info("请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符")
logger.info("这步操作将在后续的版本中优化")
logger.info(url)
return reader("完成后请输入任意字符").also {
logger.info("正在提交中...")
}
}
......
......@@ -44,6 +44,8 @@ include(':mirai-core-qqandroid')
include(':mirai-japt')
include(':mirai-console')
include(':mirai-console-graphical')
include(':mirai-console-terminal')
//include(':mirai-api')
include(':mirai-api-http')
include(':mirai-demos:mirai-demo-1')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment