Commit db8cb84c authored by Him188's avatar Him188

Rearrange implementations for MessageSource, implement recall state check

parent 7fbc06b4
......@@ -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
......
......@@ -40,7 +40,7 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B
if (this.anyIsInstance<QuoteReply>()) {
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.")
}
}
......
/*
* 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
)
}
}
/*
* 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 = { "<error: ${it.message}>" },
onSuccess = { it }
)
}"
)*/
}
}
override fun toJceData(): ImMsgBody.SourceMsg {
return delegate
}
}
/*
* 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> 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<Int>
@OptIn(ExperimentalCoroutinesApi::class)
override val sequenceId: Int
get() = sequenceIdDeferred.getCompleted()
@OptIn(MiraiExperimentalAPI::class)
internal fun startWaitingSequenceId(coroutineScope: CoroutineScope) {
sequenceIdDeferred =
coroutineScope.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(
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
......@@ -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
......
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