Add LongMessageOrigin and extract public api IMirai.downloadLongMessage

This commit is contained in:
Him188 2021-02-02 15:13:50 +08:00
parent de8e6469e0
commit 1e95c43ff6
5 changed files with 128 additions and 69 deletions

View File

@ -91,6 +91,8 @@ public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLeve
public abstract fun createImage (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image;
public synthetic fun deleteGroupAnnouncement (Lnet/mamoe/mirai/Bot;JLjava/lang/String;)Lkotlin/Unit;
public fun deleteGroupAnnouncement (Lnet/mamoe/mirai/Bot;JLjava/lang/String;)V
public fun downloadLongMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain;
public abstract fun downloadLongMessage (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun getBotFactory ()Lnet/mamoe/mirai/BotFactory;
public abstract fun getFileCacheStrategy ()Lnet/mamoe/mirai/utils/FileCacheStrategy;
public fun getGroupAnnouncement (Lnet/mamoe/mirai/Bot;JLjava/lang/String;)Lnet/mamoe/mirai/data/GroupAnnouncement;
@ -4364,6 +4366,23 @@ public final class net/mamoe/mirai/message/data/LightApp$Key : net/mamoe/mirai/m
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/LongMessageOrigin : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageMetadata {
public static final field Key Lnet/mamoe/mirai/message/data/LongMessageOrigin$Key;
public fun <init> (Ljava/lang/String;)V
public final fun component1 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/LongMessageOrigin;
public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/LongMessageOrigin;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/LongMessageOrigin;
public fun equals (Ljava/lang/Object;)Z
public fun getKey ()Lnet/mamoe/mirai/message/data/LongMessageOrigin$Key;
public synthetic fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey;
public final fun getResourceId ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/message/data/LongMessageOrigin$Key : net/mamoe/mirai/message/data/AbstractMessageKey {
}
public abstract interface class net/mamoe/mirai/message/data/MarketFace : net/mamoe/mirai/message/data/HummerMessage {
public static final field Key Lnet/mamoe/mirai/message/data/MarketFace$Key;
public static final field SERIAL_NAME Ljava/lang/String;

View File

@ -170,6 +170,14 @@ public interface IMirai : LowLevelApiAccessor {
originalMessage: MessageChain
): OfflineMessageSource
/**
* @since 2.3
*/
@JvmBlockingBridge
public suspend fun downloadLongMessage(
bot: Bot,
resourceId: String,
): MessageChain
/**
* 通过好友验证

View File

@ -0,0 +1,36 @@
/*
* 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.message.data
import net.mamoe.mirai.IMirai
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.safeCast
/**
* 标识一个长消息.
*
*
* 消息过长后会通过特殊的通道上传和下载, 每条消息都会获得一个 resourceId.
*
* 可以通过 resourceId 下载消息 [IMirai.downloadLongMessage].
* 但不保证 resourceId 一直有效.
*
* @since 2.3
*/
@MiraiExperimentalApi
public data class LongMessageOrigin(
val resourceId: String
) : MessageMetadata, ConstrainSingle {
override val key: Key get() = Key
override fun toString(): String = ""
public companion object Key : AbstractMessageKey<LongMessageOrigin>({ it.safeCast() })
}

View File

@ -16,6 +16,8 @@ import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.withContext
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.serialization.json.*
import net.mamoe.mirai.*
import net.mamoe.mirai.contact.*
@ -24,15 +26,18 @@ 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.Highway
import net.mamoe.mirai.internal.network.highway.ResourceKind
import net.mamoe.mirai.internal.network.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.data.proto.MsgTransmit
import net.mamoe.mirai.internal.network.protocol.packet.chat.*
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.network.protocol.packet.sendAndExpect
import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard
import net.mamoe.mirai.internal.utils.crypto.TEA
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.MessageSerializers
import net.mamoe.mirai.message.action.Nudge
@ -959,4 +964,59 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
kind, ids, botId, time, fromId, targetId, originalMessage, internalIds
)
override suspend fun downloadLongMessage(bot: Bot, resourceId: String): MessageChain {
bot.asQQAndroidBot()
when (val resp = MultiMsg.ApplyDown(bot.client, 1, resourceId, 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())
return transmit.msg.toMessageChainNoSource(bot.id, 0, MessageSourceKind.GROUP)
}
MultiMsg.ApplyDown.Response.MessageTooLarge -> {
error("Message is too large and cannot download")
}
}
}
}

View File

@ -9,25 +9,11 @@
package net.mamoe.mirai.internal.message
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.*
import net.mamoe.mirai.utils.safeCast
// internal runtime value, not serializable
internal data class LongMessageInternal internal constructor(override val content: String, val resId: String) :
@ -36,59 +22,9 @@ internal data class LongMessageInternal internal constructor(override val conten
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 long = Mirai.downloadLongMessage(bot, resId)
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")
}
}
return LongMessageOrigin(resId) + long
}
companion object Key :