LoginSolver swing (#257)

* LoginSolver, gui support

* make `internal`,`object`, and rename.
This commit is contained in:
Karlatemp 2020-04-24 10:03:27 +08:00 committed by GitHub
parent 03cffb897c
commit 0ede49896e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 170 additions and 4 deletions

View File

@ -0,0 +1,84 @@
/*
* Copyright 2020 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
*/
package net.mamoe.mirai.utils
/**
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
*/
import kotlinx.coroutines.CompletableDeferred
import java.awt.BorderLayout
import java.awt.Desktop
import java.awt.event.KeyEvent
import java.awt.event.KeyListener
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import javax.swing.JFrame
import javax.swing.JTextField
internal class WindowInitialzier(private val initializer: WindowInitialzier.(JFrame) -> Unit) {
private lateinit var frame0: JFrame
val frame: JFrame get() = frame0
fun java.awt.Component.append() {
frame.add(this, BorderLayout.NORTH);
}
fun java.awt.Component.last() {
frame.add(this);
}
internal fun init(frame: JFrame) {
this.frame0 = frame;
initializer(frame)
}
}
internal suspend fun openWindow(title: String = "", initializer: WindowInitialzier.(JFrame) -> Unit = {}): String {
return openWindow(title, WindowInitialzier(initializer))
}
internal suspend fun openWindow(title: String = "", initializer: WindowInitialzier = WindowInitialzier {}): String {
val frame = JFrame()
val value = JTextField()
val def = CompletableDeferred<String>()
value.addKeyListener(object : KeyListener {
override fun keyTyped(e: KeyEvent?) {
}
override fun keyPressed(e: KeyEvent?) {
when (e!!.keyCode) {
27, 10 -> {
def.complete(value.text)
}
}
}
override fun keyReleased(e: KeyEvent?) {
}
})
frame.layout = BorderLayout(10, 5)
frame.add(value, BorderLayout.SOUTH)
initializer.init(frame)
frame.pack()
frame.defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE
frame.addWindowListener(object : WindowAdapter() {
override fun windowClosing(e: WindowEvent?) {
def.complete(value.text)
}
})
frame.setLocationRelativeTo(null)
frame.title = title
frame.isVisible = true
val result = def.await()
frame.dispose()
return result
}

View File

@ -22,19 +22,46 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.core.use import kotlinx.io.core.use
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.BotNetworkHandler import java.awt.Desktop
import java.awt.Image import java.awt.Image
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.File import java.io.File
import java.io.RandomAccessFile import java.io.RandomAccessFile
import javax.imageio.ImageIO import javax.imageio.ImageIO
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
actual typealias Throws = kotlin.jvm.Throws actual typealias Throws = kotlin.jvm.Throws
@MiraiExperimentalAPI @MiraiExperimentalAPI
class DefaultLoginSolver( class DefaultLoginSolver(
val input: suspend () -> String,
val overrideLogger: MiraiLogger? = null
) : LoginSolver() {
private val degelate: LoginSolver
init {
if (Desktop.isDesktopSupported()) {
degelate = SwingSolver
} else {
degelate = DefaultLoginSolverImpl(input, overrideLogger)
}
}
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? {
return degelate.onSolvePicCaptcha(bot, data)
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
return degelate.onSolveSliderCaptcha(bot, url)
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
return degelate.onSolveUnsafeDeviceLoginVerify(bot, url)
}
}
@MiraiExperimentalAPI
class DefaultLoginSolverImpl(
private val input: suspend () -> String, private val input: suspend () -> String,
private val overrideLogger: MiraiLogger? = null private val overrideLogger: MiraiLogger? = null
) : LoginSolver() { ) : LoginSolver() {
@ -106,7 +133,14 @@ actual abstract class LoginSolver {
actual companion object { actual companion object {
actual val Default: LoginSolver actual val Default: LoginSolver
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
get() = DefaultLoginSolver(input = { withContext(Dispatchers.IO) { readLine() } ?: error("No standard input") }) get() {
if (Desktop.isDesktopSupported()) {
return SwingSolver
}
return DefaultLoginSolverImpl(input = {
withContext(Dispatchers.IO) { readLine() } ?: error("No standard input")
})
}
} }
} }
@ -149,7 +183,8 @@ private fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Doub
return (r * 30 + g * 59 + b * 11 + 50) / 100 return (r * 30 + g * 59 + b * 11 + 50) / 100
} }
fun grayCompare(g1: Int, g2: Int): Boolean = kotlin.math.min(g1, g2).toDouble() / kotlin.math.max(g1, g2) >= ignoreRate fun grayCompare(g1: Int, g2: Int): Boolean =
kotlin.math.min(g1, g2).toDouble() / kotlin.math.max(g1, g2) >= ignoreRate
val background = gray(image.getRGB(0, 0)) val background = gray(image.getRGB(0, 0))

View File

@ -0,0 +1,47 @@
/*
* Copyright 2020 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
*/
package net.mamoe.mirai.utils
import net.mamoe.mirai.Bot
import java.awt.Desktop
import java.net.URI
import javax.imageio.ImageIO
import javax.swing.ImageIcon
import javax.swing.JLabel
import javax.swing.JTextField
/**
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
*/
@MiraiExperimentalAPI
object SwingSolver : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? {
return openWindow("Mirai PicCaptcha(${bot.id})") {
val image = ImageIO.read(data.inputStream())
JLabel(ImageIcon(image)).append()
}
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
return openWindow("Mirai SliderCaptcha(${bot.id})") {
JLabel("需要滑动验证码, 完成后请关闭该窗口").append()
Desktop.getDesktop().browse(URI(url))
}
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
return openWindow("Mirai UnsafeDeviceLoginVerify(${bot.id})") {
JLabel("需要进行账户安全认证").last()
JLabel("该账户有[设备锁]/[不常用登录地点]/[不常用设备登录]的问题").last()
JLabel("完成以下账号认证即可成功登录|理论本认证在mirai每个账户中最多出现1次").last()
JTextField(url).append()
}
}
}