修复资源rkey获取异常

This commit is contained in:
fuqiuluo 2024-07-16 17:47:39 +08:00
parent 823d9911a0
commit 40d2911135
8 changed files with 214 additions and 119 deletions

View File

@ -94,7 +94,8 @@ data class DeleteReq(
@Serializable @Serializable
data class DownloadRkeyReq( data class DownloadRkeyReq(
@ProtoNumber(1) val types: List<Int> @ProtoNumber(1) val types: List<Int>,
@ProtoNumber(2) val downloadType: Int
) )
@Serializable @Serializable

View File

@ -52,11 +52,11 @@ data class DownloadRkeyRsp(
@Serializable @Serializable
data class RKeyInfo( data class RKeyInfo(
@ProtoNumber(1) val rkey: String?, @ProtoNumber(1) val rkey: String,
@ProtoNumber(2) val rkeyTtlSec: ULong?, @ProtoNumber(2) val rkeyTtlSec: ULong?,
@ProtoNumber(3) val storeId: UInt = 0u, @ProtoNumber(3) val storeId: UInt = 0u,
@ProtoNumber(4) val rkeyCreateTime: UInt?, @ProtoNumber(4) val rkeyCreateTime: UInt?,
@ProtoNumber(4) val type: UInt?, @ProtoNumber(4) val type: UInt,
) )
@Serializable @Serializable

View File

@ -4,6 +4,8 @@ import com.tencent.qphone.base.remote.FromServiceMsg
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.utils.DeflateTools import moe.fuqiuluo.shamrock.utils.DeflateTools
import moe.fuqiuluo.symbols.decodeProtobuf
import protobuf.oidb.TrpcOidb
import tencent.im.oidb.oidb_sso import tencent.im.oidb.oidb_sso
fun FromServiceMsg.decodeToOidb(): oidb_sso.OIDBSSOPkg { fun FromServiceMsg.decodeToOidb(): oidb_sso.OIDBSSOPkg {
@ -17,3 +19,15 @@ fun FromServiceMsg.decodeToOidb(): oidb_sso.OIDBSSOPkg {
}) })
} }
} }
fun FromServiceMsg.decodeToTrpcOidb(): TrpcOidb {
return kotlin.runCatching {
wupBuffer.slice(4).let {
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
}.decodeProtobuf<TrpcOidb>()
}.getOrElse {
wupBuffer.let {
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
}.decodeProtobuf<TrpcOidb>()
}
}

View File

