diff --git a/kritor b/kritor index f4fa157..505d80b 160000 --- a/kritor +++ b/kritor @@ -1 +1 @@ -Subproject commit f4fa15754e266182b5f2c08c54c88c21c61eb065 +Subproject commit 505d80b2eec8d5f226ed28390fb15f4ca41b4c70 diff --git a/xposed/src/main/java/kritor/auth/AuthInterceptor.kt b/xposed/src/main/java/kritor/auth/AuthInterceptor.kt new file mode 100644 index 0000000..37f4186 --- /dev/null +++ b/xposed/src/main/java/kritor/auth/AuthInterceptor.kt @@ -0,0 +1,64 @@ +package kritor.auth + +import io.grpc.ForwardingServerCallListener +import io.grpc.Metadata +import io.grpc.ServerCall +import io.grpc.ServerCallHandler +import io.grpc.ServerInterceptor +import moe.fuqiuluo.shamrock.config.ActiveTicket +import moe.fuqiuluo.shamrock.config.ShamrockConfig + +object AuthInterceptor: ServerInterceptor { + /** + * Intercept [ServerCall] dispatch by the `next` [ServerCallHandler]. General + * semantics of [ServerCallHandler.startCall] apply and the returned + * [io.grpc.ServerCall.Listener] must not be `null`. + * + * + * If the implementation throws an exception, `call` will be closed with an error. + * Implementations must not throw an exception if they started processing that may use `call` on another thread. + * + * @param call object to receive response messages + * @param headers which can contain extra call metadata from [ClientCall.start], + * e.g. authentication credentials. + * @param next next processor in the interceptor chain + * @return listener for processing incoming messages for `call`, never `null`. + */ + override fun interceptCall( + call: ServerCall, + headers: Metadata?, + next: ServerCallHandler + ): ServerCall.Listener { + val methodName = call.methodDescriptor.fullMethodName + val ticket = getAllTicket() + if (ticket.isNotEmpty() && !methodName.startsWith("Auth")) { + val ticketHeader = headers?.get(Metadata.Key.of("ticket", Metadata.ASCII_STRING_MARSHALLER)) + if (ticketHeader == null || ticketHeader !in ticket) { + call.close(io.grpc.Status.UNAUTHENTICATED.withDescription("Invalid ticket"), Metadata()) + return object: ServerCall.Listener() {} + } + } + return object: ForwardingServerCallListener.SimpleForwardingServerCallListener(next.startCall(call, headers)) { + } + } + + private fun getAllTicket(): List { + val result = arrayListOf() + val activeTicketName = ActiveTicket.name() + var index = 0 + while (true) { + val ticket = ShamrockConfig.getProperty(activeTicketName + if (index == 0) "" else ".$index", null) + if (ticket.isNullOrEmpty()) { + if (index == 0) { + return result + } else { + break + } + } else { + result.add(ticket) + } + index++ + } + return result + } +} \ No newline at end of file diff --git a/xposed/src/main/java/kritor/server/KritorServer.kt b/xposed/src/main/java/kritor/server/KritorServer.kt index a568247..b3b1cb9 100644 --- a/xposed/src/main/java/kritor/server/KritorServer.kt +++ b/xposed/src/main/java/kritor/server/KritorServer.kt @@ -3,11 +3,10 @@ package kritor.server import io.grpc.Grpc import io.grpc.InsecureServerCredentials -import io.grpc.ServerBuilder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.asExecutor import kritor.service.* import moe.fuqiuluo.shamrock.helper.LogCenter import kotlin.coroutines.CoroutineContext @@ -16,7 +15,9 @@ class KritorServer( private val port: Int ): CoroutineScope { private val server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) + .executor(Dispatchers.IO.asExecutor()) .addService(Authentication) + .addService(ContactService) .build()!! fun start(block: Boolean = false) { diff --git a/xposed/src/main/java/kritor/service/ContactService.kt b/xposed/src/main/java/kritor/service/ContactService.kt new file mode 100644 index 0000000..c237678 --- /dev/null +++ b/xposed/src/main/java/kritor/service/ContactService.kt @@ -0,0 +1,47 @@ +package kritor.service + +import io.grpc.Status +import io.grpc.StatusRuntimeException +import io.kritor.AuthCode +import io.kritor.authRsp +import io.kritor.contact.ContactServiceGrpcKt +import io.kritor.contact.ProfileCard +import io.kritor.contact.ProfileCardRequest +import io.kritor.contact.profileCard +import moe.fuqiuluo.shamrock.config.ActiveTicket +import moe.fuqiuluo.shamrock.config.ShamrockConfig +import qq.service.contact.ContactHelper + +object ContactService: ContactServiceGrpcKt.ContactServiceCoroutineImplBase() { + override suspend fun getProfileCard(request: ProfileCardRequest): ProfileCard { + val uin = if (request.hasUin()) request.uin + else ContactHelper.getUinByUidAsync(request.uid).toLong() + val contact = ContactHelper.getProfileCard(uin) + + contact.onFailure { + throw StatusRuntimeException(Status.INTERNAL + .withDescription(it.stackTraceToString()) + ) + } + + contact.onSuccess { + return profileCard { + this.uin = it.uin.toLong() + this.uid = if (request.hasUid()) request.uid + 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 + } + } + + throw StatusRuntimeException(Status.INTERNAL + .withDescription("logic failed") + ) + } +} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/ContactHelper.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/ContactHelper.kt deleted file mode 100644 index 4cc9ac0..0000000 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/helper/ContactHelper.kt +++ /dev/null @@ -1,32 +0,0 @@ -package moe.fuqiuluo.shamrock.helper - -import kotlinx.coroutines.suspendCancellableCoroutine -import moe.fuqiuluo.shamrock.internals.NTServiceFetcher -import kotlin.coroutines.resume - -internal object ContactHelper { - suspend fun getUinByUidAsync(uid: String): String { - if (uid.isBlank() || uid == "0") { - return "0" - } - - val kernelService = NTServiceFetcher.kernelService - val sessionService = kernelService.wrapperSession - - return suspendCancellableCoroutine { continuation -> - sessionService.uixConvertService.getUin(hashSetOf(uid)) { - continuation.resume(it) - } - }[uid]?.toString() ?: "0" - } - - suspend fun getUidByUinAsync(peerId: Long): String { - val kernelService = NTServiceFetcher.kernelService - val sessionService = kernelService.wrapperSession - return suspendCancellableCoroutine { continuation -> - sessionService.uixConvertService.getUid(hashSetOf(peerId)) { - continuation.resume(it) - } - }[peerId]!! - } -} \ No newline at end of file diff --git a/xposed/src/main/java/moe/fuqiuluo/shamrock/utils/AudioUtils.kt b/xposed/src/main/java/moe/fuqiuluo/shamrock/utils/AudioUtils.kt index f8783d0..0c512b6 100644 --- a/xposed/src/main/java/moe/fuqiuluo/shamrock/utils/AudioUtils.kt +++ b/xposed/src/main/java/moe/fuqiuluo/shamrock/utils/AudioUtils.kt @@ -8,7 +8,7 @@ import com.arthenica.ffmpegkit.FFmpegKit import com.arthenica.ffmpegkit.FFprobeKit import com.arthenica.ffmpegkit.ReturnCode import com.tencent.qqnt.kernel.nativeinterface.QQNTWrapperUtil -import qq.service.contact.LocalCacheHelper +import qq.service.internals.LocalCacheHelper import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.LogCenter import java.io.File diff --git a/xposed/src/main/java/qq/service/contact/ContactHelper.kt b/xposed/src/main/java/qq/service/contact/ContactHelper.kt new file mode 100644 index 0000000..3ddc0a8 --- /dev/null +++ b/xposed/src/main/java/qq/service/contact/ContactHelper.kt @@ -0,0 +1,87 @@ +package qq.service.contact + +import com.tencent.common.app.AppInterface +import com.tencent.mobileqq.data.Card +import com.tencent.mobileqq.profilecard.api.IProfileDataService +import com.tencent.mobileqq.profilecard.api.IProfileProtocolService +import com.tencent.mobileqq.profilecard.observer.ProfileCardObserver +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import moe.fuqiuluo.shamrock.internals.NTServiceFetcher +import qq.service.QQInterfaces +import kotlin.coroutines.resume + +object ContactHelper: QQInterfaces() { + private val refreshCardLock by lazy { Mutex() } + + suspend fun getProfileCard(uin: Long): Result { + return getProfileCardFromCache(uin).onFailure { + return refreshAndGetProfileCard(uin) + } + } + + fun getProfileCardFromCache(uin: Long): Result { + val profileDataService = app + .getRuntimeService(IProfileDataService::class.java, "all") + val card = profileDataService.getProfileCard(uin.toString(), true) + return if (card == null || card.strNick.isNullOrEmpty()) { + Result.failure(Exception("unable to fetch profile card")) + } else { + Result.success(card) + } + } + + suspend fun refreshAndGetProfileCard(uin: Long): Result { + require(app is AppInterface) + val dataService = app + .getRuntimeService(IProfileDataService::class.java, "all") + val card = refreshCardLock.withLock { + suspendCancellableCoroutine { + app.addObserver(object: ProfileCardObserver() { + override fun onGetProfileCard(success: Boolean, obj: Any) { + app.removeObserver(this) + if (!success || obj !is Card) { + it.resume(null) + } else { + dataService.saveProfileCard(obj) + it.resume(obj) + } + } + }) + app.getRuntimeService(IProfileProtocolService::class.java, "all") + .requestProfileCard(app.currentUin, uin.toString(), 12, 0L, 0.toByte(), 0L, 0L, null, "", 0L, 10004, null, 0.toByte()) + } + } + return if (card == null || card.strNick.isNullOrEmpty()) { + Result.failure(Exception("unable to fetch profile card")) + } else { + Result.success(card) + } + } + + suspend fun getUinByUidAsync(uid: String): String { + if (uid.isBlank() || uid == "0") { + return "0" + } + + val kernelService = NTServiceFetcher.kernelService + val sessionService = kernelService.wrapperSession + + return suspendCancellableCoroutine { continuation -> + sessionService.uixConvertService.getUin(hashSetOf(uid)) { + continuation.resume(it) + } + }[uid]?.toString() ?: "0" + } + + suspend fun getUidByUinAsync(peerId: Long): String { + val kernelService = NTServiceFetcher.kernelService + val sessionService = kernelService.wrapperSession + return suspendCancellableCoroutine { continuation -> + sessionService.uixConvertService.getUid(hashSetOf(peerId)) { + continuation.resume(it) + } + }[peerId]!! + } +} \ No newline at end of file diff --git a/xposed/src/main/java/qq/service/contact/LocalCacheHelper.kt b/xposed/src/main/java/qq/service/internals/LocalCacheHelper.kt similarity index 95% rename from xposed/src/main/java/qq/service/contact/LocalCacheHelper.kt rename to xposed/src/main/java/qq/service/internals/LocalCacheHelper.kt index 17ed79f..30fefb9 100644 --- a/xposed/src/main/java/qq/service/contact/LocalCacheHelper.kt +++ b/xposed/src/main/java/qq/service/internals/LocalCacheHelper.kt @@ -1,4 +1,4 @@ -package qq.service.contact +package qq.service.internals import moe.fuqiuluo.shamrock.utils.FileUtils import mqq.app.MobileQQ