mirror of
https://github.com/mamoe/mirai.git
synced 2026-08-22 14:13:32 +08:00
* [core] process `wtlogin.trans_emp` to support qrcode login
* [core] fix `wtlogin.trans_emp` protocol
* [core] optimize QRCodeLoginProcessor logic
* [core] fix `wtlogin.trans_emp` outgoing packet
* [core] cancel login when logging a bot which is inconsistent from bot factory
* [core] ignore `flag3` check on ANDROID_WATCH & name `flag1` and `flag2`
* [core] provide default `QRCodeLoginListener` for jvm
* [core] don't catch IllegalStateException in QRCodeLoginProcessor
* [core] Use `LoginSolver.createQRCodeLoginListener()` instead of property; Rename configuration name
* [core] Code improvement
* [core] remove qrcode state lock
* [core] ignore `flag3` when command is `wtlogin.trans_emp` in packet codec
* [core] enable qrcode login for macos
* [core] remove debug property in log
* [core] reformat code
* [core] rename `TransEmpResponse` to `Response`
* [core] assert `flag3Exception` not null first
* [core] remove arg client
* [core] update qrcode login notes
* [core] set custom qrcode size
* [core] Draft BotAuthorization
* [core] make SecretsProtection mpp
* [core] BotAuthorization.byXXX
* [core] Move QRCodeLoginListener to `.auth`
* [core] Protect data of BotAccount
* [core] Add SelectorRequireReconnectException
* [core] Implementation of BotAuthorization
* Revert changes of BotConfiguration
* api dump
* [core] remove passwordMd5 in `BotAccount`
* [mock] Add new bot factory function to mock bot factory
* Delete LoginCommandTest
* [core] Improve QRCode render
* [core] Introduce UnsupportedCaptchaMethodException & UnsupportedQRCodeCaptchaException
* api dump
* update docs
* [core] update `DebugRunHelper`
* [core] add simple block for BotAuthorization
* api dump
* Rename `canDoQRLogin` to `supportsQRLogin`, and specify argument names for MiraiProtocolInternal
* Remove `phoneNumber` parameter from BotAccount
* Make `BotAccount.<init>` with String password parameter TestOnly
* Rename `InconsistentBotException` to `InconsistentBotIdException`
* Rename `QRCodeLoginListener.onStatusChanged` to `QRCodeLoginListener.onStateChanged`
* Rename `BotAuthorizationResult` to `BotAuthResult`
* Rename BotAuthComponent, move internal APIs to internal module
* Logic fixup
* doc update
* QRCodeLoginListener.qrCodeStateUpdateInterval & onIntervalLoop
* console login with BotAuthorization
* update testing
* Update mirai-core-api/src/jvmMain/kotlin/utils/LoginSolver.jvm.kt
* Move AuthControl outside SsoProcessor
* Redesign auth
* Add initialTicket to producerCoroutine
* Revert protocol changes of MACOS
* Fix latch death locking
* Fix CoroutineOnDemandValueScope.receiveOrNull exceptional finish
* Fix exception collecting
* Fix DefaultBotAuthorizationFactory loading
* [core] qrcode login for IPAD protocol
* Revert "[core] qrcode login for IPAD protocol"
This reverts commit c1136a8798.
---------
Co-authored-by: Karlatemp <kar@kasukusakura.com>
Co-authored-by: Him188 <Him188@mamoe.net>
326 lines
15 KiB
Kotlin
326 lines
15 KiB
Kotlin
/*
|
|
* 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
|
|
*/
|
|
|
|
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.contact.friendgroup.FriendGroupsImpl
|
|
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.NetworkHandlerFactory
|
|
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.CombinedStateObserver.Companion.plus
|
|
import net.mamoe.mirai.internal.network.handler.state.LoggingStateObserver
|
|
import net.mamoe.mirai.internal.network.handler.state.StateChangedObserver
|
|
import net.mamoe.mirai.internal.network.handler.state.StateObserver
|
|
import net.mamoe.mirai.internal.network.handler.state.safe
|
|
import net.mamoe.mirai.internal.network.impl.ForceOfflineException
|
|
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.FriendGroupNoticeProcessor
|
|
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.actualCacheDir
|
|
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
|
|
override val friendGroups: FriendGroupsImpl by lazy { FriendGroupsImpl(this) }
|
|
|
|
val client get() = components[SsoProcessor].client
|
|
|
|
private val closing = atomic(false)
|
|
override fun close(cause: Throwable?) {
|
|
if (!this.isActive) return
|
|
if (!closing.compareAndSet(false, true)) return
|
|
|
|
if (networkInitialized) {
|
|
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 -> {
|
|
// 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 { this@QQAndroidBot.components }
|
|
|
|
// 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")),
|
|
FriendGroupNoticeProcessor(pipelineLogger.subLogger("FriendGroupNoticeProcessor")),
|
|
PrivateMessageProcessor(),
|
|
OtherClientNoticeProcessor(),
|
|
GroupRecallProcessor(),
|
|
UnconsumedNoticesAlerter(pipelineLogger.subLogger("UnconsumedNoticesAlerter")),
|
|
TraceLoggingNoticeProcessor(pipelineLogger.subLogger("TraceLoggingNoticeProcessor"))
|
|
)
|
|
)
|
|
|
|
set(SsoProcessorContext, SsoProcessorContextImpl(bot))
|
|
set(SsoProcessor, SsoProcessorImpl(get(SsoProcessorContext)))
|
|
set(
|
|
QRCodeLoginProcessor,
|
|
QRCodeLoginProcessor.parse(get(SsoProcessorContext), networkLogger.subLogger("QRCodeLoginProcessor"))
|
|
)
|
|
|
|
val cacheValidator = CacheValidatorImpl(
|
|
get(SsoProcessorContext),
|
|
configuration.actualCacheDir().resolve("validator.bin"),
|
|
networkLogger.subLogger("CacheValidator"),
|
|
)
|
|
set(CacheValidator, cacheValidator)
|
|
|
|
set(HeartbeatProcessor, HeartbeatProcessorImpl())
|
|
set(HeartbeatScheduler, TimeBasedHeartbeatSchedulerImpl(networkLogger.subLogger("HeartbeatScheduler")))
|
|
set(HttpClientProvider, HttpClientProviderImpl())
|
|
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())
|
|
|
|
cacheValidator.register(get(AccountSecretsManager))
|
|
cacheValidator.register(get(BdhSessionSyncer))
|
|
}
|
|
|
|
/**
|
|
* 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(),
|
|
)
|
|
NetworkHandlerFactory.getPlatformDefault().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 }
|