Close bot if first login failed

This commit is contained in:
Him188 2021-04-25 15:11:19 +08:00
parent 35a6d12dde
commit 95d634233c
13 changed files with 124 additions and 45 deletions

View File

@ -165,6 +165,9 @@ public inline fun <E> MutableList<E>.replaceAllKotlin(operator: (E) -> E) {
} }
} }
public fun systemProp(name: String, default: String): String =
System.getProperty(name, default) ?: default
public fun systemProp(name: String, default: Boolean): Boolean = public fun systemProp(name: String, default: Boolean): Boolean =
System.getProperty(name, default.toString())?.toBoolean() ?: default System.getProperty(name, default.toString())?.toBoolean() ?: default

View File

@ -21,6 +21,7 @@ import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl
import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.contact.uin
import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandler
import net.mamoe.mirai.internal.network.impl.netty.asCoroutineExceptionHandler import net.mamoe.mirai.internal.network.impl.netty.asCoroutineExceptionHandler
import net.mamoe.mirai.supervisorJob import net.mamoe.mirai.supervisorJob
@ -110,11 +111,18 @@ internal abstract class AbstractBot constructor(
// network // network
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
val network: NetworkHandler by lazy { createNetworkHandler() } val network: NetworkHandler by lazy { createNetworkHandler() } // the selector handles renewal of [NetworkHandler]
final override suspend fun login() { final override suspend fun login() {
if (!isActive) error("Bot is already closed and cannot relogin. Please create a new Bot instance then do login.") if (!isActive) error("Bot is already closed and cannot relogin. Please create a new Bot instance then do login.")
try {
network.resumeConnection() network.resumeConnection()
} catch (e: Throwable) { // failed to init
if (!components[SsoProcessor].firstLoginSucceed) {
this.close() // failed to do first login.
}
throw e
}
} }
protected abstract fun createNetworkHandler(): NetworkHandler protected abstract fun createNetworkHandler(): NetworkHandler

View File

@ -51,12 +51,25 @@ internal fun Bot.asQQAndroidBot(): QQAndroidBot {
} }
internal class BotDebugConfiguration( internal class BotDebugConfiguration(
var stateObserver: StateObserver? = when { var stateObserver: StateObserver? = when (systemProp(
systemProp("mirai.debug.network.state.observer.logging", false) -> "mirai.debug.network.state.observer.logging",
"off"
).toLowerCase()) {
"full" -> {
SafeStateObserver( SafeStateObserver(
LoggingStateObserver(MiraiLogger.create("States")), LoggingStateObserver(MiraiLogger.create("States"), true),
MiraiLogger.create("LoggingStateObserver errors") MiraiLogger.create("LoggingStateObserver errors")
) )
}
"off", "false" -> {
null
}
"on", "true" -> {
SafeStateObserver(
LoggingStateObserver(MiraiLogger.create("States"), false),
MiraiLogger.create("LoggingStateObserver errors")
)
}
else -> null else -> null
} }
) )
@ -69,6 +82,14 @@ internal open class QQAndroidBot constructor(
) : AbstractBot(configuration, account.id) { ) : AbstractBot(configuration, account.id) {
override val bot: QQAndroidBot get() = this override val bot: QQAndroidBot get() = this
@Deprecated(
"",
replaceWith = ReplaceWith(
"this.components[SsoProcessor].firstLoginSucceed",
"net.mamoe.mirai.internal.network.components.SsoProcessor"
)
)
internal var firstLoginSucceed: Boolean = false internal var firstLoginSucceed: Boolean = false
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -85,7 +106,7 @@ internal open class QQAndroidBot constructor(
StateChangedObserver(to = State.OK) { new -> StateChangedObserver(to = State.OK) { new ->
bot.launch(logger.asCoroutineExceptionHandler()) { bot.launch(logger.asCoroutineExceptionHandler()) {
BotOnlineEvent(bot).broadcast() BotOnlineEvent(bot).broadcast()
if (bot.firstLoginSucceed) { // TODO: 2021/4/21 actually no use if (bot.components[SsoProcessor].firstLoginSucceed) { // TODO: 2021/4/21 actually no use
BotReloginEvent(bot, new.getCause()).broadcast() BotReloginEvent(bot, new.getCause()).broadcast()
} }
} }

View File

@ -75,7 +75,7 @@ internal class BotInitProcessorImpl(
launch { context[ContactUpdater].loadAll(registerResp.origin) } launch { context[ContactUpdater].loadAll(registerResp.origin) }
} }
bot.firstLoginSucceed = true bot.components[SsoProcessor].firstLoginSucceed = true
} }
private suspend fun registerClientOnline(): StatSvc.Register.Response { private suspend fun registerClientOnline(): StatSvc.Register.Response {

View File

@ -10,8 +10,10 @@
package net.mamoe.mirai.internal.network.components package net.mamoe.mirai.internal.network.components
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.isActive import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.yield
import net.mamoe.mirai.event.ConcurrencyKind import net.mamoe.mirai.event.ConcurrencyKind
import net.mamoe.mirai.event.EventPriority import net.mamoe.mirai.event.EventPriority
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
@ -19,17 +21,27 @@ import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.asQQAndroidBot
import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentKey
import net.mamoe.mirai.internal.network.handler.NetworkHandler
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
import net.mamoe.mirai.utils.castOrNull import net.mamoe.mirai.utils.castOrNull
import net.mamoe.mirai.utils.info import net.mamoe.mirai.utils.info
import net.mamoe.mirai.utils.toHumanReadableString import net.mamoe.mirai.utils.toHumanReadableString
import kotlin.time.measureTime import kotlin.time.measureTime
/**
* Handles [BotOfflineEvent]
*/
internal interface BotOfflineEventMonitor { internal interface BotOfflineEventMonitor {
companion object : ComponentKey<BotOfflineEventMonitor> companion object : ComponentKey<BotOfflineEventMonitor>
/**
* Attach a listener to the [scope]. [scope] is usually the scope of [NetworkHandler.State.OK].
*/
fun attachJob(bot: QQAndroidBot, scope: CoroutineScope) fun attachJob(bot: QQAndroidBot, scope: CoroutineScope)
} }
private data class BotClosedByEvent(val event: BotOfflineEvent) : RuntimeException("Bot is closed by event '$event'.")
internal class BotOfflineEventMonitorImpl : BotOfflineEventMonitor { internal class BotOfflineEventMonitorImpl : BotOfflineEventMonitor {
override fun attachJob(bot: QQAndroidBot, scope: CoroutineScope) { override fun attachJob(bot: QQAndroidBot, scope: CoroutineScope) {
bot.eventChannel.parentScope(scope).subscribeAlways( bot.eventChannel.parentScope(scope).subscribeAlways(
@ -39,38 +51,37 @@ internal class BotOfflineEventMonitorImpl : BotOfflineEventMonitor {
) )
} }
// TODO: 2021/4/25 Review BotOfflineEventMonitor private suspend fun onEvent(event: BotOfflineEvent) = coroutineScope {
private suspend fun onEvent(event: BotOfflineEvent) {
val bot = event.bot.asQQAndroidBot() val bot = event.bot.asQQAndroidBot()
val network = bot.network val network = bot.network
if (
!event.bot.isActive // bot closed fun closeNetwork() {
// || _isConnecting // bot 还在登入 // TODO: 2021/4/14 处理还在登入? if (network.state == State.CLOSED) return // avoid recursive calls.
) { launch {
// Close network to avoid endless reconnection while network is ok // suspend until state becomes CLOSED to hang [onEvent] for synchronization.
// https://github.com/mamoe/mirai/issues/894 while (select { network.onStateChanged { it != State.CLOSED } }) yield()
kotlin.runCatching { network.close(null) }
return
} }
/* bot.launch { network.close(BotClosedByEvent(event)) }
if (network.areYouOk() && event !is BotOfflineEvent.Force && event !is BotOfflineEvent.MsfOffline) { }
// network 运行正常
return@subscribeAlways
}*/
when (event) { when (event) {
is BotOfflineEvent.Active -> { is BotOfflineEvent.Active -> {
// This event might also be broadcast by the network handler by a state observer.
// In that case, `network.state` will be `CLOSED` then `closeNetwork` returns immediately.
// So there won't be recursive calls.
val cause = event.cause val cause = event.cause
val msg = if (cause == null) "" else " with exception: $cause" val msg = if (cause == null) "" else " with exception: $cause"
bot.logger.info("Bot is closed manually $msg", cause) bot.logger.info("Bot is closed manually $msg", cause)
network.close(null)
closeNetwork()
} }
is BotOfflineEvent.Force -> { is BotOfflineEvent.Force -> {
bot.logger.info { "Connection occupied by another android device: ${event.message}" } bot.logger.info { "Connection occupied by another android device: ${event.message}" }
closeNetwork()
if (event.reconnect) { if (event.reconnect) {
bot.logger.info { "Reconnecting..." } bot.logger.info { "Reconnecting..." }
// delay(3000)
} else { } else {
network.close(null)
} }
} }
is BotOfflineEvent.MsfOffline, is BotOfflineEvent.MsfOffline,
@ -82,11 +93,6 @@ internal class BotOfflineEventMonitorImpl : BotOfflineEventMonitor {
} }
if (event.reconnect) { if (event.reconnect) {
if (!network.isOk()) {
// normally closed
return
}
val causeMessage = event.castOrNull<BotOfflineEvent.CauseAware>()?.cause?.toString() ?: event.toString() val causeMessage = event.castOrNull<BotOfflineEvent.CauseAware>()?.cause?.toString() ?: event.toString()
bot.logger.info { "Connection lost, retrying login ($causeMessage)." } bot.logger.info { "Connection lost, retrying login ($causeMessage)." }
@ -94,8 +100,8 @@ internal class BotOfflineEventMonitorImpl : BotOfflineEventMonitor {
val success: Boolean val success: Boolean
val time = measureTime { val time = measureTime {
success = kotlin.runCatching { success = kotlin.runCatching {
bot.login() bot.login() // selector will create new NH to replace the old, closed one, with some further comprehensive considerations. For example, limitation for attempts.
}.isSuccess // resume connection }.isSuccess
} }
if (success) { if (success) {

View File

@ -45,6 +45,8 @@ internal interface SsoProcessor {
val client: QQAndroidClient val client: QQAndroidClient
val ssoSession: SsoSession val ssoSession: SsoSession
var firstLoginSucceed: Boolean
/** /**
* The observers to launch jobs for states. * The observers to launch jobs for states.
* *
@ -78,6 +80,8 @@ internal class SsoProcessorImpl(
// public // public
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
override var firstLoginSucceed: Boolean = false
@Volatile @Volatile
override var client = createClient(ssoContext.bot) override var client = createClient(ssoContext.bot)
@ -111,6 +115,7 @@ internal class SsoProcessorImpl(
SlowLoginImpl(handler).doLogin() SlowLoginImpl(handler).doLogin()
} }
ssoContext.accountSecretsManager.saveSecrets(ssoContext.account, AccountSecretsImpl(client)) ssoContext.accountSecretsManager.saveSecrets(ssoContext.account, AccountSecretsImpl(client))
ssoContext.bot.logger.info { "Login successful." }
} }
override suspend fun logout(handler: NetworkHandler) { override suspend fun logout(handler: NetworkHandler) {

View File

@ -98,9 +98,10 @@ internal interface NetworkHandler {
OK, OK,
/** /**
* The terminal state. Cannot resume anymore. Both [resumeConnection] and [sendAndExpect] throw a [IllegalStateException]. * The terminal state. Both [resumeConnection] and [sendAndExpect] throw a [IllegalStateException].
* *
* When a handler reached [CLOSED] state, it is finalized and cannot be restored to any other states. * **Important nodes**: if [NetworkHandler] is [SelectorNetworkHandler], it might return to a normal state e.g. [INITIALIZED] if new instance of [NetworkHandler] is created.
* However callers usually do not need to pay extra attention on this behavior. Everything will just work fine if you consider [CLOSED] as a final, non-recoverable state.
* *
* At this state [resumeConnection] throws the exception caught from underlying socket implementation (i.e netty). * At this state [resumeConnection] throws the exception caught from underlying socket implementation (i.e netty).
* [sendAndExpect] throws [IllegalStateException]. * [sendAndExpect] throws [IllegalStateException].

View File

@ -15,7 +15,8 @@ import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.debug import net.mamoe.mirai.utils.debug
internal class LoggingStateObserver( internal class LoggingStateObserver(
val logger: MiraiLogger val logger: MiraiLogger,
private val showStacktrace: Boolean = false
) : StateObserver { ) : StateObserver {
override fun toString(): String { override fun toString(): String {
return "LoggingStateObserver" return "LoggingStateObserver"
@ -26,7 +27,10 @@ internal class LoggingStateObserver(
previous: NetworkHandlerSupport.BaseStateImpl, previous: NetworkHandlerSupport.BaseStateImpl,
new: NetworkHandlerSupport.BaseStateImpl new: NetworkHandlerSupport.BaseStateImpl
) { ) {
logger.debug { "State changed: ${previous.correspondingState} -> ${new.correspondingState}" } logger.debug(
{ "State changed: ${previous.correspondingState} -> ${new.correspondingState}" },
if (showStacktrace) Exception("Show stacktrace") else null
)
} }
override fun exceptionOnCreatingNewState( override fun exceptionOnCreatingNewState(
@ -34,7 +38,7 @@ internal class LoggingStateObserver(
previousState: NetworkHandlerSupport.BaseStateImpl, previousState: NetworkHandlerSupport.BaseStateImpl,
exception: Throwable exception: Throwable
) { ) {
logger.debug({ "State changed: ${previousState.correspondingState} -> $exception" }, exception) logger.debug { "State changed: ${previousState.correspondingState} -> $exception" }
} }
override fun beforeStateResume(networkHandler: NetworkHandler, state: NetworkHandlerSupport.BaseStateImpl) { override fun beforeStateResume(networkHandler: NetworkHandler, state: NetworkHandlerSupport.BaseStateImpl) {

View File

@ -36,6 +36,7 @@ import net.mamoe.mirai.internal.message.toMessageChainOnline
import net.mamoe.mirai.internal.network.MultiPacket import net.mamoe.mirai.internal.network.MultiPacket
import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.protocol.data.proto.FrdSysMsg import net.mamoe.mirai.internal.network.protocol.data.proto.FrdSysMsg
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
@ -378,7 +379,7 @@ internal suspend fun MsgComm.Msg.transform(bot: QQAndroidBot, fromSync: Boolean
} }
return null return null
} }
if (!bot.firstLoginSucceed) { if (!bot.components[SsoProcessor].firstLoginSucceed) {
return null return null
} }
val fromUin = if (fromSync) { val fromUin = if (fromSync) {
@ -483,7 +484,7 @@ internal suspend fun MsgComm.Msg.transform(bot: QQAndroidBot, fromSync: Boolean
} }
141 -> { 141 -> {
if (!bot.firstLoginSucceed || msgHead.fromUin == bot.id && !fromSync) { if (!bot.components[SsoProcessor].firstLoginSucceed || msgHead.fromUin == bot.id && !fromSync) {
return null return null
} }
val tmpHead = msgHead.c2cTmpMsgHead ?: return null val tmpHead = msgHead.c2cTmpMsgHead ?: return null

View File

@ -26,6 +26,7 @@ import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.contact.newAnonymous import net.mamoe.mirai.internal.contact.newAnonymous
import net.mamoe.mirai.internal.message.toMessageChainOnline import net.mamoe.mirai.internal.message.toMessageChainOnline
import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody 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.MsgComm
@ -70,7 +71,7 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory<Packet?>("Onlin
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? {
// 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00 // 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00
if (!bot.firstLoginSucceed) return null if (!bot.components[SsoProcessor].firstLoginSucceed) return null
val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer()) val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer())
val msgHead = pbPushMsg.msg.msgHead val msgHead = pbPushMsg.msg.msgHead

View File

@ -71,6 +71,7 @@ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : Abs
set(SsoProcessor, object : SsoProcessor { set(SsoProcessor, object : SsoProcessor {
override val client: QQAndroidClient get() = bot.client override val client: QQAndroidClient get() = bot.client
override val ssoSession: SsoSession get() = bot.client override val ssoSession: SsoSession get() = bot.client
override var firstLoginSucceed: Boolean = false
override fun createObserverChain(): StateObserver = get(StateObserver) override fun createObserverChain(): StateObserver = get(StateObserver)
override suspend fun login(handler: NetworkHandler) { override suspend fun login(handler: NetworkHandler) {
nhEvents.add(NHEvent.Login) nhEvents.add(NHEvent.Login)

View File

@ -13,7 +13,11 @@ import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.MockBot
import net.mamoe.mirai.internal.network.components.BotOfflineEventMonitor
import net.mamoe.mirai.internal.network.components.BotOfflineEventMonitorImpl
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.* import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.*
import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.internal.test.runBlockingUnit
import net.mamoe.mirai.supervisorJob import net.mamoe.mirai.supervisorJob
@ -24,6 +28,29 @@ import kotlin.test.assertTrue
internal class NettyBotLifecycleTest : AbstractNettyNHTest() { internal class NettyBotLifecycleTest : AbstractNettyNHTest() {
@Test
fun `closed on Force offline with BotOfflineEventMonitor`() = runBlockingUnit {
defaultComponents[BotOfflineEventMonitor] = BotOfflineEventMonitorImpl()
bot.login()
assertState(OK)
BotOfflineEvent.Force(bot, "test", "test").broadcast()
assertState(CLOSED)
assertFalse { network.isActive }
assertTrue { bot.isActive }
}
@Test
fun `closed on Active offline with BotOfflineEventMonitor`() = runBlockingUnit {
defaultComponents[BotOfflineEventMonitor] = BotOfflineEventMonitorImpl()
bot.login()
assertState(OK)
BotOfflineEvent.Active(bot, null).broadcast()
assertState(CLOSED)
assertFalse { network.isActive }
assertTrue { bot.isActive }
}
@Test @Test
fun `send logout on exit`() = runBlockingUnit { fun `send logout on exit`() = runBlockingUnit {
assertState(INITIALIZED) assertState(INITIALIZED)

View File

@ -14,6 +14,7 @@ import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.BotOnlineEvent import net.mamoe.mirai.event.events.BotOnlineEvent
import net.mamoe.mirai.event.events.BotReloginEvent import net.mamoe.mirai.event.events.BotReloginEvent
import net.mamoe.mirai.internal.network.components.SsoProcessor
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.INITIALIZED import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.INITIALIZED
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.OK import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.OK
@ -40,7 +41,7 @@ internal class NettyHandlerEventTest : AbstractNettyNHTest() {
assertEventBroadcasts<BotReloginEvent> { assertEventBroadcasts<BotReloginEvent> {
assertEquals(INITIALIZED, network.state) assertEquals(INITIALIZED, network.state)
bot.login() bot.login()
bot.firstLoginSucceed = true bot.components[SsoProcessor].firstLoginSucceed = true
network.setStateConnecting() network.setStateConnecting()
network.resumeConnection() network.resumeConnection()
delay(3.seconds) // `login` launches a job which broadcasts the event delay(3.seconds) // `login` launches a job which broadcasts the event
@ -52,7 +53,7 @@ internal class NettyHandlerEventTest : AbstractNettyNHTest() {
fun `BotOnlineEvent after successful reconnection`() = runBlockingUnit { fun `BotOnlineEvent after successful reconnection`() = runBlockingUnit {
assertEquals(INITIALIZED, network.state) assertEquals(INITIALIZED, network.state)
bot.login() bot.login()
bot.firstLoginSucceed = true bot.components[SsoProcessor].firstLoginSucceed = true
assertEquals(OK, network.state) assertEquals(OK, network.state)
delay(3.seconds) // `login` launches a job which broadcasts the event delay(3.seconds) // `login` launches a job which broadcasts the event
assertEventBroadcasts<BotOnlineEvent>(1) { assertEventBroadcasts<BotOnlineEvent>(1) {
@ -67,7 +68,7 @@ internal class NettyHandlerEventTest : AbstractNettyNHTest() {
fun `BotOfflineEvent after successful reconnection`() = runBlockingUnit { fun `BotOfflineEvent after successful reconnection`() = runBlockingUnit {
assertEquals(INITIALIZED, network.state) assertEquals(INITIALIZED, network.state)
bot.login() bot.login()
bot.firstLoginSucceed = true bot.components[SsoProcessor].firstLoginSucceed = true
assertEquals(OK, network.state) assertEquals(OK, network.state)
delay(3.seconds) // `login` launches a job which broadcasts the event delay(3.seconds) // `login` launches a job which broadcasts the event
assertEventBroadcasts<BotOfflineEvent>(1) { assertEventBroadcasts<BotOfflineEvent>(1) {
@ -81,7 +82,7 @@ internal class NettyHandlerEventTest : AbstractNettyNHTest() {
private fun noEventOn(setState: () -> Unit) = runBlockingUnit { private fun noEventOn(setState: () -> Unit) = runBlockingUnit {
assertState(INITIALIZED) assertState(INITIALIZED)
bot.login() bot.login()
bot.firstLoginSucceed = true bot.components[SsoProcessor].firstLoginSucceed = true
assertState(OK) assertState(OK)
network.setStateConnecting() network.setStateConnecting()
delay(3.seconds) // `login` launches a job which broadcasts the event delay(3.seconds) // `login` launches a job which broadcasts the event