Support OtherClient online status:

- Add ClientKind
- Add OtherClientOnlineEvent,OtherClientOfflineEvent
- Add Bot.otherClients
- Add OtherClientList
This commit is contained in:
Him188 2020-12-23 20:57:39 +08:00
parent f1136e9b37
commit 3b35dbcac5
11 changed files with 276 additions and 16 deletions

View File

@ -76,6 +76,12 @@ public interface Bot : CoroutineScope, ContactOrBot, UserOrBot {
// region contacts
/**
* 其他设备列表
*/
public val otherClients: OtherClientList
/**
* [User.id] [Bot.id] 相同的 [Friend] 实例
*/

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot
@ -17,6 +19,11 @@ import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol.ANDROID_PHONE
* 其他设备. 如当 [Bot] [ANDROID_PHONE] 登录时, 还可以有其他设备以 [ANDROID_PAD], iOS, PC 或其他设备登录.
*/
public interface OtherClient : Contact {
/**
* 设备类型
*/
public val kind: ClientKind
/**
* 此设备属于的 [Bot]
*/
@ -28,14 +35,40 @@ public interface OtherClient : Contact {
public override val id: Long get() = bot.id
}
/*
public enum class ClientKind {
ANDROID_PHONE,
ANDROID_PAD,
ANDROID_WATCH,
IOS_PHONE,
IOS_PAD,
MAC_OS,
WINDOWS_QQ,
WINDOWS_TIM
}*/
/**
* 设备类型
*/
public enum class ClientKind(
public val id: Int,
) {
ANDROID_PAD(68104),
AOL_CHAOJIHUIYUAN(73730),
AOL_HUIYUAN(73474),
AOL_SQQ(69378),
CAR(65806),
HRTX_IPHONE(66566),
HRTX_PC(66561),
MC_3G(65795),
MISRO_MSG(69634),
MOBILE_ANDROID(65799),
MOBILE_ANDROID_NEW(72450),
MOBILE_HD(65805),
MOBILE_HD_NEW(71426),
MOBILE_IPAD(68361),
MOBILE_IPAD_NEW(72194),
MOBILE_IPHONE(67586),
MOBILE_OTHER(65794),
MOBILE_PC(65793),
MOBILE_WINPHONE_NEW(72706),
QQ_FORELDER(70922),
QQ_SERVICE(71170),
TV_QQ(69130),
WIN8(69899),
WINPHONE(65804),
UNKNOWN(-1);
public companion object {
public operator fun get(id: Int): ClientKind? = values().find { it.id == id }
}
}

View File

@ -0,0 +1,23 @@
/*
* 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.contact
import net.mamoe.mirai.utils.MiraiInternalApi
import java.util.concurrent.ConcurrentLinkedQueue
public class OtherClientList internal constructor(
@MiraiInternalApi @JvmField
public val delegate: MutableCollection<OtherClient> = ConcurrentLinkedQueue()
) : Collection<OtherClient> by delegate {
public operator fun get(kind: ClientKind): OtherClient? = this.find { it.kind == kind }
public fun getOrFail(kind: ClientKind): OtherClient =
get(kind) ?: throw NoSuchElementException("OtherClient with kind=$kind not found.")
}

View File

@ -0,0 +1,35 @@
/*
* 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.event.events
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.OtherClient
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.internal.network.Packet
public interface OtherClientEvent : BotEvent, Packet {
public val client: OtherClient
override val bot: Bot
get() = client.bot
}
/**
* 其他设备上线
*/
public data class OtherClientOnlineEvent(
override val client: OtherClient
) : OtherClientEvent, AbstractEvent()
/**
* 其他设备离线
*/
public data class OtherClientOfflineEvent(
override val client: OtherClient
) : OtherClientEvent, AbstractEvent()

View File

@ -18,7 +18,9 @@
package net.mamoe.mirai.internal
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.OtherClientList
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BotOfflineEvent
@ -72,6 +74,9 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
override val isOnline: Boolean get() = _network.areYouOk()
val otherClientsLock = Mutex() // lock sync
override val otherClients: OtherClientList = OtherClientList()
/**
* Close server connection, resend login packet, BUT DOESN'T [BotNetworkHandler.init]
*/

View File

@ -18,6 +18,7 @@ import net.mamoe.mirai.LowLevelApi
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.*
import net.mamoe.mirai.internal.contact.OtherClientImpl
import net.mamoe.mirai.internal.contact.checkIsGroupImpl
import net.mamoe.mirai.internal.message.*
import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler
@ -38,6 +39,12 @@ internal fun Bot.asQQAndroidBot(): QQAndroidBot {
return this as QQAndroidBot
}
internal fun QQAndroidBot.createOtherClient(
kind: ClientKind
): OtherClientImpl {
return OtherClientImpl(this, coroutineContext, kind)
}
@Suppress("INVISIBLE_MEMBER", "BooleanLiteralArgument", "OverridingDeprecatedMember")
internal class QQAndroidBot constructor(
account: BotAccount,

View File

@ -0,0 +1,38 @@
/*
* 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.contact
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.ClientKind
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.OtherClient
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 kotlin.coroutines.CoroutineContext
internal class OtherClientImpl(
bot: Bot,
coroutineContext: CoroutineContext,
override val kind: ClientKind
) : OtherClient, AbstractContact(bot, coroutineContext) {
override suspend fun sendMessage(message: Message): MessageReceipt<Contact> {
TODO("Not yet implemented")
}
override suspend fun uploadImage(image: ExternalImage): Image {
TODO("Not yet implemented")
}
override fun toString(): String {
return "OtherClient(bot=${bot.id},kind=$kind)"
}
}

View File

@ -0,0 +1,27 @@
/*
* 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 network.protocol.data.jce
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.ClientKind
import net.mamoe.mirai.internal.utils.io.JceStruct
import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId
@Serializable
internal data class InstanceInfo(
@JvmField @TarsId(0) val iAppId: Int? = null,
@JvmField @TarsId(1) val tablet: Byte? = null,
@JvmField @TarsId(2) val iPlatform: Long? = null,
/**
* @see ClientKind
*/
@JvmField @TarsId(3) val iProductType: Long? = null,
@JvmField @TarsId(4) val iClientType: Long? = null
) : JceStruct

View File

@ -0,0 +1,47 @@
/*
* 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 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
// ANDROID PHONE QQ
// 2020-12-23 20:16:57 D/soutv: PK =
//SvcReqMSFLoginNotifyData(iAppId=537066423,
//status=2,
//tablet=0,
//iPlatform=109,
//title=下线通知,
//info=你的帐号在手机上退出了,
//iProductType=0,
//iClientType=65799,
//vecInstanceList=[])
// ANDROID PHONE QQ
// 2020-12-23 20:21:02 D/soutv: PK = SvcReqMSFLoginNotifyData(
//iAppId=537066423,
//status=1,
//tablet=0,
//iPlatform=109, title=上线通知, info=你的帐号在手机上登录了, iProductType=0, iClientType=65799, vecInstanceList=[InstanceInfo(iAppId=537066423, tablet=0, iPlatform=109, iProductType=0, iClientType=65799)])
@Serializable
internal data class SvcReqMSFLoginNotifyData(
@JvmField @TarsId(0) val iAppId: Long,
@JvmField @TarsId(1) val status: Byte, // 上线=1, 下线=2
@JvmField @TarsId(2) val tablet: Byte? = null,
@JvmField @TarsId(3) val iPlatform: Long? = null,
@JvmField @TarsId(4) val title: String? = "",
@JvmField @TarsId(5) val info: String? = "",
@JvmField @TarsId(6) val iProductType: Long? = null,
@JvmField @TarsId(7) val iClientType: Long? = null,
@JvmField @TarsId(8) val vecInstanceList: List<InstanceInfo>? = null
) : JceStruct

View File

@ -161,7 +161,8 @@ internal object KnownPacketFactories {
OnlinePushPbPushTransMsg,
MessageSvcPushNotify,
ConfigPushSvc.PushReq,
StatSvc.ReqMSFOffline
StatSvc.ReqMSFOffline,
StatSvc.SvcReqMSFLoginNotify
)
// SvcReqMSFLoginNotify 自己的其他设备上限
// MessageSvcPushReaded 电脑阅读了别人的消息, 告知手机

View File

@ -9,10 +9,17 @@
package net.mamoe.mirai.internal.network.protocol.packet.login
import kotlinx.coroutines.cancel
import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.ByteReadPacket
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.contact.ClientKind
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.OtherClientOfflineEvent
import net.mamoe.mirai.event.events.OtherClientOnlineEvent
import net.mamoe.mirai.internal.QQAndroidBot
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.guid
@ -23,11 +30,9 @@ import net.mamoe.mirai.internal.network.protocol.data.jce.SvcReqRegister
import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x769
import net.mamoe.mirai.internal.network.protocol.data.proto.StatSvcGetOnline
import net.mamoe.mirai.internal.network.protocol.packet.*
import net.mamoe.mirai.internal.utils.MiraiPlatformUtils
import net.mamoe.mirai.internal.utils.NetworkType
import net.mamoe.mirai.internal.utils.encodeToString
import net.mamoe.mirai.internal.utils.*
import net.mamoe.mirai.internal.utils.io.serialization.*
import net.mamoe.mirai.internal.utils.toReadPacket
import java.util.concurrent.CancellationException
@Suppress("EnumEntryName", "unused")
internal enum class RegPushReason {
@ -220,4 +225,37 @@ internal class StatSvc {
}
}
}
internal object SvcReqMSFLoginNotify :
IncomingPacketFactory<Packet?>("StatSvc.SvcReqMSFLoginNotify", "StatSvc.SvcReqMSFLoginNotify") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? =
bot.otherClientsLock.withLock {
val notify = readUniPacket(network.protocol.data.jce.SvcReqMSFLoginNotifyData.serializer())
val kind = notify.iClientType?.toInt()?.let(ClientKind::get) ?: return null
when (notify.status.toInt()) {
1 -> {
if (bot.otherClients.any { it.kind == kind }) return null
val client = bot.createOtherClient(kind)
bot.otherClients.delegate.add(client)
OtherClientOnlineEvent(client)
}
2 -> {
val client = bot.otherClients.find { it.kind == kind } ?: return null
client.cancel(CancellationException("Offline"))
bot.otherClients.delegate.remove(client)
OtherClientOfflineEvent(client)
}
else -> throw contextualBugReportException(
"decode SvcReqMSFLoginNotify (OtherClient status change)",
notify._miraiContentToString(),
additional = "unknown notify.status=${notify.status}"
)
}
}
}
}