[core] Implementation of BotAuthorization

This commit is contained in:
Karlatemp 2023-02-24 21:09:36 +08:00
parent e487ec78a4
commit 6836368f14
No known key found for this signature in database
GPG Key ID: BA173CA2B9956C59
7 changed files with 494 additions and 46 deletions

View File

@ -11,6 +11,7 @@
package net.mamoe.mirai
import net.mamoe.mirai.auth.BotAuthorization
import net.mamoe.mirai.utils.BotConfiguration
import kotlin.jvm.JvmSynthetic
@ -111,6 +112,17 @@ public interface BotFactory {
*/
public fun newBot(qq: Long, passwordMd5: ByteArray): Bot = newBot(qq, passwordMd5, BotConfiguration.Default)
public fun newBot(qq: Long, authorization: BotAuthorization): Bot =
newBot(qq, authorization, BotConfiguration.Default)
public fun newBot(qq: Long, authorization: BotAuthorization, configuration: BotConfiguration): Bot
public fun newBot(
qq: Long,
authorization: BotAuthorization,
configuration: BotConfigurationLambda /* = BotConfiguration.() -> Unit */
): Bot = newBot(qq, authorization, configuration.run { BotConfiguration().apply { invoke() } })
public companion object INSTANCE : BotFactory {
override fun newBot(qq: Long, password: String, configuration: BotConfiguration): Bot {
return Mirai.BotFactory.newBot(qq, password, configuration)
@ -160,5 +172,9 @@ public interface BotFactory {
passwordMd5: ByteArray,
configuration: BotConfiguration.() -> Unit /* = BotConfiguration.() -> Unit */
): Bot = newBot(qq, passwordMd5, BotConfiguration().apply(configuration))
override fun newBot(qq: Long, authorization: BotAuthorization, configuration: BotConfiguration): Bot {
return Mirai.BotFactory.newBot(qq, authorization, configuration)
}
}
}

View File

@ -15,6 +15,7 @@ package net.mamoe.mirai.internal
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotFactory
import net.mamoe.mirai.auth.BotAuthorization
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.DeprecatedSinceMirai
@ -39,4 +40,8 @@ internal object BotFactoryImpl : BotFactory {
passwordMd5: ByteArray,
configuration: BotConfiguration
): Bot = QQAndroidBot(BotAccount(qq, passwordMd5), configuration)
override fun newBot(qq: Long, authorization: BotAuthorization, configuration: BotConfiguration): Bot {
return QQAndroidBot(BotAccount(qq, authorization), configuration)
}
}

View File

