diff --git a/protobuf/src/main/java/protobuf/message/longmsg/LongMsg.kt b/protobuf/src/main/java/protobuf/message/longmsg/LongMsg.kt index 250fbe0..6fabca5 100644 --- a/protobuf/src/main/java/protobuf/message/longmsg/LongMsg.kt +++ b/protobuf/src/main/java/protobuf/message/longmsg/LongMsg.kt @@ -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, ) diff --git a/protobuf/src/main/java/protobuf/message/multimedia/RichMediaForPicData.kt b/protobuf/src/main/java/protobuf/message/multimedia/RichMediaForPicData.kt index e4c3b41..86c15cb 100644 --- a/protobuf/src/main/java/protobuf/message/multimedia/RichMediaForPicData.kt +++ b/protobuf/src/main/java/protobuf/message/multimedia/RichMediaForPicData.kt @@ -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 { - 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, - ) - } -} - diff --git a/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaReq.kt b/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaReq.kt index 1fc996b..569c922 100644 --- a/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaReq.kt +++ b/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaReq.kt @@ -49,8 +49,8 @@ data class UploadCompletedReq( @Serializable data class MsgInfo( @ProtoNumber(1) val msgInfoBody: List, - @ProtoNumber(2) val extBizInfo: ExtBizInfo, -) + @ProtoNumber(2) val extBizInfo: ExtBizInfo?, +): Protobuf @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 ) diff --git a/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaRsp.kt b/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaRsp.kt index f21cd7d..bf5fd13 100644 --- a/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaRsp.kt +++ b/protobuf/src/main/java/protobuf/oidb/cmd0x11c5/NtV2RichMediaRsp.kt @@ -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 + @ProtoNumber(1) val rkeys: List? ) @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, - @ProtoNumber(4) val ipv6: List, - @ProtoNumber(5) val msgSeq: ULong, + @ProtoNumber(1) val ukey: String?, + @ProtoNumber(2) val ukeyTtlSec: ULong?, + @ProtoNumber(3) val ipv4: List?, + @ProtoNumber(4) val ipv6: List?, + @ProtoNumber(5) val msgSeq: ULong?, @ProtoNumber(6) val msgInfo: MsgInfo? = null, @ProtoNumber(7) val ext: List? = 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, - @ProtoNumber(5) val ipv6: List, + @ProtoNumber(1) val subType: UInt?, + @ProtoNumber(2) val ukey: String?, + @ProtoNumber(3) val ukeyTTLSec: ULong?, + @ProtoNumber(4) val ipv4: List?, + @ProtoNumber(5) val ipv6: List?, ) @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? ) diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt index 98cbaf4..fda842b 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/MsgSvc.kt @@ -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 { + 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 { 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() + 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() + }.getOrElse { + buffer.decodeProtobuf() + } 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> { diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/ElemMaker.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/ElemMaker.kt index ac7c830..0c33da3 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/ElemMaker.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/msg/maker/ElemMaker.kt @@ -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() - 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) { diff --git a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/NtV2RichMediaSvc.kt b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/NtV2RichMediaSvc.kt index 84b5486..f355ad7 100644 --- a/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/NtV2RichMediaSvc.kt +++ b/xposed/src/main/java/moe/fuqiuluo/qqinterface/servlet/transfile/NtV2RichMediaSvc.kt @@ -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() 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 { + 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 { 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()?.buffer?.decodeProtobuf() - 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().buffer + val rsp = rspBuffer.decodeProtobuf() + if (rsp.upload == null) { + return Result.failure(Exception("unable to request upload nt pic: ${rsp.head}")) + } + return Result.success(rsp.upload!!) } /** diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMessage.kt index a13d1fd..fd7dcd4 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMessage.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/SendForwardMessage.kt @@ -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) } diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadMultiMessage.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadMultiMessage.kt index 73667b6..2a12dc4 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadMultiMessage.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/action/handlers/UploadMultiMessage.kt @@ -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) } diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt index d60ff4c..983687b 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/remote/api/MessageAction.kt @@ -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")