commonMain.QQAndroidBot.kt Maven / Gradle / Ivy
/*
* Copyright 2019-2022 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
*/
@file:Suppress("EXPERIMENTAL_API_USAGE", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package net.mamoe.mirai.internal
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.BotOnlineEvent
import net.mamoe.mirai.event.events.BotReloginEvent
import net.mamoe.mirai.internal.network.component.ComponentStorage
import net.mamoe.mirai.internal.network.component.ComponentStorageDelegate
import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
import net.mamoe.mirai.internal.network.component.withFallback
import net.mamoe.mirai.internal.network.components.*
import net.mamoe.mirai.internal.network.handler.NetworkHandler
import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
import net.mamoe.mirai.internal.network.handler.NetworkHandlerContextImpl
import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport
import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport.BaseStateImpl
import net.mamoe.mirai.internal.network.handler.selector.KeepAliveNetworkHandlerSelector
import net.mamoe.mirai.internal.network.handler.selector.NetworkException
import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler
import net.mamoe.mirai.internal.network.handler.state.*
import net.mamoe.mirai.internal.network.handler.state.CombinedStateObserver.Companion.plus
import net.mamoe.mirai.internal.network.impl.netty.ForceOfflineException
import net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandlerFactory
import net.mamoe.mirai.internal.network.notice.TraceLoggingNoticeProcessor
import net.mamoe.mirai.internal.network.notice.UnconsumedNoticesAlerter
import net.mamoe.mirai.internal.network.notice.decoders.GroupNotificationDecoder
import net.mamoe.mirai.internal.network.notice.decoders.MsgInfoDecoder
import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor
import net.mamoe.mirai.internal.network.notice.group.GroupNotificationProcessor
import net.mamoe.mirai.internal.network.notice.group.GroupOrMemberListNoticeProcessor
import net.mamoe.mirai.internal.network.notice.group.GroupRecallProcessor
import net.mamoe.mirai.internal.network.notice.priv.FriendNoticeProcessor
import net.mamoe.mirai.internal.network.notice.priv.OtherClientNoticeProcessor
import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
import net.mamoe.mirai.internal.utils.ImagePatcher
import net.mamoe.mirai.internal.utils.ImagePatcherImpl
import net.mamoe.mirai.internal.utils.subLogger
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.lateinitMutableProperty
import kotlin.contracts.contract
import kotlin.time.Duration.Companion.seconds
internal fun Bot.asQQAndroidBot(): QQAndroidBot {
contract {
returns() implies (this@asQQAndroidBot is QQAndroidBot)
}
return this as QQAndroidBot
}
@Suppress("INVISIBLE_MEMBER", "BooleanLiteralArgument", "OverridingDeprecatedMember")
internal open class QQAndroidBot constructor(
internal val account: BotAccount,
configuration: BotConfiguration,
) : AbstractBot(configuration, account.id) {
override val bot: QQAndroidBot get() = this
val client get() = components[SsoProcessor].client
override fun close(cause: Throwable?) {
if (!this.isActive) return
runBlocking {
try { // this may not be very good but
withTimeoutOrNull(5.seconds) {
components[SsoProcessor].logout(network)
}
} catch (ignored: Exception) {
}
}
super.close(cause)
}
///////////////////////////////////////////////////////////////////////////
// network
///////////////////////////////////////////////////////////////////////////
// also called by tests.
fun ComponentStorage.stateObserverChain(): StateObserver {
val components = this
val eventDispatcher = this[EventDispatcher]
return StateObserver.chainOfNotNull(
components[BotInitProcessor].asObserver(),
object : StateChangedObserver(State.OK) {
private val shouldBroadcastRelogin = atomic(false)
override fun stateChanged0(
networkHandler: NetworkHandlerSupport,
previous: BaseStateImpl,
new: BaseStateImpl,
) {
eventDispatcher.broadcastAsync(BotOnlineEvent(bot)).thenBroadcast(eventDispatcher) {
if (!shouldBroadcastRelogin.compareAndSet(false, true)) {
BotReloginEvent(bot, new.getCause())
} else null
}
}
override fun toString(): String = "StateChangedObserver(BotOnlineEventBroadcaster)"
},
StateChangedObserver("LastConnectedAddressUpdater", State.OK) {
components[ServerList].run {
lastConnectedIP = getLastPolledIP()
}
},
StateChangedObserver("LastDisconnectedAddressUpdater", State.CLOSED) {
components[ServerList].run {
lastDisconnectedIP = lastConnectedIP
}
},
StateChangedObserver("BotOfflineEventBroadcasterAfter", State.OK, State.CLOSED) { new ->
// logging performed by BotOfflineEventMonitor
val cause = new.getCause()
when {
cause is ForceOfflineException -> {
eventDispatcher.broadcastAsync(BotOfflineEvent.Force(bot, cause.title, cause.message))
}
cause is StatSvc.ReqMSFOffline.MsfOfflineToken -> {
eventDispatcher.broadcastAsync(BotOfflineEvent.MsfOffline(bot, cause))
}
cause is NetworkException && cause.recoverable -> {
eventDispatcher.broadcastAsync(BotOfflineEvent.Dropped(bot, cause))
}
cause is BotClosedByEvent -> {
}
else -> {
// handled by BotOfflineEventBroadcasterBefore
}
}
},
BeforeStateChangedObserver("BotOfflineEventBroadcasterBefore", State.OK, State.CLOSED) { new ->
// logging performed by BotOfflineEventMonitor
val cause = new.getCause()
when {
// handled by BotOfflineEventBroadcasterAfter
cause is ForceOfflineException -> {}
cause is StatSvc.ReqMSFOffline.MsfOfflineToken -> {}
cause is NetworkException && cause.recoverable -> {}
cause is BotClosedByEvent -> {}
else -> {
// any other unexpected exceptions considered as an error
// When bot is closed, eventDispatcher.isActive will be false.
// While in TestEventDispatcherImpl, eventDispatcher.isActive will always be true to enable catching the event.
if (eventDispatcher.isActive) {
eventDispatcher.broadcastAsync { BotOfflineEvent.Active(bot, cause) }
} else {
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch {
BotOfflineEvent.Active(bot, cause).broadcast()
}
}
}
}
},
).safe(logger.subLogger("StateObserver")) + LoggingStateObserver.createLoggingIfEnabled()
}
private val networkLogger: MiraiLogger by lazy { configuration.networkLoggerSupplier(this) }
final override val components: ComponentStorage get() = network.context
private val defaultBotLevelComponents: ComponentStorage by lateinitMutableProperty {
createBotLevelComponents().apply {
set(StateObserver, stateObserverChain())
}.also { components ->
components[BotOfflineEventMonitor].attachJob(bot, this)
}
}
open fun createBotLevelComponents(): ConcurrentComponentStorage = ConcurrentComponentStorage {
val components = ComponentStorageDelegate { [email protected] }
// There's no need to interrupt a broadcasting event when network handler closed.
set(EventDispatcher, EventDispatcherImpl(bot.coroutineContext, logger.subLogger("EventDispatcher")))
val pipelineLogger = networkLogger.subLogger("NoticeProcessor") // shorten name
set(
NoticeProcessorPipeline,
NoticeProcessorPipelineImpl.create(
bot,
MsgInfoDecoder(pipelineLogger.subLogger("MsgInfoDecoder")),
GroupNotificationDecoder(),
FriendNoticeProcessor(pipelineLogger.subLogger("FriendNoticeProcessor")),
GroupOrMemberListNoticeProcessor(pipelineLogger.subLogger("GroupOrMemberListNoticeProcessor")),
GroupMessageProcessor(pipelineLogger.subLogger("GroupMessageProcessor")),
GroupNotificationProcessor(pipelineLogger.subLogger("GroupNotificationProcessor")),
PrivateMessageProcessor(),
OtherClientNoticeProcessor(),
GroupRecallProcessor(),
UnconsumedNoticesAlerter(pipelineLogger.subLogger("UnconsumedNoticesAlerter")),
TraceLoggingNoticeProcessor(pipelineLogger.subLogger("TraceLoggingNoticeProcessor"))
)
)
set(SsoProcessorContext, SsoProcessorContextImpl(bot))
set(SsoProcessor, SsoProcessorImpl(get(SsoProcessorContext)))
set(HeartbeatProcessor, HeartbeatProcessorImpl())
set(HeartbeatScheduler, TimeBasedHeartbeatSchedulerImpl(networkLogger.subLogger("HeartbeatScheduler")))
set(KeyRefreshProcessor, KeyRefreshProcessorImpl(networkLogger.subLogger("KeyRefreshProcessor")))
set(ConfigPushProcessor, ConfigPushProcessorImpl(networkLogger.subLogger("ConfigPushProcessor")))
set(BotOfflineEventMonitor, BotOfflineEventMonitorImpl())
set(BotInitProcessor, BotInitProcessorImpl(bot, components, networkLogger.subLogger("BotInitProcessor")))
set(ContactCacheService, ContactCacheServiceImpl(bot, networkLogger.subLogger("ContactCacheService")))
set(ContactUpdater, ContactUpdaterImpl(bot, components, networkLogger.subLogger("ContactUpdater")))
set(
BdhSessionSyncer,
BdhSessionSyncerImpl(configuration, components, networkLogger.subLogger("BotSessionSyncer")),
)
set(
MessageSvcSyncer,
MessageSvcSyncerImpl(bot, bot.coroutineContext, networkLogger.subLogger("MessageSvcSyncer")),
)
set(
EcdhInitialPublicKeyUpdater,
EcdhInitialPublicKeyUpdaterImpl(bot, networkLogger.subLogger("ECDHInitialPublicKeyUpdater")),
)
set(ServerList, ServerListImpl(networkLogger.subLogger("ServerList")))
set(PacketLoggingStrategy, PacketLoggingStrategyImpl(bot))
set(
PacketHandler,
PacketHandlerChain(
EventBroadcasterPacketHandler(components),
CallPacketFactoryPacketHandler(bot),
LoggingPacketHandlerAdapter(get(PacketLoggingStrategy), networkLogger),
),
)
set(PacketCodec, PacketCodecImpl())
set(
OtherClientUpdater,
OtherClientUpdaterImpl(bot, components, networkLogger.subLogger("OtherClientUpdater")),
)
set(ConfigPushSyncer, ConfigPushSyncerImpl())
set(
AccountSecretsManager,
configuration.createAccountsSecretsManager(bot.logger.subLogger("AccountSecretsManager")),
)
set(ImagePatcher, ImagePatcherImpl())
}
/**
* This would overrides those from [createBotLevelComponents]
*/
open fun createNetworkLevelComponents(): ComponentStorage {
return ConcurrentComponentStorage {
set(BotClientHolder, BotClientHolderImpl(bot, networkLogger.subLogger("BotClientHolder")))
set(SyncController, SyncControllerImpl())
set(ClockHolder, ClockHolder())
}.withFallback(defaultBotLevelComponents)
}
override fun createNetworkHandler(): NetworkHandler {
return SelectorNetworkHandler(
KeepAliveNetworkHandlerSelector(
maxAttempts = configuration.reconnectionRetryTimes.coerceIn(1, Int.MAX_VALUE),
logger = networkLogger.subLogger("Selector")
) {
val context = NetworkHandlerContextImpl(
bot,
networkLogger,
createNetworkLevelComponents(),
)
NettyNetworkHandlerFactory.create(
context,
context[ServerList].pollAny().toSocketAddress(),
)
},
) // We can move the factory to configuration but this is not necessary for now.
}
}
internal fun QQAndroidBot.getGroupByUinOrFail(uin: Long) =
getGroupByUin(uin) ?: throw NoSuchElementException("group.uin=$uin")
internal fun QQAndroidBot.getGroupByUin(uin: Long) = groups.firstOrNull { it.uin == uin }
/**
* uin first
*/
internal fun QQAndroidBot.getGroupByUinOrCode(uinOrCode: Long) =
groups.firstOrNull { it.uin == uinOrCode } ?: groups.firstOrNull { it.id == uinOrCode }
/**
* uin first
*/
internal fun QQAndroidBot.getGroupByUinOrCodeOrFail(uinOrCode: Long) =
getGroupByUinOrCode(uinOrCode) ?: throw NoSuchElementException("group.code or uin=$uinOrCode")
/**
* code first
*/
internal fun QQAndroidBot.getGroupByCodeOrUin(uinOrCode: Long) =
groups.firstOrNull { it.id == uinOrCode } ?: groups.firstOrNull { it.uin == uinOrCode }
© 2015 - 2025 Weber Informatics LLC | Privacy Policy