From 0ede49896e948bdd7898f8ab964d7fba80ada008 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Fri, 24 Apr 2020 10:03:27 +0800 Subject: [PATCH] LoginSolver swing (#257) * LoginSolver, gui support * make `internal`,`object`, and rename. --- .../kotlin/net/mamoe/mirai/utils/JWTHelper.kt | 84 +++++++++++++++++++ .../net/mamoe/mirai/utils/LoginSolver.jvm.kt | 43 +++++++++- .../mirai/utils/LoginSolver.swing.jvm.kt | 47 +++++++++++ 3 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/JWTHelper.kt create mode 100644 mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.swing.jvm.kt diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/JWTHelper.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/JWTHelper.kt new file mode 100644 index 000000000..de22d2b76 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/JWTHelper.kt @@ -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 + */ + +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() + 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 +} diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.jvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.jvm.kt index bcf31d57a..8ea55e482 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.jvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.jvm.kt @@ -22,19 +22,46 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.io.core.use import net.mamoe.mirai.Bot -import net.mamoe.mirai.network.BotNetworkHandler +import java.awt.Desktop import java.awt.Image import java.awt.image.BufferedImage import java.io.File import java.io.RandomAccessFile import javax.imageio.ImageIO import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext actual typealias Throws = kotlin.jvm.Throws @MiraiExperimentalAPI 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 overrideLogger: MiraiLogger? = null ) : LoginSolver() { @@ -106,7 +133,14 @@ actual abstract class LoginSolver { actual companion object { actual val Default: LoginSolver @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 } - 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)) diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.swing.jvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.swing.jvm.kt new file mode 100644 index 000000000..f10cb6330 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.swing.jvm.kt @@ -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 + */ +@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() + } + } +} \ No newline at end of file