@ -18,6 +18,8 @@ import net.mamoe.mirai.utils.lateinitMutableProperty
internal interface BotClientHolder {
var client: QQAndroidClient
fun refreshClient()
companion object : ComponentKey<BotClientHolder>
}
@ -27,6 +29,10 @@ internal class BotClientHolderImpl(
) : BotClientHolder {
override var client: QQAndroidClient by lateinitMutableProperty { createClient(bot) }
override fun refreshClient() {
client = createClient(bot)
}
private fun createClient(bot: QQAndroidBot): QQAndroidClient {
val ssoContext = bot.components[SsoProcessorContext]
val device = ssoContext.device

View File

@ -34,11 +34,6 @@ internal interface QRCodeLoginProcessor {
internal val NOOP = object : QRCodeLoginProcessor {}
fun parse(ssoContext: SsoProcessorContext, logger: MiraiLogger): QRCodeLoginProcessor {
if (!ssoContext.bot.configuration.doQRCodeLogin) return NOOP
check(ssoContext.bot.configuration.protocol.asInternal.canDoQRCodeLogin) {
"The login protocol must be ANDROID_WATCH or MACOS while enabling qrcode login." +
"Set it by `bot.configuration.protocol = BotConfiguration.MiraiProtocol.ANDROID_WATCH`."
}
return QRCodeLoginProcessorPreLoaded(ssoContext, logger)
}
}
@ -49,6 +44,11 @@ internal class QRCodeLoginProcessorPreLoaded(
private val logger: MiraiLogger,
) : QRCodeLoginProcessor {
override fun prepareProcess(handler: NetworkHandler, client: QQAndroidClient): QRCodeLoginProcessor {
check(ssoContext.bot.configuration.protocol.asInternal.canDoQRCodeLogin) {
"The login protocol must be ANDROID_WATCH or MACOS while enabling qrcode login." +
"Set it by `bot.configuration.protocol = BotConfiguration.MiraiProtocol.ANDROID_WATCH`."
}
val loginSolver = ssoContext.bot.configuration.loginSolver
?: throw IllegalStateException(
"No LoginSolver found while enabling qrcode login. " +
@ -60,16 +60,13 @@ internal class QRCodeLoginProcessorPreLoaded(
val qrCodeLoginListener = loginSolver.createQRCodeLoginListener(client.bot)
return loginSolver.run {
QRCodeLoginProcessorImpl(qrCodeLoginListener, qrCodeSize, qrCodeMargin, qrCodeEcLevel, logger)
QRCodeLoginProcessorImpl(qrCodeLoginListener, logger)
}
}
}
internal class QRCodeLoginProcessorImpl(
private val qrCodeLoginListener: QRCodeLoginListener,
private val size: Int,
private val margin: Int,
private val ecLevel: Int,
private val logger: MiraiLogger,
) : QRCodeLoginProcessor {
@ -80,7 +77,15 @@ internal class QRCodeLoginProcessorImpl(
client: QQAndroidClient
): WtLogin.TransEmp.Response.FetchQRCode {
logger.debug { "requesting qrcode." }
val resp = handler.sendAndExpect(WtLogin.TransEmp.FetchQRCode(client, size, margin, ecLevel), attempts = 1)
val resp = handler.sendAndExpect(
WtLogin.TransEmp.FetchQRCode(
client,
size = qrCodeLoginListener.qrCodeSize,
margin = qrCodeLoginListener.qrCodeMargin,
ecLevel = qrCodeLoginListener.qrCodeEcLevel,
),
attempts = 1
)
check(resp is WtLogin.TransEmp.Response.FetchQRCode) { "Cannot fetch qrcode, resp=$resp" }
qrCodeLoginListener.onFetchQRCode(handler.context.bot, resp.imageData)
return resp

View File

@ -11,14 +11,20 @@ package net.mamoe.mirai.internal.network.components
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic
import net.mamoe.mirai.internal.QQAndroidBot
import kotlinx.coroutines.*
import net.mamoe.mirai.auth.BotAuthInfo
import net.mamoe.mirai.auth.BotAuthorization
import net.mamoe.mirai.auth.BotAuthorizationResult
import net.mamoe.mirai.auth.MiraiInternalBotAuthComponent
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.QRCodeLoginData
import net.mamoe.mirai.internal.network.WLoginSigInfo
import net.mamoe.mirai.internal.network.component.ComponentKey
import net.mamoe.mirai.internal.network.handler.NetworkHandler
import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.handler.selector.NetworkException
import net.mamoe.mirai.internal.network.handler.selector.SelectorRequireReconnectException
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
import net.mamoe.mirai.internal.network.protocol.packet.login.DeviceVerificationResultImpl
import net.mamoe.mirai.internal.network.protocol.packet.login.SmsDeviceVerificationResult
@ -31,11 +37,12 @@ import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.network.RetryLaterException
import net.mamoe.mirai.network.UnsupportedSliderCaptchaException
import net.mamoe.mirai.network.WrongPasswordException
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.info
import net.mamoe.mirai.utils.withExceptionCollector
import kotlin.coroutines.Continuation
import kotlin.coroutines.cancellation.CancellationException
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlin.jvm.Volatile
/**
@ -143,44 +150,323 @@ internal class SsoProcessorImpl(
override val ssoSession: SsoSession get() = client
private val components get() = ssoContext.bot.components
private val botAuthInfo = object : BotAuthInfo {
override val id: Long
get() = ssoContext.bot.id
override val deviceInfo: DeviceInfo
get() = ssoContext.device
override val configuration: BotConfiguration
get() = ssoContext.bot.configuration
}
/**
* Do login. Throws [LoginFailedException] if failed
*/
override suspend fun login(handler: NetworkHandler) = withExceptionCollector {
components[CacheValidator].validate()
override suspend fun login(handler: NetworkHandler) {
components[BdhSessionSyncer].loadServerListFromCache()
try {
fun initAuthControl() {
authControl = AuthControl(
botAuthInfo,
ssoContext.bot.account.authorization,
ssoContext.bot.network.logger,
ssoContext.bot,
)
}
suspend fun loginSuccess() {
components[AccountSecretsManager].saveSecrets(ssoContext.account, AccountSecretsImpl(client))
registerClientOnline(handler)
ssoContext.bot.logger.info { "Login successful." }
}
if (authControl == null) {
ssoContext.bot.account.let { account ->
if (account.accountSecretsKeyBuffer == null) {
account.accountSecretsKeyBuffer = SecretsProtection.EscapedByteBuffer(
account.authorization.calculateSecretsKey(botAuthInfo)
)
}
}
components[CacheValidator].validate()
components[BdhSessionSyncer].loadServerListFromCache()
// try fast login
if (client.wLoginSigInfoInitialized) {
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()
kotlin.runCatching {
FastLoginImpl(handler).doLogin()
}.onFailure { e ->
collectException(e)
initAuthControl()
authControl!!.exceptionCollector.collect(e)
throw SelectorRequireReconnectException()
}
loginSuccess()
return
}
}
if (authControl == null) initAuthControl()
val authControl0 = authControl!!
var nextAuthMethod: AuthMethod? = null
try {
ssoContext.bot.components[BotClientHolder].refreshClient()
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()
ssoContext.bot.account.passwordMd5Buffer = null
when (val authw = authControl0.acquireAuth().also { nextAuthMethod = it }) {
is AuthMethod.DirectError -> throw authw.exception
is AuthMethod.Error -> {
authControl = null
authControl0.exceptionCollector.collectThrow(authw.exception)
}
AuthMethod.NotAvailable -> {
authControl = null
authControl0.exceptionCollector.collectThrow(IllegalStateException("No more auth method available"))
}
is AuthMethod.Pwd -> {
ssoContext.bot.account.passwordMd5Buffer = authw.passwordMd5
SlowLoginImpl(handler, LoginType.Password).doLogin()
}
} else {
client = createClient(ssoContext.bot)
ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh()
AuthMethod.QRCode -> {
val rsp = ssoContext.bot.components[QRCodeLoginProcessor].prepareProcess(
handler, client
).process(handler, client)
val qrCodeLoginProcessor = ssoContext.bot.components[QRCodeLoginProcessor]
if (qrCodeLoginProcessor !== QRCodeLoginProcessor.NOOP) {
val qrcodeLoginData = qrCodeLoginProcessor.prepareProcess(handler, client).process(handler, client)
SlowLoginImpl(handler, LoginType.QRCode(qrcodeLoginData)).doLogin()
} else {
SlowLoginImpl(handler, LoginType.Password).doLogin()
SlowLoginImpl(handler, LoginType.QRCode(rsp)).doLogin()
}
}
} catch (e: Exception) {
// Failed to log in, invalidate secrets.
ssoContext.bot.components[AccountSecretsManager].invalidate()
throw e
authControl!!.actComplete()
authControl = null
} catch (exception: Throwable) {
authControl0.exceptionCollector.collectException(exception)
ssoContext.bot.network.logger.warning({ "Failed with auth method: $nextAuthMethod" }, exception)
if (nextAuthMethod is AuthMethod.DirectError) { // @TestOnly
authControl0.actResume()
} else if (nextAuthMethod !is AuthMethod.Error) {
if (exception !is SelectorRequireReconnectException) { // login not done
authControl0.actFailed(exception)
}
}
if (exception is NetworkException) {
if (exception.recoverable) throw exception
}
if (nextAuthMethod == null || nextAuthMethod is AuthMethod.NotAvailable || nextAuthMethod is AuthMethod.Error) {
throw exception
}
throw SelectorRequireReconnectException()
}
components[AccountSecretsManager].saveSecrets(ssoContext.account, AccountSecretsImpl(client))
registerClientOnline(handler)
ssoContext.bot.logger.info { "Login successful." }
loginSuccess()
}
sealed class AuthMethod {
object NotAvailable : AuthMethod()
object QRCode : AuthMethod() {
override fun toString(): String {
return "QRCode"
}
}
class Pwd(val passwordMd5: SecretsProtection.EscapedByteBuffer) : AuthMethod() {
override fun toString(): String {
return "Password@${hashCode()}"
}
}
/**
* Exception in [BotAuthorization]
*/
class Error(val exception: Throwable) : AuthMethod() {
override fun toString(): String {
return "Error[$exception]@${hashCode()}"
}
}
/**
* For mocking a login method throw a exception
*/
@TestOnly
class DirectError(val exception: Throwable) : AuthMethod() {
override fun toString(): String {
return "DirectError[$exception]@${hashCode()}"
}
}
}
@TestOnly
internal interface SsoProcessorAuthComponent : MiraiInternalBotAuthComponent {
suspend fun emit(method: AuthMethod)
suspend fun emitDirectError(error: Throwable) {
emit(AuthMethod.DirectError(error))
}
val botAuthorizationResult: BotAuthorizationResult
}
internal class AuthControl(
private val botAuthInfo: BotAuthInfo,
private val authorization: BotAuthorization,
private val logger: MiraiLogger,
private val scope: CoroutineScope,
) {
internal val exceptionCollector = ExceptionCollector()
@Volatile
private var authorizationContinuation: Continuation<Unit>? = null
@Volatile
private var authRspFuture = initCompletableDeferred()
@Volatile
private var isCompleted = false
private val rsp = object : BotAuthorizationResult {}
@Suppress("RemoveExplicitTypeArguments")
@OptIn(TestOnly::class)
private val authComponent = object : SsoProcessorAuthComponent {
override val botAuthorizationResult: BotAuthorizationResult get() = rsp
override suspend fun emit(method: AuthMethod) {
logger.verbose { "[AuthControl/emit] Trying emit $method" }
if (isCompleted) {
val msg = "[AuthControl/emit] Failed to emit $method because control completed"
error(msg.also { logger.verbose(it) })
}
suspendCoroutine<Unit> { next ->
val rspTarget = authRspFuture
if (!rspTarget.complete(method)) {
val msg = "[AuthControl/emit] Failed to emit $method because auth response completed"
error(msg.also { logger.verbose(it) })
}
authorizationContinuation = next
logger.verbose { "[AuthControl/emit] Emitted $method to $rspTarget" }
}
logger.verbose { "[AuthControl/emit] Authorization resumed after $method" }
}
override suspend fun authByPassword(passwordMd5: SecretsProtection.EscapedByteBuffer): BotAuthorizationResult {
emit(AuthMethod.Pwd(passwordMd5))
return rsp
}
override suspend fun authByPassword(password: String): BotAuthorizationResult {
return authByPassword(password.md5())
}
override suspend fun authByPassword(passwordMd5: ByteArray): BotAuthorizationResult {
return authByPassword(SecretsProtection.EscapedByteBuffer(passwordMd5))
}
override suspend fun authByQRCode(): BotAuthorizationResult {
emit(AuthMethod.QRCode)
return rsp
}
}
init {
scope.launch {
try {
logger.verbose { "[AuthControl/auth] Authorization started" }
authorization.authorize(authComponent, botAuthInfo)
logger.verbose { "[AuthControl/auth] Authorization exited" }
isCompleted = true
authRspFuture.complete(AuthMethod.NotAvailable)
} catch (e: Throwable) {
logger.verbose({ "[AuthControl/auth] Authorization failed" }, e)
isCompleted = true
authRspFuture.complete(AuthMethod.Error(e))
}
}
}
private fun onSpinWait() {}
suspend fun acquireAuth(): AuthMethod {
val authTarget = authRspFuture
logger.verbose { "[AuthControl/acquire] Acquiring auth method with $authTarget" }
val rsp = authTarget.await()
logger.debug { "[AuthControl/acquire] Authorization responded: $authTarget, $rsp" }
while (authorizationContinuation == null && !isCompleted) {
onSpinWait()
}
logger.verbose { "[AuthControl/acquire] authorizationContinuation setup: $authorizationContinuation, $isCompleted" }
return rsp
}
fun actFailed(cause: Throwable) {
logger.verbose { "[AuthControl/resume] Fire auth failed with cause: $cause" }
authRspFuture = initCompletableDeferred()
authorizationContinuation!!.let { cont ->
authorizationContinuation = null
cont.resumeWith(Result.failure(cause))
}
}
@TestOnly // same as act failed
fun actResume() {
logger.verbose { "[AuthControl/resume] Fire auth resume" }
authRspFuture = initCompletableDeferred()
authorizationContinuation!!.let { cont ->
authorizationContinuation = null
cont.resume(Unit)
}
}
fun actComplete() {
logger.verbose { "[AuthControl/resume] Fire auth completed" }
isCompleted = true
authRspFuture = CompletableDeferred(AuthMethod.NotAvailable)
authorizationContinuation!!.let { cont ->
authorizationContinuation = null
cont.resume(Unit)
}
}
private fun initCompletableDeferred(): CompletableDeferred<AuthMethod> {
return CompletableDeferred<AuthMethod>().also { df ->
df.invokeOnCompletion {
logger.debug { "[AuthControl/cd] $df completed with $it" }
}
}
}
}
private var authControl: AuthControl? = null
override suspend fun sendRegister(handler: NetworkHandler): StatSvc.Register.Response {
return registerClientOnline(handler).also { registerResp = it }
}
@ -197,17 +483,6 @@ internal class SsoProcessorImpl(
}
}
private fun createClient(bot: QQAndroidBot): QQAndroidClient {
val device = ssoContext.device
return QQAndroidClient(
ssoContext.account,
device = device,
accountSecrets = bot.components[AccountSecretsManager].getSecretsOrCreate(ssoContext.account, device)
).apply {
_bot = bot
}
}
///////////////////////////////////////////////////////////////////////////
// login
///////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,104 @@
/*
* Copyright 2019-2023 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/dev/LICENSE
*/
package net.mamoe.mirai.internal.network.component
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.yield
import net.mamoe.mirai.auth.BotAuthComponent
import net.mamoe.mirai.auth.BotAuthInfo
import net.mamoe.mirai.auth.BotAuthorization
import net.mamoe.mirai.auth.BotAuthorizationResult
import net.mamoe.mirai.internal.network.components.SsoProcessorContext
import net.mamoe.mirai.internal.network.components.SsoProcessorImpl
import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.DeviceInfo
import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY
import kotlin.reflect.KClass
import kotlin.test.Test
import kotlin.test.assertFails
import kotlin.test.fail
internal class BotAuthControlTest : AbstractCommonNHTest() {
val botAuthInfo = object : BotAuthInfo {
override val id: Long
get() = bot.id
override val deviceInfo: DeviceInfo
get() = bot.components[SsoProcessorContext].device
override val configuration: BotConfiguration
get() = bot.configuration
}
private suspend fun SsoProcessorImpl.AuthControl.assertRequire(exceptedType: KClass<*>) {
println("Requiring auth method")
val nextAuth = acquireAuth()
println("Got $nextAuth")
yield()
if (nextAuth is SsoProcessorImpl.AuthMethod.Error) {
fail(cause = nextAuth.exception)
}
if (exceptedType.isInstance(nextAuth)) return
fail("Type not match, excepted $exceptedType but got ${nextAuth::class}")
}
@Test
fun `auth test`() = runTest {
val control = SsoProcessorImpl.AuthControl(botAuthInfo, object : BotAuthorization {
override suspend fun authorize(authComponent: BotAuthComponent, bot: BotAuthInfo): BotAuthorizationResult {
return authComponent.authByPassword(EMPTY_BYTE_ARRAY)
}
}, bot.logger, backgroundScope)
control.assertRequire(SsoProcessorImpl.AuthMethod.Pwd::class)
control.actComplete()
control.assertRequire(SsoProcessorImpl.AuthMethod.NotAvailable::class)
}
@Test
fun `test auth failed and reselect`() = runTest {
val control = SsoProcessorImpl.AuthControl(botAuthInfo, object : BotAuthorization {
override suspend fun authorize(authComponent: BotAuthComponent, bot: BotAuthInfo): BotAuthorizationResult {
assertFails { authComponent.authByPassword(EMPTY_BYTE_ARRAY); println("!") }
println("114514")
return authComponent.authByPassword(EMPTY_BYTE_ARRAY)
}
}, bot.logger, backgroundScope)
control.assertRequire(SsoProcessorImpl.AuthMethod.Pwd::class)
control.actFailed(Throwable())
control.assertRequire(SsoProcessorImpl.AuthMethod.Pwd::class)
control.actComplete()
control.assertRequire(SsoProcessorImpl.AuthMethod.NotAvailable::class)
}
@Test
fun `failed when login complete`() = runTest {
val control = SsoProcessorImpl.AuthControl(botAuthInfo, object : BotAuthorization {
override suspend fun authorize(authComponent: BotAuthComponent, bot: BotAuthInfo): BotAuthorizationResult {
val rsp = authComponent.authByPassword(EMPTY_BYTE_ARRAY)
assertFails { authComponent.authByPassword(EMPTY_BYTE_ARRAY) }
assertFails { authComponent.authByPassword(EMPTY_BYTE_ARRAY) }
assertFails { authComponent.authByPassword(EMPTY_BYTE_ARRAY) }
return rsp
}
}, bot.logger, backgroundScope)
control.assertRequire(SsoProcessorImpl.AuthMethod.Pwd::class)
control.actComplete()
control.assertRequire(SsoProcessorImpl.AuthMethod.NotAvailable::class)
}
}

View File

@ -13,6 +13,9 @@ package net.mamoe.mirai.internal.network.framework
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import net.mamoe.mirai.auth.BotAuthInfo
import net.mamoe.mirai.auth.BotAuthorizationResult
import net.mamoe.mirai.auth.MiraiInternalBotAuthComponent
import net.mamoe.mirai.internal.*
import net.mamoe.mirai.internal.contact.uin
import net.mamoe.mirai.internal.network.KeyWithCreationTime
@ -113,6 +116,40 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs
set(SsoProcessorContext, SsoProcessorContextImpl(bot))
set(SsoProcessor, object : TestSsoProcessor(bot) {
override suspend fun login(handler: NetworkHandler) {
val botAuthInfo = object : BotAuthInfo {
override val id: Long get() = bot.id
override val deviceInfo: DeviceInfo
get() = get(SsoProcessorContext).device
override val configuration: BotConfiguration
get() = bot.configuration
}
val rsp = object : BotAuthorizationResult {}
val botAuthComponents = object : MiraiInternalBotAuthComponent {
override suspend fun authByPassword(passwordMd5: SecretsProtection.EscapedByteBuffer): BotAuthorizationResult {
bot.account.passwordMd5Buffer = passwordMd5
return rsp
}
override suspend fun authByPassword(password: String): BotAuthorizationResult {
return authByPassword(password.md5())
}
override suspend fun authByPassword(passwordMd5: ByteArray): BotAuthorizationResult {
return authByPassword(SecretsProtection.EscapedByteBuffer(passwordMd5))
}
override suspend fun authByQRCode(): BotAuthorizationResult {
TODO("Not yet implemented")
}
}
bot.account.authorization.authorize(botAuthComponents, botAuthInfo)
bot.account.accountSecretsKeyBuffer = SecretsProtection.EscapedByteBuffer(
bot.account.authorization.calculateSecretsKey(botAuthInfo)
)
nhEvents.add(NHEvent.Login)
super.login(handler)
}