mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-05 09:44:43 +08:00
Decouple SSO login processor
This commit is contained in:
parent
997ad1eb63
commit
f2b236341a
@ -108,7 +108,7 @@ internal abstract class AbstractBot constructor(
|
||||
) {
|
||||
// Close network to avoid endless reconnection while network is ok
|
||||
// https://github.com/mamoe/mirai/issues/894
|
||||
kotlin.runCatching { network.close() }
|
||||
kotlin.runCatching { network.close(null) }
|
||||
return@subscribeAlways
|
||||
}
|
||||
/*
|
||||
@ -121,7 +121,7 @@ internal abstract class AbstractBot constructor(
|
||||
val cause = event.cause
|
||||
val msg = if (cause == null) "" else " with exception: $cause"
|
||||
bot.logger.info("Bot is closed manually $msg", cause)
|
||||
network.close()
|
||||
network.close(null)
|
||||
}
|
||||
is BotOfflineEvent.Force -> {
|
||||
bot.logger.info { "Connection occupied by another android device: ${event.message}" }
|
||||
@ -131,7 +131,7 @@ internal abstract class AbstractBot constructor(
|
||||
bot.logger.info { "Reconnecting..." }
|
||||
// delay(3000)
|
||||
} else {
|
||||
network.close()
|
||||
network.close(null)
|
||||
}
|
||||
}
|
||||
is BotOfflineEvent.MsfOffline,
|
||||
@ -200,7 +200,7 @@ internal abstract class AbstractBot constructor(
|
||||
logger.info { "Bot cancelled" + throwable?.message?.let { ": $it" }.orEmpty() }
|
||||
|
||||
kotlin.runCatching {
|
||||
network.close()
|
||||
network.close(throwable)
|
||||
}
|
||||
offlineListener.cancel(CancellationException("Bot cancelled", throwable))
|
||||
|
||||
@ -220,7 +220,7 @@ internal abstract class AbstractBot constructor(
|
||||
return
|
||||
}
|
||||
|
||||
this.network.close()
|
||||
this.network.close(cause)
|
||||
|
||||
if (supervisorJob.isActive) {
|
||||
if (cause == null) {
|
||||
|
@ -17,11 +17,11 @@ import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.OtherClientInfo
|
||||
import net.mamoe.mirai.internal.contact.OtherClientImpl
|
||||
import net.mamoe.mirai.internal.contact.checkIsGroupImpl
|
||||
import net.mamoe.mirai.internal.contact.uin
|
||||
import net.mamoe.mirai.internal.network.*
|
||||
import net.mamoe.mirai.internal.network.handler.*
|
||||
import net.mamoe.mirai.internal.network.handler.impl.netty.NettyNetworkHandlerFactory
|
||||
import net.mamoe.mirai.internal.network.net.protocol.SsoContext
|
||||
import net.mamoe.mirai.internal.network.net.protocol.SsoProcessor
|
||||
import net.mamoe.mirai.internal.network.net.protocol.SsoProcessorContextImpl
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
|
||||
@ -53,8 +53,7 @@ internal fun QQAndroidBot.createOtherClient(
|
||||
internal class QQAndroidBot constructor(
|
||||
internal val account: BotAccount,
|
||||
configuration: BotConfiguration
|
||||
) : AbstractBot(configuration, account.id), SsoContext {
|
||||
override lateinit var client: QQAndroidClient
|
||||
) : AbstractBot(configuration, account.id) {
|
||||
override val bot: QQAndroidBot get() = this
|
||||
|
||||
val bdhSyncer: BdhSessionSyncer = BdhSessionSyncer(this)
|
||||
@ -66,12 +65,16 @@ internal class QQAndroidBot constructor(
|
||||
|
||||
// TODO: 2021/4/14 bdhSyncer.loadFromCache() when login
|
||||
|
||||
private val ssoProcessor: SsoProcessor by lazy { SsoProcessor(SsoProcessorContextImpl(this)) }
|
||||
|
||||
val client get() = ssoProcessor.client
|
||||
|
||||
override suspend fun sendLogout() {
|
||||
network.sendWithoutExpect(StatSvc.Register.offline(client))
|
||||
}
|
||||
|
||||
override fun createNetworkHandler(coroutineContext: CoroutineContext): NetworkHandler {
|
||||
val context = NetworkHandlerContextImpl(this, this)
|
||||
val context = NetworkHandlerContextImpl(this, ssoProcessor, configuration.networkLoggerSupplier(this))
|
||||
return SelectorNetworkHandler(
|
||||
context,
|
||||
FactoryKeepAliveNetworkHandlerSelector(NettyNetworkHandlerFactory, serverListNew, context)
|
||||
|
@ -20,7 +20,9 @@ import net.mamoe.mirai.utils.md5
|
||||
import net.mamoe.mirai.utils.toByteArray
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
|
||||
|
||||
/**
|
||||
* Secrets for authentication with server. (login)
|
||||
*/
|
||||
internal interface AccountSecrets {
|
||||
var wLoginSigInfoField: WLoginSigInfo?
|
||||
|
||||
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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.internal.network
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.internal.BotAccount
|
||||
import net.mamoe.mirai.internal.utils.actualCacheDir
|
||||
import net.mamoe.mirai.internal.utils.crypto.TEA
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.DeviceInfo
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.info
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* For a [Bot].
|
||||
*
|
||||
* @see MemoryAccountSecretsManager
|
||||
* @see FileCacheAccountSecretsManager
|
||||
* @see CombinedAccountSecretsManager
|
||||
*/
|
||||
internal interface AccountSecretsManager {
|
||||
fun saveSecrets(account: BotAccount, secrets: AccountSecrets)
|
||||
fun getSecrets(account: BotAccount): AccountSecrets?
|
||||
}
|
||||
|
||||
internal fun AccountSecretsManager.getSecretsOrCreate(account: BotAccount, device: DeviceInfo): AccountSecrets {
|
||||
var secrets = getSecrets(account)
|
||||
if (secrets == null) {
|
||||
secrets = AccountSecretsImpl(device, account)
|
||||
saveSecrets(account, secrets)
|
||||
}
|
||||
return secrets
|
||||
}
|
||||
|
||||
internal class MemoryAccountSecretsManager : AccountSecretsManager {
|
||||
@Volatile
|
||||
private var instance: AccountSecrets? = null
|
||||
|
||||
@Synchronized
|
||||
override fun saveSecrets(account: BotAccount, secrets: AccountSecrets) {
|
||||
instance = secrets
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getSecrets(account: BotAccount): AccountSecrets? = this.instance
|
||||
}
|
||||
|
||||
|
||||
internal class FileCacheAccountSecretsManager(
|
||||
val file: File,
|
||||
val logger: MiraiLogger,
|
||||
) : AccountSecretsManager {
|
||||
override fun saveSecrets(account: BotAccount, secrets: AccountSecrets) {
|
||||
if (secrets.wLoginSigInfoField == null) return
|
||||
|
||||
file.writeBytes(
|
||||
TEA.encrypt(
|
||||
AccountSecretsImpl(secrets).toByteArray(AccountSecretsImpl.serializer()),
|
||||
account.passwordMd5
|
||||
)
|
||||
)
|
||||
|
||||
logger.info { "Saved account secrets to local cache for fast login." }
|
||||
|
||||
TEA.encrypt(file.readBytes(), account.passwordMd5).loadAs(AccountSecretsImpl.serializer())
|
||||
}
|
||||
|
||||
override fun getSecrets(account: BotAccount): AccountSecrets? {
|
||||
return getSecretsImpl(account)
|
||||
}
|
||||
|
||||
private fun getSecretsImpl(account: BotAccount): AccountSecrets? {
|
||||
if (!file.exists()) return null
|
||||
val loaded = kotlin.runCatching {
|
||||
TEA.decrypt(file.readBytes(), account.passwordMd5).loadAs(AccountSecretsImpl.serializer())
|
||||
}.getOrElse { e ->
|
||||
logger.error("Failed to load account secrets from local cache. Invalidating cache...", e)
|
||||
file.delete()
|
||||
return null
|
||||
}
|
||||
|
||||
logger.info { "Loaded account secrets from local cache." }
|
||||
return loaded
|
||||
}
|
||||
}
|
||||
|
||||
internal class CombinedAccountSecretsManager(
|
||||
private val primary: AccountSecretsManager,
|
||||
private val alternative: AccountSecretsManager,
|
||||
) : AccountSecretsManager {
|
||||
override fun saveSecrets(account: BotAccount, secrets: AccountSecrets) {
|
||||
primary.saveSecrets(account, secrets)
|
||||
alternative.saveSecrets(account, secrets)
|
||||
}
|
||||
|
||||
override fun getSecrets(account: BotAccount): AccountSecrets? {
|
||||
return primary.getSecrets(account) ?: alternative.getSecrets(account)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [CombinedAccountSecretsManager] with [MemoryAccountSecretsManager] as primary and [FileCacheAccountSecretsManager] as an alternative.
|
||||
*/
|
||||
internal fun BotConfiguration.createAccountsSecretsManager(logger: MiraiLogger): AccountSecretsManager {
|
||||
return CombinedAccountSecretsManager(
|
||||
MemoryAccountSecretsManager(),
|
||||
FileCacheAccountSecretsManager(
|
||||
actualCacheDir().resolve("account.secrets"),
|
||||
logger
|
||||
)
|
||||
)
|
||||
}
|
@ -22,7 +22,7 @@ import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.data.OnlineStatus
|
||||
import net.mamoe.mirai.internal.BotAccount
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.network.net.protocol.LoginSessionAware
|
||||
import net.mamoe.mirai.internal.network.net.protocol.SsoSession
|
||||
import net.mamoe.mirai.internal.network.protocol.SyncingCacheList
|
||||
import net.mamoe.mirai.internal.network.protocol.data.jce.FileStoragePushFSSvcList
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.Tlv
|
||||
@ -73,15 +73,13 @@ internal open class QQAndroidClient(
|
||||
override val ecdh: ECDH = ECDH(),
|
||||
val device: DeviceInfo,
|
||||
accountSecrets: AccountSecrets
|
||||
) : AccountSecrets by accountSecrets, LoginSessionAware {
|
||||
) : AccountSecrets by accountSecrets, SsoSession {
|
||||
lateinit var _bot: QQAndroidBot
|
||||
val bot: QQAndroidBot get() = _bot
|
||||
|
||||
|
||||
internal var strangerSeq: Int = 0
|
||||
|
||||
val keys: Map<String, ByteArray> by lazy { allKeys() }
|
||||
|
||||
var onlineStatus: OnlineStatus = OnlineStatus.ONLINE
|
||||
|
||||
|
||||
|
@ -13,10 +13,9 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.network.Packet
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
|
||||
import net.mamoe.mirai.internal.network.net.protocol.SsoContext
|
||||
import net.mamoe.mirai.internal.network.net.protocol.SsoProcessor
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
@ -27,23 +26,18 @@ import java.util.concurrent.CancellationException
|
||||
* Immutable context for [NetworkHandler]
|
||||
*/
|
||||
internal interface NetworkHandlerContext {
|
||||
val bot: QQAndroidBot // // TODO: 2021/4/16 this is bad, making it difficult to write unit tests.
|
||||
val bot: QQAndroidBot
|
||||
// however migration requires a major change.
|
||||
|
||||
val logger: MiraiLogger
|
||||
val ssoContext: SsoContext
|
||||
val configuration: BotConfiguration
|
||||
val ssoProcessor: SsoProcessor
|
||||
}
|
||||
|
||||
internal class NetworkHandlerContextImpl(
|
||||
override val bot: QQAndroidBot,
|
||||
override val ssoContext: SsoContext,
|
||||
) : NetworkHandlerContext {
|
||||
override val configuration: BotConfiguration
|
||||
get() = bot.configuration
|
||||
|
||||
override val logger: MiraiLogger by lazy { configuration.networkLoggerSupplier(bot) }
|
||||
}
|
||||
override val ssoProcessor: SsoProcessor,
|
||||
override val logger: MiraiLogger,
|
||||
) : NetworkHandlerContext
|
||||
|
||||
/**
|
||||
* Basic interface available to application. Usually wrapped with [SelectorNetworkHandler].
|
||||
@ -116,7 +110,7 @@ internal interface NetworkHandler {
|
||||
/**
|
||||
* Closes this handler gracefully.
|
||||
*/
|
||||
fun close()
|
||||
fun close(cause: Throwable?)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// compatibility
|
||||
|
@ -39,8 +39,8 @@ internal class SelectorNetworkHandler(
|
||||
instance().sendAndExpect(packet, timeout, attempts)
|
||||
|
||||
override suspend fun sendWithoutExpect(packet: OutgoingPacket) = instance().sendWithoutExpect(packet)
|
||||
override fun close() {
|
||||
selector.getResumedInstance()?.close()
|
||||
override fun close(cause: Throwable?) {
|
||||
selector.getResumedInstance()?.close(cause)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.internal.network.Packet
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext
|
||||
import net.mamoe.mirai.internal.network.handler.logger
|
||||
import net.mamoe.mirai.internal.network.net.protocol.RawIncomingPacket
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
||||
@ -81,7 +82,8 @@ internal abstract class NetworkHandlerSupport(
|
||||
sendPacketImpl(packet)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
override fun close(cause: Throwable?) {
|
||||
logger.info { "NetworkHandler closed: $cause" }
|
||||
coroutineContext.job.cancel("NetworkHandler closed.")
|
||||
}
|
||||
|
||||
@ -138,7 +140,11 @@ internal abstract class NetworkHandlerSupport(
|
||||
private set
|
||||
|
||||
final override val state: NetworkHandler.State get() = _state.correspondingState
|
||||
protected inline fun setState(crossinline new: () -> BaseStateImpl) = synchronized(this) {
|
||||
|
||||
/**
|
||||
* You may need to call [BaseStateImpl.resumeConnection] since state is lazy.
|
||||
*/
|
||||
protected inline fun <S : BaseStateImpl> setState(crossinline new: () -> S): S = synchronized(this) {
|
||||
// we can add hooks here for debug.
|
||||
|
||||
val impl = new()
|
||||
@ -147,6 +153,7 @@ internal abstract class NetworkHandlerSupport(
|
||||
check(old !== impl) { "Old and new states cannot be the same." }
|
||||
old.cancel()
|
||||
_state = impl
|
||||
return impl
|
||||
}
|
||||
|
||||
final override suspend fun resumeConnection() {
|
||||
|
@ -29,7 +29,7 @@ import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext
|
||||
import net.mamoe.mirai.internal.network.handler.impl.NetworkHandlerSupport
|
||||
import net.mamoe.mirai.internal.network.net.protocol.PacketCodec
|
||||
import net.mamoe.mirai.internal.network.net.protocol.RawIncomingPacket
|
||||
import net.mamoe.mirai.internal.network.net.protocol.SsoController
|
||||
import net.mamoe.mirai.internal.network.net.protocol.SsoProcessor
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.utils.childScope
|
||||
import java.net.SocketAddress
|
||||
@ -39,11 +39,11 @@ internal class NettyNetworkHandler(
|
||||
context: NetworkHandlerContext,
|
||||
private val address: SocketAddress,
|
||||
) : NetworkHandlerSupport(context) {
|
||||
override fun close() {
|
||||
setState { StateClosed(null) }
|
||||
override fun close(cause: Throwable?) {
|
||||
setState { StateClosed(cause) }
|
||||
}
|
||||
|
||||
private fun closeSuper() = super.close()
|
||||
private fun closeSuper(cause: Throwable?) = super.close(cause)
|
||||
|
||||
override suspend fun sendPacketImpl(packet: OutgoingPacket) {
|
||||
val state = _state as NettyState
|
||||
@ -57,7 +57,7 @@ internal class NettyNetworkHandler(
|
||||
private inner class ByteBufToIncomingPacketDecoder : SimpleChannelInboundHandler<ByteBuf>(ByteBuf::class.java) {
|
||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||
ctx.fireChannelRead(msg.toReadPacket().use { packet ->
|
||||
PacketCodec.decodeRaw(context.bot.client, packet)
|
||||
PacketCodec.decodeRaw(context.ssoProcessor.ssoSession, packet)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -143,19 +143,24 @@ internal class NettyNetworkHandler(
|
||||
|
||||
override suspend fun resumeConnection() {
|
||||
setState { StateConnecting(PacketDecodePipeline(this@NettyNetworkHandler.coroutineContext)) }
|
||||
.resumeConnection()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Connect to server.
|
||||
* 2. Perform SSO login with [SsoProcessor]
|
||||
* 3. If failure, set state to [StateClosed]
|
||||
* 4. If success, set state to [StateOK]
|
||||
*/
|
||||
private inner class StateConnecting(
|
||||
val decodePipeline: PacketDecodePipeline,
|
||||
) : NettyState(NetworkHandler.State.CONNECTING) {
|
||||
private val ssoController = SsoController(context.ssoContext, this@NettyNetworkHandler)
|
||||
|
||||
private val connection = async { createConnection(decodePipeline) }
|
||||
|
||||
private val connectResult = async {
|
||||
val connection = connection.await()
|
||||
ssoController.login()
|
||||
context.ssoProcessor.login(this@NettyNetworkHandler)
|
||||
setState { StateOK(connection) }
|
||||
}.apply {
|
||||
invokeOnCompletion { error ->
|
||||
@ -191,6 +196,7 @@ internal class NettyNetworkHandler(
|
||||
|
||||
override suspend fun resumeConnection() {
|
||||
setState { StateConnecting(PacketDecodePipeline(this@NettyNetworkHandler.coroutineContext)) }
|
||||
.resumeConnection() // the user wil
|
||||
} // noop
|
||||
}
|
||||
|
||||
@ -198,7 +204,7 @@ internal class NettyNetworkHandler(
|
||||
val exception: Throwable?
|
||||
) : NettyState(NetworkHandler.State.OK) {
|
||||
init {
|
||||
closeSuper()
|
||||
closeSuper(exception)
|
||||
}
|
||||
|
||||
override suspend fun sendPacketImpl(packet: OutgoingPacket) = error("NetworkHandler is already closed.")
|
||||
|
@ -12,11 +12,6 @@ package net.mamoe.mirai.internal.network
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.internal.network.getRandomByteArray
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.PacketLogger
|
||||
import net.mamoe.mirai.internal.utils.crypto.TEA
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.network.NoServerAvailableException
|
||||
import net.mamoe.mirai.utils.*
|
||||
|
||||
|
||||
@ -174,65 +169,4 @@ internal open class KeyWithCreationTime(
|
||||
override fun toString(): String {
|
||||
return "KeyWithCreationTime(data=${data.toUHexString()}, creationTime=$creationTime)"
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend inline fun QQAndroidClient.useNextServers(crossinline block: suspend (host: String, port: Int) -> Unit) {
|
||||
if (bot.serverList.isEmpty()) {
|
||||
bot.bdhSyncer.loadServerListFromCache()
|
||||
if (bot.serverList.isEmpty()) {
|
||||
bot.serverList.addAll(DefaultServerList)
|
||||
}
|
||||
}
|
||||
retryCatchingExceptions(bot.serverList.size, except = LoginFailedException::class) l@{
|
||||
val pair = bot.serverList[0]
|
||||
runCatchingExceptions {
|
||||
block(pair.first, pair.second)
|
||||
return@l
|
||||
}.getOrElse {
|
||||
bot.serverList.remove(pair)
|
||||
if (it !is LoginFailedException) {
|
||||
// 不要重复打印.
|
||||
bot.logger.warning(it)
|
||||
}
|
||||
throw it
|
||||
}
|
||||
}.getOrElse {
|
||||
if (it is LoginFailedException) {
|
||||
throw it
|
||||
}
|
||||
bot.serverList.addAll(DefaultServerList)
|
||||
throw NoServerAvailableException(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("RemoveRedundantQualifierName") // bug
|
||||
internal fun generateTgtgtKey(guid: ByteArray): ByteArray =
|
||||
(getRandomByteArray(16) + guid).md5()
|
||||
|
||||
internal inline fun <R> QQAndroidClient.tryDecryptOrNull(
|
||||
data: ByteArray,
|
||||
size: Int = data.size,
|
||||
mapper: (ByteArray) -> R
|
||||
): R? {
|
||||
keys.forEach { (key, value) ->
|
||||
kotlin.runCatching {
|
||||
return mapper(TEA.decrypt(data, value, size).also { PacketLogger.verbose { "成功使用 $key 解密" } })
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun QQAndroidClient.allKeys() = mapOf(
|
||||
"16 zero" to ByteArray(16),
|
||||
"D2 key" to wLoginSigInfo.d2Key,
|
||||
"wtSessionTicketKey" to wLoginSigInfo.wtSessionTicketKey,
|
||||
"userStKey" to wLoginSigInfo.userStKey,
|
||||
"tgtgtKey" to tgtgtKey,
|
||||
"tgtKey" to wLoginSigInfo.tgtKey,
|
||||
"deviceToken" to wLoginSigInfo.deviceToken,
|
||||
"shareKeyCalculatedByConstPubKey" to ecdh.keyPair.initialShareKey
|
||||
//"t108" to wLoginSigInfo.t1,
|
||||
//"t10c" to t10c,
|
||||
//"t163" to t163
|
||||
)
|
||||
}
|
@ -17,13 +17,26 @@ import net.mamoe.mirai.internal.utils.crypto.adjustToPublicKey
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.io.use
|
||||
|
||||
/**
|
||||
* Packet decoders.
|
||||
*
|
||||
* - Transforms [ByteReadPacket] to [RawIncomingPacket]
|
||||
*/
|
||||
internal object PacketCodec {
|
||||
/**
|
||||
* 数据包相关的调试输出.
|
||||
* 它默认是关闭的.
|
||||
*/
|
||||
internal val PacketLogger: MiraiLoggerWithSwitch by lazy {
|
||||
MiraiLogger.create("Packet").withSwitch(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* It's caller's responsibility to close [input]
|
||||
* @param input received from sockets.
|
||||
* @return decoded
|
||||
*/
|
||||
fun decodeRaw(client: LoginSessionAware, input: ByteReadPacket): RawIncomingPacket = input.run {
|
||||
fun decodeRaw(client: SsoSession, input: ByteReadPacket): RawIncomingPacket = input.run {
|
||||
// login
|
||||
val flag1 = readInt()
|
||||
|
||||
@ -74,7 +87,7 @@ internal object PacketCodec {
|
||||
val body: ByteReadPacket,
|
||||
)
|
||||
|
||||
private fun parseSsoFrame(client: LoginSessionAware, bytes: ByteArray): DecodeResult =
|
||||
private fun parseSsoFrame(client: SsoSession, bytes: ByteArray): DecodeResult =
|
||||
bytes.toReadPacket().use { input ->
|
||||
val commandName: String
|
||||
val ssoSequenceId: Int
|
||||
@ -139,7 +152,7 @@ internal object PacketCodec {
|
||||
}
|
||||
|
||||
private fun ByteReadPacket.parseOicqResponse(
|
||||
client: LoginSessionAware,
|
||||
client: SsoSession,
|
||||
): ByteArray {
|
||||
check(readByte().toInt() == 2)
|
||||
this.discardExact(2)
|
||||
@ -215,7 +228,10 @@ internal object PacketCodec {
|
||||
}
|
||||
}
|
||||
|
||||
internal open class RawIncomingPacket constructor(
|
||||
/**
|
||||
* Represents a packet that has just been decrypted. Subsequent operation is normally passing it to a responsible [PacketFactory] according to [commandName] from [KnownPacketFactories].
|
||||
*/
|
||||
internal class RawIncomingPacket constructor(
|
||||
val commandName: String,
|
||||
val sequenceId: Int,
|
||||
/**
|
||||
|
@ -1,229 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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.internal.network.net.protocol
|
||||
|
||||
import net.mamoe.mirai.internal.network.AccountSecrets
|
||||
import net.mamoe.mirai.internal.network.AccountSecretsImpl
|
||||
import net.mamoe.mirai.internal.network.Packet
|
||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse.Captcha
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin10
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin2
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin20
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin9
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
|
||||
import net.mamoe.mirai.internal.utils.crypto.TEA
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
|
||||
import net.mamoe.mirai.network.*
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
|
||||
import java.io.File
|
||||
|
||||
internal interface SsoContext {
|
||||
var client: QQAndroidClient
|
||||
val configuration: BotConfiguration
|
||||
val loginSessionAware: LoginSessionAware get() = client
|
||||
val accountSecrets: AccountSecrets get() = client
|
||||
}
|
||||
|
||||
internal class SsoController(
|
||||
private val ssoContext: SsoContext,
|
||||
private val handler: NetworkHandler,
|
||||
) {
|
||||
@Throws(LoginFailedException::class)
|
||||
suspend fun login() = withExceptionCollector {
|
||||
if (ssoContext.accountSecrets.wLoginSigInfoInitialized) {
|
||||
kotlin.runCatching {
|
||||
fastLogin()
|
||||
}.onFailure { e ->
|
||||
collectException(e)
|
||||
slowLogin()
|
||||
}
|
||||
} else {
|
||||
slowLogin()
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// impl
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private val configuration get() = handler.context.configuration
|
||||
private val context get() = handler.context
|
||||
private val bot get() = context.bot
|
||||
private val logger get() = bot.logger
|
||||
private val account get() = bot.account
|
||||
|
||||
|
||||
private suspend fun fastLogin() {
|
||||
val login10 = WtLogin10(bot.client).sendAndExpect(bot)
|
||||
check(login10 is LoginPacketResponse.Success) { "Fast login failed: $login10" }
|
||||
}
|
||||
|
||||
private fun loginSolverNotNull(): LoginSolver {
|
||||
fun LoginSolver?.notnull(): LoginSolver {
|
||||
checkNotNull(this) {
|
||||
"No LoginSolver found. Please provide by BotConfiguration.loginSolver. " +
|
||||
"For example use `BotFactory.newBot(...) { loginSolver = yourLoginSolver}` in Kotlin, " +
|
||||
"use `BotFactory.newBot(..., new BotConfiguration() {{ setLoginSolver(yourLoginSolver) }})` in Java."
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
return bot.configuration.loginSolver.notnull()
|
||||
}
|
||||
|
||||
private val sliderSupported get() = bot.configuration.loginSolver?.isSliderCaptchaSupported ?: false
|
||||
|
||||
private fun createUnsupportedSliderCaptchaException(allowSlider: Boolean): UnsupportedSliderCaptchaException {
|
||||
return UnsupportedSliderCaptchaException(
|
||||
buildString {
|
||||
append("Mirai 无法完成滑块验证.")
|
||||
if (allowSlider) {
|
||||
append(" 使用协议 ")
|
||||
append(configuration.protocol)
|
||||
append(" 强制要求滑块验证, 请更换协议后重试.")
|
||||
}
|
||||
append(" 另请参阅: https://github.com/project-mirai/mirai-login-solver-selenium")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun slowLogin() = withExceptionCollector {
|
||||
|
||||
var allowSlider = sliderSupported || bot.configuration.protocol == MiraiProtocol.ANDROID_PHONE
|
||||
|
||||
var response: LoginPacketResponse = WtLogin9(bot.client, allowSlider).sendAndExpect()
|
||||
|
||||
mainloop@ while (true) {
|
||||
when (response) {
|
||||
is LoginPacketResponse.Success -> {
|
||||
logger.info { "Login successful" }
|
||||
break@mainloop
|
||||
}
|
||||
is LoginPacketResponse.DeviceLockLogin -> {
|
||||
response = WtLogin20(bot.client).sendAndExpect(bot)
|
||||
}
|
||||
|
||||
is LoginPacketResponse.UnsafeLogin -> {
|
||||
loginSolverNotNull().onSolveUnsafeDeviceLoginVerify(bot, response.url)
|
||||
response = WtLogin9(bot.client, allowSlider).sendAndExpect()
|
||||
}
|
||||
|
||||
is Captcha.Picture -> {
|
||||
var result = loginSolverNotNull().onSolvePicCaptcha(bot, response.data)
|
||||
if (result == null || result.length != 4) {
|
||||
//refresh captcha
|
||||
result = "ABCD"
|
||||
}
|
||||
response = WtLogin2.SubmitPictureCaptcha(bot.client, response.sign, result).sendAndExpect()
|
||||
}
|
||||
|
||||
is Captcha.Slider -> {
|
||||
if (sliderSupported) {
|
||||
// use solver
|
||||
val ticket = try {
|
||||
loginSolverNotNull().onSolveSliderCaptcha(bot, response.url)?.takeIf { it.isNotEmpty() }
|
||||
} catch (e: LoginFailedException) {
|
||||
throw e
|
||||
} catch (error: Throwable) {
|
||||
if (allowSlider) {
|
||||
collectException(error)
|
||||
allowSlider = false
|
||||
response = WtLogin9(bot.client, allowSlider).sendAndExpect()
|
||||
continue@mainloop
|
||||
}
|
||||
throw error
|
||||
}
|
||||
response = if (ticket == null) {
|
||||
WtLogin9(bot.client, allowSlider).sendAndExpect()
|
||||
} else {
|
||||
WtLogin2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect()
|
||||
}
|
||||
} else {
|
||||
// retry once
|
||||
if (!allowSlider) throw createUnsupportedSliderCaptchaException(allowSlider)
|
||||
allowSlider = false
|
||||
response = WtLogin9(bot.client, allowSlider).sendAndExpect()
|
||||
}
|
||||
}
|
||||
|
||||
is LoginPacketResponse.Error -> {
|
||||
if (response.message.contains("0x9a")) { //Error(title=登录失败, message=请你稍后重试。(0x9a), errorInfo=)
|
||||
throw RetryLaterException().initCause(IllegalStateException("Login failed: $response"))
|
||||
}
|
||||
val msg = response.toString()
|
||||
throw WrongPasswordException(buildString(capacity = msg.length) {
|
||||
append(msg)
|
||||
if (msg.contains("当前上网环境异常")) { // Error(title=禁止登录, message=当前上网环境异常,请更换网络环境或在常用设备上登录或稍后再试。, errorInfo=)
|
||||
append(", tips=若频繁出现, 请尝试开启设备锁")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
is LoginPacketResponse.SMSVerifyCodeNeeded -> {
|
||||
val message = "SMS required: $response, which isn't yet supported"
|
||||
logger.error(message)
|
||||
throw UnsupportedSMSLoginException(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Suppress("unused") // false positive
|
||||
internal fun initClient() {
|
||||
val device = configuration.deviceInfo?.invoke(bot) ?: DeviceInfo.random()
|
||||
ssoContext.client = QQAndroidClient(
|
||||
bot.account,
|
||||
device = device,
|
||||
accountSecrets = loadSecretsFromCacheOrCreate(device)
|
||||
).apply {
|
||||
_bot = this@SsoController.bot
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun <R : Packet?> OutgoingPacketWithRespType<R>.sendAndExpect(): R = sendAndExpect(bot)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// cache
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO: 2021/4/14 extract a cache service
|
||||
|
||||
private val cacheDir: File by lazy {
|
||||
configuration.workingDir.resolve(ssoContext.configuration.cacheDir).apply { mkdirs() }
|
||||
}
|
||||
private val accountSecretsFile: File by lazy {
|
||||
cacheDir.resolve("account.secrets")
|
||||
}
|
||||
|
||||
private fun loadSecretsFromCacheOrCreate(deviceInfo: DeviceInfo): AccountSecrets {
|
||||
val loaded = if (configuration.loginCacheEnabled && accountSecretsFile.exists()) {
|
||||
kotlin.runCatching {
|
||||
TEA.decrypt(accountSecretsFile.readBytes(), account.passwordMd5).loadAs(AccountSecretsImpl.serializer())
|
||||
}.getOrElse { e ->
|
||||
logger.error("Failed to load account secrets from local cache. Invalidating cache...", e)
|
||||
accountSecretsFile.delete()
|
||||
null
|
||||
}
|
||||
} else null
|
||||
if (loaded != null) {
|
||||
logger.info { "Loaded account secrets from local cache." }
|
||||
return loaded
|
||||
}
|
||||
|
||||
return AccountSecretsImpl(deviceInfo, account) // wLoginSigInfoField is null, no need to save.
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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.internal.network.net.protocol
|
||||
|
||||
import net.mamoe.mirai.internal.BotAccount
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.network.*
|
||||
import net.mamoe.mirai.internal.network.handler.NetworkHandler
|
||||
import net.mamoe.mirai.internal.network.handler.impl.netty.NettyNetworkHandler
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse.Captcha
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin10
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin2
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin20
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin9
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
|
||||
import net.mamoe.mirai.network.*
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
|
||||
|
||||
/**
|
||||
* Provides the information needed by the [SsoProcessor].
|
||||
*/
|
||||
internal interface SsoProcessorContext {
|
||||
val bot: QQAndroidBot
|
||||
|
||||
val account: BotAccount
|
||||
val device: DeviceInfo
|
||||
|
||||
val protocol: MiraiProtocol
|
||||
|
||||
val accountSecretsManager: AccountSecretsManager
|
||||
|
||||
val configuration: BotConfiguration
|
||||
}
|
||||
|
||||
internal class SsoProcessorContextImpl(
|
||||
override val bot: QQAndroidBot
|
||||
) : SsoProcessorContext {
|
||||
override val account: BotAccount get() = bot.account
|
||||
override val device: DeviceInfo = configuration.deviceInfo?.invoke(bot) ?: DeviceInfo.random()
|
||||
override val protocol: MiraiProtocol get() = configuration.protocol
|
||||
override val accountSecretsManager: AccountSecretsManager get() = configuration.createAccountsSecretsManager(bot.logger)
|
||||
override val configuration: BotConfiguration get() = bot.configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy that performs the process of single sing-on (SSO). (login)
|
||||
*
|
||||
* And allows to retire the [session][ssoSession] after success.
|
||||
*
|
||||
* Used by [NettyNetworkHandler.StateConnecting].
|
||||
*/
|
||||
internal class SsoProcessor(
|
||||
private val ssoContext: SsoProcessorContext,
|
||||
) {
|
||||
@Volatile
|
||||
internal var client = createClient(ssoContext.bot)
|
||||
|
||||
internal val ssoSession: SsoSession get() = client
|
||||
|
||||
/**
|
||||
* Do login. Throws [LoginFailedException] if failed
|
||||
*/
|
||||
@Throws(LoginFailedException::class)
|
||||
suspend fun login(handler: NetworkHandler) = withExceptionCollector<Unit> {
|
||||
if (client.wLoginSigInfoInitialized) {
|
||||
kotlin.runCatching {
|
||||
FastLoginImpl(handler).doLogin()
|
||||
}.onFailure { e ->
|
||||
collectException(e)
|
||||
SlowLoginImpl(handler).doLogin()
|
||||
}
|
||||
} else {
|
||||
client = createClient(ssoContext.bot)
|
||||
SlowLoginImpl(handler).doLogin()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createClient(bot: QQAndroidBot): QQAndroidClient {
|
||||
val device = ssoContext.device
|
||||
return QQAndroidClient(
|
||||
ssoContext.account,
|
||||
device = device,
|
||||
accountSecrets = ssoContext.accountSecretsManager.getSecretsOrCreate(ssoContext.account, device)
|
||||
).apply {
|
||||
_bot = bot
|
||||
}
|
||||
}
|
||||
|
||||
// we have exactly two methods----slow and fast.
|
||||
|
||||
private abstract inner class LoginStrategy(
|
||||
val handler: NetworkHandler,
|
||||
) {
|
||||
protected val context get() = handler.context
|
||||
protected val bot get() = context.bot
|
||||
protected val logger get() = bot.logger
|
||||
|
||||
protected suspend fun <R : Packet?> OutgoingPacketWithRespType<R>.sendAndExpect(): R = sendAndExpect(handler)
|
||||
|
||||
abstract suspend fun doLogin()
|
||||
}
|
||||
|
||||
private inner class SlowLoginImpl(handler: NetworkHandler) : LoginStrategy(handler) {
|
||||
|
||||
private fun loginSolverNotNull(): LoginSolver {
|
||||
fun LoginSolver?.notnull(): LoginSolver {
|
||||
checkNotNull(this) {
|
||||
"No LoginSolver found. Please provide by BotConfiguration.loginSolver. " +
|
||||
"For example use `BotFactory.newBot(...) { loginSolver = yourLoginSolver}` in Kotlin, " +
|
||||
"use `BotFactory.newBot(..., new BotConfiguration() {{ setLoginSolver(yourLoginSolver) }})` in Java."
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
return bot.configuration.loginSolver.notnull()
|
||||
}
|
||||
|
||||
private val sliderSupported get() = bot.configuration.loginSolver?.isSliderCaptchaSupported ?: false
|
||||
|
||||
private fun createUnsupportedSliderCaptchaException(allowSlider: Boolean): UnsupportedSliderCaptchaException {
|
||||
return UnsupportedSliderCaptchaException(
|
||||
buildString {
|
||||
append("Mirai 无法完成滑块验证.")
|
||||
if (allowSlider) {
|
||||
append(" 使用协议 ")
|
||||
append(ssoContext.protocol)
|
||||
append(" 强制要求滑块验证, 请更换协议后重试.")
|
||||
}
|
||||
append(" 另请参阅: https://github.com/project-mirai/mirai-login-solver-selenium")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun doLogin() = withExceptionCollector {
|
||||
|
||||
var allowSlider = sliderSupported || bot.configuration.protocol == MiraiProtocol.ANDROID_PHONE
|
||||
|
||||
var response: LoginPacketResponse = WtLogin9(client, allowSlider).sendAndExpect()
|
||||
|
||||
mainloop@ while (true) {
|
||||
when (response) {
|
||||
is LoginPacketResponse.Success -> {
|
||||
logger.info { "Login successful" }
|
||||
break@mainloop
|
||||
}
|
||||
is LoginPacketResponse.DeviceLockLogin -> {
|
||||
response = WtLogin20(client).sendAndExpect()
|
||||
}
|
||||
|
||||
is LoginPacketResponse.UnsafeLogin -> {
|
||||
loginSolverNotNull().onSolveUnsafeDeviceLoginVerify(bot, response.url)
|
||||
response = WtLogin9(client, allowSlider).sendAndExpect()
|
||||
}
|
||||
|
||||
is Captcha.Picture -> {
|
||||
var result = loginSolverNotNull().onSolvePicCaptcha(bot, response.data)
|
||||
if (result == null || result.length != 4) {
|
||||
//refresh captcha
|
||||
result = "ABCD"
|
||||
}
|
||||
response = WtLogin2.SubmitPictureCaptcha(client, response.sign, result).sendAndExpect()
|
||||
}
|
||||
|
||||
is Captcha.Slider -> {
|
||||
if (sliderSupported) {
|
||||
// use solver
|
||||
val ticket = try {
|
||||
loginSolverNotNull().onSolveSliderCaptcha(bot, response.url)?.takeIf { it.isNotEmpty() }
|
||||
} catch (e: LoginFailedException) {
|
||||
throw e
|
||||
} catch (error: Throwable) {
|
||||
if (allowSlider) {
|
||||
collectException(error)
|
||||
allowSlider = false
|
||||
response = WtLogin9(client, allowSlider).sendAndExpect()
|
||||
continue@mainloop
|
||||
}
|
||||
throw error
|
||||
}
|
||||
response = if (ticket == null) {
|
||||
WtLogin9(client, allowSlider).sendAndExpect()
|
||||
} else {
|
||||
WtLogin2.SubmitSliderCaptcha(client, ticket).sendAndExpect()
|
||||
}
|
||||
} else {
|
||||
// retry once
|
||||
if (!allowSlider) throw createUnsupportedSliderCaptchaException(allowSlider)
|
||||
allowSlider = false
|
||||
response = WtLogin9(client, allowSlider).sendAndExpect()
|
||||
}
|
||||
}
|
||||
|
||||
is LoginPacketResponse.Error -> {
|
||||
if (response.message.contains("0x9a")) { //Error(title=登录失败, message=请你稍后重试。(0x9a), errorInfo=)
|
||||
throw RetryLaterException().initCause(IllegalStateException("Login failed: $response"))
|
||||
}
|
||||
val msg = response.toString()
|
||||
throw WrongPasswordException(buildString(capacity = msg.length) {
|
||||
append(msg)
|
||||
if (msg.contains("当前上网环境异常")) { // Error(title=禁止登录, message=当前上网环境异常,请更换网络环境或在常用设备上登录或稍后再试。, errorInfo=)
|
||||
append(", tips=若频繁出现, 请尝试开启设备锁")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
is LoginPacketResponse.SMSVerifyCodeNeeded -> {
|
||||
val message = "SMS required: $response, which isn't yet supported"
|
||||
logger.error(message)
|
||||
throw UnsupportedSMSLoginException(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private inner class FastLoginImpl(handler: NetworkHandler) : LoginStrategy(handler) {
|
||||
override suspend fun doLogin() {
|
||||
val login10 = WtLogin10(client).sendAndExpect(handler)
|
||||
check(login10 is LoginPacketResponse.Success) { "Fast login failed: $login10" }
|
||||
}
|
||||
}
|
||||
}
|
@ -14,9 +14,11 @@ import net.mamoe.mirai.internal.network.WLoginSigInfo
|
||||
import net.mamoe.mirai.internal.utils.crypto.ECDH
|
||||
|
||||
/**
|
||||
* Contains secrets for encryption and decryption during a session created by [SsoProcessor] and [PacketCodec].
|
||||
*
|
||||
* @see AccountSecrets
|
||||
*/
|
||||
internal interface LoginSessionAware {
|
||||
internal interface SsoSession {
|
||||
var outgoingPacketSessionId: ByteArray
|
||||
|
||||
/**
|
@ -13,6 +13,7 @@ import kotlinx.io.core.ByteReadPacket
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.network.Packet
|
||||
import net.mamoe.mirai.internal.network.net.protocol.PacketCodec
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn
|
||||
@ -26,9 +27,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.Heartbeat
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.MiraiLoggerWithSwitch
|
||||
import net.mamoe.mirai.utils.withSwitch
|
||||
|
||||
internal sealed class PacketFactory<TPacket : Packet?> {
|
||||
/**
|
||||
@ -112,11 +111,18 @@ internal suspend inline fun <P : Packet?> IncomingPacketFactory<P>.decode(
|
||||
* 数据包相关的调试输出.
|
||||
* 它默认是关闭的.
|
||||
*/
|
||||
@Deprecated(
|
||||
"Kept for binary compatibility.",
|
||||
ReplaceWith("PacketCodec.PacketLogger", "net.mamoe.mirai.internal.network.net.protocol.PacketCodec"),
|
||||
level = DeprecationLevel.ERROR,
|
||||
)
|
||||
@PublishedApi
|
||||
internal val PacketLogger: MiraiLoggerWithSwitch by lazy {
|
||||
MiraiLogger.create("Packet").withSwitch(false)
|
||||
}
|
||||
internal val PacketLogger: MiraiLoggerWithSwitch
|
||||
get() = PacketCodec.PacketLogger
|
||||
|
||||
/**
|
||||
* Registered factories.
|
||||
*/
|
||||
internal object KnownPacketFactories {
|
||||
object OutgoingFactories : List<OutgoingPacketFactory<*>> by mutableListOf(
|
||||
WtLogin.Login,
|
||||
|
@ -19,13 +19,13 @@ import net.mamoe.mirai.internal.message.contextualBugReportException
|
||||
import net.mamoe.mirai.internal.message.toRichTextElems
|
||||
import net.mamoe.mirai.internal.network.Packet
|
||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||
import net.mamoe.mirai.internal.network.net.protocol.PacketCodec
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgTransmit
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.MultiMsg
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.PacketLogger
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket
|
||||
import net.mamoe.mirai.internal.utils._miraiContentToString
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
|
||||
@ -118,7 +118,7 @@ internal class MultiMsg {
|
||||
val proto: MultiMsg.MultiMsgApplyUpRsp
|
||||
) : Response() {
|
||||
override fun toString(): String {
|
||||
if (PacketLogger.isEnabled) {
|
||||
if (PacketCodec.PacketLogger.isEnabled) {
|
||||
return _miraiContentToString()
|
||||
}
|
||||
return "MultiMsg.ApplyUp.Response.RequireUpload"
|
||||
|
@ -13,9 +13,9 @@ import kotlinx.coroutines.CompletableDeferred
|
||||
import net.mamoe.mirai.internal.MockBot
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.network.handler.impl.NetworkHandlerSupport
|
||||
import net.mamoe.mirai.internal.network.net.protocol.SsoContext
|
||||
import net.mamoe.mirai.internal.network.net.protocol.SsoProcessor
|
||||
import net.mamoe.mirai.internal.network.net.protocol.SsoProcessorContextImpl
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
@ -24,8 +24,7 @@ import java.util.concurrent.atomic.AtomicInteger
|
||||
internal class TestNetworkHandlerContext(
|
||||
override val bot: QQAndroidBot = MockBot(),
|
||||
override val logger: MiraiLogger = MiraiLogger.create("Test"),
|
||||
override val ssoContext: SsoContext = bot,
|
||||
override val configuration: BotConfiguration = bot.configuration
|
||||
override val ssoProcessor: SsoProcessor = SsoProcessor(SsoProcessorContextImpl(bot)),
|
||||
) : NetworkHandlerContext
|
||||
|
||||
internal open class TestNetworkHandler(
|
||||
|
@ -11,7 +11,7 @@ package net.mamoe.mirai.internal.network
|
||||
|
||||
import net.mamoe.mirai.event.events.BotOnlineEvent
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.network.net.protocol.LoginSessionAware
|
||||
import net.mamoe.mirai.internal.network.net.protocol.SsoSession
|
||||
import net.mamoe.mirai.internal.utils.crypto.ECDH
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
|
||||
@ -20,12 +20,12 @@ import net.mamoe.mirai.utils.debug
|
||||
import net.mamoe.mirai.utils.withUse
|
||||
import java.io.File
|
||||
|
||||
internal class TestLoginSessionAware(
|
||||
internal class TestSsoSession(
|
||||
private val accountSecrets: AccountSecrets,
|
||||
override var outgoingPacketSessionId: ByteArray = byteArrayOf(1, 2, 3, 4),
|
||||
override var loginState: Int = 0,
|
||||
override val ecdh: ECDH = ECDH(),
|
||||
) : LoginSessionAware {
|
||||
) : SsoSession {
|
||||
override var wLoginSigInfo: WLoginSigInfo by accountSecrets::wLoginSigInfo
|
||||
override val randomKey: ByteArray by accountSecrets::randomKey
|
||||
}
|
||||
@ -39,7 +39,7 @@ internal fun loadSession(
|
||||
}
|
||||
|
||||
/**
|
||||
* secure to share with others.
|
||||
* Secure to share with others. Designed to save real data for tests.
|
||||
*/
|
||||
internal fun QQAndroidClient.dumpSessionSafe(): ByteArray {
|
||||
val secrets =
|
||||
|
Loading…
Reference in New Issue
Block a user