@ -21,6 +21,7 @@ import kotlinx.coroutines.withTimeoutOrNull
import moe.fuqiuluo.shamrock.config.ResourceGroup import moe.fuqiuluo.shamrock.config.ResourceGroup
import moe.fuqiuluo.shamrock.config.ShamrockConfig import moe.fuqiuluo.shamrock.config.ShamrockConfig
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.decodeToTrpcOidb
import moe.fuqiuluo.shamrock.tools.hex2ByteArray import moe.fuqiuluo.shamrock.tools.hex2ByteArray
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
@ -36,6 +37,8 @@ import protobuf.oidb.cmd0x11c5.CodecConfigReq
import protobuf.oidb.cmd0x11c5.CommonHead import protobuf.oidb.cmd0x11c5.CommonHead
import protobuf.oidb.cmd0x11c5.DownloadExt import protobuf.oidb.cmd0x11c5.DownloadExt
import protobuf.oidb.cmd0x11c5.DownloadReq import protobuf.oidb.cmd0x11c5.DownloadReq
import protobuf.oidb.cmd0x11c5.DownloadRkeyReq
import protobuf.oidb.cmd0x11c5.DownloadRkeyRsp
import protobuf.oidb.cmd0x11c5.FileInfo import protobuf.oidb.cmd0x11c5.FileInfo
import protobuf.oidb.cmd0x11c5.FileType import protobuf.oidb.cmd0x11c5.FileType
import protobuf.oidb.cmd0x11c5.IndexNode import protobuf.oidb.cmd0x11c5.IndexNode
@ -285,6 +288,44 @@ internal object NtV2RichMediaSvc: QQInterfaces() {
return Result.success(result) return Result.success(result)
} }
suspend fun getTempNtRKey(): Result<DownloadRkeyRsp> {
runCatching {
val req = NtV2RichMediaReq(
head = MultiMediaReqHead(
commonHead = CommonHead(
requestId = requestIdSeq.incrementAndGet().toULong(),
cmd = 202u
),
sceneInfo = SceneInfo(
requestType = 2u,
businessType = 1u,
sceneType = 0u,
),
clientMeta = ClientMeta(2u)
),
downloadRkey = DownloadRkeyReq(
types = listOf(10, 20),
downloadType = 2
)
).toByteArray()
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x9067_202", 0x9067, 202, req, true)
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
return Result.failure(Exception("failed to fetch NtTempRKey: ${fromServiceMsg?.wupBuffer?.toHexString()}"))
}
val trpc = fromServiceMsg.decodeToTrpcOidb()
if (trpc.buffer == null) {
return Result.failure(Exception("failed to fetch NtTempRKey: ${trpc.msg}"))
}
trpc.buffer?.decodeProtobuf<NtV2RichMediaRsp>()?.downloadRkeyRsp?.let {
return Result.success(it)
}
}.onFailure {
return Result.failure(it)
}
return Result.failure(Exception("failed to fetch NtTempRKey"))
}
/** /**
* 获取NT图片的RKEY * 获取NT图片的RKEY
*/ */
@ -353,13 +394,9 @@ internal object NtV2RichMediaSvc: QQInterfaces() {
).toByteArray() ).toByteArray()
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true) val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true)
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) { if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
return Result.failure(Exception("unable to get multimedia pic info: ${fromServiceMsg?.wupBuffer}")) return Result.failure(Exception("unable to get multimedia pic info: ${fromServiceMsg?.wupBuffer?.toHexString()}"))
}
val trpc = kotlin.runCatching {
fromServiceMsg.wupBuffer.decodeProtobuf<TrpcOidb>()
}.getOrElse {
fromServiceMsg.wupBuffer.slice(4).decodeProtobuf<TrpcOidb>()
} }
val trpc = fromServiceMsg.decodeToTrpcOidb()
if (trpc.buffer == null) { if (trpc.buffer == null) {
return Result.failure(Exception("unable to get multimedia pic info: ${trpc.msg}")) return Result.failure(Exception("unable to get multimedia pic info: ${trpc.msg}"))
} }
@ -457,11 +494,7 @@ internal object NtV2RichMediaSvc: QQInterfaces() {
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) { if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
return Result.failure(Exception("unable to request upload nt pic")) return Result.failure(Exception("unable to request upload nt pic"))
} }
val trpc = kotlin.runCatching { val trpc = fromServiceMsg.decodeToTrpcOidb()
fromServiceMsg.wupBuffer.decodeProtobuf<TrpcOidb>()
}.getOrElse {
fromServiceMsg.wupBuffer.slice(4).decodeProtobuf<TrpcOidb>()
}
if (trpc.buffer == null) { if (trpc.buffer == null) {
return Result.failure(Exception("unable to request upload nt pic: ${trpc.msg}")) return Result.failure(Exception("unable to request upload nt pic: ${trpc.msg}"))
} }

View File

