mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 13:12:17 +08:00
Shamrock
: 支持NT图片合并转发
Signed-off-by: 白池 <whitechi73@outlook.com>
This commit is contained in:
parent
661680e60b
commit
27f837adbe
@ -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,
|
||||
)
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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?
|
||||
)
|
||||
|
||||
|
@ -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 {
|
||||
).onFailure {
|
||||
error("消息合成失败: ${it.stackTraceToString()}")
|
||||
}.onSuccess {
|
||||
desc[++i] = record.sendMemberName.ifEmpty { record.sendNickName } + ": " + it.first
|
||||
it.second
|
||||
}
|
||||
}.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
|
||||
}
|
||||
?: TicketSvc.getNickname()) + ": " + it.first
|
||||
}.onFailure {
|
||||
error("消息合成失败: ${it.stackTraceToString()}")
|
||||
}.getOrThrow().second
|
||||
)
|
||||
)
|
||||
} else {
|
||||
error("消息节点缺少id或content字段")
|
||||
}
|
||||
} else error("消息节点缺少id或content字段")
|
||||
}.onFailure {
|
||||
LogCenter.log("消息节点解析失败:${it.stackTraceToString()}", Level.WARN)
|
||||
}.getOrElse {
|
||||
null
|
||||
}
|
||||
}.ifEmpty { return Result.failure(Exception("消息节点为空")) }
|
||||
}.getOrNull()
|
||||
}.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(
|
||||
"id" to resId,
|
||||
"filename" to filename,
|
||||
"summary" to "查看${desc.size}条转发消息",
|
||||
"desc" to desc.slice(0..if (i < 3) i else 3).joinToString("\n")
|
||||
)
|
||||
return Result.success(MessageSegment(
|
||||
type = "forward",
|
||||
data = mapOf(
|
||||
"id" to resId,
|
||||
"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>> {
|
||||
|
@ -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,82 +256,111 @@ 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) {
|
||||
MsgConstant.KCHATTYPEGROUP -> Elem(
|
||||
customFace = CustomFace(
|
||||
filePath = uploadRet.fileName,
|
||||
fileId = uploadRet.uuid.toUInt(),
|
||||
serverIp = 0u,
|
||||
serverPort = 0u,
|
||||
fileType = FileUtils.getPicType(file).toUInt(),
|
||||
useful = 1u,
|
||||
md5 = uploadRet.md5.hex2ByteArray(),
|
||||
bizType = data["subType"].asIntOrNull?.toUInt(),
|
||||
imageType = FileUtils.getPicType(file).toUInt(),
|
||||
width = picWidth.toUInt(),
|
||||
height = picHeight.toUInt(),
|
||||
size = uploadRet.fileSize.toUInt(),
|
||||
origin = isOriginal,
|
||||
thumbWidth = 0u,
|
||||
thumbHeight = 0u,
|
||||
pbReserve = CustomFace.Companion.PbReserve(
|
||||
field1 = 0,
|
||||
field3 = 0,
|
||||
field4 = 0,
|
||||
field10 = 0,
|
||||
field21 = CustomFace.Companion.Object1(
|
||||
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 = fileInfo.fileName,
|
||||
fileId = uuid,
|
||||
serverIp = 0u,
|
||||
serverPort = 0u,
|
||||
fileType = FileUtils.getPicType(file).toUInt(),
|
||||
useful = 1u,
|
||||
md5 = fileInfo.md5.hex2ByteArray(),
|
||||
bizType = data["subType"].asIntOrNull?.toUInt(),
|
||||
imageType = FileUtils.getPicType(file).toUInt(),
|
||||
width = picWidth.toUInt(),
|
||||
height = picHeight.toUInt(),
|
||||
size = fileInfo.fileSize.toUInt(),
|
||||
origin = isOriginal,
|
||||
thumbWidth = 0u,
|
||||
thumbHeight = 0u,
|
||||
pbReserve = CustomFace.Companion.PbReserve(
|
||||
field1 = 0,
|
||||
field2 = "",
|
||||
field3 = 0,
|
||||
field4 = 0,
|
||||
field5 = 0,
|
||||
md5Str = uploadRet.md5
|
||||
field10 = 0,
|
||||
field21 = CustomFace.Companion.Object1(
|
||||
field1 = 0,
|
||||
field2 = "",
|
||||
field3 = 0,
|
||||
field4 = 0,
|
||||
field5 = 0,
|
||||
md5Str = fileInfo.md5
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
MsgConstant.KCHATTYPEC2C -> Elem(
|
||||
notOnlineImage = NotOnlineImage(
|
||||
filePath = uploadRet.fileName,
|
||||
fileLen = uploadRet.fileSize.toUInt(),
|
||||
downloadPath = uploadRet.uuid,
|
||||
imgType = FileUtils.getPicType(file).toUInt(),
|
||||
picMd5 = uploadRet.md5.hex2ByteArray(),
|
||||
picHeight = picWidth.toUInt(),
|
||||
picWidth = picHeight.toUInt(),
|
||||
resId = uploadRet.uuid,
|
||||
original = isOriginal, // true
|
||||
pbReserve = NotOnlineImage.Companion.PbReserve(
|
||||
field1 = 0,
|
||||
field3 = 0,
|
||||
field4 = 0,
|
||||
field10 = 0,
|
||||
field20 = NotOnlineImage.Companion.Object1(
|
||||
MsgConstant.KCHATTYPEC2C -> Elem(
|
||||
notOnlineImage = NotOnlineImage(
|
||||
filePath = fileInfo.fileName,
|
||||
fileLen = fileInfo.fileSize.toUInt(),
|
||||
downloadPath = fileInfo.uuid,
|
||||
imgType = FileUtils.getPicType(file).toUInt(),
|
||||
picMd5 = fileInfo.md5.hex2ByteArray(),
|
||||
picHeight = picWidth.toUInt(),
|
||||
picWidth = picHeight.toUInt(),
|
||||
resId = fileInfo.uuid,
|
||||
original = isOriginal, // true
|
||||
pbReserve = NotOnlineImage.Companion.PbReserve(
|
||||
field1 = 0,
|
||||
field2 = "",
|
||||
field3 = 0,
|
||||
field4 = 0,
|
||||
field5 = 0,
|
||||
field7 = "",
|
||||
),
|
||||
md5Str = uploadRet.md5
|
||||
field10 = 0,
|
||||
field20 = NotOnlineImage.Companion.Object1(
|
||||
field1 = 0,
|
||||
field2 = "",
|
||||
field3 = 0,
|
||||
field4 = 0,
|
||||
field5 = 0,
|
||||
field7 = "",
|
||||
),
|
||||
md5Str = fileInfo.md5
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
else -> throw LogicException("Not supported chatType($chatType) for PictureMsg")
|
||||
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) {
|
||||
|
@ -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!!)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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(
|
||||
msgId = result.msgHashId,
|
||||
resId = message.data["id"] as String
|
||||
), echo = echo
|
||||
)
|
||||
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)
|
||||
}.onFailure {
|
||||
return error("合并转发消息失败: $it", echo)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user