mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-08 05:15:03 +08:00
Rewrite Group sendMessageImpl, improve current Highway impls, for music share #889 #682 and further friend message highway #194, #577
This commit is contained in:
parent
66ae897f20
commit
d60006376c
@ -266,16 +266,3 @@ public class XmlMessageBuilder(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// internal runtime value, not serializable
|
||||
internal data class LongMessage internal constructor(override val content: String, val resId: String) :
|
||||
AbstractServiceMessage() {
|
||||
override val serviceId: Int get() = 35
|
||||
|
||||
companion object Key : AbstractPolymorphicMessageKey<ServiceMessage, LongMessage>(ServiceMessage, { it.safeCast() })
|
||||
}
|
||||
|
||||
// internal runtime value, not serializable
|
||||
internal data class ForwardMessageInternal(override val content: String) : AbstractServiceMessage() {
|
||||
override val serviceId: Int get() = 35
|
||||
}
|
@ -24,7 +24,7 @@ import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.internal.contact.*
|
||||
import net.mamoe.mirai.internal.message.*
|
||||
import net.mamoe.mirai.internal.network.highway.HighwayHelper
|
||||
import net.mamoe.mirai.internal.network.highway.Highway
|
||||
import net.mamoe.mirai.internal.network.protocol.data.jce.SvcDevLoginInfo
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.*
|
||||
@ -32,7 +32,6 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.MessageSerializers
|
||||
import net.mamoe.mirai.message.action.Nudge
|
||||
import net.mamoe.mirai.message.data.*
|
||||
@ -690,102 +689,76 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
|
||||
return jsonText?.let { json.decodeFromString(GroupHonorListData.serializer(), it) }
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
@LowLevelApi
|
||||
@MiraiExperimentalApi
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
internal suspend fun lowLevelSendGroupLongOrForwardMessage(
|
||||
internal suspend fun uploadGroupMessageHighway(
|
||||
bot: Bot,
|
||||
groupCode: Long,
|
||||
message: Collection<ForwardMessage.INode>,
|
||||
isLong: Boolean,
|
||||
forwardMessage: ForwardMessage?
|
||||
): MessageReceipt<Group> = with(bot.asQQAndroidBot()) {
|
||||
): String = with(bot.asQQAndroidBot()) {
|
||||
message.forEach {
|
||||
it.messageChain.ensureSequenceIdAvailable()
|
||||
}
|
||||
|
||||
val group = getGroupOrFail(groupCode)
|
||||
|
||||
val time = currentTimeSeconds()
|
||||
val sequenceId = client.atomicNextMessageSequenceId()
|
||||
|
||||
network.run {
|
||||
val data = message.calculateValidationDataForGroup(
|
||||
sequenceId = sequenceId,
|
||||
random = Random.nextInt().absoluteValue,
|
||||
group
|
||||
)
|
||||
val data = message.calculateValidationDataForGroup(
|
||||
sequenceId = sequenceId,
|
||||
random = Random.nextInt().absoluteValue,
|
||||
group
|
||||
)
|
||||
|
||||
val response =
|
||||
MultiMsg.ApplyUp.createForGroupLongMessage(
|
||||
buType = if (isLong) 1 else 2,
|
||||
client = bot.client,
|
||||
messageData = data,
|
||||
dstUin = Mirai.calculateGroupUinByGroupCode(groupCode)
|
||||
).sendAndExpect<MultiMsg.ApplyUp.Response>()
|
||||
val response = network.run {
|
||||
MultiMsg.ApplyUp.createForGroup(
|
||||
buType = if (isLong) 1 else 2,
|
||||
client = bot.client,
|
||||
messageData = data,
|
||||
dstUin = Mirai.calculateGroupUinByGroupCode(groupCode)
|
||||
).sendAndExpect<MultiMsg.ApplyUp.Response>()
|
||||
}
|
||||
|
||||
val resId: String
|
||||
when (response) {
|
||||
is MultiMsg.ApplyUp.Response.MessageTooLarge ->
|
||||
error(
|
||||
"Internal error: message is too large, but this should be handled before sending. "
|
||||
)
|
||||
is MultiMsg.ApplyUp.Response.RequireUpload -> {
|
||||
resId = response.proto.msgResid
|
||||
|
||||
val body = LongMsg.ReqBody(
|
||||
subcmd = 1,
|
||||
platformType = 9,
|
||||
termType = 5,
|
||||
msgUpReq = listOf(
|
||||
LongMsg.MsgUpReq(
|
||||
msgType = 3, // group
|
||||
dstUin = Mirai.calculateGroupUinByGroupCode(groupCode),
|
||||
msgId = 0,
|
||||
msgUkey = response.proto.msgUkey,
|
||||
needCache = 0,
|
||||
storeType = 2,
|
||||
msgContent = data.data
|
||||
)
|
||||
)
|
||||
).toByteArray(LongMsg.ReqBody.serializer())
|
||||
|
||||
HighwayHelper.uploadImageToServers(
|
||||
bot,
|
||||
response.proto.uint32UpIp.zip(response.proto.uint32UpPort),
|
||||
response.proto.msgSig,
|
||||
body.toExternalResource(null),
|
||||
"group long message",
|
||||
27
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isLong) {
|
||||
group.sendMessage(
|
||||
RichMessage.longMessage(
|
||||
brief = message.joinToString(limit = 27) { it.messageChain.contentToString() },
|
||||
resId = resId,
|
||||
timeSeconds = time
|
||||
)
|
||||
val resId: String
|
||||
when (response) {
|
||||
is MultiMsg.ApplyUp.Response.MessageTooLarge ->
|
||||
error(
|
||||
"Internal error: message is too large, but this should be handled before sending. "
|
||||
)
|
||||
} else {
|
||||
checkNotNull(forwardMessage) { "Internal error: forwardMessage is null when sending forward" }
|
||||
group.sendMessage(
|
||||
RichMessage.forwardMessage(
|
||||
resId = resId,
|
||||
timeSeconds = time,
|
||||
// preview = message.take(5).joinToString {
|
||||
// """
|
||||
// <title size="26" color="#777777" maxLines="2" lineSpace="12">${it.message.asMessageChain().joinToString(limit = 10)}</title>
|
||||
// """.trimIndent()
|
||||
// },
|
||||
forwardMessage = forwardMessage,
|
||||
is MultiMsg.ApplyUp.Response.RequireUpload -> {
|
||||
resId = response.proto.msgResid
|
||||
|
||||
val body = LongMsg.ReqBody(
|
||||
subcmd = 1,
|
||||
platformType = 9,
|
||||
termType = 5,
|
||||
msgUpReq = listOf(
|
||||
LongMsg.MsgUpReq(
|
||||
msgType = 3, // group
|
||||
dstUin = Mirai.calculateGroupUinByGroupCode(groupCode),
|
||||
msgId = 0,
|
||||
msgUkey = response.proto.msgUkey,
|
||||
needCache = 0,
|
||||
storeType = 2,
|
||||
msgContent = data.data
|
||||
)
|
||||
)
|
||||
).toByteArray(LongMsg.ReqBody.serializer())
|
||||
|
||||
Highway.uploadResource(
|
||||
bot,
|
||||
response.proto.uint32UpIp.zip(response.proto.uint32UpPort),
|
||||
response.proto.msgSig,
|
||||
body.toExternalResource(null),
|
||||
when (isLong) {
|
||||
true -> "group long message"
|
||||
false -> "group forward message"
|
||||
},
|
||||
27
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return resId
|
||||
}
|
||||
|
||||
|
||||
|
@ -131,7 +131,7 @@ internal class QQAndroidBot constructor(
|
||||
|
||||
internal val EMPTY_BYTE_ARRAY = ByteArray(0)
|
||||
|
||||
internal fun RichMessage.Key.longMessage(brief: String, resId: String, timeSeconds: Long): RichMessage {
|
||||
internal fun RichMessage.Key.longMessage(brief: String, resId: String, timeSeconds: Long): LongMessageInternal {
|
||||
val limited: String = if (brief.length > 30) {
|
||||
brief.take(30) + "…"
|
||||
} else {
|
||||
@ -154,7 +154,7 @@ internal fun RichMessage.Key.longMessage(brief: String, resId: String, timeSecon
|
||||
</msg>
|
||||
""".trimIndent()
|
||||
|
||||
return LongMessage(template, resId)
|
||||
return LongMessageInternal(template, resId)
|
||||
}
|
||||
|
||||
|
||||
|
@ -18,15 +18,11 @@ import net.mamoe.mirai.data.GroupInfo
|
||||
import net.mamoe.mirai.data.MemberInfo
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.internal.MiraiImpl
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.message.*
|
||||
import net.mamoe.mirai.internal.message.firstIsInstanceOrNull
|
||||
import net.mamoe.mirai.internal.network.QQAndroidBotNetworkHandler
|
||||
import net.mamoe.mirai.internal.network.highway.HighwayHelper
|
||||
import net.mamoe.mirai.internal.network.highway.Highway
|
||||
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.createToGroup
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService
|
||||
@ -118,142 +114,13 @@ internal class GroupImpl(
|
||||
require(!message.isContentEmpty()) { "message is empty" }
|
||||
check(!isBotMuted) { throw BotIsBeingMutedException(this) }
|
||||
|
||||
return sendMessageImpl(message, false).also {
|
||||
logMessageSent(message)
|
||||
val chain = broadcastGroupMessagePreSendEvent(message)
|
||||
|
||||
return sendMessageImpl(message, chain, false).also {
|
||||
logMessageSent(chain)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendMessageImpl(message: Message, isForward: Boolean): MessageReceipt<Group> {
|
||||
if (message is MessageChain) {
|
||||
if (message.anyIsInstance<ForwardMessage>()) {
|
||||
return sendMessageImpl(message.singleOrNull() ?: error("ForwardMessage must be standalone"), true)
|
||||
}
|
||||
}
|
||||
|
||||
val forward = message as? ForwardMessage ?: message.castOrNull<MessageChain>()?.findIsInstance<ForwardMessage>()
|
||||
if (forward != null) {
|
||||
check(forward.nodeList.size < 200) {
|
||||
throw MessageTooLargeException(
|
||||
this, forward, forward,
|
||||
"ForwardMessage allows up to 200 nodes, but found ${forward.nodeList.size}"
|
||||
)
|
||||
}
|
||||
|
||||
return MiraiImpl.lowLevelSendGroupLongOrForwardMessage(bot, this.id, forward.nodeList, false, forward)
|
||||
}
|
||||
|
||||
val isLongOrForward = message is LongMessage || message is ForwardMessageInternal
|
||||
val msg: MessageChain = if (!isLongOrForward) {
|
||||
val chain = kotlin.runCatching {
|
||||
GroupMessagePreSendEvent(this, message).broadcast()
|
||||
}.onSuccess {
|
||||
check(!it.isCancelled) {
|
||||
throw EventCancelledException("cancelled by GroupMessagePreSendEvent")
|
||||
}
|
||||
}.getOrElse {
|
||||
throw EventCancelledException("exception thrown when broadcasting GroupMessagePreSendEvent", it)
|
||||
}.message.toMessageChain()
|
||||
|
||||
@Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER")
|
||||
var length = 0
|
||||
|
||||
@Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER") // stupid compiler
|
||||
var imageCnt = 0
|
||||
chain.verityLength(message, this, lengthCallback = {
|
||||
length = it
|
||||
}, imageCntCallback = {
|
||||
imageCnt = it
|
||||
})
|
||||
|
||||
if (length > 702 || imageCnt > 1) { // 阈值为700左右,限制到3的倍数
|
||||
return MiraiImpl.lowLevelSendGroupLongOrForwardMessage(
|
||||
bot,
|
||||
this.id,
|
||||
listOf(
|
||||
ForwardMessage.Node(
|
||||
senderId = bot.id,
|
||||
time = currentTimeSeconds().toInt(),
|
||||
messageChain = chain,
|
||||
senderName = bot.nick
|
||||
)
|
||||
),
|
||||
true, null
|
||||
)
|
||||
}
|
||||
chain
|
||||
} else message.toMessageChain()
|
||||
|
||||
msg.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
|
||||
msg.filterIsInstance<FriendImage>().forEach { image ->
|
||||
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>()
|
||||
}
|
||||
}
|
||||
|
||||
val result = bot.network.runCatching sendMsg@{
|
||||
val source: OnlineMessageSourceToGroupImpl
|
||||
MessageSvcPbSendMsg.createToGroup(
|
||||
bot.client,
|
||||
this@GroupImpl,
|
||||
msg,
|
||||
isForward
|
||||
) {
|
||||
source = it
|
||||
}.sendAndExpect<MessageSvcPbSendMsg.Response>().let {
|
||||
if (!isLongOrForward && it is MessageSvcPbSendMsg.Response.MessageTooLarge) {
|
||||
return@sendMsg MiraiImpl.lowLevelSendGroupLongOrForwardMessage(
|
||||
bot,
|
||||
this@GroupImpl.id,
|
||||
listOf(
|
||||
ForwardMessage.Node(
|
||||
senderId = bot.id,
|
||||
time = currentTimeSeconds().toInt(),
|
||||
messageChain = msg,
|
||||
senderName = bot.nick
|
||||
)
|
||||
),
|
||||
true, null
|
||||
)
|
||||
}
|
||||
check(it is MessageSvcPbSendMsg.Response.SUCCESS) {
|
||||
"Send group message failed: $it"
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
source.ensureSequenceIdAvailable()
|
||||
} catch (e: Exception) {
|
||||
bot.network.logger.warning {
|
||||
"Timeout awaiting sequenceId for group message(${
|
||||
message.contentToString()
|
||||
.take(10)
|
||||
}). Some features may not work properly"
|
||||
}
|
||||
bot.network.logger.warning(e)
|
||||
}
|
||||
|
||||
MessageReceipt(source, this@GroupImpl)
|
||||
}
|
||||
|
||||
result.fold(
|
||||
onSuccess = {
|
||||
GroupMessagePostSendEvent(this, msg, null, it)
|
||||
},
|
||||
onFailure = {
|
||||
GroupMessagePostSendEvent(this, msg, it, null)
|
||||
}
|
||||
).broadcast()
|
||||
|
||||
return result.getOrThrow()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
override suspend fun uploadImage(resource: ExternalResource): Image {
|
||||
if (BeforeImageUploadEvent(this, resource).broadcast().isCancelled) {
|
||||
@ -280,7 +147,7 @@ internal class GroupImpl(
|
||||
.also { ImageUploadEvent.Succeed(this@GroupImpl, resource, it).broadcast() }
|
||||
}
|
||||
is ImgStore.GroupPicUp.Response.RequireUpload -> {
|
||||
HighwayHelper.uploadImageToServers(
|
||||
Highway.uploadResource(
|
||||
bot,
|
||||
response.uploadIpList.zip(response.uploadPortList),
|
||||
response.uKey,
|
||||
@ -304,7 +171,7 @@ internal class GroupImpl(
|
||||
val response: PttStore.GroupPttUp.Response.RequireUpload =
|
||||
PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect()
|
||||
|
||||
HighwayHelper.uploadPttToServers(
|
||||
Highway.uploadPttToServers(
|
||||
bot,
|
||||
response.uploadIpList.zip(response.uploadPortList),
|
||||
resource,
|
||||
|
194
mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt
Normal file
194
mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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.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.GroupMessagePostSendEvent
|
||||
import net.mamoe.mirai.event.events.GroupMessagePreSendEvent
|
||||
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.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.createToGroup
|
||||
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,
|
||||
forceAsLongMessage: Boolean,
|
||||
): MessageReceipt<Group> {
|
||||
val chain = transformedMessage
|
||||
.transformSpecialMessages(this)
|
||||
.convertToLongMessageIfNeeded(originalMessage, forceAsLongMessage, this)
|
||||
|
||||
chain.findIsInstance<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
|
||||
chain.asSequence().filterIsInstance<FriendImage>().forEach { image ->
|
||||
updateFriendImageForGroupMessage(image)
|
||||
}
|
||||
|
||||
return sendMessagePacket(
|
||||
originalMessage,
|
||||
chain,
|
||||
allowResendAsLongMessage = transformedMessage.takeSingleContent<LongMessageInternal>() == null
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called only in 'public' apis.
|
||||
*/
|
||||
internal suspend fun GroupImpl.broadcastGroupMessagePreSendEvent(message: Message): MessageChain {
|
||||
return kotlin.runCatching {
|
||||
GroupMessagePreSendEvent(this, message).broadcast()
|
||||
}.onSuccess {
|
||||
check(!it.isCancelled) {
|
||||
throw EventCancelledException("cancelled by GroupMessagePreSendEvent")
|
||||
}
|
||||
}.getOrElse {
|
||||
throw EventCancelledException("exception thrown when broadcasting GroupMessagePreSendEvent", it)
|
||||
}.message.toMessageChain()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* - [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()
|
||||
}
|
||||
|
||||
/**
|
||||
* Final process
|
||||
*/
|
||||
private suspend fun GroupImpl.sendMessagePacket(
|
||||
originalMessage: Message,
|
||||
finalMessage: MessageChain,
|
||||
allowResendAsLongMessage: Boolean,
|
||||
): MessageReceipt<Group> {
|
||||
val group = this
|
||||
|
||||
val result = bot.network.runCatching sendMsg@{
|
||||
val source: OnlineMessageSourceToGroupImpl
|
||||
MessageSvcPbSendMsg.createToGroup(bot.client, group, finalMessage) {
|
||||
source = it
|
||||
}.sendAndExpect<MessageSvcPbSendMsg.Response>().let { resp ->
|
||||
if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) {
|
||||
if (allowResendAsLongMessage) {
|
||||
return@sendMsg sendMessageImpl(originalMessage, finalMessage, true)
|
||||
} 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"
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
MessageReceipt(source, group)
|
||||
}
|
||||
|
||||
GroupMessagePostSendEvent(this, finalMessage, result.exceptionOrNull(), result.getOrNull()).broadcast()
|
||||
|
||||
return result.getOrThrow()
|
||||
}
|
||||
|
||||
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(
|
||||
originalMessage: Message,
|
||||
forceAsLongMessage: Boolean,
|
||||
groupImpl: GroupImpl,
|
||||
): MessageChain {
|
||||
if (forceAsLongMessage || this.shouldSendAsLongMessage(originalMessage, groupImpl)) {
|
||||
val resId = groupImpl.uploadGroupLongMessageHighway(this)
|
||||
|
||||
return this + RichMessage.longMessage(
|
||||
brief = takeContent(27),
|
||||
resId = resId,
|
||||
timeSeconds = currentTimeSeconds()
|
||||
) // LongMessageInternal replaces all contents and preserves metadata
|
||||
}
|
||||
|
||||
return 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>()
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageChain.shouldSendAsLongMessage(originalMessage: Message, target: Contact): Boolean {
|
||||
val length = verityLength(originalMessage, target)
|
||||
|
||||
return length > 700 || countImages() > 1
|
||||
}
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.internal.asQQAndroidBot
|
||||
import net.mamoe.mirai.internal.message.LongMessageInternal
|
||||
import net.mamoe.mirai.internal.message.OnlineMessageSourceToFriendImpl
|
||||
import net.mamoe.mirai.internal.message.OnlineMessageSourceToStrangerImpl
|
||||
import net.mamoe.mirai.internal.message.ensureSequenceIdAvailable
|
||||
@ -26,6 +27,7 @@ import net.mamoe.mirai.internal.utils.estimateLength
|
||||
import net.mamoe.mirai.message.*
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.cast
|
||||
import net.mamoe.mirai.utils.castOrNull
|
||||
import net.mamoe.mirai.utils.verbose
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
@ -52,7 +54,7 @@ internal suspend fun <T : User> Friend.sendMessageImpl(
|
||||
}.getOrElse {
|
||||
throw EventCancelledException("exception thrown when broadcasting FriendMessagePreSendEvent", it)
|
||||
}.message.toMessageChain()
|
||||
chain.verityLength(message, this, {}, {})
|
||||
chain.verityLength(message, this)
|
||||
|
||||
chain.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
|
||||
@ -104,7 +106,7 @@ internal suspend fun <T : User> Stranger.sendMessageImpl(
|
||||
}.getOrElse {
|
||||
throw EventCancelledException("exception thrown when broadcasting StrangerMessagePreSendEvent", it)
|
||||
}.message.toMessageChain()
|
||||
chain.verityLength(message, this, {}, {})
|
||||
chain.verityLength(message, this)
|
||||
|
||||
chain.firstIsInstanceOrNull<QuoteReply>()?.source?.ensureSequenceIdAvailable()
|
||||
|
||||
@ -138,32 +140,27 @@ internal suspend fun <T : User> Stranger.sendMessageImpl(
|
||||
}
|
||||
|
||||
internal fun Contact.logMessageSent(message: Message) {
|
||||
if (message !is LongMessage) {
|
||||
if (message !is LongMessageInternal) {
|
||||
bot.logger.verbose("$this <- $message".replaceMagicCodes())
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun MessageChain.verityLength(
|
||||
message: Message, target: Contact,
|
||||
lengthCallback: (Int) -> Unit,
|
||||
imageCntCallback: (Int) -> Unit
|
||||
) {
|
||||
contract {
|
||||
callsInPlace(lengthCallback, InvocationKind.EXACTLY_ONCE)
|
||||
callsInPlace(imageCntCallback, InvocationKind.EXACTLY_ONCE)
|
||||
}
|
||||
internal fun MessageChain.countImages(): Int = this.count { it is Image }
|
||||
|
||||
internal fun MessageChain.verityLength(
|
||||
originalMessage: Message, target: Contact,
|
||||
): Int {
|
||||
val chain = this
|
||||
val length = estimateLength(target, 15001)
|
||||
lengthCallback(length)
|
||||
if (length > 15000 || count { it is Image }.apply { imageCntCallback(this) } > 50) {
|
||||
if (length > 15000 || countImages() > 50) {
|
||||
throw MessageTooLargeException(
|
||||
target, message, this,
|
||||
target, originalMessage, this,
|
||||
"message(${
|
||||
chain.joinToString("", limit = 10)
|
||||
}) is too large. Allow up to 50 images or 5000 chars"
|
||||
)
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
@Suppress("RemoveRedundantQualifierName") // compiler bug
|
||||
@ -214,3 +211,11 @@ internal fun String.applyCharMapping() = buildString(capacity = this.length) {
|
||||
|
||||
internal fun String.replaceMagicCodes(): String = this
|
||||
.applyCharMapping()
|
||||
|
||||
|
||||
internal fun Message.takeContent(length: Int): String =
|
||||
this.toMessageChain().joinToString("", limit = length) { it.content }
|
||||
|
||||
internal inline fun <reified T : MessageContent> Message.takeSingleContent(): T? {
|
||||
return this as? T ?: this.castOrNull<MessageChain>()?.findIsInstance()
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2019-2021 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.message
|
||||
|
||||
import net.mamoe.mirai.message.data.AbstractPolymorphicMessageKey
|
||||
import net.mamoe.mirai.message.data.AbstractServiceMessage
|
||||
import net.mamoe.mirai.message.data.ServiceMessage
|
||||
import net.mamoe.mirai.utils.safeCast
|
||||
|
||||
// internal runtime value, not serializable
|
||||
internal data class LongMessageInternal internal constructor(override val content: String, val resId: String) :
|
||||
AbstractServiceMessage() {
|
||||
override val serviceId: Int get() = 35
|
||||
|
||||
companion object Key :
|
||||
AbstractPolymorphicMessageKey<ServiceMessage, LongMessageInternal>(ServiceMessage, { it.safeCast() })
|
||||
}
|
||||
|
||||
// internal runtime value, not serializable
|
||||
internal data class ForwardMessageInternal(override val content: String) : AbstractServiceMessage() {
|
||||
override val serviceId: Int get() = 35
|
||||
|
||||
companion object Key :
|
||||
AbstractPolymorphicMessageKey<ServiceMessage, ForwardMessageInternal>(ServiceMessage, { it.safeCast() })
|
||||
}
|
@ -72,7 +72,7 @@ internal fun MessageChain.toRichTextElems(
|
||||
)
|
||||
transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN)
|
||||
}
|
||||
is LongMessage -> {
|
||||
is LongMessageInternal -> {
|
||||
check(longTextResId == null) { "There must be no more than one LongMessage element in the message chain" }
|
||||
elements.add(
|
||||
ImMsgBody.Elem(
|
||||
@ -375,7 +375,7 @@ private fun MessageChain.cleanupRubbishMessageElements(): MessageChain {
|
||||
return buildMessageChain(initialSize = this.count()) {
|
||||
this@cleanupRubbishMessageElements.forEach { element ->
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
if (last is LongMessage && element is PlainText) {
|
||||
if (last is LongMessageInternal && element is PlainText) {
|
||||
if (element == UNSUPPORTED_MERGED_MESSAGE_PLAIN) {
|
||||
previousLast = last
|
||||
last = element
|
||||
@ -535,7 +535,7 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(
|
||||
1 -> @Suppress("DEPRECATION_ERROR")
|
||||
list.add(SimpleServiceMessage(1, content))
|
||||
/**
|
||||
* [LongMessage], [ForwardMessage]
|
||||
* [LongMessageInternal], [ForwardMessage]
|
||||
*/
|
||||
35 -> {
|
||||
val resId = this.firstIsInstanceOrNull<ImMsgBody.GeneralFlags>()?.longTextResid
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2019-2021 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.network.highway
|
||||
|
||||
import kotlinx.io.core.Closeable
|
||||
import net.mamoe.mirai.utils.runBIO
|
||||
import net.mamoe.mirai.utils.withUse
|
||||
import java.io.InputStream
|
||||
|
||||
internal class ChunkedFlowSession<T>(
|
||||
private val input: InputStream,
|
||||
private val buffer: ByteArray,
|
||||
private val mapper: (buffer: ByteArray, size: Int, offset: Long) -> T
|
||||
) : Closeable {
|
||||
override fun close() {
|
||||
input.close()
|
||||
}
|
||||
|
||||
private var offset = 0L
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
internal suspend inline fun useAll(crossinline block: suspend (T) -> Unit) = withUse {
|
||||
runBIO {
|
||||
while (true) {
|
||||
val size = input.read(buffer)
|
||||
if (size == -1) return@runBIO
|
||||
block(mapper(buffer, size, offset))
|
||||
offset += size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,6 @@ import io.ktor.http.*
|
||||
import io.ktor.http.content.*
|
||||
import io.ktor.utils.io.*
|
||||
import io.ktor.utils.io.jvm.javaio.*
|
||||
import kotlinx.coroutines.InternalCoroutinesApi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
@ -34,7 +33,6 @@ import net.mamoe.mirai.internal.utils.toIpV4AddressString
|
||||
import net.mamoe.mirai.utils.*
|
||||
import java.io.InputStream
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.measureTime
|
||||
|
||||
|
||||
@ -83,10 +81,9 @@ internal suspend fun HttpClient.postImage(
|
||||
} == HttpStatusCode.OK
|
||||
|
||||
|
||||
internal object HighwayHelper {
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
@OptIn(ExperimentalTime::class)
|
||||
suspend fun uploadImageToServers(
|
||||
internal object Highway {
|
||||
|
||||
suspend fun uploadResource(
|
||||
bot: QQAndroidBot,
|
||||
servers: List<Pair<Int, Int>>,
|
||||
uKey: ByteArray,
|
||||
@ -104,7 +101,7 @@ internal object HighwayHelper {
|
||||
}
|
||||
|
||||
val time = measureTime {
|
||||
uploadImage(
|
||||
uploadResourceImpl(
|
||||
client = bot.client,
|
||||
serverIp = ip,
|
||||
serverPort = port,
|
||||
@ -120,9 +117,7 @@ internal object HighwayHelper {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
internal suspend fun uploadImage(
|
||||
private suspend fun uploadResourceImpl(
|
||||
client: QQAndroidClient,
|
||||
serverIp: String,
|
||||
serverPort: Int,
|
||||
@ -213,30 +208,6 @@ internal object HighwayHelper {
|
||||
}
|
||||
}
|
||||
|
||||
internal class ChunkedFlowSession<T>(
|
||||
private val input: InputStream,
|
||||
private val buffer: ByteArray,
|
||||
private val mapper: (buffer: ByteArray, size: Int, offset: Long) -> T
|
||||
) : Closeable {
|
||||
override fun close() {
|
||||
input.close()
|
||||
}
|
||||
|
||||
private var offset = 0L
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
internal suspend inline fun useAll(crossinline block: suspend (T) -> Unit) = withUse {
|
||||
runBIO {
|
||||
while (true) {
|
||||
val size = input.read(buffer)
|
||||
if (size == -1) return@runBIO
|
||||
block(mapper(buffer, size, offset))
|
||||
offset += size
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun createImageDataPacketSequence(
|
||||
// RequestDataTrans
|
@ -102,7 +102,7 @@ internal class MultiMsg {
|
||||
if (PacketLogger.isEnabled) {
|
||||
return _miraiContentToString()
|
||||
}
|
||||
return "MultiMsg.ApplyUp.Response.RequireUpload(proto=$proto)"
|
||||
return "MultiMsg.ApplyUp.Response.RequireUpload"
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ internal class MultiMsg {
|
||||
}
|
||||
|
||||
// captured from group
|
||||
fun createForGroupLongMessage(
|
||||
fun createForGroup(
|
||||
buType: Int,
|
||||
client: QQAndroidClient,
|
||||
messageData: MessageValidationData,
|
||||
|
@ -295,7 +295,6 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
|
||||
client: QQAndroidClient,
|
||||
targetGroup: Group,
|
||||
message: MessageChain,
|
||||
isForward: Boolean,
|
||||
source: OnlineMessageSourceToGroupImpl
|
||||
): 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())
|
||||
@ -333,9 +332,10 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
|
||||
msgRand = source.internalIds.single(),
|
||||
syncCookie = EMPTY_BYTE_ARRAY,
|
||||
msgVia = 1,
|
||||
msgCtrl = if (isForward) MsgCtrl.MsgCtrl(
|
||||
msgFlag = 4
|
||||
) else null
|
||||
msgCtrl =
|
||||
if (message[ForwardMessageInternal] != null)
|
||||
MsgCtrl.MsgCtrl(msgFlag = 4)
|
||||
else null
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -429,7 +429,6 @@ internal inline fun MessageSvcPbSendMsg.createToGroup(
|
||||
client: QQAndroidClient,
|
||||
group: Group,
|
||||
message: MessageChain,
|
||||
isForward: Boolean,
|
||||
crossinline sourceCallback: (OnlineMessageSourceToGroupImpl) -> Unit
|
||||
): OutgoingPacket {
|
||||
contract {
|
||||
@ -457,7 +456,6 @@ internal inline fun MessageSvcPbSendMsg.createToGroup(
|
||||
client,
|
||||
group,
|
||||
message,
|
||||
isForward,
|
||||
source
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user