@ -6,12 +6,16 @@ import com.tencent.mobileqq.transfile.FileMsg
import com.tencent.mobileqq.transfile.api.IProtoReqManager import com.tencent.mobileqq.transfile.api.IProtoReqManager
import com.tencent.mobileqq.transfile.protohandler.RichProto import com.tencent.mobileqq.transfile.protohandler.RichProto
import com.tencent.mobileqq.transfile.protohandler.RichProtoProc import com.tencent.mobileqq.transfile.protohandler.RichProtoProc
import com.tencent.qqnt.kernel.nativeinterface.Image
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.kernel.nativeinterface.PicElement
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.tools.decodeToOidb import moe.fuqiuluo.shamrock.tools.decodeToOidb
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import moe.fuqiuluo.shamrock.tools.toHexString import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.PlatformUtils import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.symbols.decodeProtobuf import moe.fuqiuluo.symbols.decodeProtobuf
@ -29,7 +33,6 @@ import qq.service.contact.ContactHelper
import tencent.im.cs.cmd0x346.cmd0x346 import tencent.im.cs.cmd0x346.cmd0x346
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6 import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
import tencent.im.oidb.cmd0xe37.cmd0xe37 import tencent.im.oidb.cmd0xe37.cmd0xe37
import tencent.im.oidb.oidb_sso
import kotlin.coroutines.resume import kotlin.coroutines.resume
private const val GPRO_PIC = "gchat.qpic.cn" private const val GPRO_PIC = "gchat.qpic.cn"
@ -150,6 +153,75 @@ internal object RichProtoSvc: QQInterfaces() {
} }
} }
suspend fun getTempPicDownloadUrl(
chatType: Int,
originalUrl: String,
md5: String,
image: PicElement,
storeId: Int = 0,
peer: String? = null,
subPeer: String? = null,
): String {
val isNtServer = originalUrl.startsWith("/download")
if (isNtServer) {
val tmpRKey = NtV2RichMediaSvc.getTempNtRKey()
if (tmpRKey.isSuccess) {
val tmpRKeyRsp = tmpRKey.getOrThrow()
val tmpRKeyMap = hashMapOf<UInt, String>()
tmpRKeyRsp.rkeys?.forEach { rKeyInfo ->
tmpRKeyMap[rKeyInfo.type] = rKeyInfo.rkey
}
val rkey = tmpRKeyMap[when(chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> 10u
MsgConstant.KCHATTYPEC2C -> 20u
MsgConstant.KCHATTYPEGUILD -> 10u
else -> 0u
}]
if (rkey != null) {
return "https://$MULTIMEDIA_DOMAIN$originalUrl$rkey"
}
}
}
return when (chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> getGroupPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = peer ?: "0"
)
MsgConstant.KCHATTYPEC2C -> getC2CPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = peer ?: "0",
storeId = storeId
)
MsgConstant.KCHATTYPEGUILD -> getGuildPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = peer ?: "0",
subPeer = subPeer ?: "0"
)
else -> throw UnsupportedOperationException("Not supported chat type: $chatType")
}
}
suspend fun getGroupPicDownUrl( suspend fun getGroupPicDownUrl(
originalUrl: String, originalUrl: String,
md5: String, md5: String,

View File

@ -13,8 +13,11 @@ import moe.fuqiuluo.shamrock.config.AliveReply
import moe.fuqiuluo.shamrock.config.get import moe.fuqiuluo.shamrock.config.get
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.helper.db.ImageDB
import moe.fuqiuluo.shamrock.helper.db.ImageMapping
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
import moe.fuqiuluo.shamrock.utils.PlatformUtils import moe.fuqiuluo.shamrock.utils.PlatformUtils
import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_8_VER
import qq.service.bdh.RichProtoSvc import qq.service.bdh.RichProtoSvc
import qq.service.file.GroupFileHelper import qq.service.file.GroupFileHelper
import qq.service.group.GroupHelper import qq.service.group.GroupHelper
@ -34,6 +37,65 @@ object AioListener : SimpleKernelMsgListener() {
} }
} }
private suspend fun debugTest(record: MsgRecord, text: String) {
if (record.chatType == MsgConstant.KCHATTYPEGROUP && text == ".shamrock.members") {
val contact = MessageHelper.generateContact(record)
GroupHelper.getGroupMemberList(record.peerUin, true).onSuccess {
MessageHelper.sendMessage(contact, arrayListOf(
MsgElement().apply {
elementType = MsgConstant.KELEMTYPETEXT
textElement = TextElement().apply {
content = "memberCount: ${it.size}"
}
}
), 3, MessageHelper.generateMsgId(record.chatType))
}.onFailure {
LogCenter.log("获取群成员列表失败: $it", Level.ERROR)
}
} else if (record.chatType == MsgConstant.KCHATTYPEGROUP && text == ".shamrock.root_files") {
val contact = MessageHelper.generateContact(record)
val files = GroupFileHelper.getGroupFiles(record.peerUin)
MessageHelper.sendMessage(contact, arrayListOf(
MsgElement().apply {
elementType = MsgConstant.KELEMTYPETEXT
textElement = TextElement().apply {
content = "foldersCount: ${files.foldersCount}\nfilesCount: ${files.filesCount}"
}
}
), 3, MessageHelper.generateMsgId(record.chatType))
} else if (record.chatType == MsgConstant.KCHATTYPEGROUP && text == ".shamrock.pic_url") {
val contact = MessageHelper.generateContact(record)
val pic = record.elements.filter {
it.elementType == MsgConstant.KELEMTYPEPIC
}.map {
val image = it.picElement
val md5 = (image.md5HexStr ?: image.fileName
.replace("{", "")
.replace("}", "")
.replace("-", "").split(".")[0])
.uppercase()
var storeId = 0
if (PlatformUtils.getQQVersionCode() > QQ_9_0_8_VER) {
storeId = image.storeID
}
val originalUrl = image.originImageUrl ?: ""
return@map RichProtoSvc.getTempPicDownloadUrl(record.chatType, originalUrl, md5, image, storeId)
}
MessageHelper.sendMessage(contact, arrayListOf(
MsgElement().apply {
elementType = MsgConstant.KELEMTYPETEXT
textElement = TextElement().apply {
content = "picUrl: \n${
pic.joinToString("\n")
}"
}
}
), 3, MessageHelper.generateMsgId(record.chatType))
}
}
private suspend fun onMsg(record: MsgRecord) { private suspend fun onMsg(record: MsgRecord) {
if (AliveReply.get()) { if (AliveReply.get()) {
val texts = record.elements.filter { it.elementType == MsgConstant.KELEMTYPETEXT } val texts = record.elements.filter { it.elementType == MsgConstant.KELEMTYPETEXT }
@ -50,33 +112,7 @@ object AioListener : SimpleKernelMsgListener() {
), 3, MessageHelper.generateMsgId(record.chatType)) ), 3, MessageHelper.generateMsgId(record.chatType))
return return
} }
debugTest(record, text)
if (record.chatType == MsgConstant.KCHATTYPEGROUP && text == ".shamrock.members") {
val contact = MessageHelper.generateContact(record)
GroupHelper.getGroupMemberList(record.peerUin, true).onSuccess {
MessageHelper.sendMessage(contact, arrayListOf(
MsgElement().apply {
elementType = MsgConstant.KELEMTYPETEXT
textElement = TextElement().apply {
content = "memberCount: ${it.size}"
}
}
), 3, MessageHelper.generateMsgId(record.chatType))
}.onFailure {
LogCenter.log("获取群成员列表失败: $it", Level.ERROR)
}
} else if (record.chatType == MsgConstant.KCHATTYPEGROUP && text == ".shamrock.root_files") {
val contact = MessageHelper.generateContact(record)
val files = GroupFileHelper.getGroupFiles(record.peerUin)
MessageHelper.sendMessage(contact, arrayListOf(
MsgElement().apply {
elementType = MsgConstant.KELEMTYPETEXT
textElement = TextElement().apply {
content = "foldersCount: ${files.foldersCount}\nfilesCount: ${files.filesCount}"
}
}
), 3, MessageHelper.generateMsgId(record.chatType))
}
} }
when (record.chatType) { when (record.chatType) {
MsgConstant.KCHATTYPEGROUP -> { MsgConstant.KCHATTYPEGROUP -> {

View File

@ -157,44 +157,20 @@ private object MsgConvertor {
elem.type = ElementType.IMAGE elem.type = ElementType.IMAGE
elem.setImage(ImageElement.newBuilder().apply { elem.setImage(ImageElement.newBuilder().apply {
this.file = ByteString.copyFromUtf8(md5) this.file = ByteString.copyFromUtf8(md5)
this.fileUrl = when (record.chatType) { this.fileUrl = RichProtoSvc.getTempPicDownloadUrl(record.chatType, originalUrl, md5, image, storeId,
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( peer = when(record.chatType) {
originalUrl = originalUrl, MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> record.peerUin.toString()
md5 = md5, MsgConstant.KCHATTYPEC2C -> record.senderUin.toString()
fileId = image.fileUuid, MsgConstant.KCHATTYPEGUILD -> record.channelId.ifNullOrEmpty { record.peerUin.toString() } ?: "0"
width = image.picWidth.toUInt(), else -> null
height = image.picHeight.toUInt(), },
sha = "", subPeer = when(record.chatType) {
fileSize = image.fileSize.toULong(), MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> null
peer = record.peerUin.toString() MsgConstant.KCHATTYPEC2C -> null
) MsgConstant.KCHATTYPEGUILD -> record.guildId ?: "0"
else -> null
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl( }
originalUrl = originalUrl, )
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = record.senderUin.toString(),
storeId = storeId
)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = record.channelId.ifNullOrEmpty { record.peerUin.toString() } ?: "0",
subPeer = record.guildId ?: "0"
)
else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}")
}
this.fileType = this.fileType =
if (image.isFlashPic == true) ImageElement.ImageType.FLASH else if (image.original) ImageElement.ImageType.ORIGIN else ImageElement.ImageType.COMMON if (image.isFlashPic == true) ImageElement.ImageType.FLASH else if (image.original) ImageElement.ImageType.ORIGIN else ImageElement.ImageType.COMMON
this.subType = image.picSubType this.subType = image.picSubType

View File

@ -136,44 +136,7 @@ private object ReqMsgConvertor {
val elem = Element.newBuilder() val elem = Element.newBuilder()
elem.setImage(ImageElement.newBuilder().apply { elem.setImage(ImageElement.newBuilder().apply {
this.fileMd5 = md5 this.fileMd5 = md5
this.fileUrl = when (contact.chatType) { this.fileUrl = RichProtoSvc.getTempPicDownloadUrl(contact.chatType, originalUrl, md5, image, storeId, contact.peerUid, contact.guildId)
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = contact.longPeer().toString()
)
MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = contact.longPeer().toString(),
storeId = storeId
)
MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGuildPicDownUrl(
originalUrl = originalUrl,
md5 = md5,
fileId = image.fileUuid,
width = image.picWidth.toUInt(),
height = image.picHeight.toUInt(),
sha = "",
fileSize = image.fileSize.toULong(),
peer = contact.longPeer().toString(),
subPeer = "0"
)
else -> throw UnsupportedOperationException("Not supported chat type: ${contact.chatType}")
}
this.fileType = this.fileType =
if (image.isFlashPic == true) ImageElement.ImageType.FLASH else if (image.original) ImageElement.ImageType.ORIGIN else ImageElement.ImageType.COMMON if (image.isFlashPic == true) ImageElement.ImageType.FLASH else if (image.original) ImageElement.ImageType.ORIGIN else ImageElement.ImageType.COMMON
this.subType = image.picSubType this.subType = image.picSubType