QQAndroidBot.kt 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /*
  2. * Copyright 2019-2021 Mamoe Technologies and contributors.
  3. *
  4. * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
  5. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
  6. *
  7. * https://github.com/mamoe/mirai/blob/master/LICENSE
  8. */
  9. @file:Suppress("EXPERIMENTAL_API_USAGE", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
  10. package net.mamoe.mirai.internal
  11. import kotlinx.atomicfu.atomic
  12. import kotlinx.coroutines.isActive
  13. import kotlinx.coroutines.runBlocking
  14. import net.mamoe.mirai.Bot
  15. import net.mamoe.mirai.event.events.BotOfflineEvent
  16. import net.mamoe.mirai.event.events.BotOnlineEvent
  17. import net.mamoe.mirai.event.events.BotReloginEvent
  18. import net.mamoe.mirai.internal.network.component.ComponentStorage
  19. import net.mamoe.mirai.internal.network.component.ComponentStorageDelegate
  20. import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage
  21. import net.mamoe.mirai.internal.network.component.withFallback
  22. import net.mamoe.mirai.internal.network.components.*
  23. import net.mamoe.mirai.internal.network.handler.NetworkHandler
  24. import net.mamoe.mirai.internal.network.handler.NetworkHandler.State
  25. import net.mamoe.mirai.internal.network.handler.NetworkHandlerContextImpl
  26. import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport
  27. import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport.BaseStateImpl
  28. import net.mamoe.mirai.internal.network.handler.selector.KeepAliveNetworkHandlerSelector
  29. import net.mamoe.mirai.internal.network.handler.selector.NetworkException
  30. import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler
  31. import net.mamoe.mirai.internal.network.handler.state.CombinedStateObserver.Companion.plus
  32. import net.mamoe.mirai.internal.network.handler.state.LoggingStateObserver
  33. import net.mamoe.mirai.internal.network.handler.state.StateChangedObserver
  34. import net.mamoe.mirai.internal.network.handler.state.StateObserver
  35. import net.mamoe.mirai.internal.network.handler.state.safe
  36. import net.mamoe.mirai.internal.network.impl.netty.ForceOfflineException
  37. import net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandlerFactory
  38. import net.mamoe.mirai.internal.network.notice.UnconsumedNoticesAlerter
  39. import net.mamoe.mirai.internal.network.notice.decoders.GroupNotificationDecoder
  40. import net.mamoe.mirai.internal.network.notice.decoders.MsgInfoDecoder
  41. import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor
  42. import net.mamoe.mirai.internal.network.notice.group.GroupOrMemberListNoticeProcessor
  43. import net.mamoe.mirai.internal.network.notice.group.GroupRecallProcessor
  44. import net.mamoe.mirai.internal.network.notice.priv.FriendNoticeProcessor
  45. import net.mamoe.mirai.internal.network.notice.priv.OtherClientNoticeProcessor
  46. import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageNoticeProcessor
  47. import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
  48. import net.mamoe.mirai.internal.utils.subLogger
  49. import net.mamoe.mirai.utils.BotConfiguration
  50. import net.mamoe.mirai.utils.MiraiLogger
  51. import net.mamoe.mirai.utils.lateinitMutableProperty
  52. import kotlin.contracts.contract
  53. internal fun Bot.asQQAndroidBot(): QQAndroidBot {
  54. contract {
  55. returns() implies (this@asQQAndroidBot is QQAndroidBot)
  56. }
  57. return this as QQAndroidBot
  58. }
  59. @Suppress("INVISIBLE_MEMBER", "BooleanLiteralArgument", "OverridingDeprecatedMember")
  60. internal open class QQAndroidBot constructor(
  61. internal val account: BotAccount,
  62. configuration: BotConfiguration,
  63. ) : AbstractBot(configuration, account.id) {
  64. override val bot: QQAndroidBot get() = this
  65. val client get() = components[SsoProcessor].client
  66. override fun close(cause: Throwable?) {
  67. if (!this.isActive) return
  68. runBlocking {
  69. try { // this may not be very good but
  70. components[SsoProcessor].logout(network)
  71. } catch (ignored: Exception) {
  72. }
  73. }
  74. super.close(cause)
  75. }
  76. ///////////////////////////////////////////////////////////////////////////
  77. // network
  78. ///////////////////////////////////////////////////////////////////////////
  79. // also called by tests.
  80. fun ComponentStorage.stateObserverChain(): StateObserver {
  81. val components = this
  82. val eventDispatcher = this[EventDispatcher]
  83. return StateObserver.chainOfNotNull(
  84. components[BotInitProcessor].asObserver(),
  85. object : StateChangedObserver(State.OK) {
  86. private val shouldBroadcastRelogin = atomic(false)
  87. override fun stateChanged0(
  88. networkHandler: NetworkHandlerSupport,
  89. previous: BaseStateImpl,
  90. new: BaseStateImpl,
  91. ) {
  92. eventDispatcher.broadcastAsync(BotOnlineEvent(bot)).thenBroadcast(eventDispatcher) {
  93. if (!shouldBroadcastRelogin.compareAndSet(false, true)) {
  94. BotReloginEvent(bot, new.getCause())
  95. } else null
  96. }
  97. }
  98. override fun toString(): String = "StateChangedObserver(BotOnlineEventBroadcaster)"
  99. },
  100. StateChangedObserver("LastConnectedAddressUpdater", State.OK) {
  101. components[ServerList].run {
  102. lastConnectedIP = getLastPolledIP()
  103. }
  104. },
  105. StateChangedObserver("LastDisconnectedAddressUpdater", State.CLOSED) {
  106. components[ServerList].run {
  107. lastDisconnectedIP = lastConnectedIP
  108. }
  109. },
  110. StateChangedObserver("BotOfflineEventBroadcaster", State.OK, State.CLOSED) { new ->
  111. // logging performed by BotOfflineEventMonitor
  112. val cause = new.getCause()
  113. when {
  114. cause is ForceOfflineException -> {
  115. eventDispatcher.broadcastAsync(BotOfflineEvent.Force(bot, cause.title, cause.message))
  116. }
  117. cause is StatSvc.ReqMSFOffline.MsfOfflineToken -> {
  118. eventDispatcher.broadcastAsync(BotOfflineEvent.MsfOffline(bot, cause))
  119. }
  120. cause is NetworkException && cause.recoverable -> {
  121. eventDispatcher.broadcastAsync(BotOfflineEvent.Dropped(bot, cause))
  122. }
  123. cause is BotClosedByEvent -> {
  124. }
  125. else -> {
  126. // any other unexpected exceptions considered as an error
  127. eventDispatcher.broadcastAsync(BotOfflineEvent.Active(bot, cause))
  128. }
  129. }
  130. },
  131. ).safe(logger.subLogger("StateObserver")) + LoggingStateObserver.createLoggingIfEnabled()
  132. }
  133. private val networkLogger: MiraiLogger by lazy { configuration.networkLoggerSupplier(this) }
  134. final override val components: ComponentStorage get() = network.context
  135. private val defaultBotLevelComponents: ComponentStorage by lateinitMutableProperty {
  136. createBotLevelComponents().apply {
  137. set(StateObserver, stateObserverChain())
  138. }.also { components ->
  139. components[BotOfflineEventMonitor].attachJob(bot, this)
  140. }
  141. }
  142. open fun createBotLevelComponents(): ConcurrentComponentStorage = ConcurrentComponentStorage {
  143. val components = ComponentStorageDelegate { [email protected] }
  144. // There's no need to interrupt a broadcasting event when network handler closed.
  145. set(EventDispatcher, EventDispatcherImpl(bot.coroutineContext, logger.subLogger("EventDispatcher")))
  146. val pipelineLogger = networkLogger.subLogger("NoticeProcessor") // shorten name
  147. set(
  148. NoticeProcessorPipeline,
  149. NoticeProcessorPipelineImpl.create(
  150. MsgInfoDecoder(pipelineLogger.subLogger("MsgInfoDecoder")),
  151. GroupNotificationDecoder(),
  152. FriendNoticeProcessor(pipelineLogger.subLogger("FriendNoticeProcessor")),
  153. GroupOrMemberListNoticeProcessor(pipelineLogger.subLogger("GroupOrMemberListNoticeProcessor")),
  154. GroupMessageProcessor(pipelineLogger.subLogger("GroupMessageProcessor")),
  155. PrivateMessageNoticeProcessor(),
  156. OtherClientNoticeProcessor(),
  157. UnconsumedNoticesAlerter(pipelineLogger.subLogger("UnconsumedNoticesAlerter")),
  158. GroupRecallProcessor()
  159. )
  160. )
  161. set(SsoProcessorContext, SsoProcessorContextImpl(bot))
  162. set(SsoProcessor, SsoProcessorImpl(get(SsoProcessorContext)))
  163. set(HeartbeatProcessor, HeartbeatProcessorImpl())
  164. set(HeartbeatScheduler, TimeBasedHeartbeatSchedulerImpl(networkLogger.subLogger("HeartbeatScheduler")))
  165. set(KeyRefreshProcessor, KeyRefreshProcessorImpl(networkLogger.subLogger("KeyRefreshProcessor")))
  166. set(ConfigPushProcessor, ConfigPushProcessorImpl(networkLogger.subLogger("ConfigPushProcessor")))
  167. set(BotOfflineEventMonitor, BotOfflineEventMonitorImpl())
  168. set(BotInitProcessor, BotInitProcessorImpl(bot, components, networkLogger.subLogger("BotInitProcessor")))
  169. set(ContactCacheService, ContactCacheServiceImpl(bot, networkLogger.subLogger("ContactCacheService")))
  170. set(ContactUpdater, ContactUpdaterImpl(bot, components, networkLogger.subLogger("ContactUpdater")))
  171. set(
  172. BdhSessionSyncer,
  173. BdhSessionSyncerImpl(configuration, components, networkLogger.subLogger("BotSessionSyncer")),
  174. )
  175. set(
  176. MessageSvcSyncer,
  177. MessageSvcSyncerImpl(bot, bot.coroutineContext, networkLogger.subLogger("MessageSvcSyncer")),
  178. )
  179. set(
  180. EcdhInitialPublicKeyUpdater,
  181. EcdhInitialPublicKeyUpdaterImpl(bot, networkLogger.subLogger("ECDHInitialPublicKeyUpdater")),
  182. )
  183. set(ServerList, ServerListImpl(networkLogger.subLogger("ServerList")))
  184. set(PacketLoggingStrategy, PacketLoggingStrategyImpl(bot))
  185. set(
  186. PacketHandler,
  187. PacketHandlerChain(
  188. LoggingPacketHandlerAdapter(get(PacketLoggingStrategy), networkLogger),
  189. EventBroadcasterPacketHandler(components),
  190. CallPacketFactoryPacketHandler(bot),
  191. ),
  192. )
  193. set(PacketCodec, PacketCodecImpl())
  194. set(
  195. OtherClientUpdater,
  196. OtherClientUpdaterImpl(bot, components, networkLogger.subLogger("OtherClientUpdater")),
  197. )
  198. set(ConfigPushSyncer, ConfigPushSyncerImpl())
  199. set(
  200. AccountSecretsManager,
  201. configuration.createAccountsSecretsManager(bot.logger.subLogger("AccountSecretsManager")),
  202. )
  203. }
  204. /**
  205. * This would overrides those from [createBotLevelComponents]
  206. */
  207. open fun createNetworkLevelComponents(): ComponentStorage {
  208. return ConcurrentComponentStorage {
  209. set(BotClientHolder, BotClientHolderImpl(bot, networkLogger.subLogger("BotClientHolder")))
  210. set(SyncController, SyncControllerImpl())
  211. }.withFallback(defaultBotLevelComponents)
  212. }
  213. override fun createNetworkHandler(): NetworkHandler {
  214. return SelectorNetworkHandler(
  215. KeepAliveNetworkHandlerSelector(
  216. maxAttempts = configuration.reconnectionRetryTimes.coerceIn(1, Int.MAX_VALUE),
  217. logger = networkLogger.subLogger("Selector")
  218. ) {
  219. val context = NetworkHandlerContextImpl(
  220. bot,
  221. networkLogger,
  222. createNetworkLevelComponents(),
  223. )
  224. NettyNetworkHandlerFactory.create(
  225. context,
  226. context[ServerList].pollAny().toSocketAddress(),
  227. )
  228. },
  229. ) // We can move the factory to configuration but this is not necessary for now.
  230. }
  231. }
  232. internal fun QQAndroidBot.getGroupByUinOrFail(uin: Long) =
  233. getGroupByUin(uin) ?: throw NoSuchElementException("group.uin=$uin")
  234. internal fun QQAndroidBot.getGroupByUin(uin: Long) = groups.firstOrNull { it.uin == uin }