From db8cb84c99a6eb834934095cb50f78bb879da22b Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 17 Apr 2020 14:19:32 +0800 Subject: [PATCH] Rearrange implementations for MessageSource, implement recall state check --- .../mirai/qqandroid/QQAndroidBot.common.kt | 9 +- .../qqandroid/message/MessageSourceImpl.kt | 506 ------------------ .../mirai/qqandroid/message/convension.kt | 2 +- .../qqandroid/message/incomingSourceImpl.kt | 141 +++++ .../qqandroid/message/offlineSourceImpl.kt | 110 ++++ .../qqandroid/message/outgoingSourceImpl.kt | 169 ++++++ .../commonMain/kotlin/net.mamoe.mirai/Bot.kt | 1 + 7 files changed, 428 insertions(+), 510 deletions(-) delete mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/incomingSourceImpl.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/offlineSourceImpl.kt create mode 100644 mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/outgoingSourceImpl.kt diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt index f577091ad..a68bcec24 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt @@ -291,11 +291,14 @@ internal abstract class QQAndroidBotBase constructor( @Suppress("RemoveExplicitTypeArguments") // false positive override suspend fun recall(source: MessageSource) { - // println(source._miraiContentToString()) - - check(source is MessageSourceImpl) + check(source is MessageSourceInternal) source.ensureSequenceIdAvailable() + @Suppress("BooleanLiteralArgument") // false positive + check(!source.isRecalledOrPlanned.value && source.isRecalledOrPlanned.compareAndSet(false, true)) { + "$source had already been recalled." + } + val response: PbMessageSvc.PbMsgWithDraw.Response = when (source) { is MessageSourceToGroupImpl, is MessageSourceFromGroupImpl diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt deleted file mode 100644 index 2c748b6ee..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt +++ /dev/null @@ -1,506 +0,0 @@ -/* - * Copyright 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("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE") - -package net.mamoe.mirai.qqandroid.message - -import kotlinx.atomicfu.AtomicBoolean -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.Member -import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.event.subscribingGetAsync -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.MessageSource -import net.mamoe.mirai.message.data.OfflineMessageSource -import net.mamoe.mirai.message.data.OnlineMessageSource -import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody -import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm -import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg -import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY -import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush -import net.mamoe.mirai.qqandroid.utils._miraiContentToString -import net.mamoe.mirai.qqandroid.utils.io.serialization.loadAs -import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray -import net.mamoe.mirai.utils.MiraiExperimentalAPI - - -internal interface MessageSourceImpl { - val sequenceId: Int - - var isRecalledOrPlanned: Boolean // // TODO: 2020/4/7 实现 isRecalledOrPlanned - - fun toJceData(): ImMsgBody.SourceMsg -} - -internal suspend inline fun MessageSource.ensureSequenceIdAvailable() { - if (this is MessageSourceToGroupImpl) { - this.ensureSequenceIdAvailable() - } -} - -internal class MessageSourceFromFriendImpl( - override val bot: Bot, - val msg: MsgComm.Msg -) : OnlineMessageSource.Incoming.FromFriend(), MessageSourceImpl { - override val sequenceId: Int get() = msg.msgHead.msgSeq - private val isRecalled: AtomicBoolean = atomic(false) - override var isRecalledOrPlanned: Boolean - get() = isRecalled.value - set(value) { - isRecalled.value = value - } - override val id: Int get() = msg.msgBody.richText.attr!!.random - override val time: Int get() = msg.msgHead.msgTime - override val originalMessage: MessageChain by lazy { - msg.toMessageChain( - bot, - groupIdOrZero = 0, - onlineSource = false - ) - } - override val sender: QQ get() = bot.getFriend(msg.msgHead.fromUin) - - private val elems by lazy { - msg.msgBody.richText.elems.toMutableList().also { - if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) - } - } - - override fun toJceData(): ImMsgBody.SourceMsg { - return ImMsgBody.SourceMsg( - origSeqs = listOf(msg.msgHead.msgSeq), - senderUin = msg.msgHead.fromUin, - toUin = msg.msgHead.toUin, - flag = 1, - elems = msg.msgBody.richText.elems, - type = 0, - time = msg.msgHead.msgTime, - pbReserve = SourceMsg.ResvAttr( - origUids = id.toLong() and 0xFFFF_FFFF - ).toByteArray(SourceMsg.ResvAttr.serializer()), - srcMsg = MsgComm.Msg( - msgHead = MsgComm.MsgHead( - fromUin = msg.msgHead.fromUin, // qq - toUin = msg.msgHead.toUin, // group - msgType = msg.msgHead.msgType, // 82? - c2cCmd = msg.msgHead.c2cCmd, - msgSeq = msg.msgHead.msgSeq, - msgTime = msg.msgHead.msgTime, - msgUid = id.toLong() and 0xFFFF_FFFF, // ok - // groupInfo = MsgComm.GroupInfo(groupCode = msg.msgHead.groupInfo.groupCode), - isSrcMsg = true - ), - msgBody = ImMsgBody.MsgBody( - richText = ImMsgBody.RichText( - elems = elems - ) - ) - ).toByteArray(MsgComm.Msg.serializer()) - ) - } -} - -internal class MessageSourceFromTempImpl( - override val bot: Bot, - private val msg: MsgComm.Msg -) : OnlineMessageSource.Incoming.FromTemp(), MessageSourceImpl { - override val sequenceId: Int get() = msg.msgHead.msgSeq - private val isRecalled: AtomicBoolean = atomic(false) - override var isRecalledOrPlanned: Boolean - get() = isRecalled.value - set(value) { - isRecalled.value = value - } - override val id: Int get() = msg.msgBody.richText.attr!!.random - override val time: Int get() = msg.msgHead.msgTime - override val originalMessage: MessageChain by lazy { - msg.toMessageChain( - bot, - groupIdOrZero = 0, - onlineSource = false - ) - } - override val sender: Member - get() = with(msg.msgHead) { - bot.getGroup(c2cTmpMsgHead!!.groupUin)[fromUin] - } - - private val elems by lazy { - msg.msgBody.richText.elems.toMutableList().also { - if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) - } - } - - override fun toJceData(): ImMsgBody.SourceMsg { - return ImMsgBody.SourceMsg( - origSeqs = listOf(msg.msgHead.msgSeq), - senderUin = msg.msgHead.fromUin, - toUin = msg.msgHead.toUin, - flag = 1, - elems = msg.msgBody.richText.elems, - type = 0, - time = msg.msgHead.msgTime, - pbReserve = SourceMsg.ResvAttr( - origUids = id.toLong() and 0xFFFF_FFFF - ).toByteArray(SourceMsg.ResvAttr.serializer()), - srcMsg = MsgComm.Msg( - msgHead = MsgComm.MsgHead( - fromUin = msg.msgHead.fromUin, // qq - toUin = msg.msgHead.toUin, // group - msgType = msg.msgHead.msgType, // 82? - c2cCmd = msg.msgHead.c2cCmd, - msgSeq = msg.msgHead.msgSeq, - msgTime = msg.msgHead.msgTime, - msgUid = id.toLong() and 0xFFFF_FFFF, // ok - // groupInfo = MsgComm.GroupInfo(groupCode = msg.msgHead.groupInfo.groupCode), - isSrcMsg = true - ), - msgBody = ImMsgBody.MsgBody( - richText = ImMsgBody.RichText( - elems = elems - ) - ) - ).toByteArray(MsgComm.Msg.serializer()) - ) - } -} - -internal class MessageSourceFromGroupImpl( - override val bot: Bot, - private val msg: MsgComm.Msg -) : OnlineMessageSource.Incoming.FromGroup(), MessageSourceImpl { - private val isRecalled: AtomicBoolean = atomic(false) - override var isRecalledOrPlanned: Boolean - get() = isRecalled.value - set(value) { - isRecalled.value = value - } - override val sequenceId: Int get() = msg.msgHead.msgSeq - override val id: Int get() = msg.msgBody.richText.attr!!.random - override val time: Int get() = msg.msgHead.msgTime - override val originalMessage: MessageChain by lazy { - msg.toMessageChain( - bot, - groupIdOrZero = group.id, - onlineSource = false - ) - } - override val sender: Member - get() = bot.getGroup( - msg.msgHead.groupInfo?.groupCode - ?: error("cannot find groupCode for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}") - ).getOrNull(msg.msgHead.fromUin) - ?: error("cannot find member for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}") - - - override fun toJceData(): ImMsgBody.SourceMsg { - return ImMsgBody.SourceMsg( - origSeqs = listOf(msg.msgHead.msgSeq), - senderUin = msg.msgHead.fromUin, - toUin = 0, - flag = 1, - elems = msg.msgBody.richText.elems, - type = 0, - time = msg.msgHead.msgTime, - pbReserve = EMPTY_BYTE_ARRAY, - srcMsg = EMPTY_BYTE_ARRAY - ) - } -} - -internal class OfflineMessageSourceImplByMsg( - // from other sources' originalMessage - val delegate: MsgComm.Msg, - override val bot: Bot -) : OfflineMessageSource(), MessageSourceImpl { - override val kind: Kind = if (delegate.msgHead.groupInfo != null) Kind.GROUP else Kind.FRIEND - override val id: Int - get() = delegate.msgHead.msgUid.toInt() - override val time: Int - get() = delegate.msgHead.msgTime - override val fromId: Long - get() = delegate.msgHead.fromUin - override val targetId: Long - get() = delegate.msgHead.groupInfo?.groupCode ?: delegate.msgHead.toUin - override val originalMessage: MessageChain by lazy { - delegate.toMessageChain(bot, - groupIdOrZero = delegate.msgHead.groupInfo?.groupCode ?: 0, - onlineSource = false, - isTemp = delegate.msgHead.c2cTmpMsgHead != null - ) - } - override val sequenceId: Int - get() = delegate.msgHead.msgSeq - - private val isRecalled: AtomicBoolean = atomic(false) - override var isRecalledOrPlanned: Boolean - get() = isRecalled.value - set(value) { - isRecalled.value = value - } - - override fun toJceData(): ImMsgBody.SourceMsg { - return ImMsgBody.SourceMsg( - origSeqs = listOf(delegate.msgHead.msgSeq), - senderUin = delegate.msgHead.fromUin, - toUin = 0, - flag = 1, - elems = delegate.msgBody.richText.elems, - type = 0, - time = delegate.msgHead.msgTime, - pbReserve = EMPTY_BYTE_ARRAY, - srcMsg = EMPTY_BYTE_ARRAY - ) - } -} - -internal class OfflineMessageSourceImplBySourceMsg( - // from others' quotation - val delegate: ImMsgBody.SourceMsg, - override val bot: Bot, - groupIdOrZero: Long -) : OfflineMessageSource(), MessageSourceImpl { - override val kind: Kind get() = if (delegate.srcMsg == null) Kind.GROUP else Kind.FRIEND - - private val isRecalled: AtomicBoolean = atomic(false) - override var isRecalledOrPlanned: Boolean - get() = isRecalled.value - set(value) { - isRecalled.value = value - } - override val sequenceId: Int - get() = delegate.origSeqs?.first() ?: error("cannot find sequenceId") - override val time: Int get() = delegate.time - override val originalMessage: MessageChain by lazy { delegate.toMessageChain(bot, groupIdOrZero) } - /* - override val id: Long - get() = (delegate.origSeqs?.firstOrNull() - ?: error("cannot find sequenceId from ImMsgBody.SourceMsg")).toLong().shl(32) or - delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.and(0xFFFFFFFF) - */ - - override val id: Int - get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids?.toInt() - ?: 0 - - // override val sourceMessage: MessageChain get() = delegate.toMessageChain() - override val fromId: Long get() = delegate.senderUin - override val targetId: Long by lazy { - when { - groupIdOrZero != 0L -> groupIdOrZero - delegate.toUin != 0L -> delegate.toUin - delegate.srcMsg != null -> delegate.srcMsg.loadAs(MsgComm.Msg.serializer()).msgHead.toUin - else -> 0/*error("cannot find targetId. delegate=${delegate._miraiContentToString()}, delegate.srcMsg=${ - kotlin.runCatching { delegate.srcMsg?.loadAs(MsgComm.Msg.serializer())?._miraiContentToString() } - .fold( - onFailure = { "" }, - onSuccess = { it } - ) - }" - )*/ - } - } - - override fun toJceData(): ImMsgBody.SourceMsg { - return delegate - } -} - -internal class MessageSourceToFriendImpl( - override val sequenceId: Int, - override val id: Int, - override val time: Int, - override val originalMessage: MessageChain, - override val sender: Bot, - override val target: QQ -) : OnlineMessageSource.Outgoing.ToFriend(), MessageSourceImpl { - override val bot: Bot - get() = sender - private val isRecalled: AtomicBoolean = atomic(false) - override var isRecalledOrPlanned: Boolean - get() = isRecalled.value - set(value) { - isRecalled.value = value - } - private val elems by lazy { - originalMessage.toRichTextElems(forGroup = false, withGeneralFlags = true) - } - - override fun toJceData(): ImMsgBody.SourceMsg { - val messageUid: Long = sequenceId.toLong().shl(32) or id.toLong().and(0xffFFffFF) - return ImMsgBody.SourceMsg( - origSeqs = listOf(sequenceId), - senderUin = fromId, - toUin = targetId, - flag = 1, - elems = elems, - type = 0, - time = time, - pbReserve = SourceMsg.ResvAttr( - origUids = messageUid - ).toByteArray(SourceMsg.ResvAttr.serializer()), - srcMsg = MsgComm.Msg( - msgHead = MsgComm.MsgHead( - fromUin = fromId, // qq - toUin = targetId, // group - msgType = 9, // 82? - c2cCmd = 11, - msgSeq = sequenceId, - msgTime = time, - msgUid = messageUid, // ok - // groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode), - isSrcMsg = true - ), - msgBody = ImMsgBody.MsgBody( - richText = ImMsgBody.RichText( - elems = elems.toMutableList().also { - if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) - } - ) - ) - ).toByteArray(MsgComm.Msg.serializer()) - ) - } -} - -internal class MessageSourceToTempImpl( - override val sequenceId: Int, - override val id: Int, - override val time: Int, - override val originalMessage: MessageChain, - override val sender: Bot, - override val target: Member -) : OnlineMessageSource.Outgoing.ToTemp(), MessageSourceImpl { - override val bot: Bot - get() = sender - private val isRecalled: AtomicBoolean = atomic(false) - override var isRecalledOrPlanned: Boolean - get() = isRecalled.value - set(value) { - isRecalled.value = value - } - private val elems by lazy { - originalMessage.toRichTextElems(forGroup = false, withGeneralFlags = true) - } - - override fun toJceData(): ImMsgBody.SourceMsg { - val messageUid: Long = sequenceId.toLong().shl(32) or id.toLong().and(0xffFFffFF) - return ImMsgBody.SourceMsg( - origSeqs = listOf(sequenceId), - senderUin = fromId, - toUin = targetId, - flag = 1, - elems = elems, - type = 0, - time = time, - pbReserve = SourceMsg.ResvAttr( - origUids = messageUid - ).toByteArray(SourceMsg.ResvAttr.serializer()), - srcMsg = MsgComm.Msg( - msgHead = MsgComm.MsgHead( - fromUin = fromId, // qq - toUin = targetId, // group - msgType = 9, // 82? - c2cCmd = 11, - msgSeq = sequenceId, - msgTime = time, - msgUid = messageUid, // ok - // groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode), - isSrcMsg = true - ), - msgBody = ImMsgBody.MsgBody( - richText = ImMsgBody.RichText( - elems = elems.toMutableList().also { - if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) - } - ) - ) - ).toByteArray(MsgComm.Msg.serializer()) - ) - } -} - -internal class MessageSourceToGroupImpl( - override val id: Int, - override val time: Int, - override val originalMessage: MessageChain, - override val sender: Bot, - override val target: Group -) : OnlineMessageSource.Outgoing.ToGroup(), MessageSourceImpl { - override val bot: Bot - get() = sender - private val isRecalled: AtomicBoolean = atomic(false) - override var isRecalledOrPlanned: Boolean - get() = isRecalled.value - set(value) { - isRecalled.value = value - } - private val elems by lazy { - originalMessage.toRichTextElems(forGroup = false, withGeneralFlags = true) - } - private lateinit var sequenceIdDeferred: Deferred - override val sequenceId: Int get() = sequenceIdDeferred.getCompleted() - - @OptIn(MiraiExperimentalAPI::class) - internal fun startWaitingSequenceId(coroutineScope: CoroutineScope) { - sequenceIdDeferred = - coroutineScope.subscribingGetAsync( - timeoutMillis = 3000 - ) { - if (it.messageRandom == this@MessageSourceToGroupImpl.id) { - it.sequenceId - } else null - } - } - - suspend fun ensureSequenceIdAvailable() { - sequenceIdDeferred.join() - } - - - override fun toJceData(): ImMsgBody.SourceMsg { - return ImMsgBody.SourceMsg( - origSeqs = listOf(sequenceId), - senderUin = fromId, - toUin = Group.calculateGroupUinByGroupCode(targetId), - flag = 1, - elems = elems, - type = 0, - time = time, - pbReserve = SourceMsg.ResvAttr( - origUids = id.toLong() and 0xffFFffFF // id is actually messageRandom - ).toByteArray(SourceMsg.ResvAttr.serializer()), - srcMsg = MsgComm.Msg( - msgHead = MsgComm.MsgHead( - fromUin = fromId, // qq - toUin = Group.calculateGroupUinByGroupCode(targetId), // group - msgType = 82, // 82? - c2cCmd = 1, - msgSeq = sequenceId, - msgTime = time, - msgUid = id.toLong() and 0xffFFffFF, // ok - groupInfo = MsgComm.GroupInfo(groupCode = targetId), - isSrcMsg = true - ), - msgBody = ImMsgBody.MsgBody( - richText = ImMsgBody.RichText( - elems = elems.toMutableList().also { - if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) - } - ) - ) - ).toByteArray(MsgComm.Msg.serializer()) - ) - } -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt index 4c0acc465..e5889e57a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt @@ -40,7 +40,7 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B if (this.anyIsInstance()) { when (val source = this[QuoteReply].source) { - is MessageSourceImpl -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData())) + is MessageSourceInternal -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData())) else -> error("unsupported MessageSource implementation: ${source::class.simpleName}. Don't implement your own MessageSource.") } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/incomingSourceImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/incomingSourceImpl.kt new file mode 100644 index 000000000..ffcf17e7a --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/incomingSourceImpl.kt @@ -0,0 +1,141 @@ +/* + * Copyright 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("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + +package net.mamoe.mirai.qqandroid.message + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.QQ +import net.mamoe.mirai.event.internal.MiraiAtomicBoolean +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.qqandroid.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg +import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY +import net.mamoe.mirai.qqandroid.utils._miraiContentToString +import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray + +internal interface MessageSourceInternal { + val sequenceId: Int + + val isRecalledOrPlanned: MiraiAtomicBoolean + + fun toJceData(): ImMsgBody.SourceMsg +} + +internal suspend inline fun MessageSource.ensureSequenceIdAvailable() { + if (this is MessageSourceToGroupImpl) { + this.ensureSequenceIdAvailable() + } +} + +internal class MessageSourceFromFriendImpl( + override val bot: Bot, + val msg: MsgComm.Msg +) : OnlineMessageSource.Incoming.FromFriend(), MessageSourceInternal { + override val sequenceId: Int get() = msg.msgHead.msgSeq + override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false) + override val id: Int get() = msg.msgBody.richText.attr!!.random + override val time: Int get() = msg.msgHead.msgTime + override val originalMessage: MessageChain by lazy { msg.toMessageChain(bot, 0, false) } + override val sender: QQ get() = bot.getFriend(msg.msgHead.fromUin) + + private val jceData by lazy { msg.toJceDataFriendOrTemp(id) } + + override fun toJceData(): ImMsgBody.SourceMsg = jceData +} + +private fun MsgComm.Msg.toJceDataFriendOrTemp(id: Int): ImMsgBody.SourceMsg { + val elements = msgBody.richText.elems.toMutableList().also { + if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) + } + return ImMsgBody.SourceMsg( + origSeqs = listOf(this.msgHead.msgSeq), + senderUin = this.msgHead.fromUin, + toUin = this.msgHead.toUin, + flag = 1, + elems = this.msgBody.richText.elems, + type = 0, + time = this.msgHead.msgTime, + pbReserve = SourceMsg.ResvAttr( + origUids = id.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, + msgUid = id.toLong() and 0xFFFF_FFFF, // ok + // groupInfo = MsgComm.GroupInfo(groupCode = this.msgHead.groupInfo.groupCode), + isSrcMsg = true + ), + msgBody = ImMsgBody.MsgBody( + richText = ImMsgBody.RichText( + elems = elements + ) + ) + ).toByteArray(MsgComm.Msg.serializer()) + ) +} + +internal class MessageSourceFromTempImpl( + override val bot: Bot, + private val msg: MsgComm.Msg +) : OnlineMessageSource.Incoming.FromTemp(), MessageSourceInternal { + override val sequenceId: Int get() = msg.msgHead.msgSeq + override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false) + override val id: Int get() = msg.msgBody.richText.attr!!.random + override val time: Int get() = msg.msgHead.msgTime + override val originalMessage: MessageChain by lazy { msg.toMessageChain(bot, 0, false) } + override val sender: Member get() = with(msg.msgHead) { bot.getGroup(c2cTmpMsgHead!!.groupUin)[fromUin] } + + private val jceData by lazy { msg.toJceDataFriendOrTemp(id) } + override fun toJceData(): ImMsgBody.SourceMsg = jceData +} + +internal data class MessageSourceFromGroupImpl( + override val bot: Bot, + private val msg: MsgComm.Msg +) : OnlineMessageSource.Incoming.FromGroup(), MessageSourceInternal { + override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false) + override val sequenceId: Int get() = msg.msgHead.msgSeq + override val id: Int get() = msg.msgBody.richText.attr!!.random + override val time: Int get() = msg.msgHead.msgTime + override val originalMessage: MessageChain by lazy { + msg.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = false) + } + override val sender: Member + get() = bot.getGroup( + msg.msgHead.groupInfo?.groupCode + ?: error("cannot find groupCode for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}") + ).getOrNull(msg.msgHead.fromUin) + ?: error("cannot find member for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}") + + + override fun toJceData(): ImMsgBody.SourceMsg { + return ImMsgBody.SourceMsg( + origSeqs = listOf(msg.msgHead.msgSeq), + senderUin = msg.msgHead.fromUin, + toUin = 0, + flag = 1, + elems = msg.msgBody.richText.elems, + type = 0, + time = msg.msgHead.msgTime, + pbReserve = EMPTY_BYTE_ARRAY, + srcMsg = EMPTY_BYTE_ARRAY + ) + } +} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/offlineSourceImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/offlineSourceImpl.kt new file mode 100644 index 000000000..edf40ed7e --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/offlineSourceImpl.kt @@ -0,0 +1,110 @@ +/* + * Copyright 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("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + +package net.mamoe.mirai.qqandroid.message + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.event.internal.MiraiAtomicBoolean +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineMessageSource +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg +import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY +import net.mamoe.mirai.qqandroid.utils.io.serialization.loadAs + + +internal class OfflineMessageSourceImplByMsg( + // from other sources' originalMessage + val delegate: MsgComm.Msg, + override val bot: Bot +) : OfflineMessageSource(), MessageSourceInternal { + override val kind: Kind = if (delegate.msgHead.groupInfo != null) Kind.GROUP else Kind.FRIEND + override val id: Int + get() = delegate.msgHead.msgUid.toInt() + override val time: Int + get() = delegate.msgHead.msgTime + override val fromId: Long + get() = delegate.msgHead.fromUin + override val targetId: Long + get() = delegate.msgHead.groupInfo?.groupCode ?: delegate.msgHead.toUin + override val originalMessage: MessageChain by lazy { + delegate.toMessageChain(bot, + groupIdOrZero = delegate.msgHead.groupInfo?.groupCode ?: 0, + onlineSource = false, + isTemp = delegate.msgHead.c2cTmpMsgHead != null + ) + } + override val sequenceId: Int + get() = delegate.msgHead.msgSeq + + override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false) + + override fun toJceData(): ImMsgBody.SourceMsg { + return ImMsgBody.SourceMsg( + origSeqs = listOf(delegate.msgHead.msgSeq), + senderUin = delegate.msgHead.fromUin, + toUin = 0, + flag = 1, + elems = delegate.msgBody.richText.elems, + type = 0, + time = delegate.msgHead.msgTime, + pbReserve = EMPTY_BYTE_ARRAY, + srcMsg = EMPTY_BYTE_ARRAY + ) + } +} + +internal class OfflineMessageSourceImplBySourceMsg( + // from others' quotation + val delegate: ImMsgBody.SourceMsg, + override val bot: Bot, + groupIdOrZero: Long +) : OfflineMessageSource(), MessageSourceInternal { + override val kind: Kind get() = if (delegate.srcMsg == null) Kind.GROUP else Kind.FRIEND + + override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false) + override val sequenceId: Int + get() = delegate.origSeqs?.first() ?: error("cannot find sequenceId") + override val time: Int get() = delegate.time + override val originalMessage: MessageChain by lazy { delegate.toMessageChain(bot, groupIdOrZero) } + /* + override val id: Long + get() = (delegate.origSeqs?.firstOrNull() + ?: error("cannot find sequenceId from ImMsgBody.SourceMsg")).toLong().shl(32) or + delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.and(0xFFFFFFFF) + */ + + override val id: Int + get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids?.toInt() + ?: 0 + + // override val sourceMessage: MessageChain get() = delegate.toMessageChain() + override val fromId: Long get() = delegate.senderUin + override val targetId: Long by lazy { + when { + groupIdOrZero != 0L -> groupIdOrZero + delegate.toUin != 0L -> delegate.toUin + delegate.srcMsg != null -> delegate.srcMsg.loadAs(MsgComm.Msg.serializer()).msgHead.toUin + else -> 0/*error("cannot find targetId. delegate=${delegate._miraiContentToString()}, delegate.srcMsg=${ + kotlin.runCatching { delegate.srcMsg?.loadAs(MsgComm.Msg.serializer())?._miraiContentToString() } + .fold( + onFailure = { "" }, + onSuccess = { it } + ) + }" + )*/ + } + } + + override fun toJceData(): ImMsgBody.SourceMsg { + return delegate + } +} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/outgoingSourceImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/outgoingSourceImpl.kt new file mode 100644 index 000000000..d80b0d43f --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/outgoingSourceImpl.kt @@ -0,0 +1,169 @@ +/* + * Copyright 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("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + +package net.mamoe.mirai.qqandroid.message + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.ExperimentalCoroutinesApi +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.QQ +import net.mamoe.mirai.event.internal.MiraiAtomicBoolean +import net.mamoe.mirai.event.subscribingGetAsync +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.qqandroid.network.protocol.data.proto.ImMsgBody +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush +import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray +import net.mamoe.mirai.utils.MiraiExperimentalAPI + + +private fun T.toJceDataImpl(): ImMsgBody.SourceMsg + where T : MessageSourceInternal, T : MessageSource { + + val elements = originalMessage.toRichTextElems(forGroup = false, withGeneralFlags = true) + val messageUid: Long = sequenceId.toLong().shl(32) or id.toLong().and(0xffFFffFF) + return ImMsgBody.SourceMsg( + origSeqs = listOf(sequenceId), + senderUin = fromId, + toUin = targetId, + flag = 1, + elems = elements, + type = 0, + time = time, + pbReserve = SourceMsg.ResvAttr( + origUids = messageUid + ).toByteArray(SourceMsg.ResvAttr.serializer()), + srcMsg = MsgComm.Msg( + msgHead = MsgComm.MsgHead( + fromUin = fromId, // qq + toUin = targetId, // group + msgType = 9, // 82? + c2cCmd = 11, + msgSeq = sequenceId, + msgTime = time, + msgUid = messageUid, // ok + // groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode), + isSrcMsg = true + ), + msgBody = ImMsgBody.MsgBody( + richText = ImMsgBody.RichText( + elems = elements.toMutableList().also { + if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) + } + ) + ) + ).toByteArray(MsgComm.Msg.serializer()) + ) +} + +internal class MessageSourceToFriendImpl( + override val sequenceId: Int, + override val id: Int, + override val time: Int, + override val originalMessage: MessageChain, + override val sender: Bot, + override val target: QQ +) : OnlineMessageSource.Outgoing.ToFriend(), MessageSourceInternal { + override val bot: Bot + get() = sender + override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false) + private val jceData by lazy { toJceDataImpl() } + override fun toJceData(): ImMsgBody.SourceMsg = jceData +} + +internal class MessageSourceToTempImpl( + override val sequenceId: Int, + override val id: Int, + override val time: Int, + override val originalMessage: MessageChain, + override val sender: Bot, + override val target: Member +) : OnlineMessageSource.Outgoing.ToTemp(), MessageSourceInternal { + override val bot: Bot + get() = sender + override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false) + private val jceData by lazy { toJceDataImpl() } + override fun toJceData(): ImMsgBody.SourceMsg = jceData +} + +internal class MessageSourceToGroupImpl( + override val id: Int, + override val time: Int, + override val originalMessage: MessageChain, + override val sender: Bot, + override val target: Group +) : OnlineMessageSource.Outgoing.ToGroup(), MessageSourceInternal { + override val bot: Bot + get() = sender + override var isRecalledOrPlanned: MiraiAtomicBoolean = MiraiAtomicBoolean(false) + private lateinit var sequenceIdDeferred: Deferred + + @OptIn(ExperimentalCoroutinesApi::class) + override val sequenceId: Int + get() = sequenceIdDeferred.getCompleted() + + @OptIn(MiraiExperimentalAPI::class) + internal fun startWaitingSequenceId(coroutineScope: CoroutineScope) { + sequenceIdDeferred = + coroutineScope.subscribingGetAsync( + timeoutMillis = 3000 + ) { + if (it.messageRandom == this@MessageSourceToGroupImpl.id) { + it.sequenceId + } else null + } + } + + suspend fun ensureSequenceIdAvailable() = sequenceIdDeferred.join() + + private val jceData by lazy { + val elements = originalMessage.toRichTextElems(forGroup = false, withGeneralFlags = true) + ImMsgBody.SourceMsg( + origSeqs = listOf(sequenceId), + senderUin = fromId, + toUin = Group.calculateGroupUinByGroupCode(targetId), + flag = 1, + elems = elements, + type = 0, + time = time, + pbReserve = SourceMsg.ResvAttr( + origUids = id.toLong() and 0xffFFffFF // id is actually messageRandom + ).toByteArray(SourceMsg.ResvAttr.serializer()), + srcMsg = MsgComm.Msg( + msgHead = MsgComm.MsgHead( + fromUin = fromId, // qq + toUin = Group.calculateGroupUinByGroupCode(targetId), // group + msgType = 82, // 82? + c2cCmd = 1, + msgSeq = sequenceId, + msgTime = time, + msgUid = id.toLong() and 0xffFFffFF, // ok + groupInfo = MsgComm.GroupInfo(groupCode = targetId), + isSrcMsg = true + ), + msgBody = ImMsgBody.MsgBody( + richText = ImMsgBody.RichText( + elems = elements.toMutableList().also { + if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) + } + ) + ) + ).toByteArray(MsgComm.Msg.serializer()) + ) + } + + override fun toJceData(): ImMsgBody.SourceMsg = jceData +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index 75580a14d..1279a106b 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -157,6 +157,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( * @param source 消息源. 可从 [MessageReceipt.source] 获得, 或从消息事件中的 [MessageChain] 获得. * * @throws PermissionDeniedException 当 [Bot] 无权限操作时 + * @throws IllegalStateException 当这条消息已经被撤回时 (仅同步主动操作) * * @see Bot.recall (扩展函数) 接受参数 [MessageChain] * @see MessageSource.recall