Shamrock: 支持NT图片合并转发

Signed-off-by: 白池 <whitechi73@outlook.com>
This commit is contained in:
白池 2024-03-02 18:03:13 +08:00
parent 661680e60b
commit 27f837adbe
10 changed files with 271 additions and 296 deletions

View File

@ -31,7 +31,7 @@ data class RecvLongMsgInfo(
data class SendLongMsgInfo(
@ProtoNumber(1) val type: Int? = null,
@ProtoNumber(2) val uid: LongMsgUid? = null,
@ProtoNumber(3) val groupUin: Int? = null,
@ProtoNumber(3) val groupUin: ULong? = null,
@ProtoNumber(4) val payload: ByteArray? = null,
)

View File

@ -1,55 +1 @@
@file:OptIn(ExperimentalSerializationApi::class)
package protobuf.message.multimedia
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import moe.fuqiuluo.symbols.Protobuf
@Serializable
data class RichMediaForPicData(
@ProtoNumber(1) val info: MediaInfo?,
@ProtoNumber(2) val display: DisplayMediaInfo?,
): Protobuf<RichMediaForPicData> {
companion object {
@Serializable
data class MediaInfo(
@ProtoNumber(1) val picture: Picture? = null,
)
@Serializable
data class Picture(
@ProtoNumber(1) val info: PictureInfo? = null,
@ProtoNumber(2) val fileId: String? = null,
@ProtoNumber(4) val time: ULong? = null,
)
@Serializable
data class PictureInfo(
@ProtoNumber(2) val md5Hex: String? = null,
@ProtoNumber(3) val sha: String? = null,
@ProtoNumber(4) val name: String? = null,
@ProtoNumber(6) val width: Int? = null,
@ProtoNumber(7) val height: Int? = null,
)
}
}
@Serializable
data class DisplayMediaInfo(
@ProtoNumber(1) val show: Show? = null,
) {
companion object {
@Serializable
data class Show(
@ProtoNumber(2) val text: String? = null,
@ProtoNumber(12) val download: Download? = null
)
@Serializable
data class Download(
@ProtoNumber(30) val url: String? = null,
)
}
}

View File

@ -49,8 +49,8 @@ data class UploadCompletedReq(
@Serializable
data class MsgInfo(
@ProtoNumber(1) val msgInfoBody: List<MsgInfoBody>,
@ProtoNumber(2) val extBizInfo: ExtBizInfo,
)
@ProtoNumber(2) val extBizInfo: ExtBizInfo?,
): Protobuf<MsgInfo>
@Serializable
data class MsgInfoBody(
@ -106,7 +106,7 @@ data class UploadReq(
@ProtoNumber(5) val compatQMsgSceneType: UInt? = null,
@ProtoNumber(6) val extBizInfo: ExtBizInfo? = null,
@ProtoNumber(7) val clientSeq: UInt? = null,
@ProtoNumber(8) val noNeedCompatMsg: Boolean = false,
@ProtoNumber(8) val noNeedCompatMsg: Boolean? = null,
)
@Serializable
@ -114,7 +114,7 @@ data class ExtBizInfo(
@ProtoNumber(1) val pic: PicExtBizInfo? = null,
@ProtoNumber(2) val video: VideoExtBizInfo? = null,
@ProtoNumber(3) val ptt: PttExtBizInfo? = null,
@ProtoNumber(10) val busiType: UInt,
@ProtoNumber(10) val busiType: UInt?,
)
@Serializable
@ -132,15 +132,15 @@ data class PttExtBizInfo(
@Serializable
data class VideoExtBizInfo(
@ProtoNumber(1) val fromScene: UInt,
@ProtoNumber(2) val toScene: UInt,
@ProtoNumber(3) val bytesPbReserve: ByteArray,
@ProtoNumber(1) val fromScene: UInt?,
@ProtoNumber(2) val toScene: UInt?,
@ProtoNumber(3) val bytesPbReserve: ByteArray?,
)
@Serializable
data class PicExtBizInfo(
@ProtoNumber(1) val bizType: UInt,
@ProtoNumber(2) val textSummary: String,
@ProtoNumber(1) val bizType: UInt?,
@ProtoNumber(2) val textSummary: String?,
@ProtoNumber(11) val bytesPbReserveC2c: ByteArray? = null,
@ProtoNumber(12) val bytesPbReserveTroop: ByteArray? = null,
@ProtoNumber(1001) val fromScene: UInt? = null,
@ -156,15 +156,15 @@ data class UploadInfo(
@Serializable
data class FileInfo(
@ProtoNumber(1) val fileSize: ULong,
@ProtoNumber(2) val md5: String,
@ProtoNumber(3) val sha1: String,
@ProtoNumber(4) val name: String,
@ProtoNumber(5) val fileType: FileType,
@ProtoNumber(6) val width: UInt,
@ProtoNumber(7) val height: UInt,
@ProtoNumber(8) val time: UInt,
@ProtoNumber(9) val original: UInt,
@ProtoNumber(1) val fileSize: ULong?,
@ProtoNumber(2) val md5: String?,
@ProtoNumber(3) val sha1: String?,
@ProtoNumber(4) val name: String?,
@ProtoNumber(5) val fileType: FileType?,
@ProtoNumber(6) val width: UInt?,
@ProtoNumber(7) val height: UInt?,
@ProtoNumber(8) val time: UInt?,
@ProtoNumber(9) val original: UInt?,
)
@Serializable
@ -217,7 +217,7 @@ data class IndexNode(
@ProtoNumber(3) val storeId: UInt, // 0为旧服务器 1为nt服务器
@ProtoNumber(4) val uploadTime: ULong,
@ProtoNumber(5) val ttl: ULong,
@ProtoNumber(6) val subType: UInt,
@ProtoNumber(6) val subType: UInt? = null,
@ProtoNumber(7) val storeAppId: UInt? = null
)

View File

@ -26,8 +26,8 @@ class DownloadSafeRsp
@Serializable
data class UploadKeyRenewalRsp(
@ProtoNumber(1) val ukey: String,
@ProtoNumber(2) val ukeyTtlSec: ULong,
@ProtoNumber(1) val ukey: String?,
@ProtoNumber(2) val ukeyTtlSec: ULong?,
)
@Serializable
@ -39,7 +39,7 @@ data class MsgInfoAuthRsp(
@Serializable
data class UploadCompletedRsp(
@ProtoNumber(1) val msgSeq: ULong
@ProtoNumber(1) val msgSeq: ULong?
)
@Serializable
@ -47,13 +47,13 @@ class DeleteRsp
@Serializable
data class DownloadRkeyRsp(
@ProtoNumber(1) val rkeys: List<RKeyInfo>
@ProtoNumber(1) val rkeys: List<RKeyInfo>?
)
@Serializable
data class RKeyInfo(
@ProtoNumber(1) val rkey: String,
@ProtoNumber(2) val rkeyTtlSec: ULong,
@ProtoNumber(1) val rkey: String?,
@ProtoNumber(2) val rkeyTtlSec: ULong?,
@ProtoNumber(3) val storeId: UInt = 0u,
@ProtoNumber(4) val rkeyCreateTime: UInt?,
@ProtoNumber(4) val type: UInt?,
@ -61,8 +61,8 @@ data class RKeyInfo(
@Serializable
data class DownloadRsp(
@ProtoNumber(1) val rkeyParam: String,
@ProtoNumber(2) val rkeyTtlSec: ULong,
@ProtoNumber(1) val rkeyParam: String?,
@ProtoNumber(2) val rkeyTtlSec: ULong?,
@ProtoNumber(3) val downloadInfo: DownloadInfo?,
@ProtoNumber(4) val rkeyCreateTime: UInt?
)
@ -80,16 +80,16 @@ data class DownloadInfo(
@Serializable
data class VideoExtInfo(
@ProtoNumber(1) val videoCodecFormat: UInt,
@ProtoNumber(1) val videoCodecFormat: UInt? = null,
)
@Serializable
data class UploadRsp(
@ProtoNumber(1) val ukey: String,
@ProtoNumber(2) val ukeyTtlSec: ULong,
@ProtoNumber(3) val ipv4: List<Ipv4>,
@ProtoNumber(4) val ipv6: List<Ipv6>,
@ProtoNumber(5) val msgSeq: ULong,
@ProtoNumber(1) val ukey: String?,
@ProtoNumber(2) val ukeyTtlSec: ULong?,
@ProtoNumber(3) val ipv4: List<Ipv4>?,
@ProtoNumber(4) val ipv6: List<Ipv6>?,
@ProtoNumber(5) val msgSeq: ULong?,
@ProtoNumber(6) val msgInfo: MsgInfo? = null,
@ProtoNumber(7) val ext: List<RichmediaStorageTransInfo>? = null,
@ProtoNumber(8) val compatQMsg: ByteArray? = null,
@ -98,11 +98,11 @@ data class UploadRsp(
@Serializable
data class SubFileInfo(
@ProtoNumber(1) val subType: UInt,
@ProtoNumber(2) val ukey: String,
@ProtoNumber(3) val ukeyTTLSec: ULong,
@ProtoNumber(4) val ipv4: List<Ipv4>,
@ProtoNumber(5) val ipv6: List<Ipv6>,
@ProtoNumber(1) val subType: UInt?,
@ProtoNumber(2) val ukey: String?,
@ProtoNumber(3) val ukeyTTLSec: ULong?,
@ProtoNumber(4) val ipv4: List<Ipv4>?,
@ProtoNumber(5) val ipv6: List<Ipv6>?,
)
@Serializable
@ -132,8 +132,8 @@ data class Ipv6(
@Serializable
data class RspHead(
@ProtoNumber(1) val commonHead: CommonHead,
@ProtoNumber(1) val commonHead: CommonHead?,
@ProtoNumber(2) val retCode: UInt = 0u,
@ProtoNumber(3) val msg: String
@ProtoNumber(3) val msg: String?
)

View File

@ -225,9 +225,22 @@ internal object MsgSvc : BaseSvc() {
suspend fun uploadMultiMsg(
chatType: Int,
peerId: String,
fromId: String,
fromId: String = peerId,
messages: JsonArray,
retryCnt: Int
): Result<MessageSegment> {
return uploadMultiMsg(chatType, peerId, fromId, messages).onFailure {
if (retryCnt > 0) {
return uploadMultiMsg(chatType, peerId, fromId, messages, retryCnt - 1)
}
}
}
private suspend fun uploadMultiMsg(
chatType: Int,
peerId: String,
fromId: String = peerId,
messages: JsonArray,
retryCnt: Int,
): Result<MessageSegment> {
var i = -1
val desc = MutableList(messages.size) { "" }
@ -237,9 +250,10 @@ internal object MsgSvc : BaseSvc() {
kotlin.runCatching {
val data = msg.asJsonObject["data"].asJsonObject
if (data.containsKey("id")) {
val record = getMsg(data["id"].asInt).getOrElse {
val msgId = data["id"].asInt
val record = getMsg(msgId).onFailure {
error("合并转发消息节点消息(id = ${data["id"].asInt})获取失败:$it")
}
}.getOrThrow()
PushMsgBody(
msgHead = ResponseHead(
peerUid = record.senderUid,
@ -257,9 +271,7 @@ internal object MsgSvc : BaseSvc() {
msgType = when (record.chatType) {
MsgConstant.KCHATTYPEC2C -> 9
MsgConstant.KCHATTYPEGROUP -> 82
else -> throw UnsupportedOperationException(
"Unsupported chatType: $chatType"
)
else -> throw UnsupportedOperationException("Unsupported chatType: $chatType")
},
msgSubType = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null,
divSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null,
@ -288,14 +300,16 @@ internal object MsgSvc : BaseSvc() {
record.peerUin.toString(),
"0"
).onEach { segment ->
if (segment.type == "forward")
if (segment.type == "forward") {
forwardMsg[segment.data["filename"] as String] =
segment.data["id"] as String
}.toJson()
).getOrElse { throw Exception("消息合成失败: $it") }.let {
desc[++i] = record.sendMemberName.ifEmpty { record.sendNickName } + ": " + it.first
it.second
}
}.toJson()
).onFailure {
error("消息合成失败: ${it.stackTraceToString()}")
}.onSuccess {
desc[++i] = record.sendMemberName.ifEmpty { record.sendNickName } + ": " + it.first
}.getOrThrow().second
)
)
} else if (data.containsKey("content")) {
@ -343,22 +357,21 @@ internal object MsgSvc : BaseSvc() {
elementData["id"].asString
}
}
).getOrElse { error("消息合成失败: $it") }.let {
).onSuccess {
desc[++i] = (data["name"].asStringOrNull ?: data["uin"].asStringOrNull
?: TicketSvc.getNickname()) + ": " + it.first
it.second
}
}.onFailure {
error("消息合成失败: ${it.stackTraceToString()}")
}.getOrThrow().second
)
)
} else {
error("消息节点缺少id或content字段")
}
} else error("消息节点缺少id或content字段")
}.onFailure {
LogCenter.log("消息节点解析失败:${it.stackTraceToString()}", Level.WARN)
}.getOrElse {
null
}.getOrNull()
}.ifEmpty {
return Result.failure(Exception("消息节点为空"))
}
}.ifEmpty { return Result.failure(Exception("消息节点为空")) }
val payload = LongMsgPayload(
action = mutableListOf(
@ -380,26 +393,22 @@ internal object MsgSvc : BaseSvc() {
}
}
)
LogCenter.log(payload.toByteArray().toHexString(), Level.DEBUG)
LogCenter.log({ payload.toByteArray().toHexString() }, Level.DEBUG)
val req = LongMsgReq(
sendInfo = when (chatType) {
MsgConstant.KCHATTYPEC2C -> SendLongMsgInfo(
type = 1,
uid = LongMsgUid(peerId),
uid = LongMsgUid(if(peerId.startsWith("u_")) peerId else ContactHelper.getUidByUinAsync(peerId.toLong()) ),
payload = DeflateTools.gzip(payload.toByteArray())
)
MsgConstant.KCHATTYPEGROUP -> SendLongMsgInfo(
type = 3,
uid = LongMsgUid(fromId),
groupUin = fromId.toInt(),
groupUin = fromId.toULong(),
payload = DeflateTools.gzip(payload.toByteArray())
)
else -> throw UnsupportedOperationException(
"Unsupported chatType: $chatType"
)
else -> throw UnsupportedOperationException("Unsupported chatType: $chatType")
},
setting = LongMsgSettings(
field1 = 4,
@ -407,27 +416,25 @@ internal object MsgSvc : BaseSvc() {
field3 = 9,
field4 = 0
)
)
).toByteArray()
val buffer = sendBufferAW(
"trpc.group.long_msg_interface.MsgService.SsoSendLongMsg",
true,
req.toByteArray()
) ?: return Result.failure(Exception("unable to upload multi message"))
val rsp = buffer.slice(4).decodeProtobuf<LongMsgRsp>()
val buffer = sendBufferAW("trpc.group.long_msg_interface.MsgService.SsoSendLongMsg", true, req, timeout = 30_000)
?: return Result.failure(Exception("unable to upload multi message, response timeout"))
val rsp = runCatching {
buffer.slice(4).decodeProtobuf<LongMsgRsp>()
}.getOrElse {
buffer.decodeProtobuf<LongMsgRsp>()
}
val resId = rsp.sendResult?.resId ?: return Result.failure(Exception("unable to upload multi message"))
val filename = UUID.randomUUID().toString().uppercase()
return Result.success(
MessageSegment(
"forward",
mapOf(
return Result.success(MessageSegment(
type = "forward",
data = mapOf(
"id" to resId,
"filename" to filename,
"filename" to UUID.randomUUID().toString(),
"summary" to "查看${desc.size}条转发消息",
"desc" to desc.slice(0..if (i < 3) i else 3).joinToString("\n")
)
)
)
))
}
suspend fun getMultiMsg(resId: String): Result<List<LongMsgAction>> {

View File

@ -24,6 +24,8 @@ import protobuf.message.Ptt
import protobuf.message.RichText
import protobuf.message.element.*
import protobuf.message.element.commelem.*
import protobuf.oidb.cmd0x11c5.C2CUserInfo
import protobuf.oidb.cmd0x11c5.GroupUserInfo
import java.io.File
import java.nio.ByteBuffer
import java.util.*
@ -68,7 +70,7 @@ internal class ElemMaker {
private var rich = RichText()
private val elems = mutableListOf<Elem>()
private var desc = ""
private var summary = StringBuilder()
fun getRich(): RichText {
rich.elements = elems
@ -76,7 +78,7 @@ internal class ElemMaker {
}
fun getDesc(): String {
return desc
return summary.toString()
}
private suspend fun createTextElem(
@ -86,11 +88,12 @@ internal class ElemMaker {
data: JsonObject
) {
data.checkAndThrow("text")
val text = data["text"].asString
val elem = Elem(
text = TextMsg(data["text"].asString)
text = TextMsg(text)
)
elems.add(elem)
desc += data["text"].asString
summary.append(text)
}
private suspend fun createAtElem(
@ -102,7 +105,6 @@ internal class ElemMaker {
when (chatType) {
MsgConstant.KCHATTYPEGROUP -> {
data.checkAndThrow("qq")
val qq: Long
val type: Int
val display = when (val qqStr = data["qq"].asString) {
@ -146,7 +148,7 @@ internal class ElemMaker {
text = TextMsg(str = display, attr6Buf = attr6.array())
)
elems.add(elem)
desc += display
summary.append(display)
}
MsgConstant.KCHATTYPEC2C -> {
@ -165,7 +167,7 @@ internal class ElemMaker {
text = TextMsg(str = display)
)
elems.add(elem)
desc += display
summary.append(display)
}
else -> throw UnsupportedOperationException("Unsupported chatType($chatType) for AtMsg")
@ -205,7 +207,7 @@ internal class ElemMaker {
)
}
elems.add(elem)
desc += "[表情]"
summary.append("[表情]")
}
private suspend fun createImageElem(
@ -215,7 +217,6 @@ internal class ElemMaker {
data: JsonObject
) {
val isOriginal = data["original"].asBooleanOrNull ?: true
val isFlash = data["flash"].asBooleanOrNull ?: false
val filePath = data["file"].asStringOrNull
val url = data["url"].asStringOrNull
var file: File? = null
@ -255,29 +256,59 @@ internal class ElemMaker {
picHeight = options.outWidth
}
val uploadRet = NtV2RichMediaSvc.tryUploadResourceByNt(
val fileInfo = NtV2RichMediaSvc.tryUploadResourceByNt(
chatType = chatType,
elementType = MsgConstant.KELEMTYPEPIC,
resources = arrayListOf(file),
timeout = 30.seconds
).getOrThrow().first()
LogCenter.log({ uploadRet.toString() }, Level.DEBUG)
val elem = when (chatType) {
runCatching {
fileInfo.uuid.toUInt()
}.onFailure {
NtV2RichMediaSvc.requestUploadNtPic(file, fileInfo.md5, fileInfo.sha, fileInfo.fileName, picWidth.toUInt(), picHeight.toUInt(), 5) {
when(chatType) {
MsgConstant.KCHATTYPEGROUP -> {
sceneType = 2u
grp = GroupUserInfo(peerId.toULong())
}
MsgConstant.KCHATTYPEC2C -> {
sceneType = 1u
c2c = C2CUserInfo(
accountType = 2u,
uid = ContactHelper.getUidByUinAsync(peerId.toLong())
)
}
else -> error("不支持的合并转发图片类型")
}
}.onFailure {
LogCenter.log("获取MultiMedia图片信息失败: $it", Level.ERROR)
}.onSuccess {
//LogCenter.log({ "获取MultiMedia图片信息成功: ${it.hashCode()}" }, Level.INFO)
elems.add(Elem(
commonElem = CommonElem(
serviceType = 48,
businessType = 10,
elem = it.msgInfo!!.toByteArray()
)
))
}
}.onSuccess { uuid ->
elems.add(when (chatType) {
MsgConstant.KCHATTYPEGROUP -> Elem(
customFace = CustomFace(
filePath = uploadRet.fileName,
fileId = uploadRet.uuid.toUInt(),
filePath = fileInfo.fileName,
fileId = uuid,
serverIp = 0u,
serverPort = 0u,
fileType = FileUtils.getPicType(file).toUInt(),
useful = 1u,
md5 = uploadRet.md5.hex2ByteArray(),
md5 = fileInfo.md5.hex2ByteArray(),
bizType = data["subType"].asIntOrNull?.toUInt(),
imageType = FileUtils.getPicType(file).toUInt(),
width = picWidth.toUInt(),
height = picHeight.toUInt(),
size = uploadRet.fileSize.toUInt(),
size = fileInfo.fileSize.toUInt(),
origin = isOriginal,
thumbWidth = 0u,
thumbHeight = 0u,
@ -292,22 +323,21 @@ internal class ElemMaker {
field3 = 0,
field4 = 0,
field5 = 0,
md5Str = uploadRet.md5
md5Str = fileInfo.md5
)
)
)
)
MsgConstant.KCHATTYPEC2C -> Elem(
notOnlineImage = NotOnlineImage(
filePath = uploadRet.fileName,
fileLen = uploadRet.fileSize.toUInt(),
downloadPath = uploadRet.uuid,
filePath = fileInfo.fileName,
fileLen = fileInfo.fileSize.toUInt(),
downloadPath = fileInfo.uuid,
imgType = FileUtils.getPicType(file).toUInt(),
picMd5 = uploadRet.md5.hex2ByteArray(),
picMd5 = fileInfo.md5.hex2ByteArray(),
picHeight = picWidth.toUInt(),
picWidth = picHeight.toUInt(),
resId = uploadRet.uuid,
resId = fileInfo.uuid,
original = isOriginal, // true
pbReserve = NotOnlineImage.Companion.PbReserve(
field1 = 0,
@ -322,15 +352,15 @@ internal class ElemMaker {
field5 = 0,
field7 = "",
),
md5Str = uploadRet.md5
md5Str = fileInfo.md5
)
)
)
else -> throw LogicException("Not supported chatType($chatType) for PictureMsg")
})
}
elems.add(elem)
desc += "[图片]"
summary.append("[图片]")
}
private suspend fun createReplyElem(
@ -402,7 +432,7 @@ internal class ElemMaker {
)
}
elems.add(elem)
desc += "[回复消息]"
summary.append("[回复消息]")
}
private suspend fun createJsonElem(
@ -419,7 +449,7 @@ internal class ElemMaker {
)
)
elems.add(elem)
desc += "[Json消息]"
summary .append( "[Json消息]" )
}
private suspend fun createForwardStruct(
@ -485,7 +515,7 @@ internal class ElemMaker {
)
)
elems.add(elem)
desc += "[聊天记录]"
this.summary .append( "[聊天记录]" )
}
private suspend fun createWeatherElem(
@ -517,7 +547,7 @@ internal class ElemMaker {
)
)
elems.add(elem)
desc += "[天气卡片]"
summary .append( "[天气卡片]" )
} else {
throw LogicException("无法获取城市天气")
}
@ -542,7 +572,7 @@ internal class ElemMaker {
)
)
elems.add(elem)
desc += "[戳一戳]"
summary .append( "[戳一戳]" )
}
private suspend fun createNewDiceElem(
@ -568,7 +598,7 @@ internal class ElemMaker {
)
)
elems.add(elem)
desc += "[骰子]"
summary .append( "[骰子]" )
}
private suspend fun createNewRpsElem(
@ -594,7 +624,7 @@ internal class ElemMaker {
)
)
elems.add(elem)
desc += "[包剪锤]"
summary .append( "[包剪锤]" )
}
private suspend fun createMarkdownElem(
@ -612,7 +642,7 @@ internal class ElemMaker {
)
)
elems.add(elem)
desc += "[Markdown消息]"
summary.append("[Markdown消息]")
}
private suspend fun createButtonElem(
@ -662,7 +692,7 @@ internal class ElemMaker {
)
)
elems.add(elem)
desc += "[Button消息]"
summary.append("[Button消息]")
}
private suspend fun createRecordElem(
@ -675,7 +705,7 @@ internal class ElemMaker {
rich.ptt= Ptt(
)
desc += "[语音消息]"
summary .append( "[语音消息]" )
}
private fun JsonObject.checkAndThrow(vararg key: String) {

View File

@ -46,6 +46,7 @@ import protobuf.oidb.cmd0x11c5.NtV2RichMediaRsp
import protobuf.oidb.cmd0x11c5.SceneInfo
import protobuf.oidb.cmd0x11c5.UploadInfo
import protobuf.oidb.cmd0x11c5.UploadReq
import protobuf.oidb.cmd0x11c5.UploadRsp
import protobuf.oidb.cmd0x11c5.VideoDownloadExt
import protobuf.oidb.cmd0x388.Cmd0x388ReqBody
import protobuf.oidb.cmd0x388.Cmd0x388RspBody
@ -281,7 +282,6 @@ internal object NtV2RichMediaSvc: BaseSvc() {
MsgConstant.KCHATTYPEC2C -> MessageHelper.generateContact(chatType, TicketSvc.getUin())
else -> Contact(chatType, fetchGroupResUploadTo(), null)
}
LogCenter.log(contact.toString())
val result = mutableListOf<CommonFileInfo>()
withTimeoutOrNull(timeout) {
suspendCancellableCoroutine {
@ -313,6 +313,11 @@ internal object NtV2RichMediaSvc: BaseSvc() {
}
}
}
if (result.isEmpty()) {
return Result.failure(Exception("upload failed"))
}
return Result.success(result)
}
@ -392,9 +397,6 @@ internal object NtV2RichMediaSvc: BaseSvc() {
return Result.failure(Exception("unable to get c2c nt pic"))
}
/**
* 请求上传Nt图片
*/
suspend fun requestUploadNtPic(
file: File,
md5: String,
@ -402,8 +404,27 @@ internal object NtV2RichMediaSvc: BaseSvc() {
name: String,
width: UInt,
height: UInt,
retryCnt: Int,
sceneBuilder: suspend SceneInfo.() -> Unit
) {
): Result<UploadRsp> {
return runCatching {
requestUploadNtPic(file, md5, sha, name, width, height, sceneBuilder).getOrThrow()
}.onFailure {
if (retryCnt > 0) {
return requestUploadNtPic(file, md5, sha, name, width, height, retryCnt - 1, sceneBuilder)
}
}
}
private suspend fun requestUploadNtPic(
file: File,
md5: String,
sha: String,
name: String,
width: UInt,
height: UInt,
sceneBuilder: suspend SceneInfo.() -> Unit
): Result<UploadRsp> {
val req = NtV2RichMediaReq(
head = MultiMediaReqHead(
commonHead = CommonHead(
@ -443,12 +464,17 @@ internal object NtV2RichMediaSvc: BaseSvc() {
clientRandomId = Random.nextULong(),
compatQMsgSceneType = 1u,
clientSeq = Random.nextUInt(),
noNeedCompatMsg = true
noNeedCompatMsg = false
)
).toByteArray()
val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11c5_100", 4549, 100, req, true)?.slice(4)
val rsp = buffer?.decodeProtobuf<TrpcOidb>()?.buffer?.decodeProtobuf<NtV2RichMediaRsp>()
LogCenter.log("requestUploadPic => rsp: $rsp")
val buffer = sendOidbAW("OidbSvcTrpcTcp.0x11c5_100", 4549, 100, req, true, timeout = 3_000)?.slice(4)
?: return Result.failure(Exception("no response: timeout"))
val rspBuffer = buffer.decodeProtobuf<TrpcOidb>().buffer
val rsp = rspBuffer.decodeProtobuf<NtV2RichMediaRsp>()
if (rsp.upload == null) {
return Result.failure(Exception("unable to request upload nt pic: ${rsp.head}"))
}
return Result.success(rsp.upload!!)
}
/**

View File

@ -33,31 +33,17 @@ internal object SendForwardMessage : IActionHandler() {
}
}
val peerId = when (chatType) {
MsgConstant.KCHATTYPEGROUP -> session.getLongOrNull("group_id") ?: return noParam(
"group_id",
session.echo
)
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getLongOrNull("user_id")
?: return noParam("user_id", session.echo)
else -> error("unknown chat type: $chatType")
}.toString()
val fromId = when (chatType) {
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getLongOrNull("group_id")
MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id")
?: return noParam("group_id", session.echo)
MsgConstant.KCHATTYPEC2C -> session.getLongOrNull("user_id") ?: return noParam(
"user_id",
session.echo
)
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("user_id")
?: return noParam("user_id", session.echo)
else -> error("unknown chat type: $chatType")
}.toString()
}
val fromId = session.getStringOrNull("group_id")
val retryCnt = session.getIntOrNull("retry_cnt") ?: 5
return if (session.isArray("messages")) {
val messages = session.getArray("messages")
invoke(chatType, peerId, fromId, messages, retryCnt, session.echo)
invoke(chatType, peerId, fromId ?: peerId, messages, retryCnt, session.echo)
} else {
logic("未知格式合并转发消息", session.echo)
}
@ -77,18 +63,16 @@ internal object SendForwardMessage : IActionHandler() {
echo: JsonElement = EmptyJsonString
): String {
kotlin.runCatching {
val message = MsgSvc.uploadMultiMsg(chatType, peerId, fromId, messages, retryCnt)
.getOrElse { return logic(it.message ?: "", echo) }
val result = MsgSvc.sendToAio(chatType, peerId, listOf(message).toJson(), fromId, retryCnt)
.getOrElse { return logic(it.message ?: "", echo) }
return ok(
SendForwardMessageResult(
val message = MsgSvc.uploadMultiMsg(chatType, peerId, fromId, messages, retryCnt).onFailure {
return error(it.message ?: it.stackTraceToString(), echo)
}.getOrThrow()
val result = MsgSvc.sendToAio(chatType, peerId, listOf(message).toJson(), fromId, retryCnt).onFailure {
return error(it.message ?: it.stackTraceToString(), echo)
}.getOrThrow()
return ok(SendForwardMessageResult(
msgId = result.msgHashId,
resId = message.data["id"] as String
), echo = echo
)
), echo = echo)
}.onFailure {
return error("合并转发消息失败: $it", echo)
}

View File

@ -32,31 +32,17 @@ internal object UploadMultiMessage : IActionHandler() {
}
}
val peerId = when (chatType) {
MsgConstant.KCHATTYPEGROUP -> session.getLongOrNull("group_id") ?: return noParam(
"group_id",
session.echo
)
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getLongOrNull("user_id")
?: return noParam("user_id", session.echo)
else -> error("unknown chat type: $chatType")
}.toString()
val fromId = when (chatType) {
MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getLongOrNull("group_id")
MsgConstant.KCHATTYPEGROUP -> session.getStringOrNull("group_id")
?: return noParam("group_id", session.echo)
MsgConstant.KCHATTYPEC2C -> session.getLongOrNull("user_id") ?: return noParam(
"user_id",
session.echo
)
MsgConstant.KCHATTYPEC2C, MsgConstant.KCHATTYPETEMPC2CFROMGROUP -> session.getStringOrNull("user_id")
?: return noParam("user_id", session.echo)
else -> error("unknown chat type: $chatType")
}.toString()
}
val fromId = session.getStringOrNull("group_id")
val retryCnt = session.getIntOrNull("retry_cnt") ?: 5
return if (session.isArray("messages")) {
val messages = session.getArray("messages")
invoke(chatType, peerId, fromId, messages, retryCnt, echo = session.echo)
invoke(chatType, peerId, fromId ?: peerId, messages, retryCnt, echo = session.echo)
} else {
logic("未知格式合并转发消息", session.echo)
}
@ -76,9 +62,10 @@ internal object UploadMultiMessage : IActionHandler() {
echo: JsonElement = EmptyJsonString
): String {
kotlin.runCatching {
val message = MsgSvc.uploadMultiMsg(chatType, peerId, fromId, messages, retryCnt)
.getOrElse { return logic(it.message ?: "", echo) }
MsgSvc.uploadMultiMsg(chatType, peerId, fromId, messages, retryCnt).getOrThrow()
}.onFailure {
return error("合并转发消息失败: ${it.stackTraceToString()}", echo)
}.onSuccess { message ->
return ok(
UploadForwardMessageResult(
resId = message.data["id"] as String,
@ -87,8 +74,6 @@ internal object UploadMultiMessage : IActionHandler() {
desc = message.data["desc"] as String
), echo = echo
)
}.onFailure {
return error("合并转发消息失败: $it", echo)
}
return logic("合并转发消息失败(unknown error)", echo)
}

View File

@ -312,16 +312,13 @@ fun Routing.messageAction() {
val userId = fetchPostOrNull("user_id")
val groupId = fetchPostOrNull("group_id")
val messages = fetchPostJsonArray("messages")
call.respondText(
UploadMultiMessage(
chatType,
if (chatType == MsgConstant.KCHATTYPEC2C) userId!! else groupId!!,
groupId ?: userId ?: "",
messages,
retryCnt
),
ContentType.Application.Json
)
call.respondText(UploadMultiMessage(
chatType = chatType,
peerId = if (chatType == MsgConstant.KCHATTYPEC2C) userId!! else groupId!!,
fromId = groupId ?: userId ?: "",
messages = messages,
retryCnt = retryCnt
), ContentType.Application.Json)
}
get {
respond(false, Status.InternalHandlerError, "Not support GET method")