diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt index db5604f6a..a32d966ef 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt @@ -67,7 +67,8 @@ internal class QQImpl(bot: QQAndroidBot, override val coroutineContext: Coroutin internal class MemberImpl( qq: QQImpl, - override var groupCard: String, + initGroupCard: String, + initSpecialTitle: String, group: GroupImpl, override val coroutineContext: CoroutineContext, override val permission: MemberPermission @@ -75,6 +76,37 @@ internal class MemberImpl( override val group: GroupImpl by group.unsafeWeakRef() val qq: QQImpl by qq.unsafeWeakRef() + + override var groupCard: String by Delegates.observable(initGroupCard) { _, old, new -> + check(group.botPermission != MemberPermission.MEMBER) { + "Permission Denied when trying to edit group card for $this" + } + if (group.botPermission != MemberPermission.MEMBER && new != old) { + launch { + bot.network.run { + + } + } + } + } + + override var specialTitle: String by Delegates.observable(initSpecialTitle) { _, old, new -> + check(group.botPermission == MemberPermission.OWNER) { + "Permission Denied when trying to edit special title for $this, need to be OWNER" + } + if (new != old) { + launch { + bot.network.run { + TroopManagement.EditSpecialTitle( + bot.client, + this@MemberImpl, + new + ).sendWithoutExpect() + } + } + } + } + override val bot: QQAndroidBot get() = qq.bot override suspend fun mute(durationSeconds: Int): Boolean { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index 5650d5404..690b5a5f8 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -190,32 +190,18 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler initAnonymousChat = groupInfoResponse.allowAnonymousChat, members = contactList ) - group.owner = - MemberImpl( - qq = bot.QQ(it.dwGroupOwnerUin) as QQImpl, - groupCard = "",//unknown now - group = group, - coroutineContext = group.coroutineContext, - permission = MemberPermission.OWNER - ) - if (it.dwGroupOwnerUin == bot.uin) { - group.botPermission = MemberPermission.OWNER - } toGet[group] = contactList bot.groups.delegate.addLast(group) - } - coroutineScope { - toGet.forEach { - launch { - try { - getTroopMemberList(it.key, it.value, it.key.owner.id) - groupInfo[it.key.id] = it.value.size - } catch (e: Exception) { - groupInfo[it.key.id] = -1 - bot.logger.info("群${it.key.uin}的列表拉取失败, 将采用动态加入") - } + launch { + try { + getTroopMemberList(group, contactList, it.dwGroupOwnerUin) + groupInfo[it.groupCode] = contactList.size + } catch (e: Exception) { + groupInfo[it.groupCode] = -1 + bot.logger.info("群${it.groupCode}的列表拉取失败, 将采用动态加入") + println(e.message) + println(e.logStacktrace()) } - //delay(200) } } bot.logger.info("群组列表与群成员加载完成, 共 ${troopData.groups.size}个") @@ -257,12 +243,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendWithoutExpect() } - suspend fun getGroupInfo(uin: Long) { - val data = TroopManagement.GetGroupOperationInfo( - client = bot.client, - groupCode = uin - ).sendAndExpect<TroopManagement.GetGroupOperationInfo.Response>(timeoutMillis = 3000) - } suspend fun getTroopMemberList(group: GroupImpl, list: ContactList<Member>, owner: Long): ContactList<Member> { bot.logger.info("开始获取群[${group.uin}]成员列表") @@ -276,29 +256,29 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler nextUin = nextUin ).sendAndExpect<FriendList.GetTroopMemberList.Response>(timeoutMillis = 3000) data.members.forEach { - if (it.memberUin != bot.uin) { - list.delegate.addLast( - MemberImpl( - qq = bot.QQ(it.memberUin) as QQImpl, - groupCard = it.autoRemark ?: it.nick, - group = group, - coroutineContext = group.coroutineContext, - permission = when { - it.memberUin == owner -> MemberPermission.OWNER - it.dwFlag == 1L -> MemberPermission.ADMINISTRATOR - else -> MemberPermission.MEMBER - } - ) - ) - } else { - group.owner.groupCard = it.autoRemark ?: it.nick - if (it.dwFlag == 1L) { - group.botPermission = MemberPermission.ADMINISTRATOR + val member = MemberImpl( + qq = bot.QQ(it.memberUin) as QQImpl, + initGroupCard = it.autoRemark ?: it.nick, + initSpecialTitle = it.sSpecialTitle ?: "", + group = group, + coroutineContext = group.coroutineContext, + permission = when { + it.memberUin == owner -> MemberPermission.OWNER + it.dwFlag == 1L -> MemberPermission.ADMINISTRATOR + else -> MemberPermission.MEMBER } + ) + if (member.permission == MemberPermission.OWNER) { + group.owner = member } + if (it.memberUin != bot.uin) { + list.delegate.addLast(member) + } else { + group.botPermission = member.permission + } + size += data.members.size + nextUin = data.nextUin } - size += data.members.size - nextUin = data.nextUin if (nextUin == 0L) { break } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/OIDB.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/OIDB.kt index a8b39c438..0cdc81bf4 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/OIDB.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/OIDB.kt @@ -5,6 +5,78 @@ import kotlinx.serialization.Serializable import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY +@Serializable +class Oidb0x8fc : ProtoBuf { + @Serializable + class CardNameElem( + @SerialId(1) val enumCardType: Int /* enum */ = 1, + @SerialId(2) val value: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class CommCardNameBuf( + @SerialId(1) val richCardName: List<Oidb0x8fc.RichCardNameElem>? = null + ) : ProtoBuf + + @Serializable + class ReqBody( + @SerialId(1) val groupCode: Long = 0L, + @SerialId(2) val showFlag: Int = 0, + @SerialId(3) val memLevelInfo: List<Oidb0x8fc.MemberInfo>? = null, + @SerialId(4) val levelName: List<Oidb0x8fc.LevelName>? = null, + @SerialId(5) val updateTime: Int = 0, + @SerialId(6) val officeMode: Int = 0, + @SerialId(7) val groupOpenAppid: Int = 0, + @SerialId(8) val msgClientInfo: Oidb0x8fc.ClientInfo? = null, + @SerialId(9) val authKey: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class MemberInfo( + @SerialId(1) val uin: Long = 0L, + @SerialId(2) val point: Int = 0, + @SerialId(3) val activeDay: Int = 0, + @SerialId(4) val level: Int = 0, + @SerialId(5) val specialTitle: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(6) val specialTitleExpireTime: Int = 0, + @SerialId(7) val uinName: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(8) val memberCardName: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(9) val phone: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(10) val email: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(11) val remark: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(12) val gender: Int = 0, + @SerialId(13) val job: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(14) val tribeLevel: Int = 0, + @SerialId(15) val tribePoint: Int = 0, + @SerialId(16) val richCardName: List<Oidb0x8fc.CardNameElem>? = null, + @SerialId(17) val commRichCardName: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class RichCardNameElem( + @SerialId(1) val ctrl: ByteArray = EMPTY_BYTE_ARRAY, + @SerialId(2) val text: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + class RspBody( + @SerialId(1) val groupCode: Long = 0L, + @SerialId(2) val errInfo: String = "" + ) : ProtoBuf + + @Serializable + class ClientInfo( + @SerialId(1) val implat: Int = 0, + @SerialId(2) val ingClientver: String = "" + ) : ProtoBuf + + @Serializable + class LevelName( + @SerialId(1) val level: Int = 0, + @SerialId(2) val name: String = "" + ) : ProtoBuf +} + @Serializable class Oidb0x88d : ProtoBuf { @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt index 6fb982654..8b147523d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt @@ -125,7 +125,7 @@ internal object KnownPacketFactories { ImgStore.GroupPicUp, ImageUpPacket, LongConn.OffPicDown, - TroopManagement.EditNametag, + TroopManagement.EditSpecialTitle, TroopManagement.Mute, TroopManagement.GroupOperation, TroopManagement.GetGroupOperationInfo diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt index ae0db6fd6..de1a30cdf 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt @@ -4,6 +4,7 @@ import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.buildPacket import kotlinx.io.core.readBytes import kotlinx.io.core.toByteArray +import net.mamoe.mirai.contact.Member import net.mamoe.mirai.data.Packet import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.io.serialization.loadAs @@ -12,12 +13,12 @@ import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Oidb0x88d import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Oidb0x89a +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Oidb0x8fc import net.mamoe.mirai.qqandroid.network.protocol.data.proto.OidbSso 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.OutgoingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket -import net.mamoe.mirai.qqandroid.network.protocol.packet.login.LoginPacket import net.mamoe.mirai.utils.daysToSeconds internal object TroopManagement { @@ -272,9 +273,38 @@ internal object TroopManagement { } - internal object EditNametag : OutgoingPacketFactory<LoginPacket.LoginPacketResponse>("OidbSvc.0x8fc_2") { - override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): LoginPacket.LoginPacketResponse { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + internal object EditSpecialTitle : OutgoingPacketFactory<EditSpecialTitle.Response>("OidbSvc.0x8fc_2") { + object Response : Packet + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + return Response + } + + operator fun invoke( + client: QQAndroidClient, + member: Member, + newName: String + ): OutgoingPacket { + return buildOutgoingUniPacket(client) { + writeProtoBuf( + OidbSso.OIDBSSOPkg.serializer(), + OidbSso.OIDBSSOPkg( + command = 2300, + serviceType = 2, + bodybuffer = Oidb0x8fc.ReqBody( + groupCode = member.group.id, + memLevelInfo = listOf( + Oidb0x8fc.MemberInfo( + uin = member.id, + uinName = newName.toByteArray(), + specialTitle = newName.toByteArray(), + specialTitleExpireTime = -1 + ) + ) + ).toByteArray(Oidb0x8fc.ReqBody.serializer()) + ) + ) + } } } diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt index 2a576e6d4..7bd8383fc 100644 --- a/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt @@ -6,7 +6,7 @@ import java.io.File fun main() { println( - File("""/Users/jiahua.liu/Desktop/QQAndroid-F/app/src/main/java/tencent/im/oidb/""") + File("""/Users/jiahua.liu/Desktop/QQAndroid-F/app/src/main/java/tencent/im/oidb/cmd0x8fc/""") .generateUnarrangedClasses().toMutableList().arrangeClasses().joinToString("\n\n") ) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt index b8a7e3f86..614c93899 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt @@ -22,10 +22,23 @@ interface Member : QQ, Contact { val permission: MemberPermission /** - * 群名片 (如果有) 或个人昵称. 动态更新. + * ====以下字段会在更新后异步更新到服务器==== + */ + + /** + * 群名片 */ var groupCard: String + /** + * 群头衔 + */ + var specialTitle: String + + /** + * ====以上字段会在更新后异步更新到服务器==== + */ + /** * 禁言 *