10 Commits

Author SHA1 Message Date
40ffa2b71b Update bug.md 2024-07-21 21:40:04 +08:00
cca2cbbbd7 Version Restriction Notice 2024-07-19 03:35:45 +08:00
40d2911135 修复资源rkey获取异常 2024-07-16 17:47:39 +08:00
823d9911a0 fix #338 2024-07-16 17:11:48 +08:00
4adbc12a0b fix /get_group_root_files 2024-07-08 09:53:15 +08:00
114fbfdd23 fix /get_group_root_files 2024-07-08 09:44:26 +08:00
0a563d60a1 fix crash in QQ9.0.71 2024-07-08 09:17:56 +08:00
5fb1d0aeb9 fix ping-pong 2024-07-08 09:11:49 +08:00
012ecaa85d fix #335 2024-07-08 08:55:49 +08:00
7ea127a279 update shamrock 2024-07-08 08:15:05 +08:00
34 changed files with 427 additions and 202 deletions

View File

@ -20,7 +20,8 @@ labels: bug
## 系统信息 ## 系统信息
- Shamrock 版本: - Shamrock 版本:
- QQ 版本:
- Android 版本: - Android 版本:
- LSPosed 框架版本: - LSPosed 框架版本:
- 设备的制造商和型号: - 设备的制造商和型号:

View File

@ -23,9 +23,13 @@
> Riru可能导致封禁请减少使用。 > Riru可能导致封禁请减少使用。
> 如有违反法律,请联系删除。 > 如有违反法律,请联系删除。
> 请勿在任何平台宣传,宣扬,转发本项目,请勿恶意修改企业安装包造成相关企业产生损失,如有违背,必将追责到底。 > 请勿在任何平台宣传,宣扬,转发本项目,请勿恶意修改企业安装包造成相关企业产生损失,如有违背,必将追责到底。
>
> 社区地址:[discord](https://discord.gg/MKR2wz863h)
## 兼容|迁移|替代 说明 ## 兼容|迁移|替代 说明
仅支持QQ9.0.70以上的版本,低版本问题将不再修复与处理。
- 一键移植:本项目基于 go-cqhttp 的文档进行开发实现。 - 一键移植:本项目基于 go-cqhttp 的文档进行开发实现。
- 平行部署:可多平台部署。 - 平行部署:可多平台部署。

View File

@ -17,7 +17,7 @@ android {
minSdk = 27 minSdk = 27
targetSdk = 34 targetSdk = 34
versionCode = getVersionCode() versionCode = getVersionCode()
versionName = "1.1.0" + ".r${getGitCommitCount()}." + getVersionName() versionName = "1.1.1" + ".r${getGitCommitCount()}." + getVersionName()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {

View File

@ -162,7 +162,7 @@ private fun FunctionCard(
Function( Function(
title = "主动RPC", title = "主动RPC",
desc = "Kritor协议实现RPC", desc = "Kritor协议实现RPC由Shamrock放出rpc服务",
isSwitch = ShamrockConfig[ctx, ActiveRPC] isSwitch = ShamrockConfig[ctx, ActiveRPC]
) { ) {
ShamrockConfig[ctx, ActiveRPC] = it ShamrockConfig[ctx, ActiveRPC] = it
@ -171,7 +171,7 @@ private fun FunctionCard(
Function( Function(
title = "被动RPC", title = "被动RPC",
desc = "Kritor协议实现RPC", desc = "Kritor协议实现RPC由客户端提供反向的rpc服务",
isSwitch = ShamrockConfig[ctx, PassiveRPC] isSwitch = ShamrockConfig[ctx, PassiveRPC]
) { ) {
ShamrockConfig[ctx, PassiveRPC] = it ShamrockConfig[ctx, PassiveRPC] = it

View File

@ -8,7 +8,9 @@ import moe.fuqiuluo.symbols.Protobuf
data class TrpcOidb( data class TrpcOidb(
@ProtoNumber(1) val cmd: Int = Int.MIN_VALUE, @ProtoNumber(1) val cmd: Int = Int.MIN_VALUE,
@ProtoNumber(2) val service: Int = Int.MIN_VALUE, @ProtoNumber(2) val service: Int = Int.MIN_VALUE,
@ProtoNumber(4) val buffer: ByteArray, @ProtoNumber(3) val result: UInt? = null,
@ProtoNumber(4) val buffer: ByteArray? = null,
@ProtoNumber(5) val msg: String? = null,
//@ProtoNumber(11) val traceParams: Map<String, String> = mapOf(), //@ProtoNumber(11) val traceParams: Map<String, String> = mapOf(),
@ProtoNumber(12) val flag: Int = Int.MIN_VALUE, @ProtoNumber(12) val flag: Int = Int.MIN_VALUE,
): Protobuf<TrpcOidb> ): Protobuf<TrpcOidb>

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

@ -0,0 +1,4 @@
package com.tencent.qqnt.kernel.nativeinterface;
public class GProGuildTopFeedMsg {
}

View File

@ -18,7 +18,7 @@ public interface IKernelMsgListener {
void onCustomWithdrawConfigUpdate(CustomWithdrawConfig customWithdrawConfig); void onCustomWithdrawConfigUpdate(CustomWithdrawConfig customWithdrawConfig);
void onDraftUpdate(Contact contact, ArrayList<MsgElement> arrayList, long j2); void onDraftUpdate(Contact contact, ArrayList<MsgElement> arrayList, long j);
void onEmojiDownloadComplete(EmojiNotifyInfo emojiNotifyInfo); void onEmojiDownloadComplete(EmojiNotifyInfo emojiNotifyInfo);
@ -32,7 +32,7 @@ public interface IKernelMsgListener {
void onFirstViewGroupGuildMapping(ArrayList<FirstViewGroupGuildInfo> arrayList); void onFirstViewGroupGuildMapping(ArrayList<FirstViewGroupGuildInfo> arrayList);
void onGrabPasswordRedBag(int i2, String str, int i3, RecvdOrder recvdOrder, MsgRecord msgRecord); void onGrabPasswordRedBag(int i, String str, int i2, RecvdOrder recvdOrder, MsgRecord msgRecord);
void onGroupFileInfoAdd(GroupItem groupItem); void onGroupFileInfoAdd(GroupItem groupItem);
@ -50,6 +50,8 @@ public interface IKernelMsgListener {
void onGuildNotificationAbstractUpdate(GuildNotificationAbstractInfo guildNotificationAbstractInfo); void onGuildNotificationAbstractUpdate(GuildNotificationAbstractInfo guildNotificationAbstractInfo);
void onGuildTopFeedUpdate(GProGuildTopFeedMsg gProGuildTopFeedMsg);
void onHitCsRelatedEmojiResult(DownloadRelateEmojiResultInfo downloadRelateEmojiResultInfo); void onHitCsRelatedEmojiResult(DownloadRelateEmojiResultInfo downloadRelateEmojiResultInfo);
void onHitEmojiKeywordResult(HitRelatedEmojiWordsResult hitRelatedEmojiWordsResult); void onHitEmojiKeywordResult(HitRelatedEmojiWordsResult hitRelatedEmojiWordsResult);
@ -64,7 +66,7 @@ public interface IKernelMsgListener {
void onLineDev(ArrayList<DevInfo> arrayList); void onLineDev(ArrayList<DevInfo> arrayList);
void onLogLevelChanged(long j2); void onLogLevelChanged(long j);
void onMsgAbstractUpdate(ArrayList<MsgAbstract> arrayList); void onMsgAbstractUpdate(ArrayList<MsgAbstract> arrayList);
@ -78,14 +80,16 @@ public interface IKernelMsgListener {
void onMsgInfoListUpdate(ArrayList<MsgRecord> arrayList); void onMsgInfoListUpdate(ArrayList<MsgRecord> arrayList);
void onMsgQRCodeStatusChanged(int i2); void onMsgQRCodeStatusChanged(int i);
void onMsgRecall(int i2, String str, long j2); void onMsgRecall(int i, String str, long j);
void onMsgSecurityNotify(MsgRecord msgRecord); void onMsgSecurityNotify(MsgRecord msgRecord);
void onMsgSettingUpdate(MsgSetting msgSetting); void onMsgSettingUpdate(MsgSetting msgSetting);
void onMsgWithRichLinkInfoUpdate(ArrayList<MsgRecord> arrayList);
void onNtFirstViewMsgSyncEnd(); void onNtFirstViewMsgSyncEnd();
void onNtMsgSyncEnd(); void onNtMsgSyncEnd();
@ -94,11 +98,11 @@ public interface IKernelMsgListener {
void onReadFeedEventUpdate(FirstViewDirectMsgNotifyInfo firstViewDirectMsgNotifyInfo); void onReadFeedEventUpdate(FirstViewDirectMsgNotifyInfo firstViewDirectMsgNotifyInfo);
void onRecvGroupGuildFlag(int i2); void onRecvGroupGuildFlag(int i);
void onRecvMsg(ArrayList<MsgRecord> arrayList); void onRecvMsg(ArrayList<MsgRecord> arrayList);
void onRecvMsgSvrRspTransInfo(long j2, Contact contact, int i2, int i3, String str, byte[] bArr); void onRecvMsgSvrRspTransInfo(long j, Contact contact, int i, int i2, String str, byte[] bArr);
void onRecvOnlineFileMsg(ArrayList<MsgRecord> arrayList); void onRecvOnlineFileMsg(ArrayList<MsgRecord> arrayList);
@ -106,7 +110,9 @@ public interface IKernelMsgListener {
void onRecvSysMsg(ArrayList<Byte> arrayList); void onRecvSysMsg(ArrayList<Byte> arrayList);
void onRecvUDCFlag(int i2); void onRecvUDCFlag(int i);
void onRedTouchChanged();
void onRichMediaDownloadComplete(FileTransNotifyInfo fileTransNotifyInfo); void onRichMediaDownloadComplete(FileTransNotifyInfo fileTransNotifyInfo);
@ -116,9 +122,9 @@ public interface IKernelMsgListener {
void onSearchGroupFileInfoUpdate(SearchGroupFileResult searchGroupFileResult); void onSearchGroupFileInfoUpdate(SearchGroupFileResult searchGroupFileResult);
void onSendMsgError(long j2, Contact contact, int i2, String str); void onSendMsgError(long j, Contact contact, int i, String str);
void onSysMsgNotification(int i2, long j2, long j3, boolean z, ArrayList<Byte> arrayList); void onSysMsgNotification(int i, long j, long j2, boolean z, ArrayList<Byte> arrayList);
void onTempChatInfoUpdate(TempChatInfo tempChatInfo); void onTempChatInfoUpdate(TempChatInfo tempChatInfo);
@ -130,9 +136,11 @@ public interface IKernelMsgListener {
void onUserOnlineStatusChanged(boolean z); void onUserOnlineStatusChanged(boolean z);
void onUserSecQualityChanged(QueryUserSecQualityRsp queryUserSecQualityRsp);
void onUserTabStatusChanged(ArrayList<TabStatusInfo> arrayList); void onUserTabStatusChanged(ArrayList<TabStatusInfo> arrayList);
void onlineStatusBigIconDownloadPush(int i2, long j2, String str); void onlineStatusBigIconDownloadPush(int i, long j, String str);
void onlineStatusSmallIconDownloadPush(int i2, long j2, String str); void onlineStatusSmallIconDownloadPush(int i, long j, String str);
} }

View File

@ -0,0 +1,4 @@
package com.tencent.qqnt.kernel.nativeinterface;
public class QueryUserSecQualityRsp {
}

View File

@ -156,8 +156,8 @@ internal object FriendService : FriendServiceGrpcKt.FriendServiceCoroutineImplBa
val bundle = Bundle() val bundle = Bundle()
val service = QQInterfaces.app val service = QQInterfaces.app
.getRuntimeService(IProfileProtocolService::class.java, "all") .getRuntimeService(IProfileProtocolService::class.java, "all")
if (request.hasNickName()) { if (request.hasNick()) {
bundle.putString(IProfileProtocolConst.KEY_NICK, request.nickName) bundle.putString(IProfileProtocolConst.KEY_NICK, request.nick)
} }
if (request.hasCompany()) { if (request.hasCompany()) {
bundle.putString(IProfileProtocolConst.KEY_COMPANY, request.company) bundle.putString(IProfileProtocolConst.KEY_COMPANY, request.company)

View File

@ -3,6 +3,7 @@ package kritor.service
import io.grpc.Status import io.grpc.Status
import io.grpc.StatusRuntimeException import io.grpc.StatusRuntimeException
import io.kritor.file.* import io.kritor.file.*
import moe.fuqiuluo.shamrock.tools.decodeToOidb
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.symbols.decodeProtobuf import moe.fuqiuluo.symbols.decodeProtobuf
import protobuf.auto.toByteArray import protobuf.auto.toByteArray
@ -33,8 +34,7 @@ internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCorout
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
val oidbPkg = oidb_sso.OIDBSSOPkg() val oidbPkg = fromServiceMsg.decodeToOidb()
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val rsp = oidbPkg.bytes_bodybuffer.get() val rsp = oidbPkg.bytes_bodybuffer.get()
.toByteArray() .toByteArray()
.decodeProtobuf<Oidb0x6d7RespBody>() .decodeProtobuf<Oidb0x6d7RespBody>()
@ -61,8 +61,7 @@ internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCorout
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
val oidbPkg = oidb_sso.OIDBSSOPkg() val oidbPkg = fromServiceMsg.decodeToOidb()
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>() val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
if (rsp.deleteFolder?.retCode != 0) { if (rsp.deleteFolder?.retCode != 0) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to delete folder: ${rsp.deleteFolder?.retCode}")) 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) { if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
val oidbPkg = oidb_sso.OIDBSSOPkg() val oidbPkg = fromServiceMsg.decodeToOidb()
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val rsp = oidb_0x6d6.RspBody().apply { val rsp = oidb_0x6d6.RspBody().apply {
mergeFrom(oidbPkg.bytes_bodybuffer.get().toByteArray()) mergeFrom(oidbPkg.bytes_bodybuffer.get().toByteArray())
} }
@ -112,8 +110,7 @@ internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCorout
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
val oidbPkg = oidb_sso.OIDBSSOPkg() val oidbPkg = fromServiceMsg.decodeToOidb()
oidbPkg.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>() val rsp = oidbPkg.bytes_bodybuffer.get().toByteArray().decodeProtobuf<Oidb0x6d7RespBody>()
if (rsp.renameFolder?.retCode != 0) { if (rsp.renameFolder?.retCode != 0) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to rename folder: ${rsp.renameFolder?.retCode}")) throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to rename folder: ${rsp.renameFolder?.retCode}"))

View File

@ -259,12 +259,12 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase(
.ifNullOrEmpty { memberInfo.friendnick } ?: "" .ifNullOrEmpty { memberInfo.friendnick } ?: ""
age = memberInfo.age.toInt() age = memberInfo.age.toInt()
uniqueTitle = memberInfo.mUniqueTitle ?: "" uniqueTitle = memberInfo.mUniqueTitle ?: ""
uniqueTitleExpireTime = memberInfo.mUniqueTitleExpire uniqueTitleExpireTime = memberInfo.mUniqueTitleExpire.toLong()
card = memberInfo.troopnick.ifNullOrEmpty { memberInfo.friendnick } ?: "" card = memberInfo.troopnick.ifNullOrEmpty { memberInfo.friendnick } ?: ""
joinTime = memberInfo.join_time joinTime = memberInfo.join_time
lastActiveTime = memberInfo.last_active_time lastActiveTime = memberInfo.last_active_time
level = memberInfo.level level = memberInfo.level
shutUpTimestamp = memberInfo.gagTimeStamp shutUpTime = memberInfo.gagTimeStamp
distance = memberInfo.distance distance = memberInfo.distance
addAllHonors((memberInfo.honorList ?: "") addAllHonors((memberInfo.honorList ?: "")
@ -295,12 +295,12 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase(
nick = memberInfo.nick ?: "" nick = memberInfo.nick ?: ""
age = 0 age = 0
uniqueTitle = memberInfo.memberSpecialTitle ?: "" uniqueTitle = memberInfo.memberSpecialTitle ?: ""
uniqueTitleExpireTime = memberInfo.specialTitleExpireTime.toInt() uniqueTitleExpireTime = memberInfo.specialTitleExpireTime
card = memberInfo.cardName.ifNullOrEmpty { memberInfo.nick } ?: "" card = memberInfo.cardName.ifNullOrEmpty { memberInfo.nick } ?: ""
joinTime = memberInfo.joinTime.toLong() joinTime = memberInfo.joinTime.toLong()
lastActiveTime = memberInfo.lastSpeakTime.toLong() lastActiveTime = memberInfo.lastSpeakTime.toLong()
level = memberInfo.memberLevel level = memberInfo.memberLevel
shutUpTimestamp = memberInfo.shutUpTime.toLong() shutUpTime = memberInfo.shutUpTime.toLong()
distance = 0 distance = 0
addAllHonors(memberInfo.groupHonor.let { bytes -> addAllHonors(memberInfo.groupHonor.let { bytes ->
@ -327,7 +327,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase(
this.addProhibitedUsersInfo(ProhibitedUserInfo.newBuilder().apply { this.addProhibitedUsersInfo(ProhibitedUserInfo.newBuilder().apply {
uid = ContactHelper.getUidByUinAsync(it.memberUin) uid = ContactHelper.getUidByUinAsync(it.memberUin)
uin = it.memberUin uin = it.memberUin
prohibitedTime = it.shutuptimestap prohibitedTime = it.shutuptimestap.toLong()
}) })
} }
}.build() }.build()
@ -360,7 +360,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase(
maxMemberCount = groupInfo.maxMember maxMemberCount = groupInfo.maxMember
memberCount = groupInfo.memberCount memberCount = groupInfo.memberCount
groupDesc = groupInfo.groupDesc groupDesc = groupInfo.groupDesc
createTime = groupInfo.createTime.toInt() createTime = groupInfo.createTime
groupFlag = groupInfo.groupFlag groupFlag = groupInfo.groupFlag
groupFlagExt = groupInfo.groupFlagExt groupFlagExt = groupInfo.groupFlagExt
}.build() }.build()

View File

@ -315,7 +315,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
throw StatusRuntimeException(Status.INTERNAL.withCause(it)) throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow().map { detail -> }.getOrThrow().map { detail ->
PushMessageBody.newBuilder().apply { PushMessageBody.newBuilder().apply {
this.time = detail.time this.time = detail.time.toLong()
this.messageId = detail.qqMsgId.toString() this.messageId = detail.qqMsgId.toString()
this.messageSeq = detail.msgSeq this.messageSeq = detail.msgSeq
this.contact = io.kritor.common.Contact.newBuilder().apply { this.contact = io.kritor.common.Contact.newBuilder().apply {
@ -398,10 +398,10 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp
this.messageId = it.msgId.toString() this.messageId = it.msgId.toString()
} }
this.messageSeq = it.messageSeq this.messageSeq = it.messageSeq
this.messageTime = it.senderTime.toInt() this.messageTime = it.senderTime
this.senderNick = it.senderNick this.senderNick = it.senderNick
this.senderUin = it.senderId this.senderUin = it.senderId
this.operationTime = it.operatorTime.toInt() this.operationTime = it.operatorTime
this.operatorNick = it.operatorNick this.operatorNick = it.operatorNick
this.operatorUin = it.operatorId this.operatorUin = it.operatorId
this.jsonElements = it.messageContent.toString() this.jsonElements = it.messageContent.toString()

View File

@ -20,6 +20,7 @@ private val configKeys = setOf(
ResourceGroup, ResourceGroup,
RPCAddress, RPCAddress,
RPCPort, RPCPort,
AliveReply,
) )
internal object ShamrockConfig: Properties() { internal object ShamrockConfig: Properties() {

View File

@ -42,7 +42,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
elements: ArrayList<MsgElement>, elements: ArrayList<MsgElement>,
): Boolean { ): Boolean {
transMessageEvent(record, PushMessageBody.newBuilder().apply { transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt() this.time = record.msgTime
this.messageId = record.msgId.toString() this.messageId = record.msgId.toString()
this.messageSeq = record.msgSeq this.messageSeq = record.msgSeq
this.contact = Contact.newBuilder().apply { this.contact = Contact.newBuilder().apply {
@ -65,7 +65,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
elements: ArrayList<MsgElement>, elements: ArrayList<MsgElement>,
): Boolean { ): Boolean {
transMessageEvent(record, PushMessageBody.newBuilder().apply { transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt() this.time = record.msgTime
this.messageId = record.msgId.toString() this.messageId = record.msgId.toString()
this.messageSeq = record.msgSeq this.messageSeq = record.msgSeq
this.contact = Contact.newBuilder().apply { this.contact = Contact.newBuilder().apply {
@ -90,7 +90,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
fromNick: String, fromNick: String,
): Boolean { ): Boolean {
transMessageEvent(record, PushMessageBody.newBuilder().apply { transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt() this.time = record.msgTime
this.messageId = record.msgId.toString() this.messageId = record.msgId.toString()
this.messageSeq = record.msgSeq this.messageSeq = record.msgSeq
this.contact = Contact.newBuilder().apply { this.contact = Contact.newBuilder().apply {
@ -113,7 +113,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
elements: ArrayList<MsgElement>, elements: ArrayList<MsgElement>,
): Boolean { ): Boolean {
transMessageEvent(record, PushMessageBody.newBuilder().apply { transMessageEvent(record, PushMessageBody.newBuilder().apply {
this.time = record.msgTime.toInt() this.time = record.msgTime
this.messageId = record.msgId.toString() this.messageId = record.msgId.toString()
this.messageSeq = record.msgSeq this.messageSeq = record.msgSeq
this.contact = Contact.newBuilder().apply { this.contact = Contact.newBuilder().apply {
@ -152,16 +152,16 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.PRIVATE_FILE_UPLOADED this.type = NoticeEvent.NoticeType.PRIVATE_FILE_UPLOADED
this.time = msgTime.toInt() this.time = msgTime
this.privateFileUploaded = PrivateFileUploadedNotice.newBuilder().apply { this.privateFileUploaded = PrivateFileUploadedNotice.newBuilder().apply {
this.fileId = fileId this.fileId = fileId
this.fileName = fileName this.fileName = fileName
this.operatorUid = senderUid this.operatorUid = senderUid
this.operatorUin = senderUin this.operatorUin = senderUin
this.fileSize = fileSize this.fileSize = fileSize
this.expireTime = expireTime.toInt() this.expireTime = expireTime
this.fileSubId = fileSubId this.fileSubId = fileSubId.toInt() // todo(这玩意真的是一个数字?)
this.url = url this.fileUrl = url
}.build() }.build()
}.build()) }.build())
return true return true
@ -183,7 +183,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_FILE_UPLOADED this.type = NoticeEvent.NoticeType.GROUP_FILE_UPLOADED
this.time = msgTime.toInt() this.time = msgTime
this.groupFileUploaded = GroupFileUploadedNotice.newBuilder().apply { this.groupFileUploaded = GroupFileUploadedNotice.newBuilder().apply {
this.groupId = groupId this.groupId = groupId
this.operatorUid = senderUid this.operatorUid = senderUid
@ -191,7 +191,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
this.fileId = uuid this.fileId = uuid
this.fileName = fileName this.fileName = fileName
this.fileSize = fileSize this.fileSize = fileSize
this.busId = bizId this.fileSubId = bizId
this.fileUrl = url this.fileUrl = url
}.build() }.build()
}.build()) }.build())
@ -212,7 +212,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_SIGN_IN this.type = NoticeEvent.NoticeType.GROUP_SIGN_IN
this.time = time.toInt() this.time = time
this.groupSignIn = GroupSignInNotice.newBuilder().apply { this.groupSignIn = GroupSignInNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.targetUid = ContactHelper.getUidByUinAsync(target) this.targetUid = ContactHelper.getUidByUinAsync(target)
@ -235,7 +235,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_POKE this.type = NoticeEvent.NoticeType.GROUP_POKE
this.time = time.toInt() this.time = time
this.groupPoke = GroupPokeNotice.newBuilder().apply { this.groupPoke = GroupPokeNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.action = action this.action = action
@ -261,7 +261,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_MEMBER_INCREASE this.type = NoticeEvent.NoticeType.GROUP_MEMBER_INCREASE
this.time = time.toInt() this.time = time
this.groupMemberIncrease = GroupMemberIncreasedNotice.newBuilder().apply { this.groupMemberIncrease = GroupMemberIncreasedNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.operatorUid = operatorUid this.operatorUid = operatorUid
@ -285,7 +285,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_MEMBER_DECREASE this.type = NoticeEvent.NoticeType.GROUP_MEMBER_DECREASE
this.time = time.toInt() this.time = time
this.groupMemberDecrease = GroupMemberDecreasedNotice.newBuilder().apply { this.groupMemberDecrease = GroupMemberDecreasedNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.operatorUid = operatorUid this.operatorUid = operatorUid
@ -307,7 +307,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_ADMIN_CHANGED this.type = NoticeEvent.NoticeType.GROUP_ADMIN_CHANGED
this.time = msgTime.toInt() this.time = msgTime
this.groupAdminChanged = GroupAdminChangedNotice.newBuilder().apply { this.groupAdminChanged = GroupAdminChangedNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.targetUid = targetUid this.targetUid = targetUid
@ -327,7 +327,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_WHOLE_BAN this.type = NoticeEvent.NoticeType.GROUP_WHOLE_BAN
this.time = msgTime.toInt() this.time = msgTime
this.groupWholeBan = GroupWholeBanNotice.newBuilder().apply { this.groupWholeBan = GroupWholeBanNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.isBan = isOpen this.isBan = isOpen
@ -349,7 +349,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_MEMBER_BAN this.type = NoticeEvent.NoticeType.GROUP_MEMBER_BAN
this.time = msgTime.toInt() this.time = msgTime
this.groupMemberBan = GroupMemberBanNotice.newBuilder().apply { this.groupMemberBan = GroupMemberBanNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.operatorUid = operatorUid this.operatorUid = operatorUid
@ -376,7 +376,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_RECALL this.type = NoticeEvent.NoticeType.GROUP_RECALL
this.time = time.toInt() this.time = time
this.groupRecall = GroupRecallNotice.newBuilder().apply { this.groupRecall = GroupRecallNotice.newBuilder().apply {
this.groupId = groupCode this.groupId = groupCode
this.operatorUid = operatorUid this.operatorUid = operatorUid
@ -398,7 +398,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_CARD_CHANGED this.type = NoticeEvent.NoticeType.GROUP_CARD_CHANGED
this.time = time.toInt() this.time = time
this.groupCardChanged = GroupCardChangedNotice.newBuilder().apply { this.groupCardChanged = GroupCardChangedNotice.newBuilder().apply {
this.groupId = groupId this.groupId = groupId
this.targetUin = targetId this.targetUin = targetId
@ -416,7 +416,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_MEMBER_UNIQUE_TITLE_CHANGED this.type = NoticeEvent.NoticeType.GROUP_MEMBER_UNIQUE_TITLE_CHANGED
this.time = time.toInt() this.time = time
this.groupMemberUniqueTitleChanged = GroupUniqueTitleChangedNotice.newBuilder().apply { this.groupMemberUniqueTitleChanged = GroupUniqueTitleChangedNotice.newBuilder().apply {
this.groupId = groupId this.groupId = groupId
this.targetUid = ContactHelper.getUidByUinAsync(targetUin) this.targetUid = ContactHelper.getUidByUinAsync(targetUin)
@ -437,7 +437,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.GROUP_ESSENCE_CHANGED this.type = NoticeEvent.NoticeType.GROUP_ESSENCE_CHANGED
this.time = time.toInt() this.time = time
this.groupEssenceChanged = GroupEssenceMessageNotice.newBuilder().apply { this.groupEssenceChanged = GroupEssenceMessageNotice.newBuilder().apply {
this.groupId = groupId this.groupId = groupId
this.messageId = msgId.toString() this.messageId = msgId.toString()
@ -465,7 +465,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.PRIVATE_POKE this.type = NoticeEvent.NoticeType.PRIVATE_POKE
this.time = msgTime.toInt() this.time = msgTime
this.privatePoke = PrivatePokeNotice.newBuilder().apply { this.privatePoke = PrivatePokeNotice.newBuilder().apply {
this.action = action ?: "" this.action = action ?: ""
this.operatorUid = ContactHelper.getUidByUinAsync(operator) this.operatorUid = ContactHelper.getUidByUinAsync(operator)
@ -480,7 +480,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
suspend fun transPrivateRecall(time: Long, operator: Long, msgId: Long, tipText: String): Boolean { suspend fun transPrivateRecall(time: Long, operator: Long, msgId: Long, tipText: String): Boolean {
pushNotice(NoticeEvent.newBuilder().apply { pushNotice(NoticeEvent.newBuilder().apply {
this.type = NoticeEvent.NoticeType.PRIVATE_RECALL this.type = NoticeEvent.NoticeType.PRIVATE_RECALL
this.time = time.toInt() this.time = time
this.privateRecall = PrivateRecallNotice.newBuilder().apply { this.privateRecall = PrivateRecallNotice.newBuilder().apply {
this.operatorUin = operator this.operatorUin = operator
this.messageId = msgId.toString() this.messageId = msgId.toString()
@ -499,7 +499,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
suspend fun transFriendApp(time: Long, applierUid: String, operator: Long, tipText: String, flag: String): Boolean { suspend fun transFriendApp(time: Long, applierUid: String, operator: Long, tipText: String, flag: String): Boolean {
pushRequest(RequestEvent.newBuilder().apply { pushRequest(RequestEvent.newBuilder().apply {
this.type = RequestEvent.RequestType.FRIEND_APPLY this.type = RequestEvent.RequestType.FRIEND_APPLY
this.time = time.toInt() this.time = time
this.requestId = flag this.requestId = flag
this.friendApply = FriendApplyRequest.newBuilder().apply { this.friendApply = FriendApplyRequest.newBuilder().apply {
this.applierUid = applierUid this.applierUid = applierUid
@ -520,7 +520,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushRequest(RequestEvent.newBuilder().apply { pushRequest(RequestEvent.newBuilder().apply {
this.type = RequestEvent.RequestType.GROUP_APPLY this.type = RequestEvent.RequestType.GROUP_APPLY
this.time = time.toInt() this.time = time
this.requestId = flag this.requestId = flag
this.groupApply = GroupApplyRequest.newBuilder().apply { this.groupApply = GroupApplyRequest.newBuilder().apply {
this.applierUid = applierUid this.applierUid = applierUid
@ -541,7 +541,7 @@ internal object GlobalEventTransmitter : QQInterfaces() {
): Boolean { ): Boolean {
pushRequest(RequestEvent.newBuilder().apply { pushRequest(RequestEvent.newBuilder().apply {
this.type = RequestEvent.RequestType.GROUP_APPLY this.type = RequestEvent.RequestType.GROUP_APPLY
this.time = time.toInt() this.time = time
this.requestId = flag this.requestId = flag
this.invitedGroup = InvitedJoinGroupRequest.newBuilder().apply { this.invitedGroup = InvitedJoinGroupRequest.newBuilder().apply {
this.inviterUid = inviterUid this.inviterUid = inviterUid

View 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>()
}
}

View File

@ -6,17 +6,21 @@ import android.content.Context
import android.content.Context.BATTERY_SERVICE import android.content.Context.BATTERY_SERVICE
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.os.BatteryManager import android.os.BatteryManager
import android.os.Build import android.os.Build
import android.os.Process import android.os.Process
import android.provider.Settings import android.provider.Settings
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import moe.fuqiuluo.shamrock.tools.ShamrockVersion
import mqq.app.MobileQQ import mqq.app.MobileQQ
import kotlin.random.Random import kotlin.random.Random
internal object PlatformUtils { internal object PlatformUtils {
const val QQ_9_0_8_VER = 5540 const val QQ_9_0_8_VER = 5540
const val QQ_9_0_65_VER = 6566
fun getQUA(): String { fun getQUA(): String {
return "V1_AND_SQ_${getQQVersion(MobileQQ.getContext())}_${getQQVersionCode()}_YYB_D" return "V1_AND_SQ_${getQQVersion(MobileQQ.getContext())}_${getQQVersionCode()}_YYB_D"
@ -69,6 +73,15 @@ internal object PlatformUtils {
return MobileQQ.getMobileQQ().qqProcessName == "com.tencent.tim" return MobileQQ.getMobileQQ().qqProcessName == "com.tencent.tim"
} }
fun isApkInDebug(context: Context): Boolean {
try {
val info = context.applicationInfo
return (info.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
} catch (e: Exception) {
return false
}
}
fun killProcess(context: Context, processName: String) { fun killProcess(context: Context, processName: String) {
for (processInfo in (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses) { for (processInfo in (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses) {
if (processInfo.processName == processName) { if (processInfo.processName == processName) {

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,9 +394,14 @@ 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()}"))
} }
fromServiceMsg.wupBuffer.decodeProtobuf<TrpcOidb>().buffer.decodeProtobuf<NtV2RichMediaRsp>().download?.rkeyParam?.let { val trpc = fromServiceMsg.decodeToTrpcOidb()
if (trpc.buffer == null) {
return Result.failure(Exception("unable to get multimedia pic info: ${trpc.msg}"))
}
trpc.buffer?.decodeProtobuf<NtV2RichMediaRsp>()?.download?.rkeyParam?.let {
return Result.success(it) return Result.success(it)
} }
}.onFailure { }.onFailure {
@ -448,8 +494,11 @@ 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 rspBuffer = fromServiceMsg.wupBuffer.decodeProtobuf<TrpcOidb>().buffer val trpc = fromServiceMsg.decodeToTrpcOidb()
val rsp = rspBuffer.decodeProtobuf<NtV2RichMediaRsp>() if (trpc.buffer == null) {
return Result.failure(Exception("unable to request upload nt pic: ${trpc.msg}"))
}
val rsp = trpc.buffer!!.decodeProtobuf<NtV2RichMediaRsp>()
if (rsp.upload == null) { if (rsp.upload == null) {
return Result.failure(Exception("unable to request upload nt pic: ${rsp.head}")) return Result.failure(Exception("unable to request upload nt pic: ${rsp.head}"))
} }

View File

@ -6,11 +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.slice import moe.fuqiuluo.shamrock.tools.decodeToOidb
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
@ -28,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"
@ -53,8 +57,7 @@ internal object RichProtoSvc: QQInterfaces() {
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) { if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
return "" return ""
} }
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
body.bytes_bodybuffer body.bytes_bodybuffer
.get().toByteArray() .get().toByteArray()
.decodeProtobuf<Oidb0xfc2RspBody>() .decodeProtobuf<Oidb0xfc2RspBody>()
@ -82,8 +85,7 @@ internal object RichProtoSvc: QQInterfaces() {
if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) { if (fromServiceMsg == null || fromServiceMsg.wupBuffer == null) {
return "" return ""
} }
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) val result = oidb_0x6d6.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
if (body.uint32_result.get() != 0 if (body.uint32_result.get() != 0
|| result.download_file_rsp.int32_ret_code.get() != 0) { || result.download_file_rsp.int32_ret_code.get() != 0) {
@ -130,8 +132,7 @@ internal object RichProtoSvc: QQInterfaces() {
} }
return "" return ""
} else { } else {
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val result = cmd0x346.RspBody().mergeFrom(cmd0xe37.Resp0xe37().mergeFrom( val result = cmd0x346.RspBody().mergeFrom(cmd0xe37.Resp0xe37().mergeFrom(
body.bytes_bodybuffer.get().toByteArray() body.bytes_bodybuffer.get().toByteArray()
).bytes_cmd_0x346_rsp_body.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( suspend fun getGroupPicDownUrl(
originalUrl: String, originalUrl: String,
md5: String, md5: String,

View File

@ -11,6 +11,7 @@ import com.tencent.protofile.join_group_link.join_group_link
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import moe.fuqiuluo.shamrock.tools.decodeToOidb
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
import qq.service.internals.NTServiceFetcher import qq.service.internals.NTServiceFetcher
import qq.service.QQInterfaces import qq.service.QQInterfaces
@ -190,8 +191,7 @@ internal object ContactHelper: QQInterfaces() {
val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11ca_0", 4790, 0, reqBody.toByteArray()) val fromServiceMsg = sendOidbAW("OidbSvcTrpcTcp.0x11ca_0", 4790, 0, reqBody.toByteArray())
?: error("unable to fetch contact ark_json_text") ?: error("unable to fetch contact ark_json_text")
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val rsp = oidb_0x11b2.BusinessCardV3Rsp() val rsp = oidb_0x11b2.BusinessCardV3Rsp()
rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray()) rsp.mergeFrom(body.bytes_bodybuffer.get().toByteArray())
return rsp.signed_ark_msg.get() return rsp.signed_ark_msg.get()

View File

@ -1,5 +1,3 @@
@file:OptIn(ExperimentalStdlibApi::class)
package qq.service.file package qq.service.file
import com.tencent.mobileqq.pb.ByteStringMicro import com.tencent.mobileqq.pb.ByteStringMicro
@ -9,7 +7,9 @@ import io.kritor.file.*
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.EMPTY_BYTE_ARRAY 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.slice
import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools import moe.fuqiuluo.shamrock.utils.DeflateTools
import qq.service.QQInterfaces import qq.service.QQInterfaces
import tencent.im.oidb.cmd0x6d8.oidb_0x6d8 import tencent.im.oidb.cmd0x6d8.oidb_0x6d8
@ -31,12 +31,9 @@ internal object GroupFileHelper: QQInterfaces() {
val fileCnt: Int val fileCnt: Int
val limitCnt: Int val limitCnt: Int
if (fromServiceMsg.wupBuffer != null) { if (fromServiceMsg.wupBuffer != null) {
oidb_0x6d8.RspBody().mergeFrom( val oidb1 = fromServiceMsg.decodeToOidb()
oidb_sso.OIDBSSOPkg()
.mergeFrom(fromServiceMsg.wupBuffer.slice(4)) oidb_0x6d8.RspBody().mergeFrom(oidb1.bytes_bodybuffer.get().toByteArray()).group_file_cnt_rsp.apply {
.bytes_bodybuffer.get()
.toByteArray()
).group_file_cnt_rsp.apply {
fileCnt = uint32_all_file_count.get() fileCnt = uint32_all_file_count.get()
limitCnt = uint32_limit_count.get() limitCnt = uint32_limit_count.get()
} }
@ -53,11 +50,9 @@ internal object GroupFileHelper: QQInterfaces() {
val totalSpace: Long val totalSpace: Long
val usedSpace: Long val usedSpace: Long
if (fromServiceMsg2.isSuccess && fromServiceMsg2.wupBuffer != null) { if (fromServiceMsg2.isSuccess && fromServiceMsg2.wupBuffer != null) {
oidb_0x6d8.RspBody().mergeFrom( val oidb2 = fromServiceMsg2.decodeToOidb()
oidb_sso.OIDBSSOPkg()
.mergeFrom(fromServiceMsg2.wupBuffer.slice(4)) oidb_0x6d8.RspBody().mergeFrom(oidb2.bytes_bodybuffer.get().toByteArray()).group_space_rsp.apply {
.bytes_bodybuffer.get()
.toByteArray()).group_space_rsp.apply {
totalSpace = uint64_total_space.get() totalSpace = uint64_total_space.get()
usedSpace = uint64_used_space.get() usedSpace = uint64_used_space.get()
} }
@ -95,16 +90,14 @@ internal object GroupFileHelper: QQInterfaces() {
uint32_show_onlinedoc_folder.set(0) uint32_show_onlinedoc_folder.set(0)
}) })
}.toByteArray(), timeout = 15.seconds) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request")) }.toByteArray(), timeout = 30.seconds) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request"))
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed"))
} }
val files = arrayListOf<File>() val files = arrayListOf<File>()
val folders = arrayListOf<Folder>() val folders = arrayListOf<Folder>()
if (fromServiceMsg.wupBuffer != null) { if (fromServiceMsg.wupBuffer != null) {
val oidb = oidb_sso.OIDBSSOPkg().mergeFrom(fromServiceMsg.wupBuffer.slice(4).let { val oidb = fromServiceMsg.decodeToOidb()
if (it[0] == 0x78.toByte()) DeflateTools.uncompress(it) else it
})
oidb_0x6d8.RspBody().mergeFrom(oidb.bytes_bodybuffer.get().toByteArray()) oidb_0x6d8.RspBody().mergeFrom(oidb.bytes_bodybuffer.get().toByteArray())
.file_list_info_rsp.apply { .file_list_info_rsp.apply {
@ -116,9 +109,9 @@ internal object GroupFileHelper: QQInterfaces() {
this.fileName = fileInfo.str_file_name.get() this.fileName = fileInfo.str_file_name.get()
this.fileSize = fileInfo.uint64_file_size.get() this.fileSize = fileInfo.uint64_file_size.get()
this.busId = fileInfo.uint32_bus_id.get() this.busId = fileInfo.uint32_bus_id.get()
this.uploadTime = fileInfo.uint32_upload_time.get() this.uploadTime = fileInfo.uint32_upload_time.get().toLong()
this.expireTime = fileInfo.uint32_dead_time.get() this.expireTime = fileInfo.uint32_dead_time.get().toLong()
this.modifyTime = fileInfo.uint32_modify_time.get() this.modifyTime = fileInfo.uint32_modify_time.get().toLong()
this.downloadTimes = fileInfo.uint32_download_times.get() this.downloadTimes = fileInfo.uint32_download_times.get()
this.uploader = fileInfo.uint64_uploader_uin.get() this.uploader = fileInfo.uint64_uploader_uin.get()
this.uploaderName = fileInfo.str_uploader_name.get() this.uploaderName = fileInfo.str_uploader_name.get()
@ -133,7 +126,7 @@ internal object GroupFileHelper: QQInterfaces() {
this.folderId = folderInfo.str_folder_id.get() this.folderId = folderInfo.str_folder_id.get()
this.folderName = folderInfo.str_folder_name.get() this.folderName = folderInfo.str_folder_name.get()
this.totalFileCount = folderInfo.uint32_total_file_count.get() this.totalFileCount = folderInfo.uint32_total_file_count.get()
this.createTime = folderInfo.uint32_create_time.get() this.createTime = folderInfo.uint32_create_time.get().toLong()
this.creator = folderInfo.uint64_create_uin.get() this.creator = folderInfo.uint64_create_uin.get()
this.creatorName = folderInfo.str_creator_name.get() this.creatorName = folderInfo.str_creator_name.get()
}.build()) }.build())

View File

@ -19,10 +19,13 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
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 qq.service.internals.NTServiceFetcher import qq.service.internals.NTServiceFetcher
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import moe.fuqiuluo.shamrock.tools.putBuf32Long import moe.fuqiuluo.shamrock.tools.putBuf32Long
import moe.fuqiuluo.shamrock.tools.slice 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.auto.toByteArray
import protobuf.oidb.cmd0xf16.Oidb0xf16 import protobuf.oidb.cmd0xf16.Oidb0xf16
import protobuf.oidb.cmd0xf16.SetGroupRemarkReq import protobuf.oidb.cmd0xf16.SetGroupRemarkReq
@ -453,8 +456,7 @@ internal object GroupHelper: QQInterfaces() {
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
return Result.failure(RuntimeException("[oidb] failed")) return Result.failure(RuntimeException("[oidb] failed"))
} }
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
if(body.uint32_result.get() != 0) { if(body.uint32_result.get() != 0) {
return Result.failure(RuntimeException(body.str_error_msg.get())) return Result.failure(RuntimeException(body.str_error_msg.get()))
} }
@ -475,8 +477,7 @@ internal object GroupHelper: QQInterfaces() {
if (fromServiceMsg.wupBuffer == null) { if (fromServiceMsg.wupBuffer == null) {
return Result.failure(RuntimeException("[oidb] failed")) return Result.failure(RuntimeException("[oidb] failed"))
} }
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
if(body.uint32_result.get() != 0) { if(body.uint32_result.get() != 0) {
return Result.failure(RuntimeException(body.str_error_msg.get())) 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> { 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 { val info = RefreshTroopMemberInfoLock.withLock {
service.deleteTroopMember(groupId, memberUin) service.deleteTroopMember(groupId, memberUin)

View File

@ -3,14 +3,24 @@
package qq.service.internals package qq.service.internals
import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.kernel.nativeinterface.TextElement
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import moe.fuqiuluo.shamrock.config.AliveReply
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.QQ_9_0_8_VER
import qq.service.bdh.RichProtoSvc import qq.service.bdh.RichProtoSvc
import qq.service.file.GroupFileHelper
import qq.service.group.GroupHelper
import qq.service.kernel.SimpleKernelMsgListener import qq.service.kernel.SimpleKernelMsgListener
import qq.service.msg.MessageHelper import qq.service.msg.MessageHelper
@ -27,7 +37,83 @@ 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()) {
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) { when (record.chatType) {
MsgConstant.KCHATTYPEGROUP -> { MsgConstant.KCHATTYPEGROUP -> {
if (record.senderUin == 0L) return if (record.senderUin == 0L) return

View File

@ -216,6 +216,9 @@ internal object PrimitiveListener {
}.decodeProtobuf<GroupCommonTipsEvent>() }.decodeProtobuf<GroupCommonTipsEvent>()
} }
val groupId = event.groupCode.toLong() val groupId = event.groupCode.toLong()
if (event.uniqueTitleChangeDetail == null) {
return
}
val detail = event.uniqueTitleChangeDetail!!.first() val detail = event.uniqueTitleChangeDetail!!.first()
// todo 贴表情也走的 732 16 这里 // todo 贴表情也走的 732 16 这里

View File

@ -12,6 +12,7 @@ import com.tencent.qqnt.kernel.nativeinterface.FileTransNotifyInfo
import com.tencent.qqnt.kernel.nativeinterface.FirstViewDirectMsgNotifyInfo import com.tencent.qqnt.kernel.nativeinterface.FirstViewDirectMsgNotifyInfo
import com.tencent.qqnt.kernel.nativeinterface.FirstViewGroupGuildInfo import com.tencent.qqnt.kernel.nativeinterface.FirstViewGroupGuildInfo
import com.tencent.qqnt.kernel.nativeinterface.FreqLimitInfo import com.tencent.qqnt.kernel.nativeinterface.FreqLimitInfo
import com.tencent.qqnt.kernel.nativeinterface.GProGuildTopFeedMsg
import com.tencent.qqnt.kernel.nativeinterface.GroupFileListResult import com.tencent.qqnt.kernel.nativeinterface.GroupFileListResult
import com.tencent.qqnt.kernel.nativeinterface.GroupGuildNotifyInfo import com.tencent.qqnt.kernel.nativeinterface.GroupGuildNotifyInfo
import com.tencent.qqnt.kernel.nativeinterface.GroupItem import com.tencent.qqnt.kernel.nativeinterface.GroupItem
@ -27,6 +28,7 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgAbstract
import com.tencent.qqnt.kernel.nativeinterface.MsgElement import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import com.tencent.qqnt.kernel.nativeinterface.MsgSetting import com.tencent.qqnt.kernel.nativeinterface.MsgSetting
import com.tencent.qqnt.kernel.nativeinterface.QueryUserSecQualityRsp
import com.tencent.qqnt.kernel.nativeinterface.RecvdOrder import com.tencent.qqnt.kernel.nativeinterface.RecvdOrder
import com.tencent.qqnt.kernel.nativeinterface.RelatedWordEmojiInfo import com.tencent.qqnt.kernel.nativeinterface.RelatedWordEmojiInfo
import com.tencent.qqnt.kernel.nativeinterface.SearchGroupFileResult import com.tencent.qqnt.kernel.nativeinterface.SearchGroupFileResult
@ -135,7 +137,10 @@ abstract class SimpleKernelMsgListener: IKernelMsgListener {
} }
override fun onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: DownloadRelateEmojiResultInfo?) { override fun onGuildTopFeedUpdate(gProGuildTopFeedMsg: GProGuildTopFeedMsg?) {
}
override fun onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: DownloadRelateEmojiResultInfo?) {
} }
@ -207,7 +212,10 @@ abstract class SimpleKernelMsgListener: IKernelMsgListener {
} }
override fun onNtFirstViewMsgSyncEnd() { override fun onMsgWithRichLinkInfoUpdate(arrayList: ArrayList<MsgRecord>?) {
}
override fun onNtFirstViewMsgSyncEnd() {
} }
@ -258,7 +266,10 @@ abstract class SimpleKernelMsgListener: IKernelMsgListener {
} }
override fun onRichMediaDownloadComplete(fileTransNotifyInfo: FileTransNotifyInfo?) { override fun onRedTouchChanged() {
}
override fun onRichMediaDownloadComplete(fileTransNotifyInfo: FileTransNotifyInfo?) {
} }
@ -308,7 +319,10 @@ abstract class SimpleKernelMsgListener: IKernelMsgListener {
} }
override fun onUserTabStatusChanged(arrayList: ArrayList<TabStatusInfo>?) { override fun onUserSecQualityChanged(queryUserSecQualityRsp: QueryUserSecQualityRsp?) {
}
override fun onUserTabStatusChanged(arrayList: ArrayList<TabStatusInfo>?) {
} }

View File

@ -31,6 +31,7 @@ import moe.fuqiuluo.shamrock.tools.asJsonObject
import moe.fuqiuluo.shamrock.tools.asLong import moe.fuqiuluo.shamrock.tools.asLong
import moe.fuqiuluo.shamrock.tools.asString import moe.fuqiuluo.shamrock.tools.asString
import moe.fuqiuluo.shamrock.tools.asStringOrNull import moe.fuqiuluo.shamrock.tools.asStringOrNull
import moe.fuqiuluo.shamrock.tools.decodeToOidb
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
import moe.fuqiuluo.shamrock.tools.toHexString import moe.fuqiuluo.shamrock.tools.toHexString
import moe.fuqiuluo.shamrock.utils.DeflateTools import moe.fuqiuluo.shamrock.utils.DeflateTools
@ -108,8 +109,7 @@ internal object MessageHelper: QQInterfaces() {
if (fromServiceMsg?.wupBuffer == null) { if (fromServiceMsg?.wupBuffer == null) {
return "no response" return "no response"
} }
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
return if (result.wording.has()) { return if (result.wording.has()) {
LogCenter.log("设置群精华失败: ${result.wording.get()}", Level.WARN) LogCenter.log("设置群精华失败: ${result.wording.get()}", Level.WARN)
@ -129,8 +129,7 @@ internal object MessageHelper: QQInterfaces() {
if (fromServiceMsg?.wupBuffer == null) { if (fromServiceMsg?.wupBuffer == null) {
return "no response" return "no response"
} }
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) val result = oidb_0xeac.RspBody().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
return if (result.wording.has()) { return if (result.wording.has()) {
LogCenter.log("移除群精华失败: ${result.wording.get()}", Level.WARN) LogCenter.log("移除群精华失败: ${result.wording.get()}", Level.WARN)

View File

@ -78,7 +78,7 @@ private object MsgConvertor {
elem.type = ElementType.POKE elem.type = ElementType.POKE
elem.setPoke(PokeElement.newBuilder().apply { elem.setPoke(PokeElement.newBuilder().apply {
this.id = face.vaspokeId this.id = face.vaspokeId
this.type = face.pokeType this.pokeType = face.pokeType
this.strength = face.pokeStrength this.strength = face.pokeStrength
}) })
} else { } else {
@ -157,45 +157,21 @@ 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, this.fileType =
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.type =
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

@ -65,7 +65,7 @@ suspend fun List<Elem>.toKritorResponseMessages(contact: Contact): ArrayList<Ele
this.type = ElementType.IMAGE this.type = ElementType.IMAGE
this.image = ImageElement.newBuilder().apply { this.image = ImageElement.newBuilder().apply {
this.fileMd5 = md5 this.fileMd5 = md5
this.type = if (customFace.origin == true) ImageType.ORIGIN else ImageType.COMMON this.fileType = if (customFace.origin == true) ImageType.ORIGIN else ImageType.COMMON
this.fileUrl = when (contact.chatType) { this.fileUrl = when (contact.chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
origUrl, origUrl,
@ -85,7 +85,7 @@ suspend fun List<Elem>.toKritorResponseMessages(contact: Contact): ArrayList<Ele
this.type = ElementType.IMAGE this.type = ElementType.IMAGE
this.image = ImageElement.newBuilder().apply { this.image = ImageElement.newBuilder().apply {
this.fileMd5 = md5 this.fileMd5 = md5
this.type = if (element.notOnlineImage?.original == true) ImageType.ORIGIN else ImageType.COMMON this.fileType = if (element.notOnlineImage?.original == true) ImageType.ORIGIN else ImageType.COMMON
this.fileUrl = when (contact.chatType) { this.fileUrl = when (contact.chatType) {
MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl(
origUrl, origUrl,

View File

@ -254,8 +254,8 @@ object NtMsgConvertor {
} }
private suspend fun imageConvertor(contact: Contact, msgId: Long, sourceImage: Element): Result<MsgElement> { private suspend fun imageConvertor(contact: Contact, msgId: Long, sourceImage: Element): Result<MsgElement> {
val isOriginal = sourceImage.image.type == ImageType.ORIGIN val isOriginal = sourceImage.image.fileType == ImageType.ORIGIN
val isFlash = sourceImage.image.type == ImageType.FLASH val isFlash = sourceImage.image.fileType == ImageType.FLASH
val file = when (sourceImage.image.dataCase!!) { val file = when (sourceImage.image.dataCase!!) {
ImageElement.DataCase.FILE_NAME -> { ImageElement.DataCase.FILE_NAME -> {
val fileMd5 = sourceImage.image.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "") val fileMd5 = sourceImage.image.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "")
@ -638,7 +638,7 @@ object NtMsgConvertor {
face.faceText = "" face.faceText = ""
face.faceType = 5 face.faceType = 5
face.packId = null face.packId = null
face.pokeType = sourcePoke.poke.type face.pokeType = sourcePoke.poke.pokeType
face.spokeSummary = "" face.spokeSummary = ""
face.doubleHit = 0 face.doubleHit = 0
face.vaspokeId = sourcePoke.poke.id face.vaspokeId = sourcePoke.poke.id

View File

@ -73,7 +73,7 @@ private object ReqMsgConvertor {
if (face.faceType == 5) { if (face.faceType == 5) {
elem.setPoke(PokeElement.newBuilder().apply { elem.setPoke(PokeElement.newBuilder().apply {
this.id = face.vaspokeId this.id = face.vaspokeId
this.type = face.pokeType this.pokeType = face.pokeType
this.strength = face.pokeStrength this.strength = face.pokeStrength
}) })
} else { } else {
@ -136,45 +136,8 @@ 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( this.fileType =
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.type =
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

@ -183,7 +183,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
summary.append("[回复消息]") summary.append("[回复消息]")
} }
Element.ElementType.IMAGE -> { Element.ElementType.IMAGE -> {
val type = it.image.type val type = it.image.fileType
val isOriginal = type == ImageElement.ImageType.ORIGIN val isOriginal = type == ImageElement.ImageType.ORIGIN
val file = when(it.image.dataCase!!) { val file = when(it.image.dataCase!!) {
ImageElement.DataCase.FILE_NAME -> { ImageElement.DataCase.FILE_NAME -> {
@ -391,7 +391,7 @@ suspend fun List<Element>.toRichText(contact: Contact): Result<Pair<String, Rich
commonElem = CommonElem( commonElem = CommonElem(
serviceType = 2, serviceType = 2,
elem = PokeExtra( elem = PokeExtra(
type = it.poke.type, type = it.poke.pokeType,
field7 = 0, field7 = 0,
field8 = 0 field8 = 0
).toByteArray(), ).toByteArray(),

View File

@ -10,6 +10,7 @@ import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.header import io.ktor.client.request.header
import moe.fuqiuluo.shamrock.tools.GlobalClient import moe.fuqiuluo.shamrock.tools.GlobalClient
import moe.fuqiuluo.shamrock.tools.decodeToOidb
import moe.fuqiuluo.shamrock.tools.slice import moe.fuqiuluo.shamrock.tools.slice
import mqq.app.MobileQQ import mqq.app.MobileQQ
import mqq.manager.TicketManager import mqq.manager.TicketManager
@ -140,8 +141,7 @@ internal object TicketHelper: QQInterfaces() {
val fromServiceMsg = sendOidbAW("OidbSvcTcp.0x102a", 4138, 0, req.toByteArray()) val fromServiceMsg = sendOidbAW("OidbSvcTcp.0x102a", 4138, 0, req.toByteArray())
?: return Result.failure(Exception("getLessPSKey failed")) ?: return Result.failure(Exception("getLessPSKey failed"))
if (fromServiceMsg.wupBuffer == null) return Result.failure(Exception("getLessPSKey failed: no response")) if (fromServiceMsg.wupBuffer == null) return Result.failure(Exception("getLessPSKey failed: no response"))
val body = oidb_sso.OIDBSSOPkg() val body = fromServiceMsg.decodeToOidb()
body.mergeFrom(fromServiceMsg.wupBuffer.slice(4))
val rsp = oidb_cmd0x102a.GetPSkeyResponse().mergeFrom(body.bytes_bodybuffer.get().toByteArray()) val rsp = oidb_cmd0x102a.GetPSkeyResponse().mergeFrom(body.bytes_bodybuffer.get().toByteArray())
return Result.success(rsp.private_keys.get()) return Result.success(rsp.private_keys.get())
} }