diff --git a/kritor/build.gradle.kts b/kritor/build.gradle.kts index f9b75f35..743a3aff 100644 --- a/kritor/build.gradle.kts +++ b/kritor/build.gradle.kts @@ -37,17 +37,18 @@ android { dependencies { protobuf(files("kritor/protos")) + implementation("com.google.protobuf:protobuf-java:4.26.0") + implementation(kotlinx("coroutines-core", "1.8.0")) - implementation("com.google.protobuf:protobuf-java:3.25.3") implementation(grpc("stub", "1.62.2")) - implementation(grpc("kotlin-stub", "1.4.1")) implementation(grpc("protobuf", "1.62.2")) + implementation(grpc("kotlin-stub", "1.4.1")) } protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.25.3" + artifact = "com.google.protobuf:protoc:4.26.0" } plugins { create("grpc") { diff --git a/kritor/kritor b/kritor/kritor index 2b29978a..c49df307 160000 --- a/kritor/kritor +++ b/kritor/kritor @@ -1 +1 @@ -Subproject commit 2b29978a5864492267f4afd2b040b890e2f3c182 +Subproject commit c49df3074cf193cdbdc49c3c29fda66a0d0110e8 diff --git a/processor/src/main/java/moe/fuqiuluo/ksp/impl/GrpcProcessor.kt b/processor/src/main/java/moe/fuqiuluo/ksp/impl/GrpcProcessor.kt index bc80b182..0853724c 100644 --- a/processor/src/main/java/moe/fuqiuluo/ksp/impl/GrpcProcessor.kt +++ b/processor/src/main/java/moe/fuqiuluo/ksp/impl/GrpcProcessor.kt @@ -5,22 +5,9 @@ package moe.fuqiuluo.ksp.impl import com.google.devtools.ksp.KspExperimental import com.google.devtools.ksp.getAnnotationsByType -import com.google.devtools.ksp.getClassDeclarationByName -import com.google.devtools.ksp.getJavaClassByName -import com.google.devtools.ksp.getKotlinClassByName -import com.google.devtools.ksp.processing.CodeGenerator -import com.google.devtools.ksp.processing.Dependencies -import com.google.devtools.ksp.processing.KSPLogger -import com.google.devtools.ksp.processing.Resolver -import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.* import com.google.devtools.ksp.symbol.KSAnnotated -import com.google.devtools.ksp.symbol.KSClassDeclaration -import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSFunctionDeclaration -import com.google.devtools.ksp.symbol.KSType -import com.google.devtools.ksp.symbol.KSTypeParameter -import com.google.devtools.ksp.symbol.Modifier -import com.google.devtools.ksp.validate import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier @@ -67,15 +54,16 @@ class GrpcProcessor( } funcBuilder.addStatement("return EMPTY_BYTE_ARRAY") fileSpec - .addStatement("import io.kritor.*") + .addStatement("import io.kritor.authentication.*") .addStatement("import io.kritor.core.*") - .addStatement("import io.kritor.contact.*") - .addStatement("import io.kritor.group.*") - .addStatement("import io.kritor.friend.*") + .addStatement("import io.kritor.customization.*") + .addStatement("import io.kritor.developer.*") .addStatement("import io.kritor.file.*") + .addStatement("import io.kritor.friend.*") + .addStatement("import io.kritor.group.*") + .addStatement("import io.kritor.guild.*") .addStatement("import io.kritor.message.*") .addStatement("import io.kritor.web.*") - .addStatement("import io.kritor.developer.*") .addFunction(funcBuilder.build()) .addImport("moe.fuqiuluo.symbols", "EMPTY_BYTE_ARRAY") runCatching { diff --git a/xposed/src/main/java/kritor/client/KritorClient.kt b/xposed/src/main/java/kritor/client/KritorClient.kt index e8bdefbb..f0f8aa73 100644 --- a/xposed/src/main/java/kritor/client/KritorClient.kt +++ b/xposed/src/main/java/kritor/client/KritorClient.kt @@ -4,11 +4,12 @@ package kritor.client import com.google.protobuf.ByteString import io.grpc.ManagedChannel import io.grpc.ManagedChannelBuilder -import io.kritor.ReverseServiceGrpcKt -import io.kritor.event.* -import io.kritor.ReqCode -import io.kritor.Request -import io.kritor.Response +import io.kritor.common.Request +import io.kritor.common.Response +import io.kritor.event.EventServiceGrpcKt +import io.kritor.event.EventStructure +import io.kritor.event.EventType +import io.kritor.reverse.ReverseServiceGrpcKt import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -113,7 +114,7 @@ internal class KritorClient( val rsp = handleGrpc(request.cmd, request.buf.toByteArray()) senderChannel.emit(Response.newBuilder() .setCmd(request.cmd) - .setCode(ReqCode.SUCCESS) + .setCode(Response.ResponseCode.SUCCESS) .setMsg("success") .setSeq(request.seq) .setBuf(ByteString.copyFrom(rsp)) @@ -121,7 +122,7 @@ internal class KritorClient( }.onFailure { senderChannel.emit(Response.newBuilder() .setCmd(request.cmd) - .setCode(ReqCode.INTERNAL) + .setCode(Response.ResponseCode.INTERNAL) .setMsg(it.stackTraceToString()) .setSeq(request.seq) .setBuf(ByteString.EMPTY) diff --git a/xposed/src/main/java/kritor/server/KritorServer.kt b/xposed/src/main/java/kritor/server/KritorServer.kt index 28a166cd..c0a4b996 100644 --- a/xposed/src/main/java/kritor/server/KritorServer.kt +++ b/xposed/src/main/java/kritor/server/KritorServer.kt @@ -18,8 +18,7 @@ class KritorServer( private val server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) .executor(Dispatchers.IO.asExecutor()) .intercept(AuthInterceptor) - .addService(Authentication) - .addService(ContactService) + .addService(AuthenticationService) .addService(CoreService) .addService(FriendService) .addService(GroupService) diff --git a/xposed/src/main/java/kritor/service/Authentication.kt b/xposed/src/main/java/kritor/service/AuthenticationService.kt similarity index 50% rename from xposed/src/main/java/kritor/service/Authentication.kt rename to xposed/src/main/java/kritor/service/AuthenticationService.kt index 01ef3182..a513a2dc 100644 --- a/xposed/src/main/java/kritor/service/Authentication.kt +++ b/xposed/src/main/java/kritor/service/AuthenticationService.kt @@ -2,23 +2,19 @@ package kritor.service import io.grpc.Status import io.grpc.StatusRuntimeException -import io.kritor.AuthCode -import io.kritor.AuthReq -import io.kritor.AuthRsp -import io.kritor.AuthenticationServiceGrpcKt -import io.kritor.GetAuthStateReq -import io.kritor.GetAuthStateRsp +import io.kritor.authentication.* +import io.kritor.authentication.AuthenticateResponse.AuthenticateResponseCode import kritor.auth.AuthInterceptor import moe.fuqiuluo.shamrock.config.ActiveTicket import moe.fuqiuluo.shamrock.config.ShamrockConfig import qq.service.QQInterfaces -internal object Authentication: AuthenticationServiceGrpcKt.AuthenticationServiceCoroutineImplBase() { - @Grpc("Authentication", "Auth") - override suspend fun auth(request: AuthReq): AuthRsp { +internal object AuthenticationService: AuthenticationServiceGrpcKt.AuthenticationServiceCoroutineImplBase() { + @Grpc("AuthenticationService", "Authenticate") + override suspend fun authenticate(request: AuthenticateRequest): AuthenticateResponse { if (QQInterfaces.app.account != request.account) { - return AuthRsp.newBuilder().apply { - code = AuthCode.NO_ACCOUNT + return AuthenticateResponse.newBuilder().apply { + code = AuthenticateResponseCode.NO_ACCOUNT msg = "No such account" }.build() } @@ -29,36 +25,36 @@ internal object Authentication: AuthenticationServiceGrpcKt.AuthenticationServic val ticket = ShamrockConfig.getProperty(activeTicketName + if (index == 0) "" else ".$index", null) if (ticket.isNullOrEmpty()) { if (index == 0) { - return AuthRsp.newBuilder().apply { - code = AuthCode.OK + return AuthenticateResponse.newBuilder().apply { + code = AuthenticateResponseCode.OK msg = "OK" }.build() } else { break } } else if (ticket == request.ticket) { - return AuthRsp.newBuilder().apply { - code = AuthCode.OK + return AuthenticateResponse.newBuilder().apply { + code = AuthenticateResponseCode.OK msg = "OK" }.build() } index++ } - return AuthRsp.newBuilder().apply { - code = AuthCode.NO_TICKET + return AuthenticateResponse.newBuilder().apply { + code = AuthenticateResponseCode.NO_TICKET msg = "Invalid ticket" }.build() } - @Grpc("Authentication", "GetAuthState") - override suspend fun getAuthState(request: GetAuthStateReq): GetAuthStateRsp { + @Grpc("AuthenticationService", "GetAuthenticationState") + override suspend fun getAuthenticationState(request: GetAuthenticationStateRequest): GetAuthenticationStateResponse { if (request.account != QQInterfaces.app.account) { throw StatusRuntimeException(Status.CANCELLED.withDescription("No such account")) } - return GetAuthStateRsp.newBuilder().apply { - isRequiredAuth = AuthInterceptor.getAllTicket().isNotEmpty() + return GetAuthenticationStateResponse.newBuilder().apply { + isRequired = AuthInterceptor.getAllTicket().isNotEmpty() }.build() } } \ No newline at end of file diff --git a/xposed/src/main/java/kritor/service/ContactService.kt b/xposed/src/main/java/kritor/service/ContactService.kt deleted file mode 100644 index da2a2192..00000000 --- a/xposed/src/main/java/kritor/service/ContactService.kt +++ /dev/null @@ -1,187 +0,0 @@ -package kritor.service - -import android.os.Bundle -import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi -import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst.* -import com.tencent.mobileqq.profilecard.api.IProfileProtocolService -import com.tencent.mobileqq.qroute.QRoute -import io.grpc.Status -import io.grpc.StatusRuntimeException -import io.kritor.contact.ContactServiceGrpcKt -import io.kritor.contact.GetUidRequest -import io.kritor.contact.GetUidResponse -import io.kritor.contact.GetUinByUidRequest -import io.kritor.contact.GetUinByUidResponse -import io.kritor.contact.IsBlackListUserRequest -import io.kritor.contact.IsBlackListUserResponse -import io.kritor.contact.ProfileCard -import io.kritor.contact.ProfileCardRequest -import io.kritor.contact.SetProfileCardRequest -import io.kritor.contact.SetProfileCardResponse -import io.kritor.contact.StrangerExt -import io.kritor.contact.StrangerInfo -import io.kritor.contact.StrangerInfoRequest -import io.kritor.contact.VoteUserRequest -import io.kritor.contact.VoteUserResponse -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withTimeoutOrNull -import qq.service.QQInterfaces -import qq.service.contact.ContactHelper -import kotlin.coroutines.resume - -internal object ContactService : ContactServiceGrpcKt.ContactServiceCoroutineImplBase() { - @Grpc("ContactService", "VoteUser") - override suspend fun voteUser(request: VoteUserRequest): VoteUserResponse { - ContactHelper.voteUser( - when (request.accountCase!!) { - VoteUserRequest.AccountCase.ACCOUNT_UIN -> request.accountUin - VoteUserRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid).toLong() - VoteUserRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException( - Status.INVALID_ARGUMENT - .withDescription("account not set") - ) - }, request.voteCount - ).onFailure { - throw StatusRuntimeException( - Status.INTERNAL - .withDescription(it.stackTraceToString()) - ) - } - return VoteUserResponse.newBuilder().build() - } - - @Grpc("ContactService", "GetProfileCard") - override suspend fun getProfileCard(request: ProfileCardRequest): ProfileCard { - val uin = when (request.accountCase!!) { - ProfileCardRequest.AccountCase.ACCOUNT_UIN -> request.accountUin - ProfileCardRequest.AccountCase.ACCOUNT_UID -> ContactHelper.getUinByUidAsync(request.accountUid).toLong() - ProfileCardRequest.AccountCase.ACCOUNT_NOT_SET -> throw StatusRuntimeException( - Status.INVALID_ARGUMENT - .withDescription("account not set") - ) - } - - val contact = ContactHelper.getProfileCard(uin) - - contact.onFailure { - throw StatusRuntimeException( - Status.INTERNAL - .withDescription(it.stackTraceToString()) - ) - } - - contact.onSuccess { - return ProfileCard.newBuilder().apply { - this.uin = it.uin.toLong() - this.uid = if (request.hasAccountUid()) request.accountUid - else ContactHelper.getUidByUinAsync(it.uin.toLong()) - this.name = it.strNick ?: "" - this.remark = it.strReMark ?: "" - this.level = it.iQQLevel - this.birthday = it.lBirthday - this.loginDay = it.lLoginDays.toInt() - this.voteCnt = it.lVoteCount.toInt() - this.qid = it.qid ?: "" - this.isSchoolVerified = it.schoolVerifiedFlag - }.build() - } - - throw StatusRuntimeException( - Status.INTERNAL - .withDescription("logic failed") - ) - } - - @Grpc("ContactService", "GetStrangerInfo") - override suspend fun getStrangerInfo(request: StrangerInfoRequest): StrangerInfo { - val userId = request.uin - val info = ContactHelper.refreshAndGetProfileCard(userId).onFailure { - throw StatusRuntimeException( - Status.INTERNAL - .withCause(it) - .withDescription("Unable to fetch stranger info") - ) - }.getOrThrow() - - return StrangerInfo.newBuilder().apply { - this.uid = ContactHelper.getUidByUinAsync(userId) - this.uin = (info.uin ?: "0").toLong() - this.name = info.strNick ?: "" - this.level = info.iQQLevel - this.loginDay = info.lLoginDays.toInt() - this.voteCnt = info.lVoteCount.toInt() - this.qid = info.qid ?: "" - this.isSchoolVerified = info.schoolVerifiedFlag - this.ext = StrangerExt.newBuilder().apply { - this.bigVip = info.bBigClubVipOpen == 1.toByte() - this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte() - this.qqVip = info.bQQVipOpen == 1.toByte() - this.superVip = info.bSuperQQOpen == 1.toByte() - this.voted = info.bVoted == 1.toByte() - }.build().toByteString() - }.build() - } - - @Grpc("ContactService", "GetUid") - override suspend fun getUid(request: GetUidRequest): GetUidResponse { - return GetUidResponse.newBuilder().apply { - request.uinList.forEach { - putUid(it, ContactHelper.getUidByUinAsync(it)) - } - }.build() - } - - @Grpc("ContactService", "GetUinByUid") - override suspend fun getUinByUid(request: GetUinByUidRequest): GetUinByUidResponse { - return GetUinByUidResponse.newBuilder().apply { - request.uidList.forEach { - putUin(it, ContactHelper.getUinByUidAsync(it).toLong()) - } - }.build() - } - - @Grpc("ContactService", "SetProfileCard") - override suspend fun setProfileCard(request: SetProfileCardRequest): SetProfileCardResponse { - val bundle = Bundle() - val service = QQInterfaces.app - .getRuntimeService(IProfileProtocolService::class.java, "all") - if (request.hasNickName()) { - bundle.putString(KEY_NICK, request.nickName) - } - if (request.hasCompany()) { - bundle.putString(KEY_COMPANY, request.company) - } - if (request.hasEmail()) { - bundle.putString(KEY_EMAIL, request.email) - } - if (request.hasCollege()) { - bundle.putString(KEY_COLLEGE, request.college) - } - if (request.hasPersonalNote()) { - bundle.putString(KEY_PERSONAL_NOTE, request.personalNote) - } - - if (request.hasBirthday()) { - bundle.putInt(KEY_BIRTHDAY, request.birthday) - } - if (request.hasAge()) { - bundle.putInt(KEY_AGE, request.age) - } - - service.setProfileDetail(bundle) - return super.setProfileCard(request) - } - - @Grpc("ContactService", "IsBlackListUser") - override suspend fun isBlackListUser(request: IsBlackListUserRequest): IsBlackListUserResponse { - val blacklistApi = QRoute.api(IProfileCardBlacklistApi::class.java) - val isBlack = withTimeoutOrNull(5000) { - suspendCancellableCoroutine { continuation -> - blacklistApi.isBlackOrBlackedUin(request.uin.toString()) { - continuation.resume(it) - } - } - } ?: false - return IsBlackListUserResponse.newBuilder().setIsBlackListUser(isBlack).build() - } -} \ No newline at end of file diff --git a/xposed/src/main/java/kritor/service/CoreService.kt b/xposed/src/main/java/kritor/service/CoreService.kt index b3a37df0..00e810d6 100644 --- a/xposed/src/main/java/kritor/service/CoreService.kt +++ b/xposed/src/main/java/kritor/service/CoreService.kt @@ -9,8 +9,6 @@ import moe.fuqiuluo.shamrock.tools.ShamrockVersion import moe.fuqiuluo.shamrock.utils.DownloadUtils import moe.fuqiuluo.shamrock.utils.FileUtils import moe.fuqiuluo.shamrock.utils.MD5 -import moe.fuqiuluo.shamrock.utils.MMKVFetcher -import moe.fuqiuluo.shamrock.utils.PlatformUtils import mqq.app.MobileQQ import qq.service.QQInterfaces.Companion.app import qq.service.contact.ContactHelper @@ -25,14 +23,6 @@ internal object CoreService : CoreServiceGrpcKt.CoreServiceCoroutineImplBase() { }.build() } - @Grpc("CoreService", "ClearCache") - override suspend fun clearCache(request: ClearCacheRequest): ClearCacheResponse { - FileUtils.clearCache() - MMKVFetcher.mmkvWithId("audio2silk") - .clear() - return ClearCacheResponse.newBuilder().build() - } - @Grpc("CoreService", "GetCurrentAccount") override suspend fun getCurrentAccount(request: GetCurrentAccountRequest): GetCurrentAccountResponse { return GetCurrentAccountResponse.newBuilder().apply { @@ -108,15 +98,4 @@ internal object CoreService : CoreServiceGrpcKt.CoreServiceCoroutineImplBase() { } return SwitchAccountResponse.newBuilder().build() } - - @Grpc("CoreService", "GetDeviceBattery") - override suspend fun getDeviceBattery(request: GetDeviceBatteryRequest): GetDeviceBatteryResponse { - return GetDeviceBatteryResponse.newBuilder().apply { - PlatformUtils.getDeviceBattery().let { - this.battery = it.battery - this.scale = it.scale - this.status = it.status - } - }.build() - } } \ No newline at end of file diff --git a/xposed/src/main/java/kritor/service/DeveloperService.kt b/xposed/src/main/java/kritor/service/DeveloperService.kt index 9735e8be..2a1f772c 100644 --- a/xposed/src/main/java/kritor/service/DeveloperService.kt +++ b/xposed/src/main/java/kritor/service/DeveloperService.kt @@ -2,9 +2,31 @@ package kritor.service import com.google.protobuf.ByteString import io.kritor.developer.* +import moe.fuqiuluo.shamrock.utils.FileUtils +import moe.fuqiuluo.shamrock.utils.MMKVFetcher +import moe.fuqiuluo.shamrock.utils.PlatformUtils import qq.service.QQInterfaces internal object DeveloperService: DeveloperServiceGrpcKt.DeveloperServiceCoroutineImplBase() { + @Grpc("DeveloperService", "ClearCache") + override suspend fun clearCache(request: ClearCacheRequest): ClearCacheResponse { + FileUtils.clearCache() + MMKVFetcher.mmkvWithId("audio2silk") + .clear() + return ClearCacheResponse.newBuilder().build() + } + + @Grpc("DeveloperService", "GetDeviceBattery") + override suspend fun getDeviceBattery(request: GetDeviceBatteryRequest): GetDeviceBatteryResponse { + return GetDeviceBatteryResponse.newBuilder().apply { + PlatformUtils.getDeviceBattery().let { + this.battery = it.battery + this.scale = it.scale + this.status = it.status + } + }.build() + } + @Grpc("DeveloperService", "SendPacket") override suspend fun sendPacket(request: SendPacketRequest): SendPacketResponse { return SendPacketResponse.newBuilder().apply { diff --git a/xposed/src/main/java/kritor/service/FriendService.kt b/xposed/src/main/java/kritor/service/FriendService.kt index f2863e86..746db319 100644 --- a/xposed/src/main/java/kritor/service/FriendService.kt +++ b/xposed/src/main/java/kritor/service/FriendService.kt @@ -1,23 +1,33 @@ package kritor.service +import android.os.Bundle +import com.tencent.mobileqq.profilecard.api.IProfileCardBlacklistApi +import com.tencent.mobileqq.profilecard.api.IProfileProtocolConst +import com.tencent.mobileqq.profilecard.api.IProfileProtocolService +import com.tencent.mobileqq.qroute.QRoute import io.grpc.Status import io.grpc.StatusRuntimeException import io.kritor.friend.* +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeoutOrNull +import qq.service.QQInterfaces import qq.service.contact.ContactHelper import qq.service.friend.FriendHelper +import kotlin.coroutines.resume -internal object FriendService: FriendServiceGrpcKt.FriendServiceCoroutineImplBase() { +internal object FriendService : FriendServiceGrpcKt.FriendServiceCoroutineImplBase() { @Grpc("FriendService", "GetFriendList") override suspend fun getFriendList(request: GetFriendListRequest): GetFriendListResponse { - val friendList = FriendHelper.getFriendList(if(request.hasRefresh()) request.refresh else false).onFailure { - throw StatusRuntimeException(Status.INTERNAL - .withDescription(it.stackTraceToString()) + val friendList = FriendHelper.getFriendList(if (request.hasRefresh()) request.refresh else false).onFailure { + throw StatusRuntimeException( + Status.INTERNAL + .withDescription(it.stackTraceToString()) ) }.getOrThrow() return GetFriendListResponse.newBuilder().apply { friendList.forEach { - this.addFriendList(FriendData.newBuilder().apply { + this.addFriendsInfo(FriendInfo.newBuilder().apply { uin = it.uin.toLong() uid = ContactHelper.getUidByUinAsync(uin) qid = "" @@ -27,10 +37,208 @@ internal object FriendService: FriendServiceGrpcKt.FriendServiceCoroutineImplBas level = 0 gender = it.gender.toInt() groupId = it.groupid - ext = FriendExt.newBuilder().build().toByteString() + + ext = ExtInfo.newBuilder().build() }) } }.build() } + @Grpc("FriendService", "GetFriendProfileCard") + override suspend fun getFriendProfileCard(request: GetFriendProfileCardRequest): GetFriendProfileCardResponse { + return GetFriendProfileCardResponse.newBuilder().apply { + request.targetUinsList.forEach { + ContactHelper.getProfileCard(it).getOrThrow().let { info -> + addFriendsProfileCard(ProfileCard.newBuilder().apply { + this.uin = info.uin.toLong() + this.uid = ContactHelper.getUidByUinAsync(info.uin.toLong()) + this.nick = info.strNick + this.remark = info.strReMark + this.level = info.iQQLevel + this.birthday = info.lBirthday + this.loginDay = info.lLoginDays.toInt() + this.voteCnt = info.lVoteCount.toInt() + this.qid = info.qid ?: "" + this.isSchoolVerified = info.schoolVerifiedFlag + + this.ext = ExtInfo.newBuilder().apply { + this.bigVip = info.bBigClubVipOpen == 1.toByte() + this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte() + this.qqVip = info.bQQVipOpen == 1.toByte() + this.superVip = info.bSuperQQOpen == 1.toByte() + this.voted = info.bVoted == 1.toByte() + }.build() + }.build()) + } + } + request.targetUidsList.forEach { + ContactHelper.getProfileCard(ContactHelper.getUinByUidAsync(it).toLong()).getOrThrow().let { info -> + addFriendsProfileCard(ProfileCard.newBuilder().apply { + this.uin = info.uin.toLong() + this.uid = it + this.nick = info.strNick + this.remark = info.strReMark + this.level = info.iQQLevel + this.birthday = info.lBirthday + this.loginDay = info.lLoginDays.toInt() + this.voteCnt = info.lVoteCount.toInt() + this.qid = info.qid ?: "" + this.isSchoolVerified = info.schoolVerifiedFlag + + this.ext = ExtInfo.newBuilder().apply { + this.bigVip = info.bBigClubVipOpen == 1.toByte() + this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte() + this.qqVip = info.bQQVipOpen == 1.toByte() + this.superVip = info.bSuperQQOpen == 1.toByte() + this.voted = info.bVoted == 1.toByte() + }.build() + }.build()) + } + } + }.build() + } + + @Grpc("FriendService", "GetStrangerProfileCard") + override suspend fun getStrangerProfileCard(request: GetStrangerProfileCardRequest): GetStrangerProfileCardResponse { + return GetStrangerProfileCardResponse.newBuilder().apply { + request.targetUinsList.forEach { + ContactHelper.refreshAndGetProfileCard(it).getOrThrow().let { info -> + addStrangersProfileCard(ProfileCard.newBuilder().apply { + this.uin = info.uin.toLong() + this.uid = ContactHelper.getUidByUinAsync(info.uin.toLong()) + this.nick = info.strNick + this.level = info.iQQLevel + this.birthday = info.lBirthday + this.loginDay = info.lLoginDays.toInt() + this.voteCnt = info.lVoteCount.toInt() + this.qid = info.qid ?: "" + this.isSchoolVerified = info.schoolVerifiedFlag + + this.ext = ExtInfo.newBuilder().apply { + this.bigVip = info.bBigClubVipOpen == 1.toByte() + this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte() + this.qqVip = info.bQQVipOpen == 1.toByte() + this.superVip = info.bSuperQQOpen == 1.toByte() + this.voted = info.bVoted == 1.toByte() + }.build() + }.build()) + } + } + request.targetUidsList.forEach { + ContactHelper.refreshAndGetProfileCard(ContactHelper.getUinByUidAsync(it).toLong()).getOrThrow() + .let { info -> + addStrangersProfileCard(ProfileCard.newBuilder().apply { + this.uin = info.uin.toLong() + this.uid = it + this.nick = info.strNick + this.level = info.iQQLevel + this.birthday = info.lBirthday + this.loginDay = info.lLoginDays.toInt() + this.voteCnt = info.lVoteCount.toInt() + this.qid = info.qid ?: "" + this.isSchoolVerified = info.schoolVerifiedFlag + + this.ext = ExtInfo.newBuilder().apply { + this.bigVip = info.bBigClubVipOpen == 1.toByte() + this.hollywoodVip = info.bHollywoodVipOpen == 1.toByte() + this.qqVip = info.bQQVipOpen == 1.toByte() + this.superVip = info.bSuperQQOpen == 1.toByte() + this.voted = info.bVoted == 1.toByte() + }.build() + }.build()) + } + } + }.build() + } + + @Grpc("FriendService", "SetProfileCard") + override suspend fun setProfileCard(request: SetProfileCardRequest): SetProfileCardResponse { + val bundle = Bundle() + val service = QQInterfaces.app + .getRuntimeService(IProfileProtocolService::class.java, "all") + if (request.hasNickName()) { + bundle.putString(IProfileProtocolConst.KEY_NICK, request.nickName) + } + if (request.hasCompany()) { + bundle.putString(IProfileProtocolConst.KEY_COMPANY, request.company) + } + if (request.hasEmail()) { + bundle.putString(IProfileProtocolConst.KEY_EMAIL, request.email) + } + if (request.hasCollege()) { + bundle.putString(IProfileProtocolConst.KEY_COLLEGE, request.college) + } + if (request.hasPersonalNote()) { + bundle.putString(IProfileProtocolConst.KEY_PERSONAL_NOTE, request.personalNote) + } + + if (request.hasBirthday()) { + bundle.putInt(IProfileProtocolConst.KEY_BIRTHDAY, request.birthday) + } + if (request.hasAge()) { + bundle.putInt(IProfileProtocolConst.KEY_AGE, request.age) + } + + service.setProfileDetail(bundle) + return super.setProfileCard(request) + } + + @Grpc("FriendService", "IsBlackListUser") + override suspend fun isBlackListUser(request: IsBlackListUserRequest): IsBlackListUserResponse { + val uin = when (request.targetCase!!) { + IsBlackListUserRequest.TargetCase.TARGET_UIN -> request.targetUin.toString() + IsBlackListUserRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid) + IsBlackListUserRequest.TargetCase.TARGET_NOT_SET -> throw StatusRuntimeException( + Status.INVALID_ARGUMENT + .withDescription("account not set") + ) + } + val blacklistApi = QRoute.api(IProfileCardBlacklistApi::class.java) + val isBlack = withTimeoutOrNull(5000) { + suspendCancellableCoroutine { continuation -> + blacklistApi.isBlackOrBlackedUin(uin) { + continuation.resume(it) + } + } + } ?: false + return IsBlackListUserResponse.newBuilder().setIsBlackListUser(isBlack).build() + } + + @Grpc("FriendService", "VoteUser") + override suspend fun voteUser(request: VoteUserRequest): VoteUserResponse { + ContactHelper.voteUser( + when (request.targetCase!!) { + VoteUserRequest.TargetCase.TARGET_UIN -> request.targetUin + VoteUserRequest.TargetCase.TARGET_UID -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() + VoteUserRequest.TargetCase.TARGET_NOT_SET -> throw StatusRuntimeException( + Status.INVALID_ARGUMENT + .withDescription("account not set") + ) + }, request.voteCount + ).onFailure { + throw StatusRuntimeException( + Status.INTERNAL + .withDescription(it.stackTraceToString()) + ) + } + return VoteUserResponse.newBuilder().build() + } + + @Grpc("FriendService", "GetUidByUin") + override suspend fun getUidByUin(request: GetUidRequest): GetUidResponse { + return GetUidResponse.newBuilder().apply { + request.targetUinsList.forEach { + putUidMap(it, ContactHelper.getUidByUinAsync(it)) + } + }.build() + } + + @Grpc("FriendService", "GetUinByUid") + override suspend fun getUinByUid(request: GetUinByUidRequest): GetUinByUidResponse { + return GetUinByUidResponse.newBuilder().apply { + request.targetUidsList.forEach { + putUinMap(it, ContactHelper.getUinByUidAsync(it).toLong()) + } + }.build() + } } \ No newline at end of file diff --git a/xposed/src/main/java/kritor/service/GroupFileService.kt b/xposed/src/main/java/kritor/service/GroupFileService.kt index 345b248f..41164764 100644 --- a/xposed/src/main/java/kritor/service/GroupFileService.kt +++ b/xposed/src/main/java/kritor/service/GroupFileService.kt @@ -17,7 +17,7 @@ import qq.service.file.GroupFileHelper.getGroupFileSystemInfo import tencent.im.oidb.cmd0x6d6.oidb_0x6d6 import tencent.im.oidb.oidb_sso -internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCoroutineImplBase() { +internal object GroupFileService : GroupFileServiceGrpcKt.GroupFileServiceCoroutineImplBase() { @Grpc("GroupFileService", "CreateFolder") override suspend fun createFolder(request: CreateFolderRequest): CreateFolderResponse { val data = Oidb0x6d7ReqBody( @@ -49,13 +49,15 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti @Grpc("GroupFileService", "DeleteFolder") override suspend fun deleteFolder(request: DeleteFolderRequest): DeleteFolderResponse { - val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody( - deleteFolder = DeleteFolderReq( - groupCode = request.groupId.toULong(), - appId = 3u, - folderId = request.folderId - ) - ).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request")) + val fromServiceMsg = QQInterfaces.sendOidbAW( + "OidbSvc.0x6d7_1", 1751, 1, Oidb0x6d7ReqBody( + deleteFolder = DeleteFolderReq( + groupCode = request.groupId.toULong(), + appId = 3u, + folderId = request.folderId + ) + ).toByteArray() + ) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request")) if (fromServiceMsg.wupBuffer == null) { throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) } @@ -97,14 +99,16 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti @Grpc("GroupFileService", "RenameFolder") override suspend fun renameFolder(request: RenameFolderRequest): RenameFolderResponse { - val fromServiceMsg = QQInterfaces.sendOidbAW("OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody( - renameFolder = RenameFolderReq( - groupCode = request.groupId.toULong(), - appId = 3u, - folderId = request.folderId, - folderName = request.name - ) - ).toByteArray()) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request")) + val fromServiceMsg = QQInterfaces.sendOidbAW( + "OidbSvc.0x6d7_3", 1751, 3, Oidb0x6d7ReqBody( + renameFolder = RenameFolderReq( + groupCode = request.groupId.toULong(), + appId = 3u, + folderId = request.folderId, + folderName = request.name + ) + ).toByteArray() + ) ?: throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to send oidb request")) if (fromServiceMsg.wupBuffer == null) { throw StatusRuntimeException(Status.INTERNAL.withDescription("oidb request failed")) } @@ -122,17 +126,11 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti return getGroupFileSystemInfo(request.groupId) } - @Grpc("GroupFileService", "GetRootFiles") - override suspend fun getRootFiles(request: GetRootFilesRequest): GetRootFilesResponse { - return GetRootFilesResponse.newBuilder().apply { - val response = GroupFileHelper.getGroupFiles(request.groupId) - this.addAllFiles(response.filesList) - this.addAllFolders(response.foldersList) - }.build() - } - - @Grpc("GroupFileService", "GetFiles") - override suspend fun getFiles(request: GetFilesRequest): GetFilesResponse { - return GroupFileHelper.getGroupFiles(request.groupId, request.folderId) + @Grpc("GroupFileService", "GetFileList") + override suspend fun getFileList(request: GetFileListRequest): GetFileListResponse { + return if (request.hasFolderId()) + GroupFileHelper.getGroupFiles(request.groupId, request.folderId) + else + GroupFileHelper.getGroupFiles(request.groupId) } } \ No newline at end of file diff --git a/xposed/src/main/java/kritor/service/GroupService.kt b/xposed/src/main/java/kritor/service/GroupService.kt index ac3ee10f..44dde388 100644 --- a/xposed/src/main/java/kritor/service/GroupService.kt +++ b/xposed/src/main/java/kritor/service/GroupService.kt @@ -212,7 +212,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase( }.getOrThrow() return GetGroupListResponse.newBuilder().apply { groupList.forEach { groupInfo -> - this.addGroupInfo(GroupInfo.newBuilder().apply { + this.addGroupsInfo(GroupInfo.newBuilder().apply { groupId = groupInfo.troopcode.toLong() groupName = groupInfo.troopname.ifNullOrEmpty { groupInfo.troopRemark } .ifNullOrEmpty { groupInfo.newTroopName } ?: "" @@ -231,8 +231,8 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase( override suspend fun getGroupMemberInfo(request: GetGroupMemberInfoRequest): GetGroupMemberInfoResponse { val memberInfo = GroupHelper.getTroopMemberInfoByUin( request.groupId.toString(), when (request.targetCase!!) { - GetGroupMemberInfoRequest.TargetCase.UIN -> request.uin - GetGroupMemberInfoRequest.TargetCase.UID -> ContactHelper.getUinByUidAsync(request.uid).toLong() + GetGroupMemberInfoRequest.TargetCase.TARGET_UID -> request.targetUin + GetGroupMemberInfoRequest.TargetCase.TARGET_UIN -> ContactHelper.getUinByUidAsync(request.targetUid).toLong() else -> throw StatusRuntimeException( Status.INVALID_ARGUMENT .withDescription("target not set") @@ -246,8 +246,8 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase( return GetGroupMemberInfoResponse.newBuilder().apply { groupMemberInfo = GroupMemberInfo.newBuilder().apply { uid = - if (request.targetCase == GetGroupMemberInfoRequest.TargetCase.UID) request.uid else ContactHelper.getUidByUinAsync( - request.uin + if (request.targetCase == GetGroupMemberInfoRequest.TargetCase.TARGET_UID) request.targetUid else ContactHelper.getUidByUinAsync( + request.targetUin ) uin = memberInfo.memberuin?.toLong() ?: 0 nick = memberInfo.troopnick @@ -264,7 +264,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase( shutUpTimestamp = memberInfo.gagTimeStamp distance = memberInfo.distance - addAllHonor((memberInfo.honorList ?: "") + addAllHonors((memberInfo.honorList ?: "") .split("|") .filter { it.isNotBlank() } .map { it.toInt() }) @@ -286,7 +286,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase( }.getOrThrow() return GetGroupMemberListResponse.newBuilder().apply { memberList.forEach { memberInfo -> - this.addGroupMemberInfo(GroupMemberInfo.newBuilder().apply { + this.addGroupMembersInfo(GroupMemberInfo.newBuilder().apply { uid = ContactHelper.getUidByUinAsync(memberInfo.memberuin?.toLong() ?: 0) uin = memberInfo.memberuin?.toLong() ?: 0 nick = memberInfo.troopnick @@ -303,7 +303,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase( shutUpTimestamp = memberInfo.gagTimeStamp distance = memberInfo.distance - addAllHonor((memberInfo.honorList ?: "") + addAllHonors((memberInfo.honorList ?: "") .split("|") .filter { it.isNotBlank() } .map { it.toInt() }) @@ -323,7 +323,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase( }.getOrThrow() return GetProhibitedUserListResponse.newBuilder().apply { prohibitedList.forEach { - this.addProhibitedUserInfo(ProhibitedUserInfo.newBuilder().apply { + this.addProhibitedUsersInfo(ProhibitedUserInfo.newBuilder().apply { uid = ContactHelper.getUidByUinAsync(it.memberUin) uin = it.memberUin prohibitedTime = it.shutuptimestap @@ -380,7 +380,7 @@ internal object GroupService : GroupServiceGrpcKt.GroupServiceCoroutineImplBase( .map { it.toInt() }.forEach { val honor = decodeHonor(member.memberuin.toLong(), it, member.mHonorRichFlag) if (honor != null) { - addGroupHonorInfo(GroupHonorInfo.newBuilder().apply { + addGroupHonorsInfo(GroupHonorInfo.newBuilder().apply { uid = ContactHelper.getUidByUinAsync(member.memberuin.toLong()) uin = member.memberuin.toLong() nick = member.troopnick diff --git a/xposed/src/main/java/kritor/service/MessageService.kt b/xposed/src/main/java/kritor/service/MessageService.kt index ac1e9ce8..f1b99a2a 100644 --- a/xposed/src/main/java/kritor/service/MessageService.kt +++ b/xposed/src/main/java/kritor/service/MessageService.kt @@ -7,9 +7,8 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import com.tencent.qqnt.msg.api.IMsgService import io.grpc.Status import io.grpc.StatusRuntimeException -import io.kritor.event.MessageEvent +import io.kritor.common.* import io.kritor.message.* -import io.kritor.message.EssenceMessage import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull import moe.fuqiuluo.shamrock.helper.Level @@ -55,7 +54,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp uniseq ).onFailure { throw StatusRuntimeException(Status.INTERNAL.withCause(it)) - }.getOrThrow() + }.getOrThrow().toString() }.build() } @@ -89,8 +88,8 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp return SendMessageByResIdResponse.newBuilder().build() } - @Grpc("MessageService", "ClearMessages") - override suspend fun clearMessages(request: ClearMessagesRequest): ClearMessagesResponse { + @Grpc("MessageService", "SetMessageReaded") + override suspend fun setMessageReaded(request: SetMessageReadRequest): SetMessageReadResponse { val contact = request.contact val kernelService = NTServiceFetcher.kernelService val sessionService = kernelService.wrapperSession @@ -105,7 +104,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene")) } service.clearMsgRecords(Contact(chatType, contact.peer, contact.subPeer), null) - return ClearMessagesResponse.newBuilder().build() + return SetMessageReadResponse.newBuilder().build() } @Grpc("MessageService", "RecallMessage") @@ -126,7 +125,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp val kernelService = NTServiceFetcher.kernelService val sessionService = kernelService.wrapperSession val service = sessionService.msgService - service.recallMsg(contact, arrayListOf(request.messageId)) { code, msg -> + service.recallMsg(contact, arrayListOf(request.messageId.toLong())) { code, msg -> if (code != 0) { LogCenter.log("消息撤回失败: $code:$msg", Level.WARN) } @@ -153,7 +152,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp val msg: MsgRecord = withTimeoutOrNull(5000) { val service = QRoute.api(IMsgService::class.java) suspendCancellableCoroutine { continuation -> - service.getMsgsByMsgId(contact, arrayListOf(request.messageId)) { code, _, msgRecords -> + service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords -> if (code == 0 && msgRecords.isNotEmpty()) { continuation.resume(msgRecords.first()) } else { @@ -167,13 +166,13 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp } ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found")) return GetMessageResponse.newBuilder().apply { - this.message = MessageEvent.newBuilder().apply { - this.messageId = msg.msgId + this.message = PushMessageBody.newBuilder().apply { + this.messageId = msg.msgId.toString() this.contact = request.contact this.sender = Sender.newBuilder().apply { + this.uid = msg.senderUid ?: "" this.uin = msg.senderUin this.nick = msg.sendNickName ?: "" - this.uid = msg.senderUid ?: "" }.build() this.messageSeq = msg.msgSeq this.addAllElements(msg.elements.toKritorReqMessages(contact)) @@ -213,8 +212,8 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp } ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found")) return GetMessageBySeqResponse.newBuilder().apply { - this.message = MessageEvent.newBuilder().apply { - this.messageId = msg.msgId + this.message = PushMessageBody.newBuilder().apply { + this.messageId = msg.msgId.toString() this.contact = request.contact this.sender = Sender.newBuilder().apply { this.uin = msg.senderUin @@ -245,7 +244,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp val msgs: List = withTimeoutOrNull(5000) { val service = QRoute.api(IMsgService::class.java) suspendCancellableCoroutine { continuation -> - service.getMsgs(contact, request.startMessageId, request.count, true) { code, _, msgRecords -> + service.getMsgs(contact, request.startMessageId.toLong(), request.count, true) { code, _, msgRecords -> if (code == 0 && msgRecords.isNotEmpty()) { continuation.resume(msgRecords) } else { @@ -260,8 +259,8 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp return GetHistoryMessageResponse.newBuilder().apply { msgs.forEach { - addMessages(MessageEvent.newBuilder().apply { - this.messageId = it.msgId + addMessages(PushMessageBody.newBuilder().apply { + this.messageId = it.msgId.toString() this.contact = request.contact this.sender = Sender.newBuilder().apply { this.uin = it.senderUin @@ -292,9 +291,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp } val forwardMessage = ForwardMessageHelper.uploadMultiMsg( - contact.chatType, - contact.longPeer().toString(), - contact.guildId, + contact, request.messagesList ).onFailure { throw StatusRuntimeException(Status.INTERNAL.withCause(it)) @@ -312,11 +309,11 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp MessageHelper.getForwardMsg(request.resId).onFailure { throw StatusRuntimeException(Status.INTERNAL.withCause(it)) }.getOrThrow().map { detail -> - MessageEvent.newBuilder().apply { + PushMessageBody.newBuilder().apply { this.time = detail.time - this.messageId = detail.qqMsgId + this.messageId = detail.qqMsgId.toString() this.messageSeq = detail.msgSeq - this.contact = io.kritor.message.Contact.newBuilder().apply { + this.contact = io.kritor.common.Contact.newBuilder().apply { this.scene = when (detail.msgType) { MsgConstant.KCHATTYPEC2C -> Scene.FRIEND MsgConstant.KCHATTYPEGROUP -> Scene.GROUP @@ -347,13 +344,13 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp }.build() } - @Grpc("MessageService", "DeleteEssenceMsg") - override suspend fun deleteEssenceMsg(request: DeleteEssenceMsgRequest): DeleteEssenceMsgResponse { + @Grpc("MessageService", "DeleteEssenceMessage") + override suspend fun deleteEssenceMessage(request: DeleteEssenceMessageRequest): DeleteEssenceMessageResponse { val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, request.groupId.toString()) val msg: MsgRecord = withTimeoutOrNull(5000) { val service = QRoute.api(IMsgService::class.java) suspendCancellableCoroutine { continuation -> - service.getMsgsByMsgId(contact, arrayListOf(request.messageId)) { code, _, msgRecords -> + service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords -> if (code == 0 && msgRecords.isNotEmpty()) { continuation.resume(msgRecords.first()) } else { @@ -367,17 +364,17 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp } ?: throw StatusRuntimeException(Status.NOT_FOUND.withDescription("Message not found")) if (MessageHelper.deleteEssenceMessage(request.groupId, msg.msgSeq, msg.msgRandom) == null) throw StatusRuntimeException(Status.NOT_FOUND.withDescription("delete essence message failed")) - return DeleteEssenceMsgResponse.newBuilder().build() + return DeleteEssenceMessageResponse.newBuilder().build() } - @Grpc("MessageService", "GetEssenceMessages") - override suspend fun getEssenceMessages(request: GetEssenceMessagesRequest): GetEssenceMessagesResponse { + @Grpc("MessageService", "GetEssenceMessageList") + override suspend fun getEssenceMessageList(request: GetEssenceMessageListRequest): GetEssenceMessageListResponse { val contact = MessageHelper.generateContact(MsgConstant.KCHATTYPEGROUP, request.groupId.toString()) - return GetEssenceMessagesResponse.newBuilder().apply { + return GetEssenceMessageListResponse.newBuilder().apply { MessageHelper.getEssenceMessageList(request.groupId, request.page, request.pageSize).onFailure { throw StatusRuntimeException(Status.INTERNAL.withCause(it)) }.getOrThrow().forEach { - addEssenceMessage(EssenceMessage.newBuilder().apply { + addMessages(EssenceMessageBody.newBuilder().apply { withTimeoutOrNull(5000) { val service = QRoute.api(IMsgService::class.java) suspendCancellableCoroutine { continuation -> @@ -393,10 +390,10 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp } } }?.let { - this.messageId = it.msgId + this.messageId = it.msgId.toString() } this.messageSeq = it.messageSeq - this.msgTime = it.senderTime.toInt() + this.messageTime = it.senderTime.toInt() this.senderNick = it.senderNick this.senderUin = it.senderId this.operationTime = it.operatorTime.toInt() @@ -414,7 +411,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp val msg: MsgRecord = withTimeoutOrNull(5000) { val service = QRoute.api(IMsgService::class.java) suspendCancellableCoroutine { continuation -> - service.getMsgsByMsgId(contact, arrayListOf(request.messageId)) { code, _, msgRecords -> + service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords -> if (code == 0 && msgRecords.isNotEmpty()) { continuation.resume(msgRecords.first()) } else { @@ -432,8 +429,8 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp return SetEssenceMessageResponse.newBuilder().build() } - @Grpc("MessageService", "SetMessageCommentEmoji") - override suspend fun setMessageCommentEmoji(request: SetMessageCommentEmojiRequest): SetMessageCommentEmojiResponse { + @Grpc("MessageService", "ReactMessageWithEmoji") + override suspend fun reactMessageWithEmoji(request: ReactMessageWithEmojiRequest): ReactMessageWithEmojiResponse { val contact = request.contact.let { MessageHelper.generateContact( when (it.scene!!) { @@ -450,7 +447,7 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp val msg: MsgRecord = withTimeoutOrNull(5000) { val service = QRoute.api(IMsgService::class.java) suspendCancellableCoroutine { continuation -> - service.getMsgsByMsgId(contact, arrayListOf(request.messageId)) { code, _, msgRecords -> + service.getMsgsByMsgId(contact, arrayListOf(request.messageId.toLong())) { code, _, msgRecords -> if (code == 0 && msgRecords.isNotEmpty()) { continuation.resume(msgRecords.first()) } else { @@ -468,6 +465,6 @@ internal object MessageService : MessageServiceGrpcKt.MessageServiceCoroutineImp request.faceId.toString(), request.isComment ) - return SetMessageCommentEmojiResponse.newBuilder().build() + return ReactMessageWithEmojiResponse.newBuilder().build() } } \ No newline at end of file diff --git a/xposed/src/main/java/kritor/service/QsignService.kt b/xposed/src/main/java/kritor/service/QsignService.kt index fa7beb90..90ef9a4e 100644 --- a/xposed/src/main/java/kritor/service/QsignService.kt +++ b/xposed/src/main/java/kritor/service/QsignService.kt @@ -11,9 +11,9 @@ internal object QsignService: QsignServiceGrpcKt.QsignServiceCoroutineImplBase() override suspend fun sign(request: SignRequest): SignResponse { return SignResponse.newBuilder().apply { val result = FEKit.getInstance().getSign(request.command, request.buffer.toByteArray(), request.seq, request.uin) - this.sign = ByteString.copyFrom(result.sign) - this.token = ByteString.copyFrom(result.token) - this.extra = ByteString.copyFrom(result.extra) + this.secSig = ByteString.copyFrom(result.sign) + this.secDeviceToken = ByteString.copyFrom(result.token) + this.secExtra = ByteString.copyFrom(result.extra) }.build() } diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/internals/GlobalEventTransmitter.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/internals/GlobalEventTransmitter.kt index cbe0d624..0c6e8414 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/internals/GlobalEventTransmitter.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/internals/GlobalEventTransmitter.kt @@ -5,8 +5,9 @@ package moe.fuqiuluo.shamrock.internals import com.tencent.qqnt.kernel.nativeinterface.MsgElement import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import io.kritor.event.* -import io.kritor.message.Contact -import io.kritor.message.Sender +import io.kritor.common.PushMessageBody +import io.kritor.common.Contact +import io.kritor.common.Sender import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.FlowCollector @@ -17,7 +18,7 @@ import qq.service.msg.toKritorEventMessages internal object GlobalEventTransmitter : QQInterfaces() { private val MessageEventFlow by lazy { - MutableSharedFlow>() + MutableSharedFlow>() } private val noticeEventFlow by lazy { MutableSharedFlow() @@ -30,7 +31,7 @@ internal object GlobalEventTransmitter : QQInterfaces() { private suspend fun pushRequest(requestEvent: RequestsEvent) = requestEventFlow.emit(requestEvent) - private suspend fun transMessageEvent(record: MsgRecord, message: MessageEvent) = + private suspend fun transMessageEvent(record: MsgRecord, message: PushMessageBody) = MessageEventFlow.emit(record to message) object MessageTransmitter { @@ -38,9 +39,9 @@ internal object GlobalEventTransmitter : QQInterfaces() { record: MsgRecord, elements: ArrayList, ): Boolean { - transMessageEvent(record, MessageEvent.newBuilder().apply { + transMessageEvent(record, PushMessageBody.newBuilder().apply { this.time = record.msgTime.toInt() - this.messageId = record.msgId + this.messageId = record.msgId.toString() this.messageSeq = record.msgSeq this.contact = Contact.newBuilder().apply { this.scene = scene @@ -61,9 +62,9 @@ internal object GlobalEventTransmitter : QQInterfaces() { record: MsgRecord, elements: ArrayList, ): Boolean { - transMessageEvent(record, MessageEvent.newBuilder().apply { + transMessageEvent(record, PushMessageBody.newBuilder().apply { this.time = record.msgTime.toInt() - this.messageId = record.msgId + this.messageId = record.msgId.toString() this.messageSeq = record.msgSeq this.contact = Contact.newBuilder().apply { this.scene = scene @@ -86,9 +87,9 @@ internal object GlobalEventTransmitter : QQInterfaces() { groupCode: Long, fromNick: String, ): Boolean { - transMessageEvent(record, MessageEvent.newBuilder().apply { + transMessageEvent(record, PushMessageBody.newBuilder().apply { this.time = record.msgTime.toInt() - this.messageId = record.msgId + this.messageId = record.msgId.toString() this.messageSeq = record.msgSeq this.contact = Contact.newBuilder().apply { this.scene = scene @@ -109,9 +110,9 @@ internal object GlobalEventTransmitter : QQInterfaces() { record: MsgRecord, elements: ArrayList, ): Boolean { - transMessageEvent(record, MessageEvent.newBuilder().apply { + transMessageEvent(record, PushMessageBody.newBuilder().apply { this.time = record.msgTime.toInt() - this.messageId = record.msgId + this.messageId = record.msgId.toString() this.messageSeq = record.msgSeq this.contact = Contact.newBuilder().apply { this.scene = scene @@ -147,12 +148,12 @@ internal object GlobalEventTransmitter : QQInterfaces() { url: String ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.FRIEND_FILE_COME + this.type = NoticeEvent.NoticeType.FRIEND_FILE_COME this.time = msgTime.toInt() - this.friendFileCome = FriendFileComeNotice.newBuilder().apply { + this.friendFileUploaded = FriendFileUploadedNotice.newBuilder().apply { this.fileId = fileId this.fileName = fileName - this.operator = userId + this.operatorUin = userId this.fileSize = fileSize this.expireTime = expireTime.toInt() this.fileSubId = fileSubId @@ -176,11 +177,11 @@ internal object GlobalEventTransmitter : QQInterfaces() { url: String ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.GROUP_FILE_COME + this.type = NoticeEvent.NoticeType.GROUP_FILE_COME this.time = msgTime.toInt() - this.groupFileCome = GroupFileComeNotice.newBuilder().apply { + this.groupFileUploaded = GroupFileUploadedNotice.newBuilder().apply { this.groupId = groupId - this.operator = userId + this.operatorUin = userId this.fileId = uuid this.fileName = fileName this.fileSize = fileSize @@ -204,9 +205,9 @@ internal object GlobalEventTransmitter : QQInterfaces() { groupCode: Long ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.GROUP_SIGN + this.type = NoticeEvent.NoticeType.GROUP_SIGN this.time = time.toInt() - this.groupSign = GroupSignNotice.newBuilder().apply { + this.groupSignIn = GroupSignInNotice.newBuilder().apply { this.groupId = groupCode this.targetUin = target this.action = action ?: "" @@ -227,12 +228,13 @@ internal object GlobalEventTransmitter : QQInterfaces() { groupCode: Long ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.GROUP_POKE + this.type = NoticeEvent.NoticeType.GROUP_POKE this.time = time.toInt() this.groupPoke = GroupPokeNotice.newBuilder().apply { + this.groupId = groupCode this.action = action ?: "" - this.target = target - this.operator = operator + this.targetUin = target + this.operatorUin = operator this.suffix = suffix ?: "" this.actionImage = actionImg ?: "" }.build() @@ -247,10 +249,10 @@ internal object GlobalEventTransmitter : QQInterfaces() { groupCode: Long, operator: Long, operatorUid: String, - type: GroupMemberIncreasedType + type: GroupMemberIncreasedNotice.GroupMemberIncreasedType ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.GROUP_MEMBER_INCREASE + this.type = NoticeEvent.NoticeType.GROUP_MEMBER_INCREASE this.time = time.toInt() this.groupMemberIncrease = GroupMemberIncreasedNotice.newBuilder().apply { this.groupId = groupCode @@ -271,10 +273,10 @@ internal object GlobalEventTransmitter : QQInterfaces() { groupCode: Long, operator: Long, operatorUid: String, - type: GroupMemberDecreasedType + type: GroupMemberDecreasedNotice.GroupMemberDecreasedType ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.GROUP_MEMBER_INCREASE + this.type = NoticeEvent.NoticeType.GROUP_MEMBER_INCREASE this.time = time.toInt() this.groupMemberDecrease = GroupMemberDecreasedNotice.newBuilder().apply { this.groupId = groupCode @@ -296,9 +298,9 @@ internal object GlobalEventTransmitter : QQInterfaces() { setAdmin: Boolean ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.GROUP_ADMIN_CHANGED + this.type = NoticeEvent.NoticeType.GROUP_ADMIN_CHANGED this.time = msgTime.toInt() - this.groupAdminChanged = GroupAdminChangedNotice.newBuilder().apply { + this.groupAdminChange = GroupAdminChangedNotice.newBuilder().apply { this.groupId = groupCode this.targetUid = targetUid this.targetUin = target @@ -315,12 +317,12 @@ internal object GlobalEventTransmitter : QQInterfaces() { isOpen: Boolean ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.GROUP_WHOLE_BAN + this.type = NoticeEvent.NoticeType.GROUP_WHOLE_BAN this.time = msgTime.toInt() this.groupWholeBan = GroupWholeBanNotice.newBuilder().apply { this.groupId = groupCode - this.isWholeBan = isOpen - this.operator = operator + this.isBan = isOpen + this.operatorUin = operator }.build() }.build()) return true @@ -336,17 +338,17 @@ internal object GlobalEventTransmitter : QQInterfaces() { duration: Int ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.GROUP_MEMBER_BANNED + this.type = NoticeEvent.NoticeType.GROUP_MEMBER_BANNED this.time = msgTime.toInt() - this.groupMemberBanned = GroupMemberBannedNotice.newBuilder().apply { + this.groupMemberBan = GroupMemberBanNotice.newBuilder().apply { this.groupId = groupCode this.operatorUid = operatorUid this.operatorUin = operator this.targetUid = targetUid this.targetUin = target this.duration = duration - this.type = if (duration > 0) GroupMemberBanType.BAN - else GroupMemberBanType.LIFT_BAN + this.type = if (duration > 0) GroupMemberBanNotice.GroupMemberBanType.BAN + else GroupMemberBanNotice.GroupMemberBanType.LIFT_BAN }.build() }.build()) return true @@ -363,7 +365,7 @@ internal object GlobalEventTransmitter : QQInterfaces() { tipText: String ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.GROUP_RECALL + this.type = NoticeEvent.NoticeType.GROUP_RECALL this.time = time.toInt() this.groupRecall = GroupRecallNotice.newBuilder().apply { this.groupId = groupCode @@ -371,7 +373,7 @@ internal object GlobalEventTransmitter : QQInterfaces() { this.operatorUin = operator this.targetUid = targetUid this.targetUin = target - this.messageId = msgId + this.messageId = msgId.toString() this.tipText = tipText }.build() }.build()) @@ -381,11 +383,18 @@ internal object GlobalEventTransmitter : QQInterfaces() { suspend fun transCardChange( time: Long, targetId: Long, - oldCard: String, newCard: String, groupId: Long ): Boolean { - + pushNotice(NoticeEvent.newBuilder().apply { + this.type = NoticeEvent.NoticeType.GROUP_CARD_CHANGED + this.time = time.toInt() + this.groupCardChanged = GroupCardChangedNotice.newBuilder().apply { + this.groupId = groupId + this.targetUin = targetId + this.newCard = newCard + }.build() + }.build()) return true } @@ -396,7 +405,7 @@ internal object GlobalEventTransmitter : QQInterfaces() { groupId: Long ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.GROUP_MEMBER_UNIQUE_TITLE_CHANGED + this.type = NoticeEvent.NoticeType.GROUP_MEMBER_UNIQUE_TITLE_CHANGED this.time = time.toInt() this.groupMemberUniqueTitleChanged = GroupUniqueTitleChangedNotice.newBuilder().apply { this.groupId = groupId @@ -416,13 +425,13 @@ internal object GlobalEventTransmitter : QQInterfaces() { subType: UInt ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.GROUP_ESSENCE_CHANGED + this.type = NoticeEvent.NoticeType.GROUP_ESSENCE_CHANGED this.time = time.toInt() - this.groupEssenceChanged = EssenceMessageNotice.newBuilder().apply { + this.groupEssenceChanged = GroupEssenceMessageNotice.newBuilder().apply { this.groupId = groupId - this.messageId = msgId - this.sender = senderUin - this.operator = operatorUin + this.messageId = msgId.toString() + this.targetUin = senderUin + this.operatorUin = operatorUin this.subType = subType.toInt() }.build() }.build()) @@ -443,12 +452,11 @@ internal object GlobalEventTransmitter : QQInterfaces() { actionImg: String? ): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.FRIEND_POKE + this.type = NoticeEvent.NoticeType.FRIEND_POKE this.time = msgTime.toInt() this.friendPoke = FriendPokeNotice.newBuilder().apply { this.action = action ?: "" - this.target = target - this.operator = operator + this.operatorUin = operator this.suffix = suffix ?: "" this.actionImage = actionImg ?: "" }.build() @@ -458,11 +466,11 @@ internal object GlobalEventTransmitter : QQInterfaces() { suspend fun transPrivateRecall(time: Long, operator: Long, msgId: Long, tipText: String): Boolean { pushNotice(NoticeEvent.newBuilder().apply { - this.type = NoticeType.FRIEND_RECALL + this.type = NoticeEvent.NoticeType.FRIEND_RECALL this.time = time.toInt() this.friendRecall = FriendRecallNotice.newBuilder().apply { - this.operator = operator - this.messageId = msgId + this.operatorUin = operator + this.messageId = msgId.toString() this.tipText = tipText }.build() }.build()) @@ -477,7 +485,7 @@ internal object GlobalEventTransmitter : QQInterfaces() { object RequestTransmitter { suspend fun transFriendApp(time: Long, operator: Long, tipText: String, flag: String): Boolean { pushRequest(RequestsEvent.newBuilder().apply { - this.type = RequestType.FRIEND_APPLY + this.type = RequestsEvent.RequestType.FRIEND_APPLY this.time = time.toInt() this.friendApply = FriendApplyRequest.newBuilder().apply { this.applierUin = operator @@ -490,30 +498,28 @@ internal object GlobalEventTransmitter : QQInterfaces() { suspend fun transGroupApply( time: Long, - applier: Long, + applierUin: Long, applierUid: String, reason: String, groupCode: Long, - flag: String, - type: GroupApplyType + flag: String ): Boolean { pushRequest(RequestsEvent.newBuilder().apply { - this.type = RequestType.GROUP_APPLY + this.type = RequestsEvent.RequestType.GROUP_APPLY this.time = time.toInt() this.groupApply = GroupApplyRequest.newBuilder().apply { this.applierUid = applierUid - this.applierUin = applier + this.applierUin = applierUin this.groupId = groupCode this.reason = reason this.flag = flag - this.type = type }.build() }.build()) return true } } - suspend inline fun onMessageEvent(collector: FlowCollector>) { + suspend inline fun onMessageEvent(collector: FlowCollector>) { MessageEventFlow.collect { GlobalScope.launch { collector.emit(it) diff --git a/xposed/src/main/java/qq/service/contact/ContactExt.kt b/xposed/src/main/java/qq/service/contact/ContactExt.kt index b2b79622..c84740ac 100644 --- a/xposed/src/main/java/qq/service/contact/ContactExt.kt +++ b/xposed/src/main/java/qq/service/contact/ContactExt.kt @@ -2,7 +2,7 @@ package qq.service.contact import com.tencent.qqnt.kernel.nativeinterface.Contact import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import io.kritor.message.Scene +import io.kritor.common.Scene suspend fun Contact.longPeer(): Long { return when(this.chatType) { @@ -12,7 +12,7 @@ suspend fun Contact.longPeer(): Long { } } -suspend fun io.kritor.message.Contact.longPeer(): Long { +suspend fun io.kritor.common.Contact.longPeer(): Long { return when(this.scene) { Scene.GROUP -> peer.toLong() Scene.FRIEND, Scene.STRANGER, Scene.STRANGER_FROM_GROUP -> if (peer.startsWith("u_")) ContactHelper.getUinByUidAsync(peer).toLong() else peer.toLong() diff --git a/xposed/src/main/java/qq/service/file/GroupFileHelper.kt b/xposed/src/main/java/qq/service/file/GroupFileHelper.kt index c501e8ae..07799f29 100644 --- a/xposed/src/main/java/qq/service/file/GroupFileHelper.kt +++ b/xposed/src/main/java/qq/service/file/GroupFileHelper.kt @@ -5,11 +5,7 @@ package qq.service.file import com.tencent.mobileqq.pb.ByteStringMicro import io.grpc.Status import io.grpc.StatusRuntimeException -import io.kritor.file.File -import io.kritor.file.Folder -import io.kritor.file.GetFileSystemInfoResponse -import io.kritor.file.GetFilesRequest -import io.kritor.file.GetFilesResponse +import io.kritor.file.* import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.tools.EMPTY_BYTE_ARRAY @@ -77,7 +73,7 @@ internal object GroupFileHelper: QQInterfaces() { }.build() } - suspend fun getGroupFiles(groupId: Long, folderId: String = "/"): GetFilesResponse { + suspend fun getGroupFiles(groupId: Long, folderId: String = "/"): GetFileListResponse { val fileSystemInfo = getGroupFileSystemInfo(groupId) val fromServiceMsg = sendOidbAW("OidbSvc.0x6d8_1", 1752, 1, oidb_0x6d8.ReqBody().also { it.file_list_info_req.set(oidb_0x6d8.GetFileListReqBody().apply { @@ -150,7 +146,7 @@ internal object GroupFileHelper: QQInterfaces() { throw StatusRuntimeException(Status.INTERNAL.withDescription("unable to fetch oidb response")) } - return GetFilesResponse.newBuilder().apply { + return GetFileListResponse.newBuilder().apply { this.addAllFiles(files) this.addAllFolders(folders) }.build() diff --git a/xposed/src/main/java/qq/service/internals/AioListener.kt b/xposed/src/main/java/qq/service/internals/AioListener.kt index 44125410..83188725 100644 --- a/xposed/src/main/java/qq/service/internals/AioListener.kt +++ b/xposed/src/main/java/qq/service/internals/AioListener.kt @@ -14,7 +14,7 @@ import qq.service.bdh.RichProtoSvc import qq.service.kernel.SimpleKernelMsgListener import qq.service.msg.MessageHelper -object AioListener: SimpleKernelMsgListener() { +object AioListener : SimpleKernelMsgListener() { override fun onRecvMsg(records: ArrayList) { records.forEach { GlobalScope.launch { @@ -60,7 +60,12 @@ object AioListener: SimpleKernelMsgListener() { LogCenter.log("私聊临时消息(private = ${record.senderUin}, groupId=$groupCode)") - if (!GlobalEventTransmitter.MessageTransmitter.transTempMessage(record, record.elements, groupCode, fromNick) + if (!GlobalEventTransmitter.MessageTransmitter.transTempMessage( + record, + record.elements, + groupCode, + fromNick + ) ) { LogCenter.log("私聊临时消息推送失败 -> MessageTransmitter", Level.WARN) } @@ -137,4 +142,10 @@ object AioListener: SimpleKernelMsgListener() { LogCenter.log("群聊文件消息推送失败 -> FileNoticeTransmitter", Level.WARN) } } + + @OptIn(ExperimentalStdlibApi::class) + override fun onRecvSysMsg(arrayList: ArrayList?) { + LogCenter.log("onRecvSysMsg") + LogCenter.log(arrayList?.toByteArray()?.toHexString() ?: "") + } } \ No newline at end of file diff --git a/xposed/src/main/java/qq/service/internals/PrimitiveListener.kt b/xposed/src/main/java/qq/service/internals/PrimitiveListener.kt index c2a9b918..539da50c 100644 --- a/xposed/src/main/java/qq/service/internals/PrimitiveListener.kt +++ b/xposed/src/main/java/qq/service/internals/PrimitiveListener.kt @@ -5,9 +5,8 @@ import com.tencent.mobileqq.qroute.QRoute import com.tencent.qphone.base.remote.FromServiceMsg import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.msg.api.IMsgService -import io.kritor.event.GroupApplyType -import io.kritor.event.GroupMemberDecreasedType -import io.kritor.event.GroupMemberIncreasedType +import io.kritor.event.GroupMemberDecreasedNotice.GroupMemberDecreasedType +import io.kritor.event.GroupMemberIncreasedNotice.GroupMemberIncreasedType import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -595,7 +594,7 @@ internal object PrimitiveListener { } LogCenter.log("入群申请($groupCode) $applier: \"$reason\", seq: $msgSeq") if (!GlobalEventTransmitter.RequestTransmitter - .transGroupApply(time, applier, applierUid, reason, groupCode, flag, GroupApplyType.GROUP_APPLY_ADD) + .transGroupApply(time, applier, applierUid, reason, groupCode, flag) ) { LogCenter.log("入群申请推送失败!", Level.WARN) } @@ -630,7 +629,7 @@ internal object PrimitiveListener { } LogCenter.log("邀请入群申请($groupCode): $applier") if (!GlobalEventTransmitter.RequestTransmitter - .transGroupApply(time, applier, applierUid, "", groupCode, flag, GroupApplyType.GROUP_APPLY_ADD) + .transGroupApply(time, applier, applierUid, "", groupCode, flag) ) { LogCenter.log("邀请入群申请推送失败!", Level.WARN) } @@ -658,7 +657,7 @@ internal object PrimitiveListener { "$time;$groupCode;$uin" } if (!GlobalEventTransmitter.RequestTransmitter - .transGroupApply(time, invitor, invitorUid, "", groupCode, flag, GroupApplyType.GROUP_APPLY_INVITE) + .transGroupApply(time, invitor, invitorUid, "", groupCode, flag) ) { LogCenter.log("邀请入群推送失败!", Level.WARN) } diff --git a/xposed/src/main/java/qq/service/msg/ForwardMessageHelper.kt b/xposed/src/main/java/qq/service/msg/ForwardMessageHelper.kt index 9037e554..336e9cbb 100644 --- a/xposed/src/main/java/qq/service/msg/ForwardMessageHelper.kt +++ b/xposed/src/main/java/qq/service/msg/ForwardMessageHelper.kt @@ -1,14 +1,15 @@ package qq.service.msg import com.tencent.mobileqq.qroute.QRoute +import com.tencent.qqnt.kernel.nativeinterface.Contact import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgRecord import com.tencent.qqnt.msg.api.IMsgService import io.grpc.Status import io.grpc.StatusRuntimeException -import io.kritor.message.ForwardElement -import io.kritor.message.ForwardMessageBody -import io.kritor.message.Scene +import io.kritor.common.ForwardElement +import io.kritor.common.ForwardMessageBody +import io.kritor.common.Scene import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull import moe.fuqiuluo.shamrock.helper.Level @@ -30,9 +31,7 @@ import kotlin.time.Duration.Companion.seconds internal object ForwardMessageHelper : QQInterfaces() { suspend fun uploadMultiMsg( - chatType: Int, - peerId: String, - fromId: String = peerId, + contact: Contact, messages: List, ): Result { var i = -1 @@ -41,127 +40,121 @@ internal object ForwardMessageHelper : QQInterfaces() { val msgs = messages.mapNotNull { msg -> kotlin.runCatching { - val contact = msg.contact.let { - MessageHelper.generateContact( - when (it.scene!!) { - Scene.GROUP -> MsgConstant.KCHATTYPEGROUP - Scene.FRIEND -> MsgConstant.KCHATTYPEC2C - Scene.GUILD -> MsgConstant.KCHATTYPEGUILD - Scene.STRANGER_FROM_GROUP -> MsgConstant.KCHATTYPETEMPC2CFROMGROUP - Scene.NEARBY -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN - Scene.STRANGER -> MsgConstant.KCHATTYPETEMPC2CFROMUNKNOWN - Scene.UNRECOGNIZED -> throw StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Unrecognized scene")) - }, it.peer, it.subPeer - ) - } - if (msg.hasMessageId()) { - val record: MsgRecord = withTimeoutOrNull(5000) { - val service = QRoute.api(IMsgService::class.java) - suspendCancellableCoroutine { continuation -> - service.getMsgsByMsgId( - contact, - arrayListOf(msg.messageId.toLong()) - ) { code, _, msgRecords -> - if (code == 0 && msgRecords.isNotEmpty()) { - continuation.resume(msgRecords.first()) - } else { + when (msg.forwardMessageCase) { + ForwardMessageBody.ForwardMessageCase.MESSAGE_ID -> { + val record: MsgRecord = withTimeoutOrNull(5000) { + val service = QRoute.api(IMsgService::class.java) + suspendCancellableCoroutine { continuation -> + service.getMsgsByMsgId( + contact, + arrayListOf(msg.messageId.toLong()) + ) { code, _, msgRecords -> + if (code == 0 && msgRecords.isNotEmpty()) { + continuation.resume(msgRecords.first()) + } else { + continuation.resume(null) + } + } + continuation.invokeOnCancellation { continuation.resume(null) } } - continuation.invokeOnCancellation { - continuation.resume(null) - } - } - } ?: error("合并转发消息节点消息(id = ${msg.messageId})获取失败") - PushMsgBody( - msgHead = ResponseHead( - peerUid = record.senderUid, - receiverUid = record.peerUid, - forward = ResponseForward( - friendName = record.sendNickName + } ?: error("合并转发消息节点消息(id = ${msg.messageId})获取失败") + PushMsgBody( + msgHead = ResponseHead( + peerUid = record.senderUid, + receiverUid = record.peerUid, + forward = ResponseForward( + friendName = record.sendNickName + ), + responseGrp = if (record.chatType == MsgConstant.KCHATTYPEGROUP) ResponseGrp( + groupCode = record.peerUin.toULong(), + memberCard = record.sendMemberName, + u1 = 2 + ) else null ), - responseGrp = if (record.chatType == MsgConstant.KCHATTYPEGROUP) ResponseGrp( - groupCode = record.peerUin.toULong(), - memberCard = record.sendMemberName, - u1 = 2 - ) else null - ), - contentHead = ContentHead( - msgType = when (record.chatType) { - MsgConstant.KCHATTYPEC2C -> 9 - MsgConstant.KCHATTYPEGROUP -> 82 - else -> throw UnsupportedOperationException("Unsupported chatType: $chatType") - }, - msgSubType = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null, - divSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null, - msgViaRandom = record.msgId, - sequence = record.msgSeq, // idk what this is(i++) - msgTime = record.msgTime, - u2 = 1, - u6 = 0, - u7 = 0, - msgSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) record.msgSeq else null, // seq for dm - forwardHead = ForwardHead( - u1 = 0, - u2 = 0, - u3 = 0, - ub641 = "", - avatar = "" + contentHead = ContentHead( + msgType = when (record.chatType) { + MsgConstant.KCHATTYPEC2C -> 9 + MsgConstant.KCHATTYPEGROUP -> 82 + else -> throw UnsupportedOperationException("Unsupported chatType: ${contact.chatType}") + }, + msgSubType = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null, + divSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) 175 else null, + msgViaRandom = record.msgId, + sequence = record.msgSeq, // idk what this is(i++) + msgTime = record.msgTime, + u2 = 1, + u6 = 0, + u7 = 0, + msgSeq = if (record.chatType == MsgConstant.KCHATTYPEC2C) record.msgSeq else null, // seq for dm + forwardHead = ForwardHead( + u1 = 0, + u2 = 0, + u3 = 0, + ub641 = "", + avatar = "" + ) + ), + body = MsgBody( + richText = record.elements.toKritorReqMessages(contact).toRichText(contact).onFailure { + error("消息合成失败: ${it.stackTraceToString()}") + }.onSuccess { + desc[++i] = record.sendMemberName.ifEmpty { record.sendNickName } + ": " + it.first + }.getOrThrow().second ) - ), - body = MsgBody( - richText = record.elements.toKritorReqMessages(contact).toRichText(contact).onFailure { - error("消息合成失败: ${it.stackTraceToString()}") - }.onSuccess { - desc[++i] = record.sendMemberName.ifEmpty { record.sendNickName } + ": " + it.first - }.getOrThrow().second ) - ) - } else { - PushMsgBody( - msgHead = if (msg.hasSender()) ResponseHead( - peer = if (msg.sender.hasUin()) msg.sender.uin else TicketHelper.getUin().toLong(), - peerUid = msg.sender.uid, - receiverUid = TicketHelper.getUid(), - forward = ResponseForward( - friendName = if (msg.sender.hasNick()) msg.sender.nick else TicketHelper.getNickname() - ) - ) else ResponseHead( - peer = TicketHelper.getUin().toLong(), - peerUid = TicketHelper.getUid(), - receiverUid = TicketHelper.getUid(), - forward = ResponseForward( - friendName = TicketHelper.getNickname() - ) - ), - contentHead = ContentHead( - msgType = 9, - msgSubType = 175, - divSeq = 175, - msgViaRandom = Random.nextLong(), - sequence = msg.messageSeq.toLong(), - msgTime = msg.messageTime.toLong(), - u2 = 1, - u6 = 0, - u7 = 0, - msgSeq = msg.messageSeq.toLong(), - forwardHead = ForwardHead( - u1 = 0, - u2 = 0, - u3 = 2, - ub641 = "", - avatar = "" + } + + ForwardMessageBody.ForwardMessageCase.MESSAGE -> { + val _msg = msg.message + PushMsgBody( + msgHead = if (_msg.hasSender()) ResponseHead( + peer = if (_msg.sender.hasUin()) _msg.sender.uin else TicketHelper.getUin().toLong(), + peerUid = _msg.sender.uid, + receiverUid = TicketHelper.getUid(), + forward = ResponseForward( + friendName = if (_msg.sender.hasNick()) _msg.sender.nick else TicketHelper.getNickname() + ) + ) else ResponseHead( + peer = TicketHelper.getUin().toLong(), + peerUid = TicketHelper.getUid(), + receiverUid = TicketHelper.getUid(), + forward = ResponseForward( + friendName = TicketHelper.getNickname() + ) + ), + contentHead = ContentHead( + msgType = 9, + msgSubType = 175, + divSeq = 175, + msgViaRandom = Random.nextLong(), + sequence = _msg.messageSeq, + msgTime = _msg.time.toLong(), + u2 = 1, + u6 = 0, + u7 = 0, + msgSeq = _msg.messageSeq, + forwardHead = ForwardHead( + u1 = 0, + u2 = 0, + u3 = 2, + ub641 = "", + avatar = "" + ) + ), + body = MsgBody( + richText = _msg.elementsList.toRichText(contact).onSuccess { + desc[++i] = + (if (_msg.hasSender() && _msg.sender.hasNick()) _msg.sender.nick else TicketHelper.getNickname()) + ": " + it.first + }.onFailure { + error("消息合成失败: ${it.stackTraceToString()}") + }.getOrThrow().second ) - ), - body = MsgBody( - richText = msg.elementsList.toRichText(contact).onSuccess { - desc[++i] = - (if (msg.hasSender() && msg.sender.hasNick()) msg.sender.nick else TicketHelper.getNickname()) + ": " + it.first - }.onFailure { - error("消息合成失败: ${it.stackTraceToString()}") - }.getOrThrow().second ) - ) + } + + else -> null } }.onFailure { LogCenter.log("消息节点解析失败:${it.stackTraceToString()}", Level.WARN) @@ -192,21 +185,21 @@ internal object ForwardMessageHelper : QQInterfaces() { ) val req = LongMsgReq( - sendInfo = when (chatType) { + sendInfo = when (contact.chatType) { MsgConstant.KCHATTYPEC2C -> SendLongMsgInfo( type = 1, - uid = LongMsgUid(if (peerId.startsWith("u_")) peerId else ContactHelper.getUidByUinAsync(peerId.toLong())), + uid = LongMsgUid(contact.peerUid), payload = DeflateTools.gzip(payload.toByteArray()) ) MsgConstant.KCHATTYPEGROUP -> SendLongMsgInfo( type = 3, - uid = LongMsgUid(fromId), - groupUin = fromId.toULong(), + uid = LongMsgUid(contact.peerUid), + groupUin = contact.peerUid.toULong(), payload = DeflateTools.gzip(payload.toByteArray()) ) - else -> throw UnsupportedOperationException("Unsupported chatType: $chatType") + else -> throw UnsupportedOperationException("Unsupported chatType: ${contact.chatType}") }, setting = LongMsgSettings( field1 = 4, diff --git a/xposed/src/main/java/qq/service/msg/MsgConvertor.kt b/xposed/src/main/java/qq/service/msg/MsgConvertor.kt index 17dd5000..b2cc6c54 100644 --- a/xposed/src/main/java/qq/service/msg/MsgConvertor.kt +++ b/xposed/src/main/java/qq/service/msg/MsgConvertor.kt @@ -5,7 +5,7 @@ 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.msg.api.IMsgService -import io.kritor.message.* +import io.kritor.common.* import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull import moe.fuqiuluo.shamrock.helper.ActionMsgException @@ -135,8 +135,8 @@ private object MsgConvertor { val elem = Element.newBuilder() elem.setImage(ImageElement.newBuilder().apply { - this.file = md5 - this.url = when (record.chatType) { + this.fileMd5 = md5 + this.fileUrl = when (record.chatType) { MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( originalUrl = originalUrl, md5 = md5, @@ -175,7 +175,7 @@ private object MsgConvertor { else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}") } this.type = - if (image.isFlashPic == true) ImageType.FLASH else if (image.original) ImageType.ORIGIN else ImageType.COMMON + if (image.isFlashPic == true) ImageElement.ImageType.FLASH else if (image.original) ImageElement.ImageType.ORIGIN else ImageElement.ImageType.COMMON this.subType = image.picSubType }) @@ -191,7 +191,7 @@ private object MsgConvertor { else ptt.md5HexStr elem.setVoice(VoiceElement.newBuilder().apply { - this.url = when (record.chatType) { + this.fileUrl = when (record.chatType) { MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt.fileUuid) MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl( "0", @@ -201,7 +201,7 @@ private object MsgConvertor { else -> throw UnsupportedOperationException("Not supported chat type: ${record.chatType}") } - this.file = md5 + this.fileMd5 = md5 this.magic = ptt.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE }) @@ -219,8 +219,8 @@ private object MsgConvertor { } } else video.fileName.split(".")[0].hex2ByteArray() elem.setVideo(VideoElement.newBuilder().apply { - this.file = md5.toHexString() - this.url = when (record.chatType) { + this.fileMd5 = md5.toHexString() + this.fileUrl = when (record.chatType) { MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid) MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) @@ -304,9 +304,9 @@ private object MsgConvertor { } if (sourceRecords.isNullOrEmpty()) { LogCenter.log("无法查询到回复的消息ID: seq = $msgSeq", Level.WARN) - this.messageId = reply.replayMsgId + this.messageId = reply.replayMsgId.toString() } else { - this.messageId = sourceRecords.first().msgId + this.messageId = sourceRecords.first().msgId.toString() } }) return Result.success(elem.build()) diff --git a/xposed/src/main/java/qq/service/msg/MultiConvertor.kt b/xposed/src/main/java/qq/service/msg/MultiConvertor.kt index bcb8b06f..eb4220dc 100644 --- a/xposed/src/main/java/qq/service/msg/MultiConvertor.kt +++ b/xposed/src/main/java/qq/service/msg/MultiConvertor.kt @@ -4,7 +4,9 @@ package qq.service.msg import com.tencent.qqnt.kernel.nativeinterface.Contact import com.tencent.qqnt.kernel.nativeinterface.MsgConstant -import io.kritor.message.* +import io.kritor.common.* +import io.kritor.common.Element.ElementType +import io.kritor.common.ImageElement.ImageType import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact import kotlinx.io.core.readUInt @@ -62,9 +64,9 @@ suspend fun List.toKritorResponseMessages(contact: Contact): ArrayList RichProtoSvc.getGroupPicDownUrl( origUrl, md5 @@ -82,9 +84,9 @@ suspend fun List.toKritorResponseMessages(contact: Contact): ArrayList RichProtoSvc.getGroupPicDownUrl( origUrl, md5 @@ -112,7 +114,7 @@ suspend fun List.toKritorResponseMessages(contact: Contact): ArrayList.toKritorResponseMessages(contact: Contact): ArrayList Result( - TEXT to ::textConvertor, - AT to ::atConvertor, - FACE to ::faceConvertor, - BUBBLE_FACE to ::bubbleFaceConvertor, - REPLY to ::replyConvertor, - IMAGE to ::imageConvertor, - VOICE to ::voiceConvertor, - VIDEO to ::videoConvertor, - BASKETBALL to ::basketballConvertor, - DICE to ::diceConvertor, - RPS to ::rpsConvertor, - POKE to ::pokeConvertor, - MUSIC to ::musicConvertor, - WEATHER to ::weatherConvertor, - LOCATION to ::locationConvertor, - SHARE to ::shareConvertor, - CONTACT to ::contactConvertor, - JSON to ::jsonConvertor, - FORWARD to ::forwardConvertor, - MARKDOWN to ::markdownConvertor, - BUTTON to ::buttonConvertor, + ElementType.TEXT to ::textConvertor, + ElementType.AT to ::atConvertor, + ElementType.FACE to ::faceConvertor, + ElementType.BUBBLE_FACE to ::bubbleFaceConvertor, + ElementType.REPLY to ::replyConvertor, + ElementType.IMAGE to ::imageConvertor, + ElementType.VOICE to ::voiceConvertor, + ElementType.VIDEO to ::videoConvertor, + ElementType.BASKETBALL to ::basketballConvertor, + ElementType.DICE to ::diceConvertor, + ElementType.RPS to ::rpsConvertor, + ElementType.POKE to ::pokeConvertor, + ElementType.MUSIC to ::musicConvertor, + ElementType.WEATHER to ::weatherConvertor, + ElementType.LOCATION to ::locationConvertor, + ElementType.SHARE to ::shareConvertor, + ElementType.CONTACT to ::contactConvertor, + ElementType.JSON to ::jsonConvertor, + ElementType.FORWARD to ::forwardConvertor, + ElementType.MARKDOWN to ::markdownConvertor, + ElementType.BUTTON to ::buttonConvertor, ) suspend fun convertToNtMsgs(contact: Contact, msgId: Long, msgs: Messages): ArrayList { @@ -135,41 +135,22 @@ object NtMsgConvertor { val elem = MsgElement() val at = TextElement() - if (sourceAt.at.accountCase == AtElement.AccountCase.UIN) { - val uin = sourceAt.at.uin - if (uin == 0L) { - at.content = "@全体成员" - at.atType = MsgConstant.ATTYPEALL - at.atNtUid = "0" - } else { - val info = GroupHelper.getTroopMemberInfoByUinV2(contact.peerUid, uin.toString(), true).onFailure { - LogCenter.log("无法获取群成员信息: contact=$contact, id=${uin}", Level.WARN) - }.getOrNull() - at.content = "@${ - info?.troopnick.ifNullOrEmpty { info?.friendnick } - ?: uin.toString() - }" - at.atType = MsgConstant.ATTYPEONE - at.atNtUid = ContactHelper.getUidByUinAsync(uin) - } + val uid = sourceAt.at.uid + if (uid == "all" || uid == "0") { + at.content = "@全体成员" + at.atType = MsgConstant.ATTYPEALL + at.atNtUid = "0" } else { - val uid = sourceAt.at.uid - if (uid == "all" || uid == "0") { - at.content = "@全体成员" - at.atType = MsgConstant.ATTYPEALL - at.atNtUid = "0" - } else { - val uin = ContactHelper.getUinByUidAsync(uid) - val info = GroupHelper.getTroopMemberInfoByUinV2(contact.peerUid, uin, true).onFailure { - LogCenter.log("无法获取群成员信息: contact=$contact, id=${uin}", Level.WARN) - }.getOrNull() - at.content = "@${ - info?.troopnick.ifNullOrEmpty { info?.friendnick } - ?: uin - }" - at.atType = MsgConstant.ATTYPEONE - at.atNtUid = uid - } + val uin = ContactHelper.getUinByUidAsync(uid) + val info = GroupHelper.getTroopMemberInfoByUinV2(contact.peerUid, uin, true).onFailure { + LogCenter.log("无法获取群成员信息: contact=$contact, id=${uin}", Level.WARN) + }.getOrNull() + at.content = "@${ + info?.troopnick.ifNullOrEmpty { info?.friendnick } + ?: uin + }" + at.atType = MsgConstant.ATTYPEONE + at.atNtUid = uid } elem.textElement = at elem.elementType = MsgConstant.KELEMTYPETEXT @@ -215,7 +196,11 @@ object NtMsgConvertor { return Result.success(elem) } - private suspend fun bubbleFaceConvertor(contact: Contact, msgId: Long, sourceBubbleFace: Element): Result { + private suspend fun bubbleFaceConvertor( + contact: Contact, + msgId: Long, + sourceBubbleFace: Element + ): Result { val faceId = sourceBubbleFace.bubbleFace.id val local = QQSysFaceUtil.convertToLocal(faceId) val name = QQSysFaceUtil.getFaceDescription(local) @@ -242,7 +227,7 @@ object NtMsgConvertor { element.elementType = MsgConstant.KELEMTYPEREPLY val reply = ReplyElement() - reply.replayMsgId = sourceReply.reply.messageId + reply.replayMsgId = sourceReply.reply.messageId.toLong() reply.sourceMsgIdInRecords = reply.replayMsgId if (reply.replayMsgId == 0L) { @@ -251,9 +236,10 @@ object NtMsgConvertor { withTimeoutOrNull(3000) { suspendCancellableCoroutine { - QRoute.api(IMsgService::class.java).getMsgsByMsgId(contact, arrayListOf(reply.replayMsgId)) { _, _, records -> - it.resume(records) - } + QRoute.api(IMsgService::class.java) + .getMsgsByMsgId(contact, arrayListOf(reply.replayMsgId)) { _, _, records -> + it.resume(records) + } } }?.firstOrNull()?.let { reply.replayMsgSeq = it.msgSeq @@ -270,25 +256,31 @@ object NtMsgConvertor { private suspend fun imageConvertor(contact: Contact, msgId: Long, sourceImage: Element): Result { val isOriginal = sourceImage.image.type == ImageType.ORIGIN val isFlash = sourceImage.image.type == ImageType.FLASH - val file = when(sourceImage.image.dataCase!!) { + val file = when (sourceImage.image.dataCase!!) { ImageElement.DataCase.FILE_NAME -> { - val fileMd5 = sourceImage.image.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase() + val fileMd5 = sourceImage.image.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "") + .split(".")[0].lowercase() FileUtils.getFileByMd5(fileMd5) } + ImageElement.DataCase.FILE_PATH -> { val filePath = sourceImage.image.filePath File(filePath).inputStream().use { FileUtils.saveFileToCache(it) } } - ImageElement.DataCase.FILE_BASE64 -> { - FileUtils.saveFileToCache(ByteArrayInputStream( - Base64.decode(sourceImage.image.fileBase64, Base64.DEFAULT) - )) + + ImageElement.DataCase.FILE -> { + FileUtils.saveFileToCache( + ByteArrayInputStream( + sourceImage.image.file.toByteArray() + ) + ) } - ImageElement.DataCase.URL -> { + + ImageElement.DataCase.FILE_URL -> { val tmp = FileUtils.getTmpFile() - if(DownloadUtils.download(sourceImage.image.url, tmp)) { + if (DownloadUtils.download(sourceImage.image.fileUrl, tmp)) { tmp.inputStream().use { FileUtils.saveFileToCache(it) }.also { @@ -296,16 +288,20 @@ object NtMsgConvertor { } } else { tmp.delete() - return Result.failure(LogicException("图片资源下载失败: ${sourceImage.image.url}")) + return Result.failure(LogicException("图片资源下载失败: ${sourceImage.image.fileUrl}")) } } + ImageElement.DataCase.DATA_NOT_SET -> return Result.failure(IllegalArgumentException("ImageElement data is not set")) } if (EnableOldBDH.get()) { Transfer with when (contact.chatType) { MsgConstant.KCHATTYPEGROUP -> Troop(contact.peerUid) - MsgConstant.KCHATTYPETEMPC2CFROMGROUP, MsgConstant.KCHATTYPEC2C -> Private(contact.longPeer().toString()) + MsgConstant.KCHATTYPETEMPC2CFROMGROUP, MsgConstant.KCHATTYPEC2C -> Private( + contact.longPeer().toString() + ) + MsgConstant.KCHATTYPEGUILD -> Troop(contact.peerUid) else -> return Result.failure(Exception("Not supported chatType(${contact.chatType}) for PictureMsg")) } trans PictureResource(file) @@ -360,25 +356,29 @@ object NtMsgConvertor { } private suspend fun voiceConvertor(contact: Contact, msgId: Long, sourceVoice: Element): Result { - var file = when(sourceVoice.voice.dataCase!!) { + var file = when (sourceVoice.voice.dataCase!!) { VoiceElement.DataCase.FILE_NAME -> { - val fileMd5 = sourceVoice.voice.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase() + val fileMd5 = sourceVoice.voice.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "") + .split(".")[0].lowercase() FileUtils.getFileByMd5(fileMd5) } + VoiceElement.DataCase.FILE_PATH -> { val filePath = sourceVoice.voice.filePath File(filePath).inputStream().use { FileUtils.saveFileToCache(it) } } - VoiceElement.DataCase.FILE_BASE64 -> { - FileUtils.saveFileToCache(ByteArrayInputStream( - Base64.decode(sourceVoice.voice.fileBase64, Base64.DEFAULT) - )) + + VoiceElement.DataCase.FILE -> { + FileUtils.saveFileToCache( + sourceVoice.voice.file.toByteArray().inputStream() + ) } - VoiceElement.DataCase.URL -> { + + VoiceElement.DataCase.FILE_URL -> { val tmp = FileUtils.getTmpFile() - if(DownloadUtils.download(sourceVoice.voice.url, tmp)) { + if (DownloadUtils.download(sourceVoice.voice.fileUrl, tmp)) { tmp.inputStream().use { FileUtils.saveFileToCache(it) }.also { @@ -386,9 +386,10 @@ object NtMsgConvertor { } } else { tmp.delete() - return Result.failure(LogicException("音频资源下载失败: ${sourceVoice.voice.url}")) + return Result.failure(LogicException("音频资源下载失败: ${sourceVoice.voice.fileUrl}")) } } + VoiceElement.DataCase.DATA_NOT_SET -> return Result.failure(IllegalArgumentException("VoiceElement data is not set")) } @@ -439,7 +440,10 @@ object NtMsgConvertor { if (EnableOldBDH.get()) { if (!(Transfer with when (contact.chatType) { MsgConstant.KCHATTYPEGROUP -> Troop(contact.peerUid) - MsgConstant.KCHATTYPETEMPC2CFROMGROUP, MsgConstant.KCHATTYPEC2C -> Private(contact.longPeer().toString()) + MsgConstant.KCHATTYPETEMPC2CFROMGROUP, MsgConstant.KCHATTYPEC2C -> Private( + contact.longPeer().toString() + ) + MsgConstant.KCHATTYPEGUILD -> Troop(contact.peerUid) else -> return Result.failure(Exception("Not supported chatType(${contact.chatType}) for VoiceMsg")) } trans VoiceResource(file)) @@ -485,27 +489,31 @@ object NtMsgConvertor { private suspend fun videoConvertor(contact: Contact, msgId: Long, sourceVideo: Element): Result { val elem = MsgElement() - val video = VideoElement() + val video = com.tencent.qqnt.kernel.nativeinterface.VideoElement() + + val file = when (sourceVideo.video.dataCase!!) { + VideoElement.DataCase.FILE -> { + FileUtils.saveFileToCache( + sourceVideo.video.file.toByteArray().inputStream() + ) + } - val file = when(sourceVideo.video.dataCase!!) { - io.kritor.message.VideoElement.DataCase.FILE_NAME -> { - val fileMd5 = sourceVideo.video.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase() + VideoElement.DataCase.FILE_NAME -> { + val fileMd5 = sourceVideo.video.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "") + .split(".")[0].lowercase() FileUtils.getFileByMd5(fileMd5) } - io.kritor.message.VideoElement.DataCase.FILE_PATH -> { + + VideoElement.DataCase.FILE_PATH -> { val filePath = sourceVideo.video.filePath File(filePath).inputStream().use { FileUtils.saveFileToCache(it) } } - io.kritor.message.VideoElement.DataCase.FILE_BASE64 -> { - FileUtils.saveFileToCache(ByteArrayInputStream( - Base64.decode(sourceVideo.video.fileBase64, Base64.DEFAULT) - )) - } - io.kritor.message.VideoElement.DataCase.URL -> { + + VideoElement.DataCase.FILE_URL -> { val tmp = FileUtils.getTmpFile() - if(DownloadUtils.download(sourceVideo.video.url, tmp)) { + if (DownloadUtils.download(sourceVideo.video.fileUrl, tmp)) { tmp.inputStream().use { FileUtils.saveFileToCache(it) }.also { @@ -513,10 +521,11 @@ object NtMsgConvertor { } } else { tmp.delete() - return Result.failure(LogicException("视频资源下载失败: ${sourceVideo.video.url}")) + return Result.failure(LogicException("视频资源下载失败: ${sourceVideo.video.fileUrl}")) } } - io.kritor.message.VideoElement.DataCase.DATA_NOT_SET -> return Result.failure(IllegalArgumentException("VideoElement data is not set")) + + VideoElement.DataCase.DATA_NOT_SET -> return Result.failure(IllegalArgumentException("VideoElement data is not set")) } video.videoMd5 = QQNTWrapperUtil.CppProxy.genFileMd5Hex(file.absolutePath) @@ -543,7 +552,10 @@ object NtMsgConvertor { if (EnableOldBDH.get()) { Transfer with when (contact.chatType) { MsgConstant.KCHATTYPEGROUP -> Troop(contact.peerUid) - MsgConstant.KCHATTYPETEMPC2CFROMGROUP, MsgConstant.KCHATTYPEC2C -> Private(contact.longPeer().toString()) + MsgConstant.KCHATTYPETEMPC2CFROMGROUP, MsgConstant.KCHATTYPEC2C -> Private( + contact.longPeer().toString() + ) + MsgConstant.KCHATTYPEGUILD -> Troop(contact.peerUid) else -> return Result.failure(Exception("Not supported chatType(${contact.chatType}) for VideoMsg")) } trans VideoResource(file, File(thumbPath.toString())) @@ -567,7 +579,11 @@ object NtMsgConvertor { return Result.success(elem) } - private suspend fun basketballConvertor(contact: Contact, msgId: Long, sourceBasketball: Element): Result { + private suspend fun basketballConvertor( + contact: Contact, + msgId: Long, + sourceBasketball: Element + ): Result { val elem = MsgElement() elem.elementType = MsgConstant.KELEMTYPEFACE val face = FaceElement() @@ -697,7 +713,11 @@ object NtMsgConvertor { } private suspend fun locationConvertor(contact: Contact, msgId: Long, sourceLocation: Element): Result { - LbsHelper.tryShareLocation(contact, sourceLocation.location.lat.toDouble(), sourceLocation.location.lon.toDouble()).onFailure { + LbsHelper.tryShareLocation( + contact, + sourceLocation.location.lat.toDouble(), + sourceLocation.location.lon.toDouble() + ).onFailure { LogCenter.log("无法发送位置分享", Level.ERROR) } return Result.failure(ActionMsgException) @@ -801,7 +821,8 @@ object NtMsgConvertor { val action = button.action val permission = action.permission return runCatching { - InlineKeyboardButton(button.id, renderData.label, renderData.visitedLabel, renderData.style, + InlineKeyboardButton( + button.id, renderData.label, renderData.visitedLabel, renderData.style, action.type, 0, action.unsupportedTips, action.data, false, @@ -811,7 +832,8 @@ object NtMsgConvertor { false, 0, false, arrayListOf() ) }.getOrElse { - InlineKeyboardButton(button.id, renderData.label, renderData.visitedLabel, renderData.style, + InlineKeyboardButton( + button.id, renderData.label, renderData.visitedLabel, renderData.style, action.type, 0, action.unsupportedTips, action.data, false, diff --git a/xposed/src/main/java/qq/service/msg/ReqMessageConvertor.kt b/xposed/src/main/java/qq/service/msg/ReqMessageConvertor.kt index cd9ae043..9bde05cd 100644 --- a/xposed/src/main/java/qq/service/msg/ReqMessageConvertor.kt +++ b/xposed/src/main/java/qq/service/msg/ReqMessageConvertor.kt @@ -5,7 +5,7 @@ import com.tencent.qqnt.kernel.nativeinterface.Contact import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.kernel.nativeinterface.MsgElement import com.tencent.qqnt.msg.api.IMsgService -import io.kritor.message.* +import io.kritor.common.* import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull import moe.fuqiuluo.shamrock.helper.ActionMsgException @@ -135,8 +135,8 @@ private object ReqMsgConvertor { val elem = Element.newBuilder() elem.setImage(ImageElement.newBuilder().apply { - this.file = md5 - this.url = when (contact.chatType) { + this.fileMd5 = md5 + this.fileUrl = when (contact.chatType) { MsgConstant.KCHATTYPEDISC, MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupPicDownUrl( originalUrl = originalUrl, md5 = md5, @@ -175,7 +175,7 @@ private object ReqMsgConvertor { else -> throw UnsupportedOperationException("Not supported chat type: ${contact.chatType}") } this.type = - if (image.isFlashPic == true) ImageType.FLASH else if (image.original) ImageType.ORIGIN else ImageType.COMMON + if (image.isFlashPic == true) ImageElement.ImageType.FLASH else if (image.original) ImageElement.ImageType.ORIGIN else ImageElement.ImageType.COMMON this.subType = image.picSubType }) @@ -191,7 +191,7 @@ private object ReqMsgConvertor { else ptt.md5HexStr elem.setVoice(VoiceElement.newBuilder().apply { - this.url = when (contact.chatType) { + this.fileUrl = when (contact.chatType) { MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CPttDownUrl("0", ptt.fileUuid) MsgConstant.KCHATTYPEGROUP, MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupPttDownUrl( "0", @@ -201,7 +201,7 @@ private object ReqMsgConvertor { else -> throw UnsupportedOperationException("Not supported chat type: ${contact.chatType}") } - this.file = md5 + this.fileMd5 = md5 this.magic = ptt.voiceChangeType != MsgConstant.KPTTVOICECHANGETYPENONE }) @@ -219,8 +219,8 @@ private object ReqMsgConvertor { } } else video.fileName.split(".")[0].hex2ByteArray() elem.setVideo(VideoElement.newBuilder().apply { - this.file = md5.toHexString() - this.url = when (contact.chatType) { + this.fileMd5 = md5.toHexString() + this.fileUrl = when (contact.chatType) { MsgConstant.KCHATTYPEGROUP -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) MsgConstant.KCHATTYPEC2C -> RichProtoSvc.getC2CVideoDownUrl("0", md5, video.fileUuid) MsgConstant.KCHATTYPEGUILD -> RichProtoSvc.getGroupVideoDownUrl("0", md5, video.fileUuid) @@ -233,7 +233,11 @@ private object ReqMsgConvertor { suspend fun convertMarketFace(contact: Contact, element: MsgElement): Result { val marketFace = element.marketFaceElement val elem = Element.newBuilder() - return Result.failure(ActionMsgException) + elem.setMarketFace(MarketFaceElement.newBuilder().apply { + this.id = marketFace.emojiId + }) + // TODO + return Result.success(elem.build()) } suspend fun convertStructJson(contact: Contact, element: MsgElement): Result { @@ -300,9 +304,9 @@ private object ReqMsgConvertor { } if (sourceRecords.isNullOrEmpty()) { LogCenter.log("无法查询到回复的消息ID: seq = $msgSeq", Level.WARN) - this.messageId = reply.replayMsgId + this.messageId = reply.replayMsgId.toString() } else { - this.messageId = sourceRecords.first().msgId + this.messageId = sourceRecords.first().msgId.toString() } }) return Result.success(elem.build()) @@ -393,7 +397,7 @@ private object ReqMsgConvertor { }) }.build() }) - this.applicationId = inlineKeyboard.botAppid + this.botAppid = inlineKeyboard.botAppid }) return Result.success(elem.build()) } diff --git a/xposed/src/main/java/qq/service/msg/ReqMultiConvertor.kt b/xposed/src/main/java/qq/service/msg/ReqMultiConvertor.kt index 6a07a4d0..023f386d 100644 --- a/xposed/src/main/java/qq/service/msg/ReqMultiConvertor.kt +++ b/xposed/src/main/java/qq/service/msg/ReqMultiConvertor.kt @@ -7,11 +7,8 @@ import com.tencent.mobileqq.qroute.QRoute import com.tencent.qqnt.kernel.nativeinterface.Contact import com.tencent.qqnt.kernel.nativeinterface.MsgConstant import com.tencent.qqnt.msg.api.IMsgService -import io.kritor.message.AtElement -import io.kritor.message.Element -import io.kritor.message.ElementType -import io.kritor.message.ImageElement -import io.kritor.message.ImageType +import io.kritor.common.Element +import io.kritor.common.ImageElement import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull import moe.fuqiuluo.shamrock.helper.Level @@ -68,7 +65,7 @@ suspend fun List.toRichText(contact: Contact): Result { + Element.ElementType.TEXT -> { val text = it.text.text val elem = Elem( text = TextMsg(text) @@ -76,13 +73,11 @@ suspend fun List.toRichText(contact: Contact): Result { + Element.ElementType.AT -> { when (contact.chatType) { MsgConstant.KCHATTYPEGROUP -> { - val qq = when (it.at.accountCase) { - AtElement.AccountCase.UIN -> it.at.uin.toString() - else -> ContactHelper.getUinByUidAsync(it.at.uid) - } + val qq = ContactHelper.getUinByUidAsync(it.at.uid) + val type: Int val nick = if (it.at.uid == "all" || it.at.uin == 0L) { type = 1 @@ -112,10 +107,7 @@ suspend fun List.toRichText(contact: Contact): Result { - val qq = when (it.at.accountCase) { - AtElement.AccountCase.UIN -> it.at.uin.toString() - else -> ContactHelper.getUinByUidAsync(it.at.uid) - } + val qq = ContactHelper.getUinByUidAsync(it.at.uid) val display = "@" + (ContactHelper.getProfileCard(qq.toLong()).onSuccess { it.strNick.ifNullOrEmpty { qq } }.onFailure { @@ -130,7 +122,7 @@ suspend fun List.toRichText(contact: Contact): Result throw UnsupportedOperationException("Unsupported chatType($contact) for AtMsg") } } - ElementType.FACE -> { + Element.ElementType.FACE -> { val faceId = it.face.id val elem = if (it.face.isBig) { Elem( @@ -159,12 +151,12 @@ suspend fun List.toRichText(contact: Contact): Result throw UnsupportedOperationException("Unsupported ElementType.BUBBLE_FACE") - ElementType.REPLY -> { + Element.ElementType.BUBBLE_FACE -> throw UnsupportedOperationException("Unsupported Element.ElementType.BUBBLE_FACE") + Element.ElementType.REPLY -> { val msgId = it.reply.messageId withTimeoutOrNull(3000) { suspendCancellableCoroutine { - QRoute.api(IMsgService::class.java).getMsgsByMsgId(contact, arrayListOf(msgId)) { _, _, records -> + QRoute.api(IMsgService::class.java).getMsgsByMsgId(contact, arrayListOf(msgId.toLong())) { _, _, records -> it.resume(records) } } @@ -191,9 +183,9 @@ suspend fun List.toRichText(contact: Contact): Result { + Element.ElementType.IMAGE -> { val type = it.image.type - val isOriginal = type == ImageType.ORIGIN + val isOriginal = type == ImageElement.ImageType.ORIGIN val file = when(it.image.dataCase!!) { ImageElement.DataCase.FILE_NAME -> { val fileMd5 = it.image.fileName.replace(regex = "[{}\\-]".toRegex(), replacement = "").split(".")[0].lowercase() @@ -205,16 +197,16 @@ suspend fun List.toRichText(contact: Contact): Result { + ImageElement.DataCase.FILE -> { FileUtils.saveFileToCache( ByteArrayInputStream( - Base64.decode(it.image.fileBase64, Base64.DEFAULT) + it.image.file.toByteArray() ) ) } - ImageElement.DataCase.URL -> { + ImageElement.DataCase.FILE_URL -> { val tmp = FileUtils.getTmpFile() - if(DownloadUtils.download(it.image.url, tmp)) { + if(DownloadUtils.download(it.image.fileUrl, tmp)) { tmp.inputStream().use { FileUtils.saveFileToCache(it) }.also { @@ -222,7 +214,7 @@ suspend fun List.toRichText(contact: Contact): Result throw IllegalArgumentException("ImageElement data is not set") @@ -352,10 +344,10 @@ suspend fun List.toRichText(contact: Contact): Result throw UnsupportedOperationException("Unsupported ElementType.VOICE") - ElementType.VIDEO -> throw UnsupportedOperationException("Unsupported ElementType.VIDEO") - ElementType.BASKETBALL -> throw UnsupportedOperationException("Unsupported ElementType.BASKETBALL") - ElementType.DICE -> { + Element.ElementType.VOICE -> throw UnsupportedOperationException("Unsupported Element.ElementType.VOICE") + Element.ElementType.VIDEO -> throw UnsupportedOperationException("Unsupported Element.ElementType.VIDEO") + Element.ElementType.BASKETBALL -> throw UnsupportedOperationException("Unsupported Element.ElementType.BASKETBALL") + Element.ElementType.DICE -> { val elem = Elem( commonElem = CommonElem( serviceType = 37, @@ -375,7 +367,7 @@ suspend fun List.toRichText(contact: Contact): Result { + Element.ElementType.RPS -> { val elem = Elem( commonElem = CommonElem( serviceType = 37, @@ -395,7 +387,7 @@ suspend fun List.toRichText(contact: Contact): Result { + Element.ElementType.POKE -> { val elem = Elem( commonElem = CommonElem( serviceType = 2, @@ -410,8 +402,8 @@ suspend fun List.toRichText(contact: Contact): Result throw UnsupportedOperationException("Unsupported ElementType.MUSIC") - ElementType.WEATHER -> { + Element.ElementType.MUSIC -> throw UnsupportedOperationException("Unsupported Element.ElementType.MUSIC") + Element.ElementType.WEATHER -> { var code = it.weather.code.toIntOrNull() if (code == null) { val city = it.weather.city @@ -438,11 +430,11 @@ suspend fun List.toRichText(contact: Contact): Result throw UnsupportedOperationException("Unsupported ElementType.LOCATION") - ElementType.SHARE -> throw UnsupportedOperationException("Unsupported ElementType.SHARE") - ElementType.GIFT -> throw UnsupportedOperationException("Unsupported ElementType.GIFT") - ElementType.MARKET_FACE -> throw UnsupportedOperationException("Unsupported ElementType.MARKET_FACE") - ElementType.FORWARD -> { + Element.ElementType.LOCATION -> throw UnsupportedOperationException("Unsupported Element.ElementType.LOCATION") + Element.ElementType.SHARE -> throw UnsupportedOperationException("Unsupported Element.ElementType.SHARE") + Element.ElementType.GIFT -> throw UnsupportedOperationException("Unsupported Element.ElementType.GIFT") + Element.ElementType.MARKET_FACE -> throw UnsupportedOperationException("Unsupported Element.ElementType.MARKET_FACE") + Element.ElementType.FORWARD -> { val resId = it.forward.resId val filename = UUID.randomUUID().toString().uppercase() var content = it.forward.summary @@ -496,8 +488,8 @@ suspend fun List.toRichText(contact: Contact): Result throw UnsupportedOperationException("Unsupported ElementType.CONTACT") - ElementType.JSON -> { + Element.ElementType.CONTACT -> throw UnsupportedOperationException("Unsupported Element.ElementType.CONTACT") + Element.ElementType.JSON -> { val elem = Elem( lightApp = LightAppElem( data = byteArrayOf(1) + DeflateTools.compress(it.json.json.toByteArray()) @@ -506,9 +498,9 @@ suspend fun List.toRichText(contact: Contact): Result throw UnsupportedOperationException("Unsupported ElementType.XML") - ElementType.FILE -> throw UnsupportedOperationException("Unsupported ElementType.FILE") - ElementType.MARKDOWN -> { + Element.ElementType.XML -> throw UnsupportedOperationException("Unsupported Element.ElementType.XML") + Element.ElementType.FILE -> throw UnsupportedOperationException("Unsupported Element.ElementType.FILE") + Element.ElementType.MARKDOWN -> { val elem = Elem( commonElem = CommonElem( serviceType = 45, @@ -519,7 +511,7 @@ suspend fun List.toRichText(contact: Contact): Result { + Element.ElementType.BUTTON -> { val elem = Elem( commonElem = CommonElem( serviceType = 46, @@ -552,7 +544,7 @@ suspend fun List.toRichText(contact: Contact): Result.toRichText(contact: Contact): Result throw UnsupportedOperationException("Unsupported ElementType.NODE") - ElementType.UNRECOGNIZED -> throw UnsupportedOperationException("Unsupported ElementType.UNRECOGNIZED") + Element.ElementType.UNRECOGNIZED -> throw UnsupportedOperationException("Unsupported Element.ElementType.UNRECOGNIZED") } } catch (e: Throwable) { LogCenter.log("转换消息失败(Multi): ${e.stackTraceToString()}", Level.ERROR)