mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-09 18:00:33 +08:00
Implement long message for private session and unify message send as SendMessageHandler
, fix send failure with quoted image fix #892
This commit is contained in:
parent
32362f02c3
commit
74c4369931
@ -695,9 +695,9 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
|
||||
return jsonText?.let { json.decodeFromString(GroupHonorListData.serializer(), it) }
|
||||
}
|
||||
|
||||
internal suspend fun uploadGroupMessageHighway(
|
||||
internal suspend fun uploadMessageHighway(
|
||||
bot: Bot,
|
||||
groupCode: Long,
|
||||
sendMessageHandler: SendMessageHandler<*>,
|
||||
message: Collection<ForwardMessage.INode>,
|
||||
isLong: Boolean,
|
||||
): String = with(bot.asQQAndroidBot()) {
|
||||
@ -705,14 +705,12 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
|
||||
it.messageChain.ensureSequenceIdAvailable()
|
||||
}
|
||||
|
||||
val group = getGroupOrFail(groupCode)
|
||||
|
||||
val sequenceId = client.atomicNextMessageSequenceId()
|
||||
|
||||
val data = message.calculateValidationDataForGroup(
|
||||
val data = message.calculateValidationData(
|
||||
sequenceId = sequenceId,
|
||||
random = Random.nextInt().absoluteValue,
|
||||
group
|
||||
sendMessageHandler
|
||||
)
|
||||
|
||||
val response = network.run {
|
||||
@ -720,7 +718,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
|
||||
buType = if (isLong) 1 else 2,
|
||||
client = bot.client,
|
||||
messageData = data,
|
||||
dstUin = Mirai.calculateGroupUinByGroupCode(groupCode)
|
||||
dstUin = sendMessageHandler.targetUin
|
||||
).sendAndExpect<MultiMsg.ApplyUp.Response>()
|
||||
}
|
||||
|
||||
@ -740,7 +738,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
|
||||
msgUpReq = listOf(
|
||||
LongMsg.MsgUpReq(
|
||||
msgType = 3, // group
|
||||
dstUin = Mirai.calculateGroupUinByGroupCode(groupCode),
|
||||
dstUin = sendMessageHandler.targetUin,
|
||||
msgId = 0,
|
||||
msgUkey = response.proto.msgUkey,
|
||||
needCache = 0,
|
||||
@ -755,8 +753,8 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
|
||||
bot = bot,
|
||||
resource = resource,
|
||||
kind = when (isLong) {
|
||||
true -> ResourceKind.GROUP_LONG_MESSAGE
|
||||
false -> ResourceKind.GROUP_FORWARD_MESSAGE
|
||||
true -> ResourceKind.LONG_MESSAGE
|
||||
false -> ResourceKind.FORWARD_MESSAGE
|
||||
},
|
||||
commandId = 27,
|
||||
initialTicket = response.proto.msgSig
|
||||
|
@ -17,9 +17,7 @@ import net.mamoe.mirai.contact.Stranger
|
||||
import net.mamoe.mirai.contact.User
|
||||
import net.mamoe.mirai.data.UserInfo
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.BeforeImageUploadEvent
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.ImageUploadEvent
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.internal.message.OfflineFriendImage
|
||||
import net.mamoe.mirai.internal.message.getImageType
|
||||
import net.mamoe.mirai.internal.network.highway.ChannelKind
|
||||
@ -29,7 +27,11 @@ import net.mamoe.mirai.internal.network.highway.postImage
|
||||
import net.mamoe.mirai.internal.network.highway.tryServers
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.isContentEmpty
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@ -135,4 +137,25 @@ internal abstract class AbstractUser(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
internal suspend fun <C : User> SendMessageHandler<out C>.sendMessageImpl(
|
||||
message: Message,
|
||||
preSendEventConstructor: (C, Message) -> MessagePreSendEvent,
|
||||
postSendEventConstructor: (C, MessageChain, Throwable?, MessageReceipt<C>?) -> MessagePostSendEvent<C>,
|
||||
): MessageReceipt<C> {
|
||||
require(!message.isContentEmpty()) { "message is empty" }
|
||||
|
||||
val chain = contact.broadcastMessagePreSendEvent(message, preSendEventConstructor)
|
||||
|
||||
val result = this
|
||||
.runCatching { sendMessage(message, chain, SendMessageStep.FIRST) }
|
||||
|
||||
// logMessageSent(result.getOrNull()?.source?.plus(chain) ?: chain) // log with source
|
||||
contact.logMessageSent(chain)
|
||||
|
||||
postSendEventConstructor(contact, chain, result.exceptionOrNull(), result.getOrNull()).broadcast()
|
||||
|
||||
return result.getOrThrow()
|
||||
}
|
@ -24,12 +24,13 @@ import net.mamoe.mirai.LowLevelApi
|
||||
import net.mamoe.mirai.contact.Friend
|
||||
import net.mamoe.mirai.data.FriendInfo
|
||||
import net.mamoe.mirai.data.FriendInfoImpl
|
||||
import net.mamoe.mirai.event.events.FriendMessagePostSendEvent
|
||||
import net.mamoe.mirai.event.events.FriendMessagePreSendEvent
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
|
||||
import net.mamoe.mirai.internal.utils.C2CPkgMsgParsingCache
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.isContentEmpty
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@ -80,16 +81,12 @@ internal class FriendImpl(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val handler: FriendSendMessageHandler by lazy { FriendSendMessageHandler(this) }
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
override suspend fun sendMessage(message: Message): MessageReceipt<Friend> {
|
||||
require(!message.isContentEmpty()) { "message is empty" }
|
||||
return sendMessageImpl(
|
||||
message,
|
||||
friendReceiptConstructor = { MessageReceipt(it, this) },
|
||||
tReceiptConstructor = { MessageReceipt(it, this) }
|
||||
).also {
|
||||
logMessageSent(message)
|
||||
}
|
||||
return handler.sendMessageImpl(message, ::FriendMessagePreSendEvent, ::FriendMessagePostSendEvent)
|
||||
}
|
||||
|
||||
override fun toString(): String = "Friend($id)"
|
||||
|
@ -120,9 +120,10 @@ internal class GroupImpl(
|
||||
require(!message.isContentEmpty()) { "message is empty" }
|
||||
check(!isBotMuted) { throw BotIsBeingMutedException(this) }
|
||||
|
||||
val chain = broadcastGroupMessagePreSendEvent(message)
|
||||
val chain = broadcastMessagePreSendEvent(message, ::GroupMessagePreSendEvent)
|
||||
|
||||
val result = sendMessageImpl(message, chain, GroupMessageSendingStep.FIRST)
|
||||
val result = GroupSendMessageHandler(this)
|
||||
.runCatching { sendMessage(message, chain, SendMessageStep.FIRST) }
|
||||
|
||||
// logMessageSent(result.getOrNull()?.source?.plus(chain) ?: chain) // log with source
|
||||
logMessageSent(chain)
|
||||
|
@ -12,61 +12,22 @@
|
||||
package net.mamoe.mirai.internal.contact
|
||||
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.MessageTooLargeException
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.GroupMessagePreSendEvent
|
||||
import net.mamoe.mirai.event.nextEventOrNull
|
||||
import net.mamoe.mirai.internal.MiraiImpl
|
||||
import net.mamoe.mirai.internal.forwardMessage
|
||||
import net.mamoe.mirai.internal.longMessage
|
||||
import net.mamoe.mirai.internal.message.*
|
||||
import net.mamoe.mirai.internal.network.Packet
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.MusicSharePacket
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.SendMessageMultiProtocol
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushPbPushGroupMsg
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||
|
||||
/**
|
||||
* Might be recalled with [transformedMessage] `is` [LongMessageInternal] if length estimation failed ([sendMessagePacket])
|
||||
*/
|
||||
internal suspend fun GroupImpl.sendMessageImpl(
|
||||
originalMessage: Message,
|
||||
transformedMessage: Message,
|
||||
step: GroupMessageSendingStep,
|
||||
): Result<MessageReceipt<Group>> { // Result<MessageReceipt<Group>>
|
||||
val chain = transformedMessage
|
||||
.transformSpecialMessages(this)
|
||||
.convertToLongMessageIfNeeded(step, this)
|
||||
|
||||
chain.findIsInstance<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
|
||||
chain.asSequence().filterIsInstance<FriendImage>().forEach { image ->
|
||||
updateFriendImageForGroupMessage(image)
|
||||
}
|
||||
|
||||
return kotlin.runCatching {
|
||||
sendMessagePacket(
|
||||
originalMessage,
|
||||
transformedMessage,
|
||||
chain,
|
||||
step
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import net.mamoe.mirai.event.events.MessagePreSendEvent
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.toMessageChain
|
||||
|
||||
/**
|
||||
* Called only in 'public' apis.
|
||||
*/
|
||||
internal suspend fun GroupImpl.broadcastGroupMessagePreSendEvent(message: Message): MessageChain {
|
||||
internal suspend fun <C : Contact> C.broadcastMessagePreSendEvent(
|
||||
message: Message,
|
||||
eventConstructor: (C, Message) -> MessagePreSendEvent
|
||||
): MessageChain {
|
||||
return kotlin.runCatching {
|
||||
GroupMessagePreSendEvent(this, message).broadcast()
|
||||
eventConstructor(this, message).broadcast()
|
||||
}.onSuccess {
|
||||
check(!it.isCancelled) {
|
||||
throw EventCancelledException("cancelled by GroupMessagePreSendEvent")
|
||||
@ -77,178 +38,6 @@ internal suspend fun GroupImpl.broadcastGroupMessagePreSendEvent(message: Messag
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* - [ForwardMessage] -> [ForwardMessageInternal] (by uploading through highway)
|
||||
* - ... any others for future
|
||||
*/
|
||||
private suspend fun Message.transformSpecialMessages(contact: Contact): MessageChain {
|
||||
return takeSingleContent<ForwardMessage>()?.let { forward ->
|
||||
check(forward.nodeList.size <= 200) {
|
||||
throw MessageTooLargeException(
|
||||
contact, forward, forward,
|
||||
"ForwardMessage allows up to 200 nodes, but found ${forward.nodeList.size}"
|
||||
)
|
||||
}
|
||||
|
||||
val resId = MiraiImpl.uploadGroupMessageHighway(contact.bot, contact.id, forward.nodeList, false)
|
||||
RichMessage.forwardMessage(
|
||||
resId = resId,
|
||||
timeSeconds = currentTimeSeconds(),
|
||||
forwardMessage = forward,
|
||||
)
|
||||
}?.toMessageChain() ?: toMessageChain()
|
||||
}
|
||||
|
||||
internal enum class GroupMessageSendingStep {
|
||||
internal enum class SendMessageStep {
|
||||
FIRST, LONG_MESSAGE, FRAGMENTED
|
||||
}
|
||||
|
||||
/**
|
||||
* Final process
|
||||
*/
|
||||
private suspend fun GroupImpl.sendMessagePacket(
|
||||
originalMessage: Message,
|
||||
transformedMessage: Message,
|
||||
finalMessage: MessageChain,
|
||||
step: GroupMessageSendingStep,
|
||||
): MessageReceipt<Group> {
|
||||
|
||||
val group = this
|
||||
|
||||
var source: OnlineMessageSourceToGroupImpl? = null
|
||||
|
||||
bot.network.run {
|
||||
SendMessageMultiProtocol.createToGroup(
|
||||
bot.client, group, finalMessage,
|
||||
step == GroupMessageSendingStep.FRAGMENTED
|
||||
) { source = it }.forEach { packet ->
|
||||
|
||||
when (val resp = packet.sendAndExpect<Packet>()) {
|
||||
is MessageSvcPbSendMsg.Response -> {
|
||||
if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) {
|
||||
return when (step) {
|
||||
GroupMessageSendingStep.FIRST -> {
|
||||
sendMessageImpl(
|
||||
originalMessage,
|
||||
transformedMessage,
|
||||
GroupMessageSendingStep.LONG_MESSAGE
|
||||
)
|
||||
}
|
||||
GroupMessageSendingStep.LONG_MESSAGE -> {
|
||||
sendMessageImpl(
|
||||
originalMessage,
|
||||
transformedMessage,
|
||||
GroupMessageSendingStep.FRAGMENTED
|
||||
)
|
||||
|
||||
}
|
||||
else -> {
|
||||
throw MessageTooLargeException(
|
||||
group,
|
||||
originalMessage,
|
||||
finalMessage,
|
||||
"Message '${finalMessage.content.take(10)}' is too large."
|
||||
)
|
||||
}
|
||||
}.getOrThrow()
|
||||
}
|
||||
check(resp is MessageSvcPbSendMsg.Response.SUCCESS) {
|
||||
"Send group message failed: $resp"
|
||||
}
|
||||
}
|
||||
is MusicSharePacket.Response -> {
|
||||
resp.pkg.checkSuccess("send group music share")
|
||||
|
||||
val receipt: OnlinePushPbPushGroupMsg.SendGroupMessageReceipt =
|
||||
nextEventOrNull(3000) { it.fromAppId == 3116 }
|
||||
?: OnlinePushPbPushGroupMsg.SendGroupMessageReceipt.EMPTY
|
||||
|
||||
source = OnlineMessageSourceToGroupImpl(
|
||||
group,
|
||||
internalIds = intArrayOf(receipt.messageRandom),
|
||||
providedSequenceIds = intArrayOf(receipt.sequenceId),
|
||||
sender = bot,
|
||||
target = group,
|
||||
time = currentTimeSeconds().toInt(),
|
||||
originalMessage = finalMessage
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
check(source != null) {
|
||||
"Internal error: source is not initialized"
|
||||
}
|
||||
|
||||
try {
|
||||
source!!.ensureSequenceIdAvailable()
|
||||
} catch (e: Exception) {
|
||||
bot.network.logger.warning(
|
||||
"Timeout awaiting sequenceId for group message(${finalMessage.content.take(10)}). Some features may not work properly",
|
||||
e
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
return MessageReceipt(source!!, group)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun GroupImpl.uploadGroupLongMessageHighway(
|
||||
chain: MessageChain
|
||||
) = MiraiImpl.uploadGroupMessageHighway(
|
||||
bot, this.id,
|
||||
listOf(
|
||||
ForwardMessage.Node(
|
||||
senderId = bot.id,
|
||||
time = currentTimeSeconds().toInt(),
|
||||
messageChain = chain,
|
||||
senderName = bot.nick
|
||||
)
|
||||
),
|
||||
true
|
||||
)
|
||||
|
||||
private suspend fun MessageChain.convertToLongMessageIfNeeded(
|
||||
step: GroupMessageSendingStep,
|
||||
groupImpl: GroupImpl,
|
||||
): MessageChain {
|
||||
suspend fun sendLongImpl(): MessageChain {
|
||||
val resId = groupImpl.uploadGroupLongMessageHighway(this)
|
||||
return this + RichMessage.longMessage(
|
||||
brief = takeContent(27),
|
||||
resId = resId,
|
||||
timeSeconds = currentTimeSeconds()
|
||||
) // LongMessageInternal replaces all contents and preserves metadata
|
||||
}
|
||||
return when (step) {
|
||||
GroupMessageSendingStep.FIRST -> {
|
||||
// 只需要在第一次发送的时候验证长度
|
||||
// 后续重试直接跳过
|
||||
if (contains(ForceAsLongMessage)) {
|
||||
sendLongImpl()
|
||||
}
|
||||
verityLength(this, groupImpl)
|
||||
this
|
||||
}
|
||||
GroupMessageSendingStep.LONG_MESSAGE -> {
|
||||
sendLongImpl()
|
||||
}
|
||||
GroupMessageSendingStep.FRAGMENTED -> this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures server holds the cache
|
||||
*/
|
||||
private suspend fun GroupImpl.updateFriendImageForGroupMessage(image: FriendImage) {
|
||||
bot.network.run {
|
||||
ImgStore.GroupPicUp(
|
||||
bot.client,
|
||||
uin = bot.id,
|
||||
groupCode = id,
|
||||
md5 = image.md5,
|
||||
size = if (image is OnlineFriendImageImpl) image.delegate.fileLen else 0
|
||||
).sendAndExpect<ImgStore.GroupPicUp.Response>()
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +20,10 @@ import net.mamoe.mirai.data.MemberInfo
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.internal.message.OnlineMessageSourceToTempImpl
|
||||
import net.mamoe.mirai.internal.message.ensureSequenceIdAvailable
|
||||
import net.mamoe.mirai.internal.message.firstIsInstanceOrNull
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToTemp
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.cast
|
||||
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
@ -48,66 +45,24 @@ internal class NormalMemberImpl constructor(
|
||||
|
||||
override fun toString(): String = "NormalMember($id)"
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@JvmSynthetic
|
||||
private val handler by lazy { GroupTempSendMessageHandler(this) }
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
override suspend fun sendMessage(message: Message): MessageReceipt<NormalMember> {
|
||||
require(!message.isContentEmpty()) { "message is empty" }
|
||||
|
||||
val asFriend = this.asFriendOrNull()
|
||||
val asStranger = this.asStrangerOrNull()
|
||||
|
||||
return (asFriend?.sendMessageImpl(
|
||||
message,
|
||||
friendReceiptConstructor = { MessageReceipt(it, asFriend) },
|
||||
tReceiptConstructor = { MessageReceipt(it, this) }
|
||||
) ?: asStranger?.sendMessageImpl(
|
||||
message,
|
||||
strangerReceiptConstructor = { MessageReceipt(it, asStranger) },
|
||||
tReceiptConstructor = { MessageReceipt(it, this) }
|
||||
) ?: sendMessageImpl(message)).also { logMessageSent(message) }
|
||||
return asFriendOrNull()?.sendMessage(message)?.convert()
|
||||
?: asStrangerOrNull()?.sendMessage(message)?.convert()
|
||||
?: handler.sendMessageImpl<NormalMember>(
|
||||
message = message,
|
||||
preSendEventConstructor = ::GroupTempMessagePreSendEvent,
|
||||
postSendEventConstructor = ::GroupTempMessagePostSendEvent.cast()
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun sendMessageImpl(message: Message): MessageReceipt<NormalMember> {
|
||||
val chain = kotlin.runCatching {
|
||||
GroupTempMessagePreSendEvent(this, message).broadcast()
|
||||
}.onSuccess {
|
||||
check(!it.isCancelled) {
|
||||
throw EventCancelledException("cancelled by GroupTempMessagePreSendEvent")
|
||||
}
|
||||
}.getOrElse {
|
||||
throw EventCancelledException("exception thrown when broadcasting GroupTempMessagePreSendEvent", it)
|
||||
}.message.toMessageChain()
|
||||
|
||||
chain.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
|
||||
val result = bot.network.runCatching {
|
||||
val source: OnlineMessageSourceToTempImpl
|
||||
MessageSvcPbSendMsg.createToTemp(
|
||||
bot.client,
|
||||
this@NormalMemberImpl,
|
||||
chain
|
||||
) {
|
||||
source = it
|
||||
}.sendAndExpect<MessageSvcPbSendMsg.Response>().let {
|
||||
check(it is MessageSvcPbSendMsg.Response.SUCCESS) {
|
||||
"Send temp message failed: $it"
|
||||
}
|
||||
}
|
||||
MessageReceipt(source, this@NormalMemberImpl)
|
||||
}
|
||||
|
||||
result.fold(
|
||||
onSuccess = {
|
||||
GroupTempMessagePostSendEvent(this, chain, null, it)
|
||||
},
|
||||
onFailure = {
|
||||
GroupTempMessagePostSendEvent(this, chain, it, null)
|
||||
}
|
||||
).broadcast()
|
||||
|
||||
return result.getOrThrow()
|
||||
private fun MessageReceipt<User>.convert(): MessageReceipt<NormalMemberImpl> {
|
||||
return MessageReceipt(OnlineMessageSourceToTempImpl(source, this@NormalMemberImpl), this@NormalMemberImpl)
|
||||
}
|
||||
|
||||
|
||||
@Suppress("PropertyName")
|
||||
internal var _nameCard: String = memberInfo.nameCard
|
||||
|
||||
|
357
mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt
Normal file
357
mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt
Normal file
@ -0,0 +1,357 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.contact
|
||||
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.event.nextEventOrNull
|
||||
import net.mamoe.mirai.internal.MiraiImpl
|
||||
import net.mamoe.mirai.internal.asQQAndroidBot
|
||||
import net.mamoe.mirai.internal.forwardMessage
|
||||
import net.mamoe.mirai.internal.longMessage
|
||||
import net.mamoe.mirai.internal.message.*
|
||||
import net.mamoe.mirai.internal.network.Packet
|
||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.MusicSharePacket
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToFriend
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToGroup
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.castOrNull
|
||||
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||
import java.lang.UnsupportedOperationException
|
||||
|
||||
/**
|
||||
* 通用处理器
|
||||
*/
|
||||
internal abstract class SendMessageHandler<C : Contact> {
|
||||
abstract val contact: C
|
||||
abstract val senderName: String
|
||||
|
||||
val messageSourceKind: MessageSourceKind
|
||||
get() {
|
||||
return when (contact) {
|
||||
is Group -> MessageSourceKind.GROUP
|
||||
is Friend -> MessageSourceKind.FRIEND
|
||||
is Member -> MessageSourceKind.TEMP
|
||||
is Stranger -> MessageSourceKind.STRANGER
|
||||
else -> error("Unsupported contact: $contact")
|
||||
}
|
||||
}
|
||||
|
||||
val bot get() = contact.bot.asQQAndroidBot()
|
||||
|
||||
val targetUserUin: Long? get() = contact.castOrNull<User>()?.uin
|
||||
val targetGroupUin: Long? get() = contact.castOrNull<Group>()?.uin
|
||||
val targetGroupCode: Long? get() = contact.castOrNull<Group>()?.groupCode
|
||||
|
||||
val targetOtherClientBotUin: Long? get() = contact.castOrNull<OtherClient>()?.bot?.id
|
||||
|
||||
val targetUin: Long get() = targetGroupUin ?: targetOtherClientBotUin ?: contact.id
|
||||
|
||||
val groupInfo: MsgComm.GroupInfo?
|
||||
get() = if (isToGroup) MsgComm.GroupInfo(
|
||||
groupCode = targetGroupCode!!,
|
||||
groupCard = senderName // Cinnamon
|
||||
) else null
|
||||
|
||||
val isToGroup: Boolean get() = contact is Group
|
||||
|
||||
suspend fun MessageChain.convertToLongMessageIfNeeded(
|
||||
step: SendMessageStep,
|
||||
): MessageChain {
|
||||
suspend fun sendLongImpl(): MessageChain {
|
||||
val resId = uploadLongMessageHighway(this)
|
||||
return this + RichMessage.longMessage(
|
||||
brief = takeContent(27),
|
||||
resId = resId,
|
||||
timeSeconds = currentTimeSeconds()
|
||||
) // LongMessageInternal replaces all contents and preserves metadata
|
||||
}
|
||||
return when (step) {
|
||||
SendMessageStep.FIRST -> {
|
||||
// 只需要在第一次发送的时候验证长度
|
||||
// 后续重试直接跳过
|
||||
if (contains(ForceAsLongMessage)) {
|
||||
sendLongImpl()
|
||||
}
|
||||
|
||||
if (!contains(IgnoreLengthCheck)) {
|
||||
verityLength(this, contact)
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
SendMessageStep.LONG_MESSAGE -> {
|
||||
if (contains(DontAsLongMessage)) this // fragmented
|
||||
else sendLongImpl()
|
||||
}
|
||||
SendMessageStep.FRAGMENTED -> this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Final process
|
||||
*/
|
||||
suspend fun sendMessagePacket(
|
||||
originalMessage: Message,
|
||||
transformedMessage: Message,
|
||||
finalMessage: MessageChain,
|
||||
step: SendMessageStep,
|
||||
): MessageReceipt<C> {
|
||||
|
||||
val group = contact
|
||||
|
||||
var source: OnlineMessageSource.Outgoing? = null
|
||||
|
||||
bot.network.run {
|
||||
sendMessageMultiProtocol(
|
||||
bot.client, finalMessage,
|
||||
fragmented = step == SendMessageStep.FRAGMENTED
|
||||
) { source = it }.forEach { packet ->
|
||||
|
||||
when (val resp = packet.sendAndExpect<Packet>()) {
|
||||
is MessageSvcPbSendMsg.Response -> {
|
||||
if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) {
|
||||
return when (step) {
|
||||
SendMessageStep.FIRST -> {
|
||||
sendMessage(originalMessage, transformedMessage, SendMessageStep.LONG_MESSAGE)
|
||||
}
|
||||
SendMessageStep.LONG_MESSAGE -> {
|
||||
sendMessage(originalMessage, transformedMessage, SendMessageStep.FRAGMENTED)
|
||||
|
||||
}
|
||||
else -> {
|
||||
throw MessageTooLargeException(
|
||||
group,
|
||||
originalMessage,
|
||||
finalMessage,
|
||||
"Message '${finalMessage.content.take(10)}' is too large."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
check(resp is MessageSvcPbSendMsg.Response.SUCCESS) {
|
||||
"Send group message failed: $resp"
|
||||
}
|
||||
}
|
||||
is MusicSharePacket.Response -> {
|
||||
resp.pkg.checkSuccess("send group music share")
|
||||
|
||||
source = constructSourceFromMusicShareResponse(finalMessage, resp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
check(source != null) {
|
||||
"Internal error: source is not initialized"
|
||||
}
|
||||
|
||||
try {
|
||||
source!!.ensureSequenceIdAvailable()
|
||||
} catch (e: Exception) {
|
||||
bot.network.logger.warning(
|
||||
"Timeout awaiting sequenceId for group message(${finalMessage.content.take(10)}). Some features may not work properly",
|
||||
e
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
return MessageReceipt(source!!, contact)
|
||||
}
|
||||
}
|
||||
|
||||
fun sendMessageMultiProtocol(
|
||||
client: QQAndroidClient,
|
||||
message: MessageChain,
|
||||
fragmented: Boolean,
|
||||
sourceCallback: (OnlineMessageSource.Outgoing) -> Unit
|
||||
): List<OutgoingPacket> {
|
||||
message.takeSingleContent<MusicShare>()?.let { musicShare ->
|
||||
return listOf(
|
||||
MusicSharePacket(
|
||||
client, musicShare, contact.id,
|
||||
targetKind = if (isToGroup) MessageSourceKind.GROUP else MessageSourceKind.FRIEND // always FRIEND
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return messageSvcSendMessage(client, contact, message, fragmented, sourceCallback)
|
||||
}
|
||||
|
||||
abstract val messageSvcSendMessage: (
|
||||
client: QQAndroidClient,
|
||||
contact: C,
|
||||
message: MessageChain,
|
||||
fragmented: Boolean,
|
||||
sourceCallback: (OnlineMessageSource.Outgoing) -> Unit,
|
||||
) -> List<OutgoingPacket>
|
||||
|
||||
abstract suspend fun constructSourceFromMusicShareResponse(
|
||||
finalMessage: MessageChain,
|
||||
response: MusicSharePacket.Response
|
||||
): OnlineMessageSource.Outgoing
|
||||
|
||||
open suspend fun uploadLongMessageHighway(
|
||||
chain: MessageChain
|
||||
): String = with(contact) {
|
||||
return MiraiImpl.uploadMessageHighway(
|
||||
bot, this@SendMessageHandler,
|
||||
listOf(
|
||||
ForwardMessage.Node(
|
||||
senderId = bot.id,
|
||||
time = currentTimeSeconds().toInt(),
|
||||
messageChain = chain,
|
||||
senderName = bot.nick
|
||||
)
|
||||
),
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
open suspend fun postTransformActions(chain: MessageChain) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* - [ForwardMessage] -> [ForwardMessageInternal] (by uploading through highway)
|
||||
* - ... any others for future
|
||||
*/
|
||||
internal suspend fun <C : Contact> SendMessageHandler<C>.transformSpecialMessages(message: Message): MessageChain {
|
||||
return message.takeSingleContent<ForwardMessage>()?.let { forward ->
|
||||
check(forward.nodeList.size <= 200) {
|
||||
throw MessageTooLargeException(
|
||||
contact, forward, forward,
|
||||
"ForwardMessage allows up to 200 nodes, but found ${forward.nodeList.size}"
|
||||
)
|
||||
}
|
||||
|
||||
val resId = MiraiImpl.uploadMessageHighway(
|
||||
bot = contact.bot,
|
||||
sendMessageHandler = this,
|
||||
message = forward.nodeList,
|
||||
isLong = false,
|
||||
)
|
||||
RichMessage.forwardMessage(
|
||||
resId = resId,
|
||||
timeSeconds = currentTimeSeconds(),
|
||||
forwardMessage = forward,
|
||||
)
|
||||
}?.toMessageChain() ?: message.toMessageChain()
|
||||
}
|
||||
|
||||
/**
|
||||
* Might be recalled with [transformedMessage] `is` [LongMessageInternal] if length estimation failed ([sendMessagePacket])
|
||||
*/
|
||||
internal suspend fun <C : Contact> SendMessageHandler<C>.sendMessage(
|
||||
originalMessage: Message,
|
||||
transformedMessage: Message,
|
||||
step: SendMessageStep,
|
||||
): MessageReceipt<C> { // Result cannot be in interface.
|
||||
val chain = transformSpecialMessages(transformedMessage)
|
||||
.convertToLongMessageIfNeeded(step)
|
||||
|
||||
chain.findIsInstance<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
|
||||
postTransformActions(chain)
|
||||
|
||||
return sendMessagePacket(originalMessage, transformedMessage, chain, step)
|
||||
}
|
||||
|
||||
internal sealed class UserSendMessageHandler<C : AbstractUser>(
|
||||
override val contact: C,
|
||||
) : SendMessageHandler<C>() {
|
||||
override val senderName: String get() = bot.nick
|
||||
|
||||
override suspend fun constructSourceFromMusicShareResponse(
|
||||
finalMessage: MessageChain,
|
||||
response: MusicSharePacket.Response
|
||||
): OnlineMessageSource.Outgoing {
|
||||
throw UnsupportedOperationException("Sending MusicShare to user is not yet supported")
|
||||
}
|
||||
}
|
||||
|
||||
internal class FriendSendMessageHandler(
|
||||
contact: FriendImpl,
|
||||
) : UserSendMessageHandler<FriendImpl>(contact) {
|
||||
override val messageSvcSendMessage: (client: QQAndroidClient, contact: FriendImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSource.Outgoing) -> Unit) -> List<OutgoingPacket> =
|
||||
MessageSvcPbSendMsg::createToFriend
|
||||
}
|
||||
|
||||
internal class StrangerSendMessageHandler(
|
||||
contact: StrangerImpl,
|
||||
) : UserSendMessageHandler<StrangerImpl>(contact) {
|
||||
override val messageSvcSendMessage: (client: QQAndroidClient, contact: StrangerImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSource.Outgoing) -> Unit) -> List<OutgoingPacket> =
|
||||
MessageSvcPbSendMsg::createToStranger
|
||||
}
|
||||
|
||||
internal class GroupTempSendMessageHandler(
|
||||
contact: NormalMemberImpl,
|
||||
) : UserSendMessageHandler<NormalMemberImpl>(contact) {
|
||||
override val messageSvcSendMessage: (client: QQAndroidClient, contact: NormalMemberImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSource.Outgoing) -> Unit) -> List<OutgoingPacket> =
|
||||
MessageSvcPbSendMsg::createToTemp
|
||||
}
|
||||
|
||||
internal class GroupSendMessageHandler(
|
||||
override val contact: GroupImpl,
|
||||
) : SendMessageHandler<GroupImpl>() {
|
||||
override val messageSvcSendMessage: (client: QQAndroidClient, contact: GroupImpl, message: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSource.Outgoing) -> Unit) -> List<OutgoingPacket> =
|
||||
MessageSvcPbSendMsg::createToGroup
|
||||
override val senderName: String
|
||||
get() = contact.botAsMember.nameCardOrNick
|
||||
|
||||
override suspend fun postTransformActions(chain: MessageChain) {
|
||||
chain.asSequence().filterIsInstance<FriendImage>().forEach { image ->
|
||||
contact.updateFriendImageForGroupMessage(image)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun constructSourceFromMusicShareResponse(
|
||||
finalMessage: MessageChain,
|
||||
response: MusicSharePacket.Response
|
||||
): OnlineMessageSource.Outgoing {
|
||||
|
||||
val receipt: OnlinePushPbPushGroupMsg.SendGroupMessageReceipt =
|
||||
nextEventOrNull(3000) { it.fromAppId == 3116 }
|
||||
?: OnlinePushPbPushGroupMsg.SendGroupMessageReceipt.EMPTY
|
||||
|
||||
return OnlineMessageSourceToGroupImpl(
|
||||
contact,
|
||||
internalIds = intArrayOf(receipt.messageRandom),
|
||||
providedSequenceIds = intArrayOf(receipt.sequenceId),
|
||||
sender = bot,
|
||||
target = contact,
|
||||
time = currentTimeSeconds().toInt(),
|
||||
originalMessage = finalMessage
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Ensures server holds the cache
|
||||
*/
|
||||
private suspend fun GroupImpl.updateFriendImageForGroupMessage(image: FriendImage) {
|
||||
bot.network.run {
|
||||
ImgStore.GroupPicUp(
|
||||
bot.client,
|
||||
uin = bot.id,
|
||||
groupCode = id,
|
||||
md5 = image.md5,
|
||||
size = if (image is OnlineFriendImageImpl) image.delegate.fileLen else 0
|
||||
).sendAndExpect<ImgStore.GroupPicUp.Response>()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -20,14 +20,17 @@ package net.mamoe.mirai.internal.contact
|
||||
import kotlinx.atomicfu.AtomicInt
|
||||
import kotlinx.atomicfu.atomic
|
||||
import net.mamoe.mirai.LowLevelApi
|
||||
import net.mamoe.mirai.contact.Stranger
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.data.FriendInfoImpl
|
||||
import net.mamoe.mirai.data.StrangerInfo
|
||||
import net.mamoe.mirai.event.events.StrangerMessagePostSendEvent
|
||||
import net.mamoe.mirai.event.events.StrangerMessagePreSendEvent
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.message.OnlineMessageSourceToStrangerImpl
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.list.StrangerList
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.isContentEmpty
|
||||
import net.mamoe.mirai.utils.cast
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@ -75,16 +78,20 @@ internal class StrangerImpl(
|
||||
}
|
||||
}
|
||||
|
||||
private val handler by lazy { StrangerSendMessageHandler(this) }
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
override suspend fun sendMessage(message: Message): MessageReceipt<Stranger> {
|
||||
require(!message.isContentEmpty()) { "message is empty" }
|
||||
return sendMessageImpl(
|
||||
message,
|
||||
strangerReceiptConstructor = { MessageReceipt(it, this) },
|
||||
tReceiptConstructor = { MessageReceipt(it, this) }
|
||||
).also {
|
||||
logMessageSent(message)
|
||||
}
|
||||
return asFriendOrNull()?.sendMessage(message)?.convert()
|
||||
?: handler.sendMessageImpl<Stranger>(
|
||||
message = message,
|
||||
preSendEventConstructor = ::StrangerMessagePreSendEvent,
|
||||
postSendEventConstructor = ::StrangerMessagePostSendEvent.cast()
|
||||
)
|
||||
}
|
||||
|
||||
private fun MessageReceipt<User>.convert(): MessageReceipt<StrangerImpl> {
|
||||
return MessageReceipt(OnlineMessageSourceToStrangerImpl(source, this@StrangerImpl), this@StrangerImpl)
|
||||
}
|
||||
|
||||
override fun toString(): String = "Stranger($id)"
|
||||
|
@ -58,12 +58,14 @@ internal suspend fun <T : User> Friend.sendMessageImpl(
|
||||
|
||||
chain.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
|
||||
|
||||
lateinit var source: OnlineMessageSourceToFriendImpl
|
||||
val result = bot.network.runCatching {
|
||||
MessageSvcPbSendMsg.createToFriend(
|
||||
bot.client,
|
||||
this@sendMessageImpl,
|
||||
chain
|
||||
chain,
|
||||
false
|
||||
) {
|
||||
source = it
|
||||
}.forEach { packet ->
|
||||
@ -116,12 +118,16 @@ internal suspend fun <T : User> Stranger.sendMessageImpl(
|
||||
bot.client,
|
||||
this@sendMessageImpl,
|
||||
chain,
|
||||
false,
|
||||
) {
|
||||
source = it
|
||||
}.sendAndExpect<MessageSvcPbSendMsg.Response>().let {
|
||||
check(it is MessageSvcPbSendMsg.Response.SUCCESS) {
|
||||
"Send stranger message failed: $it"
|
||||
}
|
||||
}.forEach { pk ->
|
||||
pk.sendAndExpect<net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg.Response>()
|
||||
.let {
|
||||
kotlin.check(it is net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg.Response.SUCCESS) {
|
||||
"Send temp message failed: $it"
|
||||
}
|
||||
}
|
||||
}
|
||||
strangerReceiptConstructor(source)
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.internal.message
|
||||
|
||||
import net.mamoe.mirai.message.data.*
|
||||
@ -19,7 +21,24 @@ internal object ForceAsLongMessage : MessageMetadata, ConstrainSingle, InternalF
|
||||
AbstractMessageKey<ForceAsLongMessage>({ it.safeCast() }) {
|
||||
override val key: MessageKey<ForceAsLongMessage> get() = this
|
||||
|
||||
override fun toString(): String = "ForceLongMessage"
|
||||
override fun toString(): String = ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制不发 long
|
||||
*/
|
||||
internal object DontAsLongMessage : MessageMetadata, ConstrainSingle, InternalFlagOnlyMessage,
|
||||
AbstractMessageKey<DontAsLongMessage>({ it.safeCast() }) {
|
||||
override val key: MessageKey<DontAsLongMessage> get() = this
|
||||
|
||||
override fun toString(): String = ""
|
||||
}
|
||||
|
||||
internal object IgnoreLengthCheck : MessageMetadata, ConstrainSingle, InternalFlagOnlyMessage,
|
||||
AbstractMessageKey<IgnoreLengthCheck>({ it.safeCast() }) {
|
||||
override val key: MessageKey<IgnoreLengthCheck> get() = this
|
||||
|
||||
override fun toString(): String = ""
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,7 +33,7 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
internal interface MessageSourceInternal {
|
||||
@Transient
|
||||
val sequenceIds: IntArray
|
||||
val sequenceIds: IntArray // ids
|
||||
|
||||
@Transient
|
||||
val internalIds: IntArray // randomId
|
||||
|
@ -103,6 +103,12 @@ internal class OnlineMessageSourceToStrangerImpl(
|
||||
override val sender: Bot,
|
||||
override val target: Stranger
|
||||
) : OnlineMessageSource.Outgoing.ToStranger(), MessageSourceInternal {
|
||||
|
||||
constructor(
|
||||
delegate: OnlineMessageSource.Outgoing,
|
||||
target: Stranger
|
||||
) : this(delegate.ids, delegate.internalIds, delegate.time, delegate.originalMessage, delegate.sender, target)
|
||||
|
||||
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceToStranger")
|
||||
|
||||
override val bot: Bot
|
||||
@ -123,6 +129,11 @@ internal class OnlineMessageSourceToTempImpl(
|
||||
override val sender: Bot,
|
||||
override val target: Member
|
||||
) : OnlineMessageSource.Outgoing.ToTemp(), MessageSourceInternal {
|
||||
constructor(
|
||||
delegate: OnlineMessageSource.Outgoing,
|
||||
target: Member
|
||||
) : this(delegate.ids, delegate.internalIds, delegate.time, delegate.originalMessage, delegate.sender, target)
|
||||
|
||||
object Serializer : MessageSourceSerializerImpl("OnlineMessageSourceToTemp")
|
||||
|
||||
override val bot: Bot
|
||||
|
@ -89,8 +89,8 @@ internal enum class ResourceKind(
|
||||
PRIVATE_VOICE("private voice"),
|
||||
GROUP_VOICE("group voice"),
|
||||
|
||||
GROUP_LONG_MESSAGE("group long message"),
|
||||
GROUP_FORWARD_MESSAGE("group forward message"),
|
||||
LONG_MESSAGE("long message"),
|
||||
FORWARD_MESSAGE("forward message"),
|
||||
;
|
||||
|
||||
override fun toString(): String = display
|
||||
|
@ -12,9 +12,8 @@
|
||||
package net.mamoe.mirai.internal.network.protocol.packet.chat
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.contact.groupCode
|
||||
import net.mamoe.mirai.internal.contact.SendMessageHandler
|
||||
import net.mamoe.mirai.internal.message.toRichTextElems
|
||||
import net.mamoe.mirai.internal.network.Packet
|
||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||
@ -45,15 +44,16 @@ internal class MessageValidationData(
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Collection<ForwardMessage.INode>.calculateValidationDataForGroup(
|
||||
internal fun Collection<ForwardMessage.INode>.calculateValidationData(
|
||||
sequenceId: Int,
|
||||
random: Int,
|
||||
targetGroup: Group,
|
||||
handler: SendMessageHandler<*>,
|
||||
): MessageValidationData {
|
||||
val msgList = map { chain ->
|
||||
MsgComm.Msg(
|
||||
msgHead = MsgComm.MsgHead(
|
||||
fromUin = chain.senderId,
|
||||
toUin = handler.targetUserUin ?: 0,
|
||||
msgSeq = sequenceId,
|
||||
msgTime = chain.time,
|
||||
msgUid = 0x01000000000000000L or random.toLongUnsigned(),
|
||||
@ -62,16 +62,13 @@ internal fun Collection<ForwardMessage.INode>.calculateValidationDataForGroup(
|
||||
msgId = 1
|
||||
),
|
||||
msgType = 82, // troop
|
||||
groupInfo = MsgComm.GroupInfo(
|
||||
groupCode = targetGroup.groupCode,
|
||||
groupCard = chain.senderName // Cinnamon
|
||||
),
|
||||
groupInfo = handler.groupInfo,
|
||||
isSrcMsg = false
|
||||
),
|
||||
msgBody = ImMsgBody.MsgBody(
|
||||
richText = ImMsgBody.RichText(
|
||||
elems = chain.messageChain.toMessageChain()
|
||||
.toRichTextElems(targetGroup, withGeneralFlags = false).toMutableList()
|
||||
.toRichTextElems(handler.contact, withGeneralFlags = false).toMutableList()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -9,33 +9,3 @@
|
||||
|
||||
package net.mamoe.mirai.internal.network.protocol.packet.chat
|
||||
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.internal.contact.takeSingleContent
|
||||
import net.mamoe.mirai.internal.message.OnlineMessageSourceToGroupImpl
|
||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToGroup
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSourceKind
|
||||
import net.mamoe.mirai.message.data.MusicShare
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
internal object SendMessageMultiProtocol {
|
||||
inline fun createToGroup(
|
||||
client: QQAndroidClient,
|
||||
group: Group,
|
||||
message: MessageChain,
|
||||
fragmented: Boolean,
|
||||
crossinline sourceCallback: (OnlineMessageSourceToGroupImpl) -> Unit
|
||||
): List<OutgoingPacket> {
|
||||
contract { callsInPlace(sourceCallback, InvocationKind.AT_MOST_ONCE) }
|
||||
|
||||
message.takeSingleContent<MusicShare>()?.let { musicShare ->
|
||||
return listOf(MusicSharePacket(client, musicShare, group.id, targetKind = MessageSourceKind.GROUP))
|
||||
}
|
||||
|
||||
return MessageSvcPbSendMsg.createToGroup(client, group, message, fragmented, sourceCallback)
|
||||
}
|
||||
}
|
@ -146,29 +146,55 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
|
||||
* 发送陌生人消息
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
internal fun createToStrangerImpl(
|
||||
internal inline fun createToStrangerImpl(
|
||||
client: QQAndroidClient,
|
||||
target: Stranger,
|
||||
message: MessageChain,
|
||||
source: OnlineMessageSourceToStrangerImpl
|
||||
): OutgoingPacket = buildOutgoingUniPacket(client) {
|
||||
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
|
||||
fragmented: Boolean,
|
||||
source: (OnlineMessageSourceToStrangerImpl) -> Unit
|
||||
): List<OutgoingPacket> {
|
||||
|
||||
///return@buildOutgoingUniPacket
|
||||
writeProtoBuf(
|
||||
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
|
||||
routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = target.uin)),
|
||||
contentHead = MsgComm.ContentHead(pkgNum = 1),
|
||||
msgBody = ImMsgBody.MsgBody(
|
||||
val sequenceIds = AtomicReference<IntArray>()
|
||||
val randIds = AtomicReference<IntArray>()
|
||||
return buildOutgoingMessageCommon(
|
||||
client = client,
|
||||
message = message,
|
||||
fragmentTranslator = {
|
||||
ImMsgBody.MsgBody(
|
||||
richText = ImMsgBody.RichText(
|
||||
elems = message.toRichTextElems(messageTarget = target, withGeneralFlags = true)
|
||||
elems = it.toRichTextElems(messageTarget = target, withGeneralFlags = true)
|
||||
)
|
||||
),
|
||||
msgSeq = source.sequenceIds.single(),
|
||||
msgRand = source.internalIds.single(),
|
||||
syncCookie = client.syncingController.syncCookie ?: byteArrayOf()
|
||||
// msgVia = 1
|
||||
)
|
||||
)
|
||||
},
|
||||
pbSendMsgReq = { msgBody, msgSeq, msgRand, contentHead ->
|
||||
MsgSvc.PbSendMsgReq(
|
||||
routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = target.uin)),
|
||||
contentHead = contentHead,
|
||||
msgBody = msgBody,
|
||||
msgSeq = msgSeq,
|
||||
msgRand = msgRand,
|
||||
syncCookie = client.syncingController.syncCookie ?: byteArrayOf()
|
||||
// msgVia = 1
|
||||
)
|
||||
},
|
||||
sequenceIds = sequenceIds,
|
||||
randIds = randIds,
|
||||
sequenceIdsInitializer = { size ->
|
||||
IntArray(size) { client.nextFriendSeq() }
|
||||
},
|
||||
postInit = {
|
||||
source(
|
||||
OnlineMessageSourceToStrangerImpl(
|
||||
internalIds = randIds.get(),
|
||||
sender = client.bot,
|
||||
target = target,
|
||||
time = currentTimeSeconds().toInt(),
|
||||
sequenceIds = sequenceIds.get(),
|
||||
originalMessage = message
|
||||
)
|
||||
)
|
||||
},
|
||||
doFragmented = fragmented
|
||||
)
|
||||
}
|
||||
|
||||
@ -180,6 +206,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
|
||||
client: QQAndroidClient,
|
||||
targetFriend: Friend,
|
||||
message: MessageChain,
|
||||
fragmented: Boolean,
|
||||
crossinline sourceCallback: (OnlineMessageSourceToFriendImpl) -> Unit
|
||||
): List<OutgoingPacket> {
|
||||
contract {
|
||||
@ -225,7 +252,8 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
|
||||
originalMessage = message
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
doFragmented = fragmented
|
||||
)
|
||||
}
|
||||
/*= buildOutgoingUniPacket(client) {
|
||||
@ -269,6 +297,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
|
||||
client: QQAndroidClient,
|
||||
targetMember: Member,
|
||||
message: MessageChain,
|
||||
fragmented: Boolean,
|
||||
source: OnlineMessageSourceToTempImpl
|
||||
): OutgoingPacket = buildOutgoingUniPacket(client) {
|
||||
writeProtoBuf(
|
||||
@ -439,8 +468,9 @@ internal inline fun MessageSvcPbSendMsg.createToTemp(
|
||||
client: QQAndroidClient,
|
||||
member: Member,
|
||||
message: MessageChain,
|
||||
fragmented: Boolean,
|
||||
crossinline sourceCallback: (OnlineMessageSourceToTempImpl) -> Unit
|
||||
): OutgoingPacket {
|
||||
): List<OutgoingPacket> {
|
||||
contract {
|
||||
callsInPlace(sourceCallback, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
@ -457,33 +487,27 @@ internal inline fun MessageSvcPbSendMsg.createToTemp(
|
||||
client,
|
||||
member,
|
||||
message,
|
||||
fragmented,
|
||||
source
|
||||
)
|
||||
).let { listOf(it) }
|
||||
}
|
||||
|
||||
internal inline fun MessageSvcPbSendMsg.createToStranger(
|
||||
client: QQAndroidClient,
|
||||
stranger: Stranger,
|
||||
message: MessageChain,
|
||||
fragmented: Boolean,
|
||||
crossinline sourceCallback: (OnlineMessageSourceToStrangerImpl) -> Unit
|
||||
): OutgoingPacket {
|
||||
): List<OutgoingPacket> {
|
||||
contract {
|
||||
callsInPlace(sourceCallback, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
val source = OnlineMessageSourceToStrangerImpl(
|
||||
internalIds = intArrayOf(Random.nextInt().absoluteValue),
|
||||
sender = client.bot,
|
||||
target = stranger,
|
||||
time = currentTimeSeconds().toInt(),
|
||||
sequenceIds = intArrayOf(client.atomicNextMessageSequenceId()),
|
||||
originalMessage = message
|
||||
)
|
||||
sourceCallback(source)
|
||||
return createToStrangerImpl(
|
||||
client,
|
||||
stranger,
|
||||
message,
|
||||
source
|
||||
fragmented,
|
||||
sourceCallback
|
||||
)
|
||||
}
|
||||
|
||||
@ -491,6 +515,7 @@ internal inline fun MessageSvcPbSendMsg.createToFriend(
|
||||
client: QQAndroidClient,
|
||||
qq: Friend,
|
||||
message: MessageChain,
|
||||
fragmented: Boolean,
|
||||
crossinline sourceCallback: (OnlineMessageSourceToFriendImpl) -> Unit
|
||||
): List<OutgoingPacket> {
|
||||
contract {
|
||||
@ -500,6 +525,7 @@ internal inline fun MessageSvcPbSendMsg.createToFriend(
|
||||
client,
|
||||
qq,
|
||||
message,
|
||||
fragmented,
|
||||
sourceCallback
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user