Commit f784e85d authored by Him188's avatar Him188

Structured contact list syncing

parent 51ee123a
...@@ -17,11 +17,18 @@ import net.mamoe.mirai.contact.Group ...@@ -17,11 +17,18 @@ import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.filteringGetOrNull import net.mamoe.mirai.contact.filteringGetOrNull
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.data.FriendInfo
import net.mamoe.mirai.data.GroupInfo
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.QQAndroidGroupInfo
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import kotlin.collections.asSequence
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
...@@ -31,7 +38,7 @@ internal expect class QQAndroidBot constructor( ...@@ -31,7 +38,7 @@ internal expect class QQAndroidBot constructor(
configuration: BotConfiguration configuration: BotConfiguration
) : QQAndroidBotBase ) : QQAndroidBotBase
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
internal abstract class QQAndroidBotBase constructor( internal abstract class QQAndroidBotBase constructor(
context: Context, context: Context,
account: BotAccount, account: BotAccount,
...@@ -48,10 +55,15 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -48,10 +55,15 @@ internal abstract class QQAndroidBotBase constructor(
override val uin: Long get() = client.uin override val uin: Long get() = client.uin
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList()) override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
override val selfQQ: QQ by lazy { QQ(uin) } override val selfQQ: QQ by lazy {
QQ(object : FriendInfo {
override val uin: Long get() = this@QQAndroidBotBase.uin
override val nick: String get() = this@QQAndroidBotBase.nick
})
}
override fun QQ(id: Long): QQ { override fun QQ(friendInfo: FriendInfo): QQ {
return QQImpl(this as QQAndroidBot, coroutineContext, id) return QQImpl(this as QQAndroidBot, coroutineContext, friendInfo.uin, friendInfo)
} }
override fun createNetworkHandler(coroutineContext: CoroutineContext): QQAndroidBotNetworkHandler { override fun createNetworkHandler(coroutineContext: CoroutineContext): QQAndroidBotNetworkHandler {
...@@ -69,6 +81,45 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -69,6 +81,45 @@ internal abstract class QQAndroidBotBase constructor(
return groups.delegate.filteringGetOrNull { (it as GroupImpl).uin == uin } return groups.delegate.filteringGetOrNull { (it as GroupImpl).uin == uin }
} }
override suspend fun queryGroupList(): Sequence<Long> {
return network.run {
FriendList.GetTroopListSimplify(bot.client)
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
}.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode }
}
override suspend fun queryGroupInfo(id: Long): GroupInfo = network.run {
TroopManagement.GetGroupInfo(
client = bot.client,
groupCode = id
).sendAndExpect<QQAndroidGroupInfo>().apply {
if (this.delegate.groupUin == null) {
this.delegate.groupUin = Group.calculateGroupUinByGroupCode(id)
}
}
}
override suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo> = network.run {
var nextUin = 0L
var sequence = sequenceOf<MemberInfoImpl>()
while (true) {
val data = FriendList.GetTroopMemberList(
client = bot.client,
targetGroupUin = groupUin,
targetGroupCode = groupCode,
nextUin = nextUin
).sendAndExpect<FriendList.GetTroopMemberList.Response>(timeoutMillis = 3000)
sequence += data.members.asSequence().map { troopMemberInfo ->
MemberInfoImpl(troopMemberInfo.apply {
}, ownerId)
}
nextUin = data.nextUin
if (nextUin == 0L) {
break
}
}
return sequence
}
override fun onEvent(event: BotEvent): Boolean { override fun onEvent(event: BotEvent): Boolean {
return firstLoginSucceed return firstLoginSucceed
......
...@@ -18,22 +18,18 @@ import kotlinx.io.core.ByteReadPacket ...@@ -18,22 +18,18 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input import kotlinx.io.core.Input
import kotlinx.io.core.buildPacket import kotlinx.io.core.buildPacket
import kotlinx.io.core.use import kotlinx.io.core.use
import net.mamoe.mirai.contact.ContactList
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.data.MultiPacket import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.* import net.mamoe.mirai.event.*
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.qqandroid.FriendInfoImpl
import net.mamoe.mirai.qqandroid.GroupImpl import net.mamoe.mirai.qqandroid.GroupImpl
import net.mamoe.mirai.qqandroid.MemberImpl
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.QQImpl import net.mamoe.mirai.qqandroid.QQImpl
import net.mamoe.mirai.qqandroid.event.PacketReceivedEvent import net.mamoe.mirai.qqandroid.event.PacketReceivedEvent
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.* import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat import net.mamoe.mirai.qqandroid.network.protocol.packet.login.Heartbeat
...@@ -148,7 +144,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -148,7 +144,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
totalFriendCount = data.totalFriendCount totalFriendCount = data.totalFriendCount
data.friendList.forEach { data.friendList.forEach {
// atomic add // atomic add
bot.qqs.delegate.addLast(QQImpl(bot, bot.coroutineContext, it.friendUin)).also { bot.qqs.delegate.addLast(QQImpl(bot, bot.coroutineContext, it.friendUin, FriendInfoImpl(it))).also {
currentFriendCount++ currentFriendCount++
} }
} }
...@@ -171,32 +167,17 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -171,32 +167,17 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2) .sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
troopListData.groups.forEach { troopNum -> troopListData.groups.forEach { troopNum ->
val contactList = ContactList(LockFreeLinkedList<Member>())
val groupInfoResponse =
TroopManagement.GetGroupOperationInfo(
client = bot.client,
groupCode = troopNum.groupCode
).sendAndExpect<TroopManagement.GetGroupOperationInfo.Response>()
val group =
GroupImpl(
bot = bot,
coroutineContext = bot.coroutineContext,
id = troopNum.groupCode,
uin = troopNum.groupUin,
_name = troopNum.groupName,
_announcement = troopNum.groupMemo,
_allowMemberInvite = groupInfoResponse.allowMemberInvite,
_confessTalk = groupInfoResponse.confessTalk,
_muteAll = troopNum.dwShutUpTimestamp != 0L,
_autoApprove = groupInfoResponse.autoApprove,
_anonymousChat = groupInfoResponse.allowAnonymousChat,
members = contactList
)
bot.groups.delegate.addLast(group)
launch { launch {
try { try {
fillTroopMemberList(group, contactList, troopNum.dwGroupOwnerUin) bot.groups.delegate.addLast(
GroupImpl(
bot = bot,
coroutineContext = bot.coroutineContext,
id = troopNum.groupCode,
groupInfo = bot.queryGroupInfo(troopNum.groupCode),
members = bot.queryGroupMemberList(troopNum.groupUin, troopNum.groupCode, troopNum.dwGroupOwnerUin)
)
)
} catch (e: Exception) { } catch (e: Exception) {
bot.logger.error("群${troopNum.groupCode}的列表拉取失败, 一段时间后将会重试") bot.logger.error("群${troopNum.groupCode}的列表拉取失败, 一段时间后将会重试")
bot.logger.error(e) bot.logger.error(e)
...@@ -242,47 +223,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -242,47 +223,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
return lastException return lastException
} }
suspend fun fillTroopMemberList(group: GroupImpl, list: ContactList<Member>, owner: Long) {
bot.logger.verbose("开始获取群[${group.uin}]成员列表")
var size = 0
var nextUin = 0L
while (true) {
val data = FriendList.GetTroopMemberList(
client = bot.client,
targetGroupUin = group.uin,
targetGroupCode = group.id,
nextUin = nextUin
).sendAndExpect<FriendList.GetTroopMemberList.Response>(timeoutMillis = 3000)
data.members.forEach { troopMemberInfo ->
val member = MemberImpl(
qq = (bot.QQ(troopMemberInfo.memberUin) as QQImpl).also { it.nick = troopMemberInfo.nick },
_groupCard = troopMemberInfo.sName ?: "",
_specialTitle = troopMemberInfo.sSpecialTitle ?: "",
group = group,
coroutineContext = group.coroutineContext,
permission = when {
troopMemberInfo.memberUin == owner -> MemberPermission.OWNER
troopMemberInfo.dwFlag == 1L -> MemberPermission.ADMINISTRATOR
else -> MemberPermission.MEMBER
}
)
if (member.permission == MemberPermission.OWNER) {
group.owner = member
}
if (troopMemberInfo.memberUin != bot.uin) {
list.delegate.addLast(member)
} else {
group.botPermission = member.permission
}
size += data.members.size
nextUin = data.nextUin
}
if (nextUin == 0L) {
break
}
}
}
/** /**
* 缓存超时处理的 [Job]. 超时后将清空缓存, 以免阻碍后续包的处理 * 缓存超时处理的 [Job]. 超时后将清空缓存, 以免阻碍后续包的处理
*/ */
......
...@@ -135,7 +135,7 @@ internal object KnownPacketFactories { ...@@ -135,7 +135,7 @@ internal object KnownPacketFactories {
TroopManagement.EditSpecialTitle, TroopManagement.EditSpecialTitle,
TroopManagement.Mute, TroopManagement.Mute,
TroopManagement.GroupOperation, TroopManagement.GroupOperation,
TroopManagement.GetGroupOperationInfo, TroopManagement.GetGroupInfo,
TroopManagement.EditGroupNametag, TroopManagement.EditGroupNametag,
TroopManagement.Kick, TroopManagement.Kick,
Heartbeat.Alive Heartbeat.Alive
......
...@@ -14,6 +14,7 @@ import kotlinx.io.core.buildPacket ...@@ -14,6 +14,7 @@ import kotlinx.io.core.buildPacket
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import kotlinx.io.core.toByteArray import kotlinx.io.core.toByteArray
import kotlinx.serialization.toUtf8Bytes import kotlinx.serialization.toUtf8Bytes
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
...@@ -23,12 +24,27 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.ModifyGroupCardReq ...@@ -23,12 +24,27 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.ModifyGroupCardReq
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.stUinInfo import net.mamoe.mirai.qqandroid.network.protocol.data.jce.stUinInfo
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.* import net.mamoe.mirai.qqandroid.network.protocol.data.proto.*
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.utils.daysToSeconds import net.mamoe.mirai.utils.daysToSeconds
import net.mamoe.mirai.utils.io.encodeToString import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.data.GroupInfo as MiraiGroupInfo
internal inline class QQAndroidGroupInfo(
internal val delegate: Oidb0x88d.GroupInfo
) : MiraiGroupInfo, Packet {
override val uin: Long get() = delegate.groupUin ?: error("cannot find groupUin")
override val owner: Long get() = delegate.groupOwner ?: error("cannot find groupOwner")
override val groupCode: Long get() = Group.calculateGroupCodeByGroupUin(uin)
override val memo: String get() = delegate.groupMemo ?: error("cannot find groupMemo")
override val name: String get() = delegate.groupName ?: error("cannot find groupName")
override val allowMemberInvite get() = delegate.groupFlagExt?.and(0x000000c0) != 0
override val allowAnonymousChat get() = delegate.groupFlagExt?.and(0x40000000) == 0
override val autoApprove get() = delegate.groupFlagext3?.and(0x00100000) == 0
override val confessTalk get() = delegate.groupFlagext3?.and(0x00002000) == 0
override val muteAll: Boolean get() = delegate.shutupTimestamp != 0
}
internal class TroopManagement { internal class TroopManagement {
...@@ -70,16 +86,7 @@ internal class TroopManagement { ...@@ -70,16 +86,7 @@ internal class TroopManagement {
} }
internal object GetGroupOperationInfo : OutgoingPacketFactory<GetGroupOperationInfo.Response>("OidbSvc.0x88d_7") { internal object GetGroupInfo : OutgoingPacketFactory<QQAndroidGroupInfo>("OidbSvc.0x88d_7") {
class Response(
val allowAnonymousChat: Boolean,
val allowMemberInvite: Boolean,
val autoApprove: Boolean,
val confessTalk: Boolean
) : Packet {
override fun toString(): String = "Response(GroupInfo)"
}
operator fun invoke( operator fun invoke(
client: QQAndroidClient, client: QQAndroidClient,
groupCode: Long groupCode: Long
...@@ -109,8 +116,8 @@ internal class TroopManagement { ...@@ -109,8 +116,8 @@ internal class TroopManagement {
cmduinUinFlag = 0, cmduinUinFlag = 0,
createSourceFlag = 0, createSourceFlag = 0,
noCodeFingerOpenFlag = 0, noCodeFingerOpenFlag = 0,
ingGroupQuestion = EMPTY_BYTE_ARRAY, ingGroupQuestion = "",
ingGroupAnswer = EMPTY_BYTE_ARRAY ingGroupAnswer = ""
), ),
groupCode = groupCode groupCode = groupCode
) )
...@@ -121,14 +128,9 @@ internal class TroopManagement { ...@@ -121,14 +128,9 @@ internal class TroopManagement {
} }
} }
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): QQAndroidGroupInfo {
with(this.readBytes().loadAs(OidbSso.OIDBSSOPkg.serializer()).bodybuffer.loadAs(Oidb0x88d.RspBody.serializer()).stzrspgroupinfo!![0].stgroupinfo!!) { with(this.readBytes().loadAs(OidbSso.OIDBSSOPkg.serializer()).bodybuffer.loadAs(Oidb0x88d.RspBody.serializer()).stzrspgroupinfo!![0].stgroupinfo!!) {
return Response( return QQAndroidGroupInfo(this)
allowMemberInvite = (this.groupFlagExt?.and(0x000000c0) != 0),
allowAnonymousChat = (this.groupFlagExt?.and(0x40000000) == 0),
autoApprove = (this.groupFlagext3?.and(0x00100000) == 0),
confessTalk = (this.groupFlagext3?.and(0x00002000) == 0)
)
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment