Support parsing fragmented message.

close #440
This commit is contained in:
Karlatemp 2020-12-20 15:40:25 +08:00
parent c934ff5b89
commit 28249b317c
No known key found for this signature in database
GPG Key ID: 21FBDDF664FF06F8
7 changed files with 179 additions and 78 deletions

View File

@ -35,6 +35,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcP
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToGroup
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService
import net.mamoe.mirai.internal.utils.GroupPkgMsgParsingCache
import net.mamoe.mirai.internal.utils.MiraiPlatformUtils
import net.mamoe.mirai.internal.utils.estimateLength
import net.mamoe.mirai.internal.utils.toUHexString
@ -460,4 +461,6 @@ internal class GroupImpl(
override fun toString(): String = "Group($id)"
val groupPkgMsgParsingCache = GroupPkgMsgParsingCache()
}

View File

@ -23,6 +23,7 @@ import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.internal.network.protocol.data.proto.HummerCommelem
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.utils.*
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
@ -234,24 +235,52 @@ internal fun MsgComm.Msg.toMessageChain(
isTemp: Boolean = false
): MessageChain = toMessageChain(bot, bot.id, groupIdOrZero, onlineSource, isTemp)
internal fun List<MsgOnlinePush.PbPushMsg>.toMessageChain(
bot: Bot,
groupIdOrZero: Long,
onlineSource: Boolean,
isTemp: Boolean = false
): MessageChain = toMessageChain(bot, bot.id, groupIdOrZero, onlineSource, isTemp)
@JvmName("toMessageChain1")
internal fun List<MsgOnlinePush.PbPushMsg>.toMessageChain(
bot: Bot?,
botId: Long,
groupIdOrZero: Long,
onlineSource: Boolean,
isTemp: Boolean = false
): MessageChain = map{it.msg}.toMessageChain(bot, botId, groupIdOrZero, onlineSource, isTemp)
internal fun MsgComm.Msg.toMessageChain(
bot: Bot?,
botId: Long,
groupIdOrZero: Long,
onlineSource: Boolean,
isTemp: Boolean = false
): MessageChain {
val elements = this.msgBody.richText.elems
): MessageChain = listOf(this).toMessageChain(bot, botId, groupIdOrZero, onlineSource, isTemp)
val pptMsg = msgBody.richText.ptt?.run {
internal fun List<MsgComm.Msg>.toMessageChain(
bot: Bot?,
botId: Long,
groupIdOrZero: Long,
onlineSource: Boolean,
isTemp: Boolean = false
): MessageChain {
val elements = this.flatMap { it.msgBody.richText.elems }
@OptIn(ExperimentalStdlibApi::class)
val ppts = buildList<Message> {
this@toMessageChain.forEach { msg ->
msg.msgBody.richText.ptt?.run {
// when (fileType) {
// 4 -> Voice(String(fileName), fileMd5, fileSize.toLong(),String(downPara))
// else -> null
// }
Voice(String(fileName), fileMd5, fileSize.toLong(), format, String(downPara))
add(Voice(String(fileName), fileMd5, fileSize.toLong(), format, String(downPara)))
}
}
}
return buildMessageChain(elements.size + 1 + if (pptMsg == null) 0 else 1) {
return buildMessageChain(elements.size + 1 + ppts.size) {
if (onlineSource) {
checkNotNull(bot) { "bot is null" }
when {
@ -263,7 +292,6 @@ internal fun MsgComm.Msg.toMessageChain(
+OfflineMessageSourceImplByMsg(this@toMessageChain, botId)
}
elements.joinToMessageChain(groupIdOrZero, botId, this)
pptMsg?.let(::add)
}.cleanupRubbishMessageElements()
}

View File

@ -26,6 +26,7 @@ import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.utils.mapToIntArray
import java.util.concurrent.atomic.AtomicBoolean
internal interface MessageSourceInternal {
@ -64,44 +65,44 @@ internal suspend inline fun Message.ensureSequenceIdAvailable() {
internal class MessageSourceFromFriendImpl(
override val bot: Bot,
val msg: MsgComm.Msg
val msg: List<MsgComm.Msg>
) : OnlineMessageSource.Incoming.FromFriend(), MessageSourceInternal {
override val sequenceIds: IntArray get() = intArrayOf(msg.msgHead.msgSeq)
override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq }
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val ids: IntArray get() = sequenceIds// msg.msgBody.richText.attr!!.random
override val internalIds: IntArray get() = intArrayOf(msg.msgBody.richText.attr!!.random)
override val time: Int get() = msg.msgHead.msgTime
override val internalIds: IntArray get() = msg.mapToIntArray { it.msgBody.richText.attr!!.random }
override val time: Int get() = msg.first().msgHead.msgTime
override val originalMessage: MessageChain by lazy { msg.toMessageChain(bot, bot.id, 0, false) }
override val sender: Friend get() = bot.getFriendOrFail(msg.msgHead.fromUin)
override val sender: Friend get() = bot.getFriendOrFail(msg.first().msgHead.fromUin)
private val jceData by lazy { msg.toJceDataFriendOrTemp(internalIds) }
override fun toJceData(): ImMsgBody.SourceMsg = jceData
}
private fun MsgComm.Msg.toJceDataFriendOrTemp(ids: IntArray): ImMsgBody.SourceMsg {
val elements = msgBody.richText.elems.toMutableList().also {
private fun List<MsgComm.Msg>.toJceDataFriendOrTemp(ids: IntArray): ImMsgBody.SourceMsg {
val elements = flatMap {it.msgBody.richText.elems}.toMutableList().also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
return ImMsgBody.SourceMsg(
origSeqs = intArrayOf(this.msgHead.msgSeq),
senderUin = this.msgHead.fromUin,
toUin = this.msgHead.toUin,
origSeqs = mapToIntArray { it.msgHead.msgSeq },
senderUin = first().msgHead.fromUin,
toUin = first().msgHead.toUin,
flag = 1,
elems = this.msgBody.richText.elems,
elems = flatMap{it.msgBody.richText.elems},
type = 0,
time = this.msgHead.msgTime,
time = this.first().msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr(
origUids = ids.map { it.toLong() and 0xFFFF_FFFF }
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = this.msgHead.fromUin, // qq
toUin = this.msgHead.toUin, // group
msgType = this.msgHead.msgType, // 82?
c2cCmd = this.msgHead.c2cCmd,
msgSeq = this.msgHead.msgSeq,
msgTime = this.msgHead.msgTime,
fromUin = this.first().msgHead.fromUin, // qq
toUin = this.first().msgHead.toUin, // group
msgType = this.first().msgHead.msgType, // 82?
c2cCmd = this.first().msgHead.c2cCmd,
msgSeq = this.first().msgHead.msgSeq,
msgTime = this.first().msgHead.msgTime,
msgUid = ids.single().toLong() and 0xFFFF_FFFF, // ok
// groupInfo = MsgComm.GroupInfo(groupCode = this.msgHead.groupInfo.groupCode),
isSrcMsg = true
@ -117,16 +118,24 @@ private fun MsgComm.Msg.toJceDataFriendOrTemp(ids: IntArray): ImMsgBody.SourceMs
internal class MessageSourceFromTempImpl(
override val bot: Bot,
private val msg: MsgComm.Msg
private val msg: List<MsgComm.Msg>
) : OnlineMessageSource.Incoming.FromTemp(), MessageSourceInternal {
override val sequenceIds: IntArray get() = intArrayOf(msg.msgHead.msgSeq)
override val internalIds: IntArray get() = intArrayOf(msg.msgBody.richText.attr!!.random)
override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq }
override val internalIds: IntArray get() = msg.mapToIntArray{it.msgBody.richText.attr!!.random }
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val ids: IntArray get() = sequenceIds//
override val time: Int get() = msg.msgHead.msgTime
override val originalMessage: MessageChain by lazy { msg.toMessageChain(bot, 0, false) }
override val time: Int get() = msg.first().msgHead.msgTime
override val originalMessage: MessageChain by lazy {
msg.toMessageChain(
bot,
bot.id,
groupIdOrZero = 0,
onlineSource = false,
isTemp = false,
)
}
override val sender: Member
get() = with(msg.msgHead) {
get() = with(msg.first().msgHead) {
bot.getGroupOrFail(c2cTmpMsgHead!!.groupUin).getOrFail(fromUin)
}
@ -136,24 +145,24 @@ internal class MessageSourceFromTempImpl(
internal data class MessageSourceFromGroupImpl(
override val bot: Bot,
private val msg: MsgComm.Msg
private val msg: List<MsgComm.Msg>
) : OnlineMessageSource.Incoming.FromGroup(), MessageSourceInternal {
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override val sequenceIds: IntArray get() = intArrayOf(msg.msgHead.msgSeq)
override val internalIds: IntArray get() = intArrayOf(msg.msgBody.richText.attr!!.random)
override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq }
override val internalIds: IntArray get() = msg.mapToIntArray{ it.msgBody.richText.attr!!.random }
override val ids: IntArray get() = sequenceIds
override val time: Int get() = msg.msgHead.msgTime
override val time: Int get() = msg.first().msgHead.msgTime
override val originalMessage: MessageChain by lazy {
msg.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = false)
msg.toMessageChain(bot, bot.id, groupIdOrZero = group.id, onlineSource = false)
}
override val sender: Member by lazy {
(bot.getGroup(
msg.msgHead.groupInfo?.groupCode
msg.first().msgHead.groupInfo?.groupCode
?: error("cannot find groupCode for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}")
) as GroupImpl).run {
get(msg.msgHead.fromUin)
?: msg.msgBody.richText.elems.firstOrNull { it.anonGroupMsg != null }?.run {
get(msg.first().msgHead.fromUin)
?: msg.first().msgBody.richText.elems.firstOrNull { it.anonGroupMsg != null }?.run {
newAnonymous(anonGroupMsg!!.anonNick.encodeToString())
}
?: error("cannot find member for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}")
@ -162,13 +171,13 @@ internal data class MessageSourceFromGroupImpl(
override fun toJceData(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
origSeqs = intArrayOf(msg.msgHead.msgSeq),
senderUin = msg.msgHead.fromUin,
origSeqs = intArrayOf(msg.first().msgHead.msgSeq),
senderUin = msg.first().msgHead.fromUin,
toUin = 0,
flag = 1,
elems = msg.msgBody.richText.elems,
elems = msg.flatMap { it.msgBody.richText.elems },
type = 0,
time = msg.msgHead.msgTime,
time = msg.first().msgHead.msgTime,
pbReserve = EMPTY_BYTE_ARRAY,
srcMsg = EMPTY_BYTE_ARRAY
)

View File

@ -25,41 +25,41 @@ import java.util.concurrent.atomic.AtomicBoolean
internal class OfflineMessageSourceImplByMsg(
// from other sources' originalMessage
val delegate: MsgComm.Msg,
val delegate: List<MsgComm.Msg>,
override val botId: Long,
) : OfflineMessageSource(), MessageSourceInternal {
override val kind: MessageSourceKind =
if (delegate.msgHead.groupInfo != null) MessageSourceKind.GROUP else MessageSourceKind.FRIEND
if (delegate.first().msgHead.groupInfo != null) MessageSourceKind.GROUP else MessageSourceKind.FRIEND
override val ids: IntArray get() = sequenceIds
override val internalIds: IntArray = intArrayOf(delegate.msgHead.msgUid.toInt())
override val internalIds: IntArray = delegate.mapToIntArray { it.msgHead.msgUid.toInt() }
override val time: Int
get() = delegate.msgHead.msgTime
get() = delegate.first().msgHead.msgTime
override val fromId: Long
get() = delegate.msgHead.fromUin
get() = delegate.first().msgHead.fromUin
override val targetId: Long
get() = delegate.msgHead.groupInfo?.groupCode ?: delegate.msgHead.toUin
get() = delegate.first().msgHead.groupInfo?.groupCode ?: delegate.first().msgHead.toUin
override val originalMessage: MessageChain by lazy {
delegate.toMessageChain(
null,
botId,
groupIdOrZero = delegate.msgHead.groupInfo?.groupCode ?: 0,
groupIdOrZero = delegate.first().msgHead.groupInfo?.groupCode ?: 0,
onlineSource = false,
isTemp = delegate.msgHead.c2cTmpMsgHead != null
isTemp = delegate.first().msgHead.c2cTmpMsgHead != null
)
}
override val sequenceIds: IntArray = intArrayOf(delegate.msgHead.msgSeq)
override val sequenceIds: IntArray = delegate.mapToIntArray { it.msgHead.msgSeq }
override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false)
override fun toJceData(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
origSeqs = intArrayOf(delegate.msgHead.msgSeq),
senderUin = delegate.msgHead.fromUin,
origSeqs = delegate.mapToIntArray { it.msgHead.msgSeq },
senderUin = delegate.first().msgHead.fromUin,
toUin = 0,
flag = 1,
elems = delegate.msgBody.richText.elems,
elems = delegate.flatMap { it.msgBody.richText.elems },
type = 0,
time = delegate.msgHead.msgTime,
time = delegate.first().msgHead.msgTime,
pbReserve = EMPTY_BYTE_ARRAY,
srcMsg = EMPTY_BYTE_ARRAY
)

View File

@ -60,19 +60,23 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory<Packet?>("Onlin
pbPushMsg.msg.msgHead.msgSeq
)
}
val group =
bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
val msgs = group.groupPkgMsgParsingCache.put(pbPushMsg)
if (msgs.isEmpty()) return null
var extraInfo: ImMsgBody.ExtraInfo? = null
var anonymous: ImMsgBody.AnonymousGroupMsg? = null
for (elem in pbPushMsg.msg.msgBody.richText.elems) {
when {
elem.extraInfo != null -> extraInfo = elem.extraInfo
elem.anonGroupMsg != null -> anonymous = elem.anonGroupMsg
for (msg in msgs) {
for (elem in msg.msg.msgBody.richText.elems) {
when {
elem.extraInfo != null -> extraInfo = elem.extraInfo
elem.anonGroupMsg != null -> anonymous = elem.anonGroupMsg
}
}
}
val group =
bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
val sender = if (anonymous != null) {
group.newAnonymous(anonymous.anonNick.encodeToString())
} else {
@ -106,7 +110,7 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory<Packet?>("Onlin
}
},
sender = sender,
message = pbPushMsg.msg.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true),
message = msgs.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true),
permission = when {
flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
flags and 8 != 0 -> MemberPermission.OWNER

View File

@ -44,6 +44,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857
import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.internal.network.protocol.packet.buildResponseUniPacket
import net.mamoe.mirai.internal.utils.*
import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.internal.utils.encodeToString
import net.mamoe.mirai.internal.utils.io.ProtoBuf
@ -53,6 +54,7 @@ import net.mamoe.mirai.internal.utils.read
import net.mamoe.mirai.internal.utils.toUHexString
import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.debug
import net.mamoe.mirai.utils.mapToIntArray
//0C 01 B1 89 BE 09 5E 3D 72 A6 00 01 73 68 FC 06 00 00 00 3C
@ -357,22 +359,21 @@ private object Transformers732 : Map<Int, Lambda732> by mapOf(
val operator =
if (recallReminder.uin == bot.id) group.botAsMember
else group[recallReminder.uin] ?: return@lambda732 emptySequence()
val firstPkg = recallReminder.recalledMsgList.firstOrNull() ?: return@lambda732 emptySequence()
return@lambda732 recallReminder.recalledMsgList.asSequence().mapNotNull { pkg ->
when {
pkg.authorUin == bot.id && operator.id == bot.id -> null
else -> {
MessageRecallEvent.GroupRecall(
bot,
pkg.authorUin,
intArrayOf(pkg.seq),
intArrayOf(pkg.msgRandom),
pkg.time,
operator,
group
)
}
}
return@lambda732 when {
firstPkg.authorUin == bot.id && operator.id == bot.id -> emptySequence()
else -> sequenceOf(
MessageRecallEvent.GroupRecall(
bot,
firstPkg.authorUin,
recallReminder.recalledMsgList.mapToIntArray { it.seq },
recallReminder.recalledMsgList.mapToIntArray { it.msgRandom },
firstPkg.time,
operator,
group
)
)
}
}
)

View File

@ -0,0 +1,56 @@
/*
* 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.utils
import kotlinx.atomicfu.locks.withLock
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush
import net.mamoe.mirai.utils.currentTimeMillis
import java.util.concurrent.locks.ReentrantLock
internal class GroupPkgMsgParsingCache {
class PkgMsg(
val size: Int,
val divSeq: Int,
val data: MutableMap<Int, MsgOnlinePush.PbPushMsg>,
) {
val createTime = currentTimeMillis()
}
private val deque = ArrayList<PkgMsg>(16)
private val accessLock = ReentrantLock()
private fun clearInvalid() {
deque.removeIf {
currentTimeMillis() - it.createTime > 10000L
}
}
fun put(msg: MsgOnlinePush.PbPushMsg): List<MsgOnlinePush.PbPushMsg> {
val head = msg.msg.contentHead ?: return listOf(msg)
val size = head.pkgNum
if (size < 2) return listOf(msg)
accessLock.withLock {
clearInvalid()
val seq = head.divSeq
val index = head.pkgIndex
val pkgMsg = deque.find {
it.divSeq == seq
} ?: PkgMsg(size, seq, mutableMapOf()).also { deque.add(it) }
pkgMsg.data[index] = msg
if (pkgMsg.data.size == pkgMsg.size) {
deque.removeIf { it.divSeq == seq }
return pkgMsg.data.entries.asSequence()
.sortedBy { it.key }
.map { it.value }
.toList()
}
return emptyList()
}
}
}