mirror of
https://github.com/whitechi73/OpenShamrock.git
synced 2024-08-14 05:12:17 +00:00
Compare commits
4 Commits
4adbc12a0b
...
master
Author | SHA1 | Date | |
---|---|---|---|
40ffa2b71b | |||
cca2cbbbd7 | |||
40d2911135 | |||
823d9911a0 |
1
.github/ISSUE_TEMPLATE/bug.md
vendored
1
.github/ISSUE_TEMPLATE/bug.md
vendored
@ -21,6 +21,7 @@ labels: bug
|
||||
## 系统信息
|
||||
|
||||
- Shamrock 版本:
|
||||
- QQ 版本:
|
||||
- Android 版本:
|
||||
- LSPosed 框架版本:
|
||||
- 设备的制造商和型号:
|
||||
|
@ -28,6 +28,8 @@
|
||||
|
||||
## 兼容|迁移|替代 说明
|
||||
|
||||
仅支持QQ9.0.70以上的版本,低版本问题将不再修复与处理。
|
||||
|
||||
- 一键移植:本项目基于 go-cqhttp 的文档进行开发实现。
|
||||
- 平行部署:可多平台部署。
|
||||
|
||||
|
@ -17,7 +17,7 @@ android {
|
||||
minSdk = 27
|
||||
targetSdk = 34
|
||||
versionCode = getVersionCode()
|
||||
versionName = "1.1.0" + ".r${getGitCommitCount()}." + getVersionName()
|
||||
versionName = "1.1.1" + ".r${getGitCommitCount()}." + getVersionName()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
@ -94,7 +94,8 @@ data class DeleteReq(
|
||||
|
||||
@Serializable
|
||||
data class DownloadRkeyReq(
|
||||
@ProtoNumber(1) val types: List<Int>
|
||||
@ProtoNumber(1) val types: List<Int>,
|
||||
@ProtoNumber(2) val downloadType: Int
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
@ -52,11 +52,11 @@ data class DownloadRkeyRsp(
|
||||
|
||||
@Serializable
|
||||
data class RKeyInfo(
|
||||
@ProtoNumber(1) val rkey: String?,
|
||||
@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?,
|
||||
@ProtoNumber(4) val type: UInt,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
@ -3,6 +3,7 @@ package kritor.service
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.kritor.file.*
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.auto.toByteArray
|
||||
@ -33,8 +34,7 @@ internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCorout
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||
val rsp = oidbPkg.bytes_bodybuffer.get()
|
||||
.toByteArray()
|
||||
.decodeProtobuf<Oidb0x6d7RespBody>()
|
||||
@ -61,8 +61,7 @@ internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCorout
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
||||
if (rsp.deleteFolder?.retCode != 0) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete folder: ${rsp.deleteFolder?.retCode}"))
|
||||
@ -86,8 +85,7 @@ internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCorout
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||
val rsp = oidb_0x6d6.RspBody().apply {
|
||||
mergeFrom(oidbPkg.bytes_bodybuffer.get().toByteArray())
|
||||
}
|
||||
@ -112,8 +110,7 @@ internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCorout
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
|
||||
}
|
||||
val oidbPkg = oidb_sso.OIDBSSOPkg()
|
||||
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val oidbPkg = fromServiceMsg.decodeToOidb()
|
||||
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
|
||||
if (rsp.renameFolder?.retCode != 0) {
|
||||
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to rename folder: ${rsp.renameFolder?.retCode}"))
|
||||
|
33
xposed/src/main/java/moe/fuqiuluo/shamrock/tools/Trpc.kt
Normal file
33
xposed/src/main/java/moe/fuqiuluo/shamrock/tools/Trpc.kt
Normal file
@ -0,0 +1,33 @@
|
||||
package moe.fuqiuluo.shamrock.tools
|
||||
|
||||
import com.tencent.qphone.base.remote.FromServiceMsg
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
import protobuf.oidb.TrpcOidb
|
||||
import tencent.im.oidb.oidb_sso
|
||||
|
||||
fun FromServiceMsg.decodeToOidb(): oidb_sso.OIDBSSOPkg {
|
||||
return kotlin.runCatching {
|
||||
oidb_sso.OIDBSSOPkg().mergeFrom(wupBuffer.slice(4).let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
}.getOrElse {
|
||||
oidb_sso.OIDBSSOPkg().mergeFrom(wupBuffer.let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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>()
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ import kotlin.random.Random
|
||||
|
||||
internal object PlatformUtils {
|
||||
const val QQ_9_0_8_VER = 5540
|
||||
const val QQ_9_0_65_VER = 6566
|
||||
|
||||
fun getQUA(): String {
|
||||
return "V1_AND_SQ_${getQQVersion(MobileQQ.getContext())}_${getQQVersionCode()}_YYB_D"
|
||||
|
@ -21,6 +21,7 @@ import kotlinx.coroutines.withTimeoutOrNull
|
||||
import moe.fuqiuluo.shamrock.config.ResourceGroup
|
||||
import moe.fuqiuluo.shamrock.config.ShamrockConfig
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToTrpcOidb
|
||||
import moe.fuqiuluo.shamrock.tools.hex2ByteArray
|
||||
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
@ -36,6 +37,8 @@ import protobuf.oidb.cmd0x11c5.CodecConfigReq
|
||||
import protobuf.oidb.cmd0x11c5.CommonHead
|
||||
import protobuf.oidb.cmd0x11c5.DownloadExt
|
||||
import protobuf.oidb.cmd0x11c5.DownloadReq
|
||||
import protobuf.oidb.cmd0x11c5.DownloadRkeyReq
|
||||
import protobuf.oidb.cmd0x11c5.DownloadRkeyRsp
|
||||
import protobuf.oidb.cmd0x11c5.FileInfo
|
||||
import protobuf.oidb.cmd0x11c5.FileType
|
||||
import protobuf.oidb.cmd0x11c5.IndexNode
|
||||
@ -285,6 +288,44 @@ internal object NtV2RichMediaSvc: QQInterfaces() {
|
||||
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
|
||||
*/
|
||||
@ -353,13 +394,9 @@ internal object NtV2RichMediaSvc: QQInterfaces() {
|
||||
).toByteArray()
|
||||
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11c5_200", 4549, 200, req, true)
|
||||
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||
return Result.failure(Exception("unable to get multimedia pic info: ${fromServiceMsg?.wupBuffer}"))
|
||||
}
|
||||
val trpc = kotlin.runCatching {
|
||||
fromServiceMsg.wupBuffer.decodeProtobuf<TrpcOidb>()
|
||||
}.getOrElse {
|
||||
fromServiceMsg.wupBuffer.slice(4).decodeProtobuf<TrpcOidb>()
|
||||
return Result.failure(Exception("unable to get multimedia pic info: ${fromServiceMsg?.wupBuffer?.toHexString()}"))
|
||||
}
|
||||
val trpc = fromServiceMsg.decodeToTrpcOidb()
|
||||
if (trpc.buffer == null) {
|
||||
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) {
|
||||
return Result.failure(Exception("unable to request upload nt pic"))
|
||||
}
|
||||
val trpc = kotlin.runCatching {
|
||||
fromServiceMsg.wupBuffer.decodeProtobuf<TrpcOidb>()
|
||||
}.getOrElse {
|
||||
fromServiceMsg.wupBuffer.slice(4).decodeProtobuf<TrpcOidb>()
|
||||
}
|
||||
val trpc = fromServiceMsg.decodeToTrpcOidb()
|
||||
if (trpc.buffer == null) {
|
||||
return Result.failure(Exception("unable to request upload nt pic: ${trpc.msg}"))
|
||||
}
|
||||
|
@ -6,11 +6,16 @@ import com.tencent.mobileqq.transfile.FileMsg
|
||||
import com.tencent.mobileqq.transfile.api.IProtoReqManager
|
||||
import com.tencent.mobileqq.transfile.protohandler.RichProto
|
||||
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.serialization.ExperimentalSerializationApi
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.symbols.decodeProtobuf
|
||||
@ -28,7 +33,6 @@ import qq.service.contact.ContactHelper
|
||||
import tencent.im.cs.cmd0x346.cmd0x346
|
||||
import tencent.im.oidb.cmd0x6d6.oidb_0x6d6
|
||||
import tencent.im.oidb.cmd0xe37.cmd0xe37
|
||||
import tencent.im.oidb.oidb_sso
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
private const val GPRO_PIC = "gchat.qpic.cn"
|
||||
@ -53,8 +57,7 @@ internal object RichProtoSvc: QQInterfaces() {
|
||||
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||
return ""
|
||||
}
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
body.bytes_bodybuffer
|
||||
.get().toByteArray()
|
||||
.decodeProtobuf<Oidb0xfc2RspBody>()
|
||||
@ -82,8 +85,7 @@ internal object RichProtoSvc: QQInterfaces() {
|
||||
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
|
||||
return ""
|
||||
}
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
if (body.uint32_result.get() != 0
|
||||
|| result.download_file_rsp.int32_ret_code.get() != 0) {
|
||||
@ -130,8 +132,7 @@ internal object RichProtoSvc: QQInterfaces() {
|
||||
}
|
||||
return ""
|
||||
} else {
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
val result = cmd0x346.RspBody().mergeFrom(cmd0xe37.Resp0xe37().mergeFrom(
|
||||
body.bytes_bodybuffer.get().toByteArray()
|
||||
).bytes_cmd_0x346_rsp_body.get().toByteArray())
|
||||
@ -152,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(
|
||||
originalUrl: String,
|
||||
md5: String,
|
||||
|
@ -11,6 +11,7 @@ import com.tencent.protofile.join_group_link.join_group_link
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import qq.service.internals.NTServiceFetcher
|
||||
import qq.service.QQInterfaces
|
||||
@ -190,8 +191,7 @@ internal object ContactHelper: QQInterfaces() {
|
||||
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11ca_0", 4790, 0, reqBody.toByteArray())
|
||||
?: error("unable to fetch contact ark_json_text")
|
||||
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
val rsp = oidb_0x11b2.BusinessCardV3Rsp()
|
||||
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
return rsp.signed_ark_msg.get()
|
||||
|
@ -1,5 +1,3 @@
|
||||
@file:OptIn(ExperimentalStdlibApi::class)
|
||||
|
||||
package qq.service.file
|
||||
|
||||
import com.tencent.mobileqq.pb.ByteStringMicro
|
||||
@ -9,6 +7,7 @@ import io.kritor.file.*
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
@ -32,16 +31,7 @@ internal object GroupFileHelper: QQInterfaces() {
|
||||
val fileCnt: Int
|
||||
val limitCnt: Int
|
||||
if (fromServiceMsg.wupBuffer != null) {
|
||||
val oidb1 = kotlin.runCatching {
|
||||
oidb_sso.OIDBSSOPkg().mergeFrom(fromServiceMsg.wupBuffer.let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
}.getOrElse {
|
||||
LogCenter.log("unable to parse oidb response(OidbSvc.0x6d8_1): ${fromServiceMsg.wupBuffer.toHexString()}, ${it.stackTraceToString()}", Level.ERROR)
|
||||
oidb_sso.OIDBSSOPkg().mergeFrom(fromServiceMsg.wupBuffer.slice(4).let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
}
|
||||
val oidb1 = fromServiceMsg.decodeToOidb()
|
||||
|
||||
oidb_0x6d8.RspBody().mergeFrom(oidb1.bytes_bodybuffer.get().toByteArray()).group_file_cnt_rsp.apply {
|
||||
fileCnt = uint32_all_file_count.get()
|
||||
@ -60,17 +50,7 @@ internal object GroupFileHelper: QQInterfaces() {
|
||||
val totalSpace: Long
|
||||
val usedSpace: Long
|
||||
if (fromServiceMsg2.isSuccess && fromServiceMsg2.wupBuffer != null) {
|
||||
val oidb2 = kotlin.runCatching {
|
||||
oidb_sso.OIDBSSOPkg().mergeFrom(fromServiceMsg2.wupBuffer.let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
}.onFailure {
|
||||
LogCenter.log("unable to parse oidb response(OidbSvc.0x6d8_1): ${fromServiceMsg2.wupBuffer.toHexString()}, ${it.stackTraceToString()}", Level.ERROR)
|
||||
}.getOrElse {
|
||||
oidb_sso.OIDBSSOPkg().mergeFrom(fromServiceMsg2.wupBuffer.slice(4).let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
}
|
||||
val oidb2 = fromServiceMsg2.decodeToOidb()
|
||||
|
||||
oidb_0x6d8.RspBody().mergeFrom(oidb2.bytes_bodybuffer.get().toByteArray()).group_space_rsp.apply {
|
||||
totalSpace = uint64_total_space.get()
|
||||
@ -117,17 +97,7 @@ internal object GroupFileHelper: QQInterfaces() {
|
||||
val files = arrayListOf<File>()
|
||||
val folders = arrayListOf<Folder>()
|
||||
if (fromServiceMsg.wupBuffer != null) {
|
||||
val oidb = kotlin.runCatching {
|
||||
oidb_sso.OIDBSSOPkg().mergeFrom(fromServiceMsg.wupBuffer.slice(4).let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
}.onFailure {
|
||||
LogCenter.log("unable to parse oidb response: ${fromServiceMsg.wupBuffer.toHexString()}", Level.ERROR)
|
||||
}.getOrElse {
|
||||
oidb_sso.OIDBSSOPkg().mergeFrom(fromServiceMsg.wupBuffer.let {
|
||||
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
|
||||
})
|
||||
}
|
||||
val oidb = fromServiceMsg.decodeToOidb()
|
||||
|
||||
oidb_0x6d8.RspBody().mergeFrom(oidb.bytes_bodybuffer.get().toByteArray())
|
||||
.file_list_info_rsp.apply {
|
||||
|
@ -19,10 +19,13 @@ import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
import moe.fuqiuluo.shamrock.helper.LogCenter
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import qq.service.internals.NTServiceFetcher
|
||||
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
|
||||
import moe.fuqiuluo.shamrock.tools.putBuf32Long
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_65_VER
|
||||
import protobuf.auto.toByteArray
|
||||
import protobuf.oidb.cmd0xf16.Oidb0xf16
|
||||
import protobuf.oidb.cmd0xf16.SetGroupRemarkReq
|
||||
@ -453,8 +456,7 @@ internal object GroupHelper: QQInterfaces() {
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
return Result.failure(RuntimeException("[oidb] failed"))
|
||||
}
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
if(body.uint32_result.get() != 0) {
|
||||
return Result.failure(RuntimeException(body.str_error_msg.get()))
|
||||
}
|
||||
@ -475,8 +477,7 @@ internal object GroupHelper: QQInterfaces() {
|
||||
if (fromServiceMsg.wupBuffer == null) {
|
||||
return Result.failure(RuntimeException("[oidb] failed"))
|
||||
}
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
if(body.uint32_result.get() != 0) {
|
||||
return Result.failure(RuntimeException(body.str_error_msg.get()))
|
||||
}
|
||||
@ -637,6 +638,9 @@ internal object GroupHelper: QQInterfaces() {
|
||||
}
|
||||
|
||||
private suspend fun requestTroopMemberInfo(service: ITroopMemberInfoService, groupId: String, memberUin: String, timeout: Long = 10_000): Result<TroopMemberInfo> {
|
||||
if(PlatformUtils.getQQVersionCode() >= QQ_9_0_65_VER) {
|
||||
return Result.failure(Exception("当前版本不支持该API"))
|
||||
}
|
||||
val info = RefreshTroopMemberInfoLock.withLock {
|
||||
service.deleteTroopMember(groupId, memberUin)
|
||||
|
||||
|
@ -13,8 +13,11 @@ import moe.fuqiuluo.shamrock.config.AliveReply
|
||||
import moe.fuqiuluo.shamrock.config.get
|
||||
import moe.fuqiuluo.shamrock.helper.Level
|
||||
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.utils.PlatformUtils
|
||||
import moe.fuqiuluo.shamrock.utils.PlatformUtils.QQ_9_0_8_VER
|
||||
import qq.service.bdh.RichProtoSvc
|
||||
import qq.service.file.GroupFileHelper
|
||||
import qq.service.group.GroupHelper
|
||||
@ -34,23 +37,7 @@ object AioListener : SimpleKernelMsgListener() {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onMsg(record: MsgRecord) {
|
||||
if (AliveReply.get()) {
|
||||
val texts = record.elements.filter { it.elementType == MsgConstant.KELEMTYPETEXT }
|
||||
val text = texts.joinToString { it.textElement.content }
|
||||
if (texts.isNotEmpty() && text == "ping") {
|
||||
val contact = MessageHelper.generateContact(record)
|
||||
MessageHelper.sendMessage(contact, arrayListOf(
|
||||
MsgElement().apply {
|
||||
elementType = MsgConstant.KELEMTYPETEXT
|
||||
textElement = TextElement().apply {
|
||||
content = "pong"
|
||||
}
|
||||
}
|
||||
), 3, MessageHelper.generateMsgId(record.chatType))
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -76,7 +63,56 @@ object AioListener : SimpleKernelMsgListener() {
|
||||
}
|
||||
}
|
||||
), 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) {
|
||||
if (AliveReply.get()) {
|
||||
val texts = record.elements.filter { it.elementType == MsgConstant.KELEMTYPETEXT }
|
||||
val text = texts.joinToString { it.textElement.content }
|
||||
if (texts.isNotEmpty() && text == "ping") {
|
||||
val contact = MessageHelper.generateContact(record)
|
||||
MessageHelper.sendMessage(contact, arrayListOf(
|
||||
MsgElement().apply {
|
||||
elementType = MsgConstant.KELEMTYPETEXT
|
||||
textElement = TextElement().apply {
|
||||
content = "pong"
|
||||
}
|
||||
}
|
||||
), 3, MessageHelper.generateMsgId(record.chatType))
|
||||
return
|
||||
}
|
||||
debugTest(record, text)
|
||||
}
|
||||
when (record.chatType) {
|
||||
MsgConstant.KCHATTYPEGROUP -> {
|
||||
|
@ -31,6 +31,7 @@ import moe.fuqiuluo.shamrock.tools.asJsonObject
|
||||
import moe.fuqiuluo.shamrock.tools.asLong
|
||||
import moe.fuqiuluo.shamrock.tools.asString
|
||||
import moe.fuqiuluo.shamrock.tools.asStringOrNull
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import moe.fuqiuluo.shamrock.tools.toHexString
|
||||
import moe.fuqiuluo.shamrock.utils.DeflateTools
|
||||
@ -108,8 +109,7 @@ internal object MessageHelper: QQInterfaces() {
|
||||
if (fromServiceMsg?.wupBuffer == null) {
|
||||
return "no response"
|
||||
}
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
return if (result.wording.has()) {
|
||||
LogCenter.log("设置群精华失败: ${result.wording.get()}", Level.WARN)
|
||||
@ -129,8 +129,7 @@ internal object MessageHelper: QQInterfaces() {
|
||||
if (fromServiceMsg?.wupBuffer == null) {
|
||||
return "no response"
|
||||
}
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
return if (result.wording.has()) {
|
||||
LogCenter.log("移除群精华失败: ${result.wording.get()}", Level.WARN)
|
||||
|
@ -157,44 +157,20 @@ private object MsgConvertor {
|
||||
elem.type = ElementType.IMAGE
|
||||
elem.setImage(ImageElement.newBuilder().apply {
|
||||
this.file = ByteString.copyFromUtf8(md5)
|
||||
this.fileUrl = when (record.chatType) {
|
||||
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 = record.peerUin.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 = 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.fileUrl = RichProtoSvc.getTempPicDownloadUrl(record.chatType, originalUrl, md5, image, storeId,
|
||||
peer = when(record.chatType) {
|
||||
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> record.peerUin.toString()
|
||||
MsgConstant.KCHATTYPEC2C -> record.senderUin.toString()
|
||||
MsgConstant.KCHATTYPEGUILD -> record.channelId.ifNullOrEmpty { record.peerUin.toString() } ?: "0"
|
||||
else -> null
|
||||
},
|
||||
subPeer = when(record.chatType) {
|
||||
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> null
|
||||
MsgConstant.KCHATTYPEC2C -> null
|
||||
MsgConstant.KCHATTYPEGUILD -> record.guildId ?: "0"
|
||||
else -> null
|
||||
}
|
||||
)
|
||||
this.fileType =
|
||||
if (image.isFlashPic == true) ImageElement.ImageType.FLASH else if (image.original) ImageElement.ImageType.ORIGIN else ImageElement.ImageType.COMMON
|
||||
this.subType = image.picSubType
|
||||
|
@ -136,44 +136,7 @@ private object ReqMsgConvertor {
|
||||
val elem = Element.newBuilder()
|
||||
elem.setImage(ImageElement.newBuilder().apply {
|
||||
this.fileMd5 = md5
|
||||
this.fileUrl = when (contact.chatType) {
|
||||
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.fileUrl = RichProtoSvc.getTempPicDownloadUrl(contact.chatType, originalUrl, md5, image, storeId, contact.peerUid, contact.guildId)
|
||||
this.fileType =
|
||||
if (image.isFlashPic == true) ImageElement.ImageType.FLASH else if (image.original) ImageElement.ImageType.ORIGIN else ImageElement.ImageType.COMMON
|
||||
this.subType = image.picSubType
|
||||
|
@ -10,6 +10,7 @@ import io.ktor.client.plugins.HttpTimeout
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import moe.fuqiuluo.shamrock.tools.GlobalClient
|
||||
import moe.fuqiuluo.shamrock.tools.decodeToOidb
|
||||
import moe.fuqiuluo.shamrock.tools.slice
|
||||
import mqq.app.MobileQQ
|
||||
import mqq.manager.TicketManager
|
||||
@ -140,8 +141,7 @@ internal object TicketHelper: QQInterfaces() {
|
||||
val fromServiceMsg = sendOidbAW("OidbSvcTcp.0x102a", 4138, 0, req.toByteArray())
|
||||
?: return Result.failure(Exception("getLessPSKey failed"))
|
||||
if (fromServiceMsg.wupBuffer == null) return Result.failure(Exception("getLessPSKey failed: no response"))
|
||||
val body = oidb_sso.OIDBSSOPkg()
|
||||
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
|
||||
val body = fromServiceMsg.decodeToOidb()
|
||||
val rsp = oidb_cmd0x102a.GetPSkeyResponse().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
|
||||
return Result.success(rsp.private_keys.get())
|
||||
}
|
||||
|
Reference in New Issue
Block a user