Support long message refinement, close #692

This commit is contained in:
Him188 2021-02-02 14:51:29 +08:00
parent 1768872bab
commit de8e6469e0
11 changed files with 215 additions and 40 deletions

View File

@ -57,6 +57,7 @@ internal class QQAndroidBot constructor(
configuration: BotConfiguration
) : AbstractBot<QQAndroidBotNetworkHandler>(configuration, account.id) {
var client: QQAndroidClient = initClient()
private set
fun initClient(): QQAndroidClient {
client = QQAndroidClient(

View File

@ -22,7 +22,7 @@ import net.mamoe.mirai.internal.network.highway.ChannelKind
import net.mamoe.mirai.internal.network.highway.Highway
import net.mamoe.mirai.internal.network.highway.ResourceKind.PRIVATE_IMAGE
import net.mamoe.mirai.internal.network.highway.postImage
import net.mamoe.mirai.internal.network.highway.tryServers
import net.mamoe.mirai.internal.network.highway.tryServersUpload
import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn
@ -148,7 +148,7 @@ internal abstract class AbstractUser(
)
}.recoverCatchingSuppressed {
// try upload by http on provided servers
tryServers(
tryServersUpload(
bot = bot,
servers = resp.serverIp.zip(resp.serverPort),
resourceSize = resource.size,

View File

@ -192,7 +192,7 @@ internal class GroupImpl(
}.recoverCatchingSuppressed {
when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect()) {
is PttStore.GroupPttUp.Response.RequireUpload -> {
tryServers(
tryServersUpload(
bot,
resp.uploadIpList.zip(resp.uploadPortList),
resource.size,

View File

@ -9,24 +9,111 @@
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
import io.ktor.client.request.*
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.internal.asQQAndroidBot
import net.mamoe.mirai.internal.network.highway.ChannelKind
import net.mamoe.mirai.internal.network.highway.ResourceKind
import net.mamoe.mirai.internal.network.highway.tryDownload
import net.mamoe.mirai.internal.network.highway.tryServersDownload
import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgTransmit
import net.mamoe.mirai.internal.network.protocol.packet.chat.MultiMsg
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.*
// internal runtime value, not serializable
internal data class LongMessageInternal internal constructor(override val content: String, val resId: String) :
AbstractServiceMessage() {
AbstractServiceMessage(), RefinableMessage {
override val serviceId: Int get() = 35
override suspend fun refine(contact: Contact, context: MessageChain): Message {
val bot = contact.bot.asQQAndroidBot()
when (val resp = MultiMsg.ApplyDown(bot.client, 1, resId, 1).sendAndExpect(bot)) {
is MultiMsg.ApplyDown.Response.RequireDownload -> {
val http = Mirai.Http
val origin = resp.origin
val data = if (origin.msgExternInfo?.channelType == 2) {
tryDownload(
bot = bot,
host = "https://ssl.htdata.qq.com",
port = 0,
resourceKind = ResourceKind.LONG_MESSAGE,
channelKind = ChannelKind.HTTP
) { host, port ->
http.get<ByteArray>("$host${origin.thumbDownPara}:$port")
}
} else tryServersDownload(
bot = bot,
servers = origin.uint32DownIp.zip(origin.uint32DownPort),
resourceKind = ResourceKind.LONG_MESSAGE,
channelKind = ChannelKind.HTTP
) { ip, port ->
http.get("http://$ip${origin.thumbDownPara}:$port")
}
val body = data.read {
check(readByte() == 40.toByte()) {
"bad data while MultiMsg.ApplyDown: ${data.toUHexString()}"
}
val headLength = readInt()
val bodyLength = readInt()
discardExact(headLength)
readBytes(bodyLength)
}
val decrypted = TEA.decrypt(body, origin.msgKey)
val longResp =
decrypted.loadAs(LongMsg.RspBody.serializer())
val down = longResp.msgDownRsp.single()
check(down.result == 0) {
"Message download failed, result=${down.result}, resId=${down.msgResid}, msgContent=${down.msgContent.toUHexString()}"
}
val content = down.msgContent.ungzip()
val transmit = content.loadAs(MsgTransmit.PbMultiMsgTransmit.serializer())
val source = context.source
return transmit.msg.toMessageChainNoSource(bot.id, contact.castOrNull<Group>()?.id ?: 0, source.kind)
}
MultiMsg.ApplyDown.Response.MessageTooLarge -> {
error("Message is too large and cannot download")
}
}
}
companion object Key :
AbstractPolymorphicMessageKey<ServiceMessage, LongMessageInternal>(ServiceMessage, { it.safeCast() })
}
// internal runtime value, not serializable
internal data class ForwardMessageInternal(override val content: String) : AbstractServiceMessage() {
internal data class ForwardMessageInternal(override val content: String) : AbstractServiceMessage(), RefinableMessage {
override val serviceId: Int get() = 35
override suspend fun refine(contact: Contact, context: MessageChain): Message {
// val bot = contact.bot.asQQAndroidBot()
// TODO: 2021/2/2 Support forward message refinement
// https://github.com/mamoe/mirai/issues/623
return this
}
companion object Key :
AbstractPolymorphicMessageKey<ServiceMessage, ForwardMessageInternal>(ServiceMessage, { it.safeCast() })
}
internal interface RefinableMessage : SingleMessage {
suspend fun refine(
contact: Contact,
context: MessageChain,
): Message?
}

View File

@ -12,6 +12,7 @@ package net.mamoe.mirai.internal.message
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.joinToMessageChain
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.toVoice
@ -410,4 +411,21 @@ private object ReceiveMessageTransformer {
format,
kotlinx.io.core.String(downPara)
)
}
}
/**
* 解析 [ForwardMessageInternal], [LongMessageInternal]
*/
internal suspend fun MessageChain.refine(contact: Contact): MessageChain {
if (none { it is RefinableMessage }) return this
val builder = MessageChainBuilder(this.size)
for (singleMessage in this) {
if (singleMessage is RefinableMessage) {
val v = singleMessage.refine(contact, this)
if (v != null) builder.add(v)
} else {
builder.add(singleMessage)
}
}
return builder.build()
}

View File

@ -38,6 +38,8 @@ import kotlin.math.roundToInt
import kotlin.time.measureTime
internal object Highway {
@Suppress("ArrayInDataClass")
data class BdhUploadResponse(
var extendInfo: ByteArray? = null,
@ -55,7 +57,7 @@ internal object Highway {
): BdhUploadResponse {
val bdhSession = bot.client.bdhSession.await() // no need to care about timeout. proceed by bot init
return tryServers(
return tryServersUpload(
bot = bot,
servers = if (tryOnce) listOf(bdhSession.ssoAddresses.random()) else bdhSession.ssoAddresses,
resourceSize = resource.size,
@ -113,7 +115,7 @@ internal enum class ChannelKind(
override fun toString(): String = display
}
internal suspend inline fun <reified R> tryServers(
internal suspend inline fun <reified R> tryServersUpload(
bot: QQAndroidBot,
servers: Collection<Pair<Int, Int>>,
resourceSize: Long,
@ -147,6 +149,59 @@ internal suspend inline fun <reified R> tryServers(
resp as R
}
internal suspend inline fun <reified R> tryServersDownload(
bot: QQAndroidBot,
servers: Collection<Pair<Int, Int>>,
resourceKind: ResourceKind,
channelKind: ChannelKind,
crossinline implOnEachServer: suspend (ip: String, port: Int) -> R
) = servers.retryWithServers(
5000,
onFail = { throw IllegalStateException("cannot download $resourceKind, failed on all servers.", it) }
) { ip, port ->
tryUploadImplEach(bot, channelKind, resourceKind, ip, port, implOnEachServer)
}
internal suspend inline fun <reified R> tryDownload(
bot: QQAndroidBot,
host: String,
port: Int,
resourceKind: ResourceKind,
channelKind: ChannelKind,
crossinline implOnEachServer: suspend (ip: String, port: Int) -> R
) = runCatching {
tryUploadImplEach(bot, channelKind, resourceKind, host, port, implOnEachServer)
}.getOrElse { throw IllegalStateException("cannot upload $resourceKind, failed on all servers.", it) }
private suspend inline fun <reified R> tryUploadImplEach(
bot: QQAndroidBot,
channelKind: ChannelKind,
resourceKind: ResourceKind,
host: String,
port: Int,
crossinline implOnEachServer: suspend (ip: String, port: Int) -> R
): R {
bot.network.logger.verbose {
"[${channelKind}] Downloading $resourceKind to ${host}:$port"
}
var resp: R? = null
runCatching {
resp = implOnEachServer(host, port)
}.onFailure {
bot.network.logger.verbose {
"[${channelKind}] Downloading $resourceKind to ${host}:$port failed: $it"
}
throw it
}
bot.network.logger.verbose {
"[${channelKind}] Downloading $resourceKind: succeed"
}
return resp as R
}
internal suspend fun ChunkedFlowSession<ByteReadPacket>.sendSequentially(
socket: PlatformSocket,
respCallback: (resp: CSDataHighwayHead.RspDataHighwayHead) -> Unit = {}

View File

@ -16,19 +16,19 @@ import net.mamoe.mirai.internal.utils.io.ProtoBuf
internal class MsgTransmit : ProtoBuf {
@Serializable
internal class PbMultiMsgItem(
internal class PbMultiMsgItem(
@ProtoNumber(1) @JvmField val fileName: String = "",
@ProtoNumber(2) @JvmField val buffer: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
internal class PbMultiMsgNew(
internal class PbMultiMsgNew(
@ProtoNumber(1) @JvmField val msg: List<MsgComm.Msg> = emptyList()
) : ProtoBuf
@Serializable
internal class PbMultiMsgTransmit(
internal class PbMultiMsgTransmit(
@ProtoNumber(1) @JvmField val msg: List<MsgComm.Msg> = emptyList(),
@ProtoNumber(2) @JvmField val pbItemList: List<MsgTransmit.PbMultiMsgItem> = emptyList()
@ProtoNumber(2) @JvmField val pbItemList: List<PbMultiMsgItem> = emptyList()
) : ProtoBuf
}

View File

@ -31,11 +31,11 @@ internal class MultiMsg : ProtoBuf {
@Serializable
internal class MultiMsgApplyDownRsp(
@ProtoNumber(1) @JvmField val result: Int = 0,
@ProtoNumber(2) @JvmField val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(2) @JvmField val thumbDownPara: String = "",
@ProtoNumber(3) @JvmField val msgKey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(4) @JvmField val uint32DownIp: List<Int> = emptyList(),
@ProtoNumber(5) @JvmField val uint32DownPort: List<Int> = emptyList(),
@ProtoNumber(6) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(6) @JvmField val msgResid: String = "",
@ProtoNumber(7) @JvmField val msgExternInfo: ExternMsg? = null,
@ProtoNumber(8) @JvmField val bytesDownIpV6: List<ByteArray> = emptyList(),
@ProtoNumber(9) @JvmField val uint32DownV6Port: List<Int> = emptyList()

View File

@ -14,6 +14,7 @@ package net.mamoe.mirai.internal.network.protocol.packet.chat
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.SendMessageHandler
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.message.toRichTextElems
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
@ -152,8 +153,13 @@ internal class MultiMsg {
}
}
object ApplyDown: OutgoingPacketFactory<ApplyDown.Response>("MultiMsg.ApplyDown") {
object ApplyDown : OutgoingPacketFactory<ApplyDown.Response>("MultiMsg.ApplyDown") {
sealed class Response : Packet {
class RequireDownload(
val origin: MultiMsg.MultiMsgApplyDownRsp
) : Response() {
override fun toString(): String = "MultiMsg.ApplyDown.Response"
}
object MessageTooLarge : Response()
}
@ -171,7 +177,7 @@ internal class MultiMsg {
buildVer = "8.2.0.1296",
multimsgApplydownReq = listOf(
MultiMsg.MultiMsgApplyDownReq(
msgResid = resId,
msgResid = resId,
msgType = msgType,
)
),
@ -185,7 +191,18 @@ internal class MultiMsg {
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
return Response.MessageTooLarge
val body = readProtoBuf(MultiMsg.RspBody.serializer())
val response = body.multimsgApplydownRsp.first()
return when (response.result) {
0 -> Response.RequireDownload(response)
193 -> Response.MessageTooLarge
//1 -> Response.OK(resId = response.msgResid)
else -> throw contextualBugReportException(
"MultiMsg.ApplyDown",
response._miraiContentToString(),
additional = "Decode failure result=${response.result}"
)
}
}
}
}

View File

@ -30,6 +30,7 @@ import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.*
import net.mamoe.mirai.internal.message.OnlineMessageSourceFromFriendImpl
import net.mamoe.mirai.internal.message.refine
import net.mamoe.mirai.internal.message.toMessageChainOnline
import net.mamoe.mirai.internal.network.MultiPacket
import net.mamoe.mirai.internal.network.Packet
@ -49,6 +50,8 @@ import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf
import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.message.data.MessageSourceKind.STRANGER
import net.mamoe.mirai.message.data.MessageSourceKind.TEMP
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.buildMessageChain
import net.mamoe.mirai.utils.*
@ -389,29 +392,22 @@ internal suspend fun MsgComm.Msg.transform(bot: QQAndroidBot, fromSync: Boolean
friend.checkIsFriendImpl()
friend.lastMessageSequence.loop {
//我也不知道为什么要这样写,但它就是能跑
return if (friend.lastMessageSequence.value != msgHead.msgSeq && friend.lastMessageSequence.compareAndSet(
it,
msgHead.msgSeq
) && contentHead?.autoReply != 1
return if (friend.lastMessageSequence.value != msgHead.msgSeq
&& friend.lastMessageSequence.compareAndSet(it, msgHead.msgSeq)
&& contentHead?.autoReply != 1
) {
val msgs = friend.friendPkgMsgParsingCache.tryMerge(this)
if (msgs.isNotEmpty()) {
if (fromSync) {
FriendMessageSyncEvent(
friend,
msgs.toMessageChainOnline(
bot = bot, groupIdOrZero = 0,
messageSourceKind = MessageSourceKind.FRIEND
),
msgs.toMessageChainOnline(bot, 0, MessageSourceKind.FRIEND).refine(friend),
msgHead.msgTime
)
} else {
FriendMessageEvent(
friend,
msgs.toMessageChainOnline(
bot = bot, groupIdOrZero = 0,
MessageSourceKind.FRIEND
),
msgs.toMessageChainOnline(bot, 0, MessageSourceKind.FRIEND).refine(friend),
msgHead.msgTime
)
}
@ -430,13 +426,13 @@ internal suspend fun MsgComm.Msg.transform(bot: QQAndroidBot, fromSync: Boolean
if (fromSync) {
StrangerMessageSyncEvent(
stranger,
listOf(this).toMessageChainOnline(bot, groupIdOrZero = 0, MessageSourceKind.STRANGER),
listOf(this).toMessageChainOnline(bot, 0, STRANGER).refine(stranger),
msgHead.msgTime
)
} else {
StrangerMessageEvent(
stranger,
listOf(this).toMessageChainOnline(bot, groupIdOrZero = 0, MessageSourceKind.STRANGER),
listOf(this).toMessageChainOnline(bot, 0, STRANGER).refine(stranger),
msgHead.msgTime
)
}
@ -510,13 +506,13 @@ internal suspend fun MsgComm.Msg.transform(bot: QQAndroidBot, fromSync: Boolean
return if (fromSync) {
GroupTempMessageSyncEvent(
member,
listOf(this).toMessageChainOnline(bot, 0, MessageSourceKind.TEMP),
listOf(this).toMessageChainOnline(bot, 0, TEMP).refine(member),
msgHead.msgTime
)
} else {
GroupTempMessageEvent(
member,
listOf(this).toMessageChainOnline(bot, 0, MessageSourceKind.TEMP),
listOf(this).toMessageChainOnline(bot, 0, TEMP).refine(member),
msgHead.msgTime
)
}

View File

@ -23,6 +23,7 @@ import net.mamoe.mirai.event.events.GroupMessageSyncEvent
import net.mamoe.mirai.event.events.MemberCardChangeEvent
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.*
import net.mamoe.mirai.internal.message.refine
import net.mamoe.mirai.internal.message.toMessageChainOnline
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
@ -33,7 +34,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory
import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.message.data.MessageSourceKind
import net.mamoe.mirai.message.data.MessageSourceKind.GROUP
import net.mamoe.mirai.utils.*
/**
@ -119,7 +120,7 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory<Packet?>("Onlin
if (isFromSelfAccount) {
return GroupMessageSyncEvent(
message = msgs.map { it.msg }.toMessageChainOnline(bot, group.id, MessageSourceKind.GROUP,),
message = msgs.map { it.msg }.toMessageChainOnline(bot, group.id, GROUP).refine(group),
time = msgHead.msgTime,
group = group,
sender = sender,
@ -132,7 +133,7 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory<Packet?>("Onlin
return GroupMessageEvent(
senderName = name,
sender = sender,
message = msgs.map { it.msg }.toMessageChainOnline(bot, group.id, MessageSourceKind.GROUP),
message = msgs.map { it.msg }.toMessageChainOnline(bot, group.id, GROUP).refine(group),
permission = findMemberPermission(extraInfo?.flags ?: 0, sender, bot),
time = msgHead.msgTime
)