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 1a510b69a..d073274e1 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/message.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/message.kt @@ -552,6 +552,42 @@ public class GroupMessageEvent( "GroupMessageEvent(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)" } +/** + * 机器人在其他客户端发送消息同步到这个客户端的事件. + * + * 本事件发生于**机器人账号**在另一个客户端向一个群或一个好友主动发送消息, 这条消息同步到机器人这个客户端上. + * + * @see MessageEvent + */ +public interface MessageSyncEvent : MessageEvent + + +/** + * 机器人在其他客户端发送群消息同步到这个客户端的事件 + * + * @see MessageSyncEvent + */ +public class GroupMessageSyncEvent( + override val group: Group, + override val message: MessageChain, + override val sender: Member, + override val senderName: String, + override val time: Int +) : AbstractMessageEvent(), GroupAwareMessageEvent, MessageSyncEvent { + init { + val source = message[MessageSource] ?: error("Cannot find MessageSource from message") + check(source is OnlineMessageSource.Incoming.FromGroup) { "source provided to a GroupMessage must be an instance of OnlineMessageSource.Incoming.FromGroup" } + } + + override val bot: Bot get() = group.bot + override val subject: Group get() = group + override val source: OnlineMessageSource.Incoming.FromGroup get() = message.source as OnlineMessageSource.Incoming.FromGroup + + public override fun toString(): String = + "OtherClientGroupMessageSyncEvent(group=${group.id}, message=$message)" +} + + /** * 机器人收到的群临时会话消息的事件 * diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index b33ec213f..3d8a3484f 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -69,6 +69,8 @@ internal class GroupImpl( ) : Group, AbstractContact(bot, coroutineContext) { companion object + val groupPkgMsgParsingCache = GroupPkgMsgParsingCache() + val uin: Long = groupInfo.uin override lateinit var owner: NormalMember @@ -460,6 +462,4 @@ internal class GroupImpl( override fun toString(): String = "Group($id)" - - val groupPkgMsgParsingCache = GroupPkgMsgParsingCache() } diff --git a/mirai-core/src/commonMain/kotlin/contact/util.kt b/mirai-core/src/commonMain/kotlin/contact/util.kt index d34b31a31..cb0b3c7ae 100644 --- a/mirai-core/src/commonMain/kotlin/contact/util.kt +++ b/mirai-core/src/commonMain/kotlin/contact/util.kt @@ -21,10 +21,7 @@ import net.mamoe.mirai.internal.message.ensureSequenceIdAvailable import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToFriend import net.mamoe.mirai.message.* -import net.mamoe.mirai.message.data.Message -import net.mamoe.mirai.message.data.QuoteReply -import net.mamoe.mirai.message.data.asMessageChain -import net.mamoe.mirai.message.data.firstIsInstanceOrNull +import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.verbose import kotlin.contracts.InvocationKind @@ -87,20 +84,21 @@ internal suspend fun Friend.sendMessageImpl( } internal fun Contact.logMessageSent(message: Message) { - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - if (message !is net.mamoe.mirai.message.data.LongMessage) { + if (message !is LongMessage) { bot.logger.verbose("$this <- $message".replaceMagicCodes()) } } @Suppress("RemoveRedundantQualifierName") // compiler bug internal fun net.mamoe.mirai.event.events.MessageEvent.logMessageReceived() { + fun renderGroupMessage(group: Group, senderName: String, sender: Member, message: MessageChain): String { + val displayId = if (sender is AnonymousMember) "匿名" else sender.id.toString() + return "[${group.name}(${group.id})] ${senderName}($displayId) -> $message".replaceMagicCodes() + } + when (this) { is net.mamoe.mirai.event.events.GroupMessageEvent -> bot.logger.verbose { - "[${group.name}(${group.id})] ${senderName}(${ - if (sender is AnonymousMember) "匿名" - else sender.id - }) -> $message".replaceMagicCodes() + renderGroupMessage(group, senderName, sender, message) } is net.mamoe.mirai.event.events.TempMessageEvent -> bot.logger.verbose { "[${group.name}(${group.id})] $senderName(Temp ${sender.id}) -> $message".replaceMagicCodes() @@ -109,8 +107,12 @@ 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}(${sender.id}) -> $message".replaceMagicCodes() + "${client.kind} -> $message".replaceMagicCodes() } + is GroupMessageSyncEvent -> bot.logger.verbose { + renderGroupMessage(group, senderName, sender, message) + } + else -> bot.logger.verbose(toString()) } } diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt index 3b92dba98..ec5687087 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt @@ -246,6 +246,12 @@ internal open class QQAndroidClient( ) val onlinePushReqPushCacheList = SyncingCacheList(50) + + internal data class PendingGroupMessageReceiptSyncId( + val messageRandom: Int, + ) + + val pendingGroupMessageReceiptCacheList = SyncingCacheList(50) } val syncingController = MessageSvcSyncData() diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/SyncingCacheList.kt b/mirai-core/src/commonMain/kotlin/network/protocol/SyncingCacheList.kt index cb78cdb3c..2c7aa9205 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/SyncingCacheList.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/SyncingCacheList.kt @@ -20,4 +20,19 @@ internal class SyncingCacheList(private val size: Int = 50) { if (packetIdList.size >= size) packetIdList.removeFirst() return true } + + @Synchronized + fun removeFirst(condition: (E) -> Boolean): Boolean { + val itr = packetIdList.listIterator() + for (element in itr) { + if (element.let(condition)) { + itr.remove() + return true + } + } + return false + } + + @Synchronized + fun contains(condition: (E) -> Boolean): Boolean = packetIdList.any(condition) } \ No newline at end of file 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 new file mode 100644 index 000000000..b420e7d2c --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcRequestPushReadedNotify.kt @@ -0,0 +1,94 @@ +/* + * 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 + */ + +@file:Suppress("SpellCheckingInspection") + +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 SvcRequestPushReadedNotify( + @JvmField @TarsId(0) val notifyType: Byte, + @JvmField @TarsId(1) val vC2CReadedNotify: List? = null, + @JvmField @TarsId(2) val vGroupReadedNotify: List? = null, + @JvmField @TarsId(3) val vDisReadedNotify: List? = null +) : JceStruct + + +@Serializable +internal class C2CMsgReadedNotify( + @JvmField @TarsId(0) val peerUin: Long? = null, + @JvmField @TarsId(1) val lastReadTime: Long? = null, + @JvmField @TarsId(2) val flag: Long? = null, + @JvmField @TarsId(3) val phoneNum: String? = "", + @JvmField @TarsId(4) val bindedUin: Long? = null +) : JceStruct + +@Serializable +internal class DisMsgReadedNotify( + @JvmField @TarsId(0) val disUin: Long? = null, + @JvmField @TarsId(1) val opType: Long? = null, + @JvmField @TarsId(2) val memberSeq: Long? = null, + @JvmField @TarsId(3) val disMsgSeq: Long? = null +) : JceStruct + + +@Serializable +internal class GPicInfo( + @JvmField @TarsId(0) val vPath: ByteArray, + @JvmField @TarsId(1) val vHost: ByteArray? = null +) : JceStruct + + +@Serializable +internal class GroupMsgHead( + @JvmField @TarsId(0) val usCmdType: Int, + @JvmField @TarsId(1) val totalPkg: Byte, + @JvmField @TarsId(2) val curPkg: Byte, + @JvmField @TarsId(3) val usPkgSeq: Int, + @JvmField @TarsId(4) val dwReserved: Long +) : JceStruct + +@Serializable +internal class GroupMsgReadedNotify( + @JvmField @TarsId(0) val groupCode: Long? = null, + @JvmField @TarsId(1) val opType: Long? = null, + @JvmField @TarsId(2) val memberSeq: Long? = null, + @JvmField @TarsId(3) val groupMsgSeq: Long? = null +) : JceStruct + +@Serializable +internal class RequestPushGroupMsg( + @JvmField @TarsId(0) val uin: Long, + @JvmField @TarsId(1) val type: Byte, + @JvmField @TarsId(2) val service: String = "", + @JvmField @TarsId(3) val cmd: String = "", + @JvmField @TarsId(4) val groupCode: Long, + @JvmField @TarsId(5) val groupType: Byte, + @JvmField @TarsId(6) val sendUin: Long, + @JvmField @TarsId(7) val lsMsgSeq: Long, + @JvmField @TarsId(8) val uMsgTime: Int, + @JvmField @TarsId(9) val infoSeq: Long, + @JvmField @TarsId(10) val shMsgLen: Short, + @JvmField @TarsId(11) val vMsg: ByteArray, + @JvmField @TarsId(12) val groupCard: String? = "", + @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(17) val fromInstId: Long? = null, + @JvmField @TarsId(18) val stGroupMsgHead: GroupMsgHead? = null, + @JvmField @TarsId(19) val wUserActive: Int? = null, + @JvmField @TarsId(20) val vMarketFace: List? = null, + @JvmField @TarsId(21) val uSuperQQBubbleId: Long? = null +) : JceStruct + diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/shareData.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/shareData.kt new file mode 100644 index 000000000..c3c8b7584 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/jce/shareData.kt @@ -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.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 = "" +) : JceStruct diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt index 68f20cf61..809d435f4 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt @@ -160,6 +160,7 @@ internal object KnownPacketFactories { OnlinePushReqPush, OnlinePushPbPushTransMsg, MessageSvcPushNotify, + MessageSvcPushReaded, ConfigPushSvc.PushReq, StatSvc.ReqMSFOffline, StatSvc.SvcReqMSFLoginNotify diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt index c6199be5b..5917f3e08 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt @@ -23,6 +23,7 @@ import net.mamoe.mirai.internal.message.MessageSourceToTempImpl import net.mamoe.mirai.internal.message.toRichTextElems import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient +import net.mamoe.mirai.internal.network.QQAndroidClient.MessageSvcSyncData.PendingGroupMessageReceiptSyncId import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgCtrl @@ -374,16 +375,24 @@ internal inline fun MessageSvcPbSendMsg.createToGroup( contract { callsInPlace(sourceCallback, InvocationKind.EXACTLY_ONCE) } + val messageRandom = Random.nextInt().absoluteValue val source = MessageSourceToGroupImpl( group, - internalIds = intArrayOf(Random.nextInt().absoluteValue), + internalIds = intArrayOf(messageRandom), sender = client.bot, target = group, time = currentTimeSeconds().toInt(), originalMessage = message//, // sourceMessage = message ) + sourceCallback(source) + + client.syncingController.pendingGroupMessageReceiptCacheList.addCache( + PendingGroupMessageReceiptSyncId( + messageRandom = messageRandom, + ) + ) return createToGroupImpl( client, group, diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PushReaded.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PushReaded.kt new file mode 100644 index 000000000..d4d192998 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PushReaded.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.packet.chat.receive + +import kotlinx.io.core.ByteReadPacket +import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.network.Packet +import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory + +internal object MessageSvcPushReaded : IncomingPacketFactory( + "MessageSvc.PushReaded", "" +) { + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? { + // val notify = readUniPacket(SvcRequestPushReadedNotify.serializer()) + + // just ignore. + return null + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt index 6268c0bce..62ee0cfd0 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt @@ -12,12 +12,14 @@ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import kotlinx.io.core.ByteReadPacket +import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.nameCardOrNick import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.GroupMessageEvent +import net.mamoe.mirai.event.events.GroupMessageSyncEvent import net.mamoe.mirai.event.events.MemberCardChangeEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.GroupImpl @@ -25,6 +27,7 @@ import net.mamoe.mirai.internal.contact.MemberImpl import net.mamoe.mirai.internal.message.toMessageChain import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x8fc import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory @@ -53,18 +56,30 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory("Onlin // 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00 if (!bot.firstLoginSucceed) return null val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer()) - // bot.logger.debug(pbPushMsg._miraiContentToString()) - if (pbPushMsg.msg.msgHead.fromUin == bot.id) { - return SendGroupMessageReceipt( - pbPushMsg.msg.msgBody.richText.attr!!.random, - pbPushMsg.msg.msgHead.msgSeq - ) + val msgHead = pbPushMsg.msg.msgHead + + val isFromSelfAccount = msgHead.fromUin == bot.id + if (isFromSelfAccount) { + val messageRandom = pbPushMsg.msg.msgBody.richText.attr?.random ?: return null + + if (bot.client.syncingController.pendingGroupMessageReceiptCacheList.contains { it.messageRandom == messageRandom }) { + // message sent by bot + return SendGroupMessageReceipt( + messageRandom, + msgHead.msgSeq + ) + } + // else: sync form other device } - val group = - bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群 - val msgs = group.groupPkgMsgParsingCache.put(pbPushMsg) - if (msgs.isEmpty()) return null + + if (msgHead.groupInfo == null) return null + + val group = bot.getGroup(msgHead.groupInfo.groupCode) as GroupImpl? ?: return null // 机器人还正在进群 + + + // fragmented message + val msgs = group.groupPkgMsgParsingCache.tryMerge(pbPushMsg).ifEmpty { return null } var extraInfo: ImMsgBody.ExtraInfo? = null var anonymous: ImMsgBody.AnonymousGroupMsg? = null @@ -78,50 +93,75 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory("Onlin } } - val sender = if (anonymous != null) { - group.newAnonymous(anonymous.anonNick.encodeToString(), anonymous.anonId.encodeToBase64()) - } else { - group[pbPushMsg.msg.msgHead.fromUin] as MemberImpl + + val sender: Member // null if sync from other client + val name: String + + if (anonymous != null) { // anonymous member + sender = group.newAnonymous(anonymous.anonNick.encodeToString(), anonymous.anonId.encodeToBase64()) + name = sender.nameCard + } else { // normal member chat + sender = group[msgHead.fromUin] as MemberImpl + name = findSenderName(extraInfo, msgHead.groupInfo) ?: sender.nameCardOrNick } - val name = if (anonymous != null) { - sender.nameCard + if (isFromSelfAccount) { + return GroupMessageSyncEvent( + message = msgs.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true), + time = msgHead.msgTime, + group = group, + sender = sender, + senderName = name, + ) } else { - extraInfo?.groupCard?.takeIf { it.isNotEmpty() }?.run { - kotlin.runCatching { - if (this[0] == 0x0A.toByte()) { - val nameBuf = loadAs(Oidb0x8fc.CommCardNameBuf.serializer()) - if (nameBuf.richCardName.isNotEmpty()) { - return@runCatching nameBuf.richCardName.joinToString("") { it.text.encodeToString() } - } - } - return@runCatching null - }.getOrNull() ?: encodeToString() - } ?: pbPushMsg.msg.msgHead.groupInfo.groupCard.takeIf { it.isNotEmpty() } - ?: sender.nameCardOrNick // 没有 extraInfo 就从 head 里取 - } - val flags = extraInfo?.flags ?: 0 - return GroupMessageEvent( - senderName = name.also { - if (sender is MemberImpl && it != sender.nameCard) { - val origin = sender._nameCard - sender._nameCard = name - MemberCardChangeEvent(origin, name, sender).broadcast() - } - }, - sender = sender, - message = msgs.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true), - permission = when { - flags and 16 != 0 -> MemberPermission.ADMINISTRATOR - flags and 8 != 0 -> MemberPermission.OWNER - flags == 0 || flags == 1 -> MemberPermission.MEMBER - else -> { - bot.logger.warning { "判断群 ${sender.group.id} 的群员 ${sender.id} 的权限失败: ${flags._miraiContentToString()}. 请完整截图或复制此日志并确认其真实权限后发送给 mirai 维护者以帮助解决问题." } - sender.permission - } - }, - time = pbPushMsg.msg.msgHead.msgTime - ) + broadcastNameCardChangedEventIfNecessary(sender, name) + + return GroupMessageEvent( + senderName = name, + sender = sender, + message = msgs.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true), + permission = findMemberPermission(extraInfo?.flags ?: 0, sender, bot), + time = msgHead.msgTime + ) + } } + + private suspend inline fun broadcastNameCardChangedEventIfNecessary(sender: Member, name: String) { + val currentNameCard = sender.nameCard + if (sender is MemberImpl && name != currentNameCard) { + sender._nameCard = name + MemberCardChangeEvent(currentNameCard, name, sender).broadcast() + } + } + + private fun findMemberPermission( + flags: Int, + sender: Member, + bot: QQAndroidBot, + ) = when { + flags and 16 != 0 -> MemberPermission.ADMINISTRATOR + flags and 8 != 0 -> MemberPermission.OWNER + flags == 0 || flags == 1 -> MemberPermission.MEMBER + else -> { + bot.logger.warning { "判断群 ${sender.group.id} 的群员 ${sender.id} 的权限失败: ${flags._miraiContentToString()}. 请完整截图或复制此日志并确认其真实权限后发送给 mirai 维护者以帮助解决问题." } + sender.permission + } + } + + private fun findSenderName( + extraInfo: ImMsgBody.ExtraInfo?, + groupInfo: MsgComm.GroupInfo + ) = extraInfo?.groupCard?.takeIf { it.isNotEmpty() }?.decodeCommCardNameBuf() + ?: groupInfo.groupCard.takeIf { it.isNotEmpty() } + + private fun ByteArray.decodeCommCardNameBuf() = kotlin.runCatching { + if (this[0] == 0x0A.toByte()) { + val nameBuf = loadAs(Oidb0x8fc.CommCardNameBuf.serializer()) + if (nameBuf.richCardName.isNotEmpty()) { + return@runCatching nameBuf.richCardName.joinToString("") { it.text.encodeToString() } + } + } + return@runCatching null + }.getOrNull() ?: encodeToString() } diff --git a/mirai-core/src/commonMain/kotlin/utils/GroupPkgMsgParsingCache.kt b/mirai-core/src/commonMain/kotlin/utils/GroupPkgMsgParsingCache.kt index c3d56e73b..94d0fbd14 100644 --- a/mirai-core/src/commonMain/kotlin/utils/GroupPkgMsgParsingCache.kt +++ b/mirai-core/src/commonMain/kotlin/utils/GroupPkgMsgParsingCache.kt @@ -14,6 +14,9 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.utils.currentTimeMillis import java.util.concurrent.locks.ReentrantLock +/** + * fragmented message + */ internal class GroupPkgMsgParsingCache { class PkgMsg( val size: Int, @@ -31,7 +34,7 @@ internal class GroupPkgMsgParsingCache { } } - fun put(msg: MsgOnlinePush.PbPushMsg): List { + fun tryMerge(msg: MsgOnlinePush.PbPushMsg): List { val head = msg.msg.contentHead ?: return listOf(msg) val size = head.pkgNum if (size < 2) return listOf(msg)