From 3ce6f092a1a05c65a531bf99143c0b41d2f68d9a Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 24 Dec 2020 23:37:52 +0800 Subject: [PATCH] Support OtherClient list sync after login, support deviceName --- .../src/commonMain/kotlin/IMirai.kt | 12 ++- .../commonMain/kotlin/contact/OtherClient.kt | 77 ++++++++++++++-- .../kotlin/contact/OtherClientList.kt | 8 +- .../commonMain/kotlin/event/events/message.kt | 2 +- .../kotlin/event/events/otherClient.kt | 12 ++- mirai-core/src/commonMain/kotlin/MiraiImpl.kt | 10 ++ .../src/commonMain/kotlin/QQAndroidBot.kt | 6 +- .../kotlin/contact/OtherClientImpl.kt | 11 +-- .../src/commonMain/kotlin/contact/util.kt | 2 +- .../network/QQAndroidBotNetworkHandler.kt | 20 +++- .../jce/{shareData.kt => DeviceItemDes.kt} | 11 +-- .../protocol/data/jce/SvcDevLoginInfo.kt | 91 +++++++++++++++++++ .../data/jce/SvcRequestPushReadedNotify.kt | 2 +- .../data/jce/SvcRspGetDevLoginInfo.kt | 26 ++++++ .../chat/receive/MessageSvc.PbGetMsg.kt | 3 +- .../network/protocol/packet/login/StatSvc.kt | 90 ++++++++++++++---- .../serialization/tars/internal/TarsInput.kt | 10 +- .../kotlin/utils/io/serialization/utils.kt | 10 +- 18 files changed, 329 insertions(+), 74 deletions(-) rename mirai-core/src/commonMain/kotlin/network/protocol/data/jce/{shareData.kt => DeviceItemDes.kt} (62%) create mode 100644 mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcDevLoginInfo.kt create mode 100644 mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcRspGetDevLoginInfo.kt diff --git a/mirai-core-api/src/commonMain/kotlin/IMirai.kt b/mirai-core-api/src/commonMain/kotlin/IMirai.kt index 44b29a7ac..665283921 100644 --- a/mirai-core-api/src/commonMain/kotlin/IMirai.kt +++ b/mirai-core-api/src/commonMain/kotlin/IMirai.kt @@ -14,9 +14,7 @@ package net.mamoe.mirai import net.mamoe.kjbb.JvmBlockingBridge -import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.contact.Friend -import net.mamoe.mirai.contact.PermissionDeniedException +import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent import net.mamoe.mirai.event.events.MemberJoinRequestEvent import net.mamoe.mirai.event.events.NewFriendRequestEvent @@ -179,6 +177,14 @@ public interface IMirai : LowLevelApiAccessor { message: String = "" ) + /** + * 获取在线的 [OtherClient] 列表 + */ + @JvmBlockingBridge + public suspend fun getOnlineOtherClientsList( + bot: Bot, + ): List + /** * 忽略加群验证(需管理员权限) * diff --git a/mirai-core-api/src/commonMain/kotlin/contact/OtherClient.kt b/mirai-core-api/src/commonMain/kotlin/contact/OtherClient.kt index 3e77881f1..7eae0c135 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/OtherClient.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/OtherClient.kt @@ -12,21 +12,21 @@ package net.mamoe.mirai.contact import net.mamoe.mirai.Bot +import net.mamoe.mirai.event.events.OtherClientOnlineEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol.ANDROID_PAD import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol.ANDROID_PHONE import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiExperimentalApi +import net.mamoe.mirai.utils.MiraiInternalApi /** * 其他设备. 如当 [Bot] 以 [ANDROID_PHONE] 登录时, 还可以有其他设备以 [ANDROID_PAD], iOS, PC 或其他设备登录. */ public interface OtherClient : Contact { - /** - * 设备类型 - */ - public val kind: ClientKind + public val info: OtherClientInfo /** * 此设备属于的 [Bot] @@ -47,11 +47,72 @@ public interface OtherClient : Contact { } } +@MiraiInternalApi +public inline val OtherClient.appId: Int + get() = info.appId +public inline val OtherClient.platform: Platform get() = info.platform +public inline val OtherClient.deviceName: String get() = info.deviceName +public inline val OtherClient.deviceKind: String get() = info.deviceKind + +@MiraiExperimentalApi +public data class OtherClientInfo @MiraiInternalApi constructor( + + /** + * 仅运行时识别. 随着客户端更新此 ID 可能有变化. + * + * 不可能有 [appId] 相同的两个客户端t在线. + */ + public val appId: Int, + + /** + * 登录平台 + */ + public val platform: Platform, + + /** + * 示例: + * - Mi 10 Pro + * - 电脑 + * - xxx 的 iPad + * - mirai + */ + public val deviceName: String, + + /** + * 示例: + * - Mi 10 Pro + * - DESKTOP-ABCDEFG + * - iPad + * - mirai + */ + public val deviceKind: String, +) + /** - * 设备类型 + * @see OtherClientInfo.platform + */ +public enum class Platform( + @MiraiInternalApi public val terminalId: Int, + @MiraiInternalApi public val platformId: Int, +) { + IOS(3, 1), + MOBILE(2, 2), // android + WINDOWS(1, 3), + + UNKNOWN(0, 0) + ; + + public companion object { + @MiraiInternalApi + public fun getByTerminalId(terminalId: Int): Platform? = values().find { it.terminalId == terminalId } + } +} + +/** + * 详细设备类型. 在登录时查询到的设备列表中无此信息. 只在 [OtherClientOnlineEvent] 才有. */ public enum class ClientKind( - public val id: Int, + @MiraiInternalApi public val id: Int, ) { ANDROID_PAD(68104), AOL_CHAOJIHUIYUAN(73730), @@ -76,9 +137,7 @@ public enum class ClientKind( QQ_SERVICE(71170), TV_QQ(69130), WIN8(69899), - WINPHONE(65804), - - UNKNOWN(-1); + WINPHONE(65804); public companion object { public operator fun get(id: Int): ClientKind? = values().find { it.id == id } diff --git a/mirai-core-api/src/commonMain/kotlin/contact/OtherClientList.kt b/mirai-core-api/src/commonMain/kotlin/contact/OtherClientList.kt index 63d1dc95f..67be94759 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/OtherClientList.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/OtherClientList.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.contact +import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import java.util.concurrent.ConcurrentLinkedQueue @@ -16,8 +17,9 @@ public class OtherClientList internal constructor( @MiraiInternalApi @JvmField public val delegate: MutableCollection = ConcurrentLinkedQueue() ) : Collection by delegate { - public operator fun get(kind: ClientKind): OtherClient? = this.find { it.kind == kind } + @MiraiExperimentalApi + public operator fun get(appId: Int): OtherClient? = this.find { it.appId == appId } - public fun getOrFail(kind: ClientKind): OtherClient = - get(kind) ?: throw NoSuchElementException("OtherClient with kind=$kind not found.") + public fun getOrFail(appId: Int): OtherClient = + get(appId) ?: throw NoSuchElementException("OtherClient with appId=$appId not found.") } \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/message.kt b/mirai-core-api/src/commonMain/kotlin/event/events/message.kt index b375e578a..26d02b025 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/message.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/message.kt @@ -502,7 +502,7 @@ public class OtherClientMessageEvent constructor( public override val senderName: String get() = sender.nick public override val source: OnlineMessageSource.Incoming.FromFriend get() = message.source as OnlineMessageSource.Incoming.FromFriend - public override fun toString(): String = "OtherClientMessageEvent(client=${client.kind}, message=$message)" + public override fun toString(): String = "OtherClientMessageEvent(client=${client.platform}, message=$message)" } /** diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/otherClient.kt b/mirai-core-api/src/commonMain/kotlin/event/events/otherClient.kt index d905c3dac..1e5f698fa 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/otherClient.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/otherClient.kt @@ -10,9 +10,11 @@ package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.ClientKind import net.mamoe.mirai.contact.OtherClient import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.internal.network.Packet +import net.mamoe.mirai.utils.MiraiInternalApi public interface OtherClientEvent : BotEvent, Packet { public val client: OtherClient @@ -23,13 +25,17 @@ public interface OtherClientEvent : BotEvent, Packet { /** * 其他设备上线 */ -public data class OtherClientOnlineEvent( - override val client: OtherClient +public data class OtherClientOnlineEvent @MiraiInternalApi constructor( + override val client: OtherClient, + /** + * 详细设备类型,通常非 `null`. + */ + val kind: ClientKind? ) : OtherClientEvent, AbstractEvent() /** * 其他设备离线 */ public data class OtherClientOfflineEvent( - override val client: OtherClient + override val client: OtherClient, ) : OtherClientEvent, AbstractEvent() \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index 4143dd71c..38af7ea9e 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -28,6 +28,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg import net.mamoe.mirai.internal.network.protocol.packet.chat.* import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList +import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.utils.MiraiPlatformUtils import net.mamoe.mirai.internal.utils.encodeToString import net.mamoe.mirai.internal.utils.io.serialization.toByteArray @@ -156,6 +157,15 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { group.checkBotPermission(MemberPermission.ADMINISTRATOR) } + override suspend fun getOnlineOtherClientsList(bot: Bot): List { + bot.asQQAndroidBot() + val response = bot.network.run { + StatSvc.GetDevLoginInfo(bot.client).sendAndExpect() + } + + return response.deviceList.map { it.toOtherClientInfo() } + } + override suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean) { checkGroupPermission(event.bot, event.groupId) { event::class.simpleName ?: "" } @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") diff --git a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt index 047658aa9..a28c93a79 100644 --- a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt +++ b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt @@ -23,7 +23,6 @@ import net.mamoe.mirai.internal.contact.checkIsGroupImpl import net.mamoe.mirai.internal.message.* import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.internal.network.QQAndroidClient -import net.mamoe.mirai.internal.network.protocol.data.jce.InstanceInfo import net.mamoe.mirai.internal.network.protocol.packet.chat.* import net.mamoe.mirai.message.data.* import net.mamoe.mirai.network.LoginFailedException @@ -41,10 +40,9 @@ internal fun Bot.asQQAndroidBot(): QQAndroidBot { } internal fun QQAndroidBot.createOtherClient( - kind: ClientKind, - instanceInfo: InstanceInfo, + info: OtherClientInfo, ): OtherClientImpl { - return OtherClientImpl(this, coroutineContext, kind, instanceInfo) + return OtherClientImpl(this, coroutineContext, info) } @Suppress("INVISIBLE_MEMBER", "BooleanLiteralArgument", "OverridingDeprecatedMember") diff --git a/mirai-core/src/commonMain/kotlin/contact/OtherClientImpl.kt b/mirai-core/src/commonMain/kotlin/contact/OtherClientImpl.kt index 8a433bfd0..88d2c7d29 100644 --- a/mirai-core/src/commonMain/kotlin/contact/OtherClientImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/OtherClientImpl.kt @@ -10,23 +10,18 @@ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.ClientKind import net.mamoe.mirai.contact.OtherClient -import net.mamoe.mirai.internal.network.protocol.data.jce.InstanceInfo +import net.mamoe.mirai.contact.OtherClientInfo import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.ExternalImage -import net.mamoe.mirai.utils.cast import kotlin.coroutines.CoroutineContext -internal val OtherClient.instanceInfo: InstanceInfo get() = this.cast().instanceInfo - internal class OtherClientImpl( bot: Bot, coroutineContext: CoroutineContext, - override val kind: ClientKind, - val instanceInfo: InstanceInfo + override val info: OtherClientInfo, ) : OtherClient, AbstractContact(bot, coroutineContext) { override suspend fun sendMessage(message: Message): MessageReceipt { throw UnsupportedOperationException("OtherClientImpl.sendMessage is not yet supported.") @@ -37,7 +32,7 @@ internal class OtherClientImpl( } override fun toString(): String { - return "OtherClient(bot=${bot.id},kind=$kind)" + return "OtherClient(bot=${bot.id},deviceName=${info.deviceName},platform=${info.platform})" } } diff --git a/mirai-core/src/commonMain/kotlin/contact/util.kt b/mirai-core/src/commonMain/kotlin/contact/util.kt index cb0b3c7ae..d643cb22e 100644 --- a/mirai-core/src/commonMain/kotlin/contact/util.kt +++ b/mirai-core/src/commonMain/kotlin/contact/util.kt @@ -107,7 +107,7 @@ internal fun net.mamoe.mirai.event.events.MessageEvent.logMessageReceived() { "${sender.nick}(${sender.id}) -> $message".replaceMagicCodes() } is net.mamoe.mirai.event.events.OtherClientMessageEvent -> bot.logger.verbose { - "${client.kind} -> $message".replaceMagicCodes() + "${client.platform} -> $message".replaceMagicCodes() } is GroupMessageSyncEvent -> bot.logger.verbose { renderGroupMessage(group, senderName, sender, message) diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt index 75e8f9213..fcf4393cb 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt @@ -20,6 +20,8 @@ import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.buildPacket import kotlinx.io.core.readBytes import net.mamoe.mirai.Mirai +import net.mamoe.mirai.contact.deviceName +import net.mamoe.mirai.contact.platform import net.mamoe.mirai.event.* import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOnlineEvent @@ -27,6 +29,7 @@ import net.mamoe.mirai.event.events.BotReloginEvent import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.* +import net.mamoe.mirai.internal.createOtherClient import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopNum import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.packet.* @@ -247,11 +250,24 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo // println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}") registerClientOnline() + startHeartbeatJobOrKill() + + bot.otherClientsLock.withLock { + updateOtherClientsList() + } } - private suspend fun registerClientOnline(timeoutMillis: Long = 3000) { - StatSvc.Register(bot.client).sendAndExpect(timeoutMillis) + private suspend fun registerClientOnline() { + StatSvc.Register(bot.client).sendAndExpect() + } + + private suspend fun updateOtherClientsList() { + val list = Mirai.getOnlineOtherClientsList(bot) + bot.otherClients.delegate.clear() + bot.otherClients.delegate.addAll(list.map { bot.createOtherClient(it) }) + + bot.logger.info { "Online OtherClients: " + bot.otherClients.joinToString { "${it.deviceName}(${it.platform.name})" } } } // caches diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/shareData.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/DeviceItemDes.kt similarity index 62% rename from mirai-core/src/commonMain/kotlin/network/protocol/data/jce/shareData.kt rename to mirai-core/src/commonMain/kotlin/network/protocol/data/jce/DeviceItemDes.kt index c3c8b7584..21c978075 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/shareData.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/DeviceItemDes.kt @@ -6,18 +6,13 @@ * * https://github.com/mamoe/mirai/blob/master/LICENSE */ - -package net.mamoe.mirai.internal.network.protocol.data.jce +package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId -@Suppress("ClassName", "SpellCheckingInspection") @Serializable -internal class shareData( - @JvmField @TarsId(0) val pkgname: String = "", - @JvmField @TarsId(1) val msgtail: String = "", - @JvmField @TarsId(2) val picurl: String = "", - @JvmField @TarsId(3) val url: String = "" +internal class DeviceItemDes( + @JvmField @TarsId(0) val vecItemDes: ByteArray ) : JceStruct diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcDevLoginInfo.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcDevLoginInfo.kt new file mode 100644 index 000000000..e30bfb5ac --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcDevLoginInfo.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2019-2020 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.protocol.data.jce + +import kotlinx.serialization.Serializable +import net.mamoe.mirai.contact.OtherClientInfo +import net.mamoe.mirai.contact.Platform +import net.mamoe.mirai.internal.utils.io.JceStruct +import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId + +@Serializable +internal data class SvcDevLoginInfo( + @JvmField @TarsId(0) val iAppId: Long, + // @JvmField @TarsId(1) val vecGuid: ByteArray? = null, + @JvmField @TarsId(2) val iLoginTime: Long, + @JvmField @TarsId(3) val iLoginPlatform: Long? = null, // 1: ios, 2: android, 3: windows, 4: symbian, 5: feature + @JvmField @TarsId(4) val loginLocation: String? = "", + @JvmField @TarsId(5) val deviceName: String? = "", + @JvmField @TarsId(6) val deviceTypeInfo: String? = "", + // @JvmField @TarsId(7) val stDeviceItemDes: DeviceItemDes? = null, + @JvmField @TarsId(8) val iTerType: Long? = null, // 1:windows, 2: mobile, 3: ios + @JvmField @TarsId(9) val iProductType: Long? = null, // always 0 + @JvmField @TarsId(10) val iCanBeKicked: Long? = null // isOnline +) : JceStruct + +internal fun SvcDevLoginInfo.toOtherClientInfo() = OtherClientInfo( + iAppId.toInt(), + Platform.getByTerminalId(iTerType?.toInt() ?: 0) ?: Platform.UNKNOWN, + deviceName.orEmpty(), + deviceTypeInfo.orEmpty() +) + + +/* +vecCurrentLoginDevInfo=[SvcDevLoginInfo#1676411955 { + deviceName=mirai + deviceTypeInfo=mirai + iAppId=0x000000002002E738(537061176) + iCanBeKicked=0x0000000000000001(1) + iLoginPlatform=0x0000000000000002(2) + iLoginTime=0x000000005FE4A45C(1608819804) + iProductType=0x0000000000000000(0) + iTerType=0x0000000000000002(2) +}, SvcDevLoginInfo#1676411955 { + deviceName=xxx的iPad + deviceTypeInfo=iPad + iAppId=0x000000002002FB7C(537066364) + iCanBeKicked=0x0000000000000001(1) + iLoginPlatform=0x0000000000000001(1) + iLoginTime=0x000000005FE4A418(1608819736) + iProductType=0x0000000000000000(0) + iTerType=0x0000000000000003(3) +}, SvcDevLoginInfo#1676411955 { + deviceName=Mi 10 Pro + deviceTypeInfo=Mi 10 Pro + iAppId=0x000000002002FBB7(537066423) + iCanBeKicked=0x0000000000000001(1) + iLoginPlatform=0x0000000000000002(2) + iLoginTime=0x000000005FE4A628(1608820264) + iProductType=0x0000000000000000(0) + iTerType=0x0000000000000002(2) +}, SvcDevLoginInfo#1676411955 { + deviceName=DESKTOP-KMQEB7V + deviceTypeInfo=电脑 + iAppId=0x0000000000000001(1) + iCanBeKicked=0x0000000000000001(1) + iLoginPlatform=0x0000000000000003(3) + iLoginTime=0x000000005FE4A5C1(1608820161) + iProductType=0x0000000000000000(0) + iTerType=0x0000000000000001(1) + loginLocation=中国湖北省武汉市 +}] + */ +@Serializable +internal class SvcReqGetDevLoginInfo( + @JvmField @TarsId(0) val vecGuid: ByteArray, + @JvmField @TarsId(1) val appName: String = "", + @JvmField @TarsId(2) val iLoginType: Long = 1L, + @JvmField @TarsId(3) val iTimeStamp: Long, + @JvmField @TarsId(4) val iNextItemIndex: Long, + @JvmField @TarsId(5) val iRequireMax: Long, + @JvmField @TarsId(6) val iGetDevListType: Long? = 7L // 1: online list 2: recent list? 4: getAuthLoginDevList? +) : JceStruct + diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcRequestPushReadedNotify.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcRequestPushReadedNotify.kt index e34be31a9..cad35a1f1 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcRequestPushReadedNotify.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcRequestPushReadedNotify.kt @@ -84,7 +84,7 @@ internal class RequestPushGroupMsg( @JvmField @TarsId(13) val uAppShareID: Long? = null, @JvmField @TarsId(14) val vGPicInfo: List? = null, @JvmField @TarsId(15) val vAppShareCookie: ByteArray? = null, - @JvmField @TarsId(16) val stShareData: shareData? = null, + @JvmField @TarsId(16) val stShareData: ShareData? = null, @JvmField @TarsId(17) val fromInstId: Long? = null, @JvmField @TarsId(18) val stGroupMsgHead: GroupMsgHead? = null, @JvmField @TarsId(19) val wUserActive: Int? = null, diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcRspGetDevLoginInfo.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcRspGetDevLoginInfo.kt new file mode 100644 index 000000000..0f3e2112f --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcRspGetDevLoginInfo.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2019-2020 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.protocol.data.jce + +import kotlinx.serialization.Serializable +import net.mamoe.mirai.internal.utils.io.JceStruct +import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId + +@Serializable +internal class SvcRspGetDevLoginInfo( + @JvmField @TarsId(0) val iResult: Int, + @JvmField @TarsId(1) val result: String? = "", + @JvmField @TarsId(2) val iNextItemIndex: Long, + @JvmField @TarsId(3) val iTotalItemCount: Long, + @JvmField @TarsId(4) val vecCurrentLoginDevInfo: List? = null, + @JvmField @TarsId(5) val vecHistoryLoginDevInfo: List? = null, + @JvmField @TarsId(6) val vecAuthLoginDevInfo: List? = null +) : JceStruct + diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt index 5afffd3a2..7c52757cd 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt @@ -23,6 +23,7 @@ import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.NormalMember +import net.mamoe.mirai.contact.appId import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.Event @@ -409,7 +410,7 @@ private suspend fun MsgComm.Msg.transform(bot: QQAndroidBot): Packet? { with(data.msgHeader ?: return null) { if (srcUin != dstUin || dstUin != bot.id) return null - val client = bot.otherClients.find { it.instanceInfo.iAppId == srcInstId } + val client = bot.otherClients.find { it.appId == srcInstId } ?: return null// don't compare with dstAppId. diff. val chain = buildMessageChain { diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/StatSvc.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/StatSvc.kt index 9e279895c..abee85264 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/StatSvc.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/StatSvc.kt @@ -13,7 +13,9 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.sync.withLock import kotlinx.io.core.ByteReadPacket import kotlinx.serialization.protobuf.ProtoBuf +import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.ClientKind +import net.mamoe.mirai.contact.appId import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.OtherClientOfflineEvent import net.mamoe.mirai.event.events.OtherClientOnlineEvent @@ -22,6 +24,7 @@ import net.mamoe.mirai.internal.createOtherClient import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient +import net.mamoe.mirai.internal.network.getRandomByteArray import net.mamoe.mirai.internal.network.guid import net.mamoe.mirai.internal.network.protocol.data.jce.* import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x769 @@ -29,6 +32,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.StatSvcGetOnline import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.utils.* import net.mamoe.mirai.internal.utils.io.serialization.* +import net.mamoe.mirai.utils.currentTimeMillis import java.util.concurrent.CancellationException @Suppress("EnumEntryName", "unused") @@ -107,9 +111,9 @@ internal class StatSvc { writeJceStruct( RequestPacket.serializer(), RequestPacket( - sServantName = "PushService", - sFuncName = "SvcReqRegister", - iRequestId = 0, + servantName = "PushService", + funcName = "SvcReqRegister", + requestId = 0, sBuffer = jceRequestSBuffer( "SvcReqRegister", SvcReqRegister.serializer(), @@ -205,9 +209,9 @@ internal class StatSvc { writeJceStruct( RequestPacket.serializer(), RequestPacket( - sServantName = "StatSvc", - sFuncName = "RspMSFForceOffline", - iRequestId = 0, + servantName = "StatSvc", + funcName = "RspMSFForceOffline", + requestId = 0, sBuffer = jceRequestSBuffer( "RspMSFForceOffline", RspMSFForceOffline.serializer(), @@ -230,25 +234,29 @@ internal class StatSvc { bot.otherClientsLock.withLock { val notify = readUniPacket(SvcReqMSFLoginNotifyData.serializer()) - val kind = notify.iClientType?.toInt()?.let(ClientKind::get) ?: return null + val appId = notify.iAppId.toInt() when (notify.status.toInt()) { - 1 -> { - if (bot.otherClients.any { it.kind == kind }) return null - val client = bot.createOtherClient( - kind, - notify.vecInstanceList?.find { it.iClientType == notify.iClientType } - ?: throw contextualBugReportException( - "decode SvcReqMSFLoginNotify (OtherClient online)", - notify._miraiContentToString(), - additional = "Failed to find corresponding instanceInfo." - )) + 1 -> { // online + if (bot.otherClients.any { it.appId == appId }) return null + + val info = Mirai.getOnlineOtherClientsList(bot).find { it.appId == appId } + ?: throw contextualBugReportException( + "SvcReqMSFLoginNotify (OtherClient online)", + notify._miraiContentToString(), + additional = "Failed to find corresponding instanceInfo." + ) + + val client = bot.createOtherClient(info) bot.otherClients.delegate.add(client) - OtherClientOnlineEvent(client) + OtherClientOnlineEvent( + client, + ClientKind[notify.iClientType?.toInt() ?: 0] + ) } - 2 -> { - val client = bot.otherClients.find { it.kind == kind } ?: return null + 2 -> { // off + val client = bot.otherClients.find { it.appId == appId } ?: return null client.cancel(CancellationException("Offline")) bot.otherClients.delegate.remove(client) OtherClientOfflineEvent(client) @@ -262,4 +270,46 @@ internal class StatSvc { } } } + + internal object GetDevLoginInfo : OutgoingPacketFactory("StatSvc.GetDevLoginInfo") { + + @Suppress("unused") // false positive + data class Response( + val deviceList: List, + ) : Packet { + override fun toString(): String { + return "StatSvc.GetDevLoginInfo.Response(deviceList.size=${deviceList.size})" + } + } + + operator fun invoke( + client: QQAndroidClient, + ) = buildOutgoingUniPacket(client) { + writeJceRequestPacket( + servantName = "StatSvc", + funcName = "SvcReqGetDevLoginInfo", + serializer = SvcReqGetDevLoginInfo.serializer(), + body = SvcReqGetDevLoginInfo( + iLoginType = 2, + iRequireMax = 20, + iTimeStamp = currentTimeMillis(), + iGetDevListType = 1, + vecGuid = getRandomByteArray(16), // 服务器防止频繁查询 + iNextItemIndex = 0, + appName = client.protocol.apkId //"com.tencent.mobileqq" + ) + ) + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + val resp = readUniPacket(SvcRspGetDevLoginInfo.serializer()) + + // result 62 maybe too frequent + return Response( + resp.vecCurrentLoginDevInfo?.takeIf { it.isNotEmpty() } + ?: resp.vecAuthLoginDevInfo?.takeIf { it.isNotEmpty() } + ?: resp.vecHistoryLoginDevInfo.orEmpty() + ) + } + } } diff --git a/mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/internal/TarsInput.kt b/mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/internal/TarsInput.kt index 9cc2ab648..672dc8425 100644 --- a/mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/internal/TarsInput.kt +++ b/mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/internal/TarsInput.kt @@ -191,7 +191,7 @@ internal class TarsInput( Tars.BYTE -> input.readByte().toInt() Tars.SHORT -> input.readShort().toInt() Tars.INT -> input.readInt() - else -> error("type mismatch: $head") + else -> error("type mismatch: $head, expecting int.") } } @@ -200,7 +200,7 @@ internal class TarsInput( Tars.ZERO_TYPE -> 0 Tars.BYTE -> input.readByte().toShort() Tars.SHORT -> input.readShort() - else -> error("type mismatch: $head") + else -> error("type mismatch: $head, expecting short.") } } @@ -211,7 +211,7 @@ internal class TarsInput( Tars.SHORT -> input.readShort().toLong() Tars.INT -> input.readInt().toLong() Tars.LONG -> input.readLong() - else -> error("type mismatch ${head.type}") + else -> error("type mismatch ${head}, expecting long.") } } @@ -220,7 +220,7 @@ internal class TarsInput( return when (head.type) { Tars.ZERO_TYPE -> 0 Tars.BYTE -> input.readByte() - else -> error("type mismatch: $head") + else -> error("type mismatch: $head, expecting byte.") } } @@ -228,7 +228,7 @@ internal class TarsInput( return when (head.type) { Tars.ZERO_TYPE -> 0f Tars.FLOAT -> input.readFloat() - else -> error("type mismatch: $head") + else -> error("type mismatch: $head, expecting float.") } } diff --git a/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt b/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt index 448c640b3..6516f180c 100644 --- a/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt +++ b/mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt @@ -96,18 +96,18 @@ internal fun ByteReadPacket.readUniPacket( } } -private fun Map.firstValue(): V = this.entries.first().value +private fun Map.singleValue(): V = this.entries.single().value -private fun ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R { +internal fun ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R { val request = this.readJceStruct(RequestPacket.serializer()) return block(if (name == null) when (request.version?.toInt() ?: 3) { - 2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue() - 3 -> request.sBuffer.loadAs(RequestDataVersion3.serializer()).map.firstValue() + 2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.singleValue().singleValue() + 3 -> request.sBuffer.loadAs(RequestDataVersion3.serializer()).map.singleValue() else -> error("unsupported version ${request.version}") } else when (request.version?.toInt() ?: 3) { 2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.getOrElse(name) { error("cannot find $name") } - .firstValue() + .singleValue() 3 -> request.sBuffer.loadAs(RequestDataVersion3.serializer()).map.getOrElse(name) { error("cannot find $name") } else -> error("unsupported version ${request.version}") })