Skip to content
This repository has been archived by the owner on Aug 12, 2024. It is now read-only.

Commit

Permalink
Shamrock: 精华消息支持
Browse files Browse the repository at this point in the history
Signed-off-by: 白池 <whitechi73@outlook.com>
  • Loading branch information
whitechi73 committed Mar 18, 2024
1 parent 6c9b282 commit ee5fcc3
Show file tree
Hide file tree
Showing 23 changed files with 1,536 additions and 20 deletions.
8 changes: 6 additions & 2 deletions annotations/src/main/java/kritor/service/Grpc.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package kritor.service

import kotlin.reflect.KClass

@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION)
annotation class Grpc(
val serviceName: String,
val funcName: String
)
val funcName: String,

)
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,7 @@ private fun APIInfoCard(
text = rpcAddress,
hint = "请输入回调地址",
error = "输入的地址不合法",
checker = {
it.isEmpty() || it.contains(":")
},
checker = { true },
confirm = {
ShamrockConfig[ctx, RPCAddress] = rpcAddress.value
AppRuntime.log("设置回调RPC地址为[${rpcAddress.value}]。")
Expand Down
96 changes: 96 additions & 0 deletions processor/src/main/java/moe/fuqiuluo/ksp/impl/GrpcProcessor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
@file:Suppress("UNCHECKED_CAST")
@file:OptIn(KspExperimental::class)

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.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
import kritor.service.Grpc

class GrpcProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger
): SymbolProcessor {
private val subPackage = arrayOf("contact", "core", "file", "friend", "group", "message", "web")

override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation(Grpc::class.qualifiedName!!)
val actions = (symbols as Sequence<KSFunctionDeclaration>).toList()

if (actions.isEmpty()) return emptyList()

// 怎么返回nullable的结果
val packageName = "kritor.handlers"
val funcBuilder = FunSpec.builder("handleGrpc")
.addModifiers(KModifier.SUSPEND)
.addParameter("cmd", String::class)
.addParameter("data", ByteArray::class)
.returns(ByteArray::class)
val fileSpec = FileSpec.scriptBuilder("AutoGrpcHandlers", packageName)

logger.warn("Found ${actions.size} grpc-actions")

//logger.error(resolver.getClassDeclarationByName("io.kritor.AuthReq").toString())
//logger.error(resolver.getJavaClassByName("io.kritor.AuthReq").toString())
//logger.error(resolver.getKotlinClassByName("io.kritor.AuthReq").toString())

actions.forEach { action ->
val methodName = action.qualifiedName?.asString()!!
val grpcMethod = action.getAnnotationsByType(Grpc::class).first()
val service = grpcMethod.serviceName
val funcName = grpcMethod.funcName
funcBuilder.addStatement("if (cmd == \"${service}.${funcName}\") {\t")

val reqType = action.parameters[0].type.toString()
val rspType = action.returnType.toString()
funcBuilder.addStatement("val resp: $rspType = $methodName($reqType.parseFrom(data))")
funcBuilder.addStatement("return resp.toByteArray()")

funcBuilder.addStatement("}")
}
funcBuilder.addStatement("return EMPTY_BYTE_ARRAY")
fileSpec
.addStatement("import io.kritor.*")
.addStatement("import io.kritor.core.*")
.addStatement("import io.kritor.contact.*")
.addStatement("import io.kritor.group.*")
.addStatement("import io.kritor.friend.*")
.addStatement("import io.kritor.file.*")
.addStatement("import io.kritor.message.*")
.addStatement("import io.kritor.web.*")
.addFunction(funcBuilder.build())
.addImport("moe.fuqiuluo.symbols", "EMPTY_BYTE_ARRAY")
runCatching {
codeGenerator.createNewFile(
dependencies = Dependencies(aggregating = false),
packageName = packageName,
fileName = fileSpec.name
).use { outputStream ->
outputStream.writer().use {
fileSpec.build().writeTo(it)
}
}
}

return emptyList()
}
}
17 changes: 17 additions & 0 deletions processor/src/main/java/moe/fuqiuluo/ksp/providers/GrpcProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package moe.fuqiuluo.ksp.providers

import com.google.auto.service.AutoService
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import moe.fuqiuluo.ksp.impl.GrpcProcessor

@AutoService(SymbolProcessorProvider::class)
class GrpcProvider: SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return GrpcProcessor(
environment.codeGenerator,
environment.logger
)
}
}
2 changes: 1 addition & 1 deletion xposed/src/main/assets/config.properties
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ active_ticket=
enable_self_message=false

# 旧BDH兼容开关
enable_old_bdh=false
enable_old_bdh=true

# 反JVM调用栈跟踪
anti_jvm_trace=true
Expand Down
142 changes: 142 additions & 0 deletions xposed/src/main/java/kritor/client/KritorClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
@file:OptIn(DelicateCoroutinesApi::class)
package kritor.client

import com.google.protobuf.ByteString
import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import io.kritor.ReverseServiceGrpcKt
import io.kritor.event.EventServiceGrpcKt
import io.kritor.event.EventType
import io.kritor.event.eventStructure
import io.kritor.event.messageEvent
import io.kritor.reverse.ReqCode
import io.kritor.reverse.Request
import io.kritor.reverse.Response
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.launch
import kritor.handlers.handleGrpc
import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter
import moe.fuqiuluo.shamrock.internals.GlobalEventTransmitter
import kotlin.time.Duration.Companion.seconds

internal class KritorClient(
val host: String,
val port: Int
) {
private lateinit var channel: ManagedChannel

private lateinit var channelJob: Job
private val senderChannel = MutableSharedFlow<Response>()

fun start() {
runCatching {
if (::channel.isInitialized && isActive()){
channel.shutdown()
}
channel = ManagedChannelBuilder
.forAddress(host, port)
.usePlaintext()
.enableRetry() // 允许尝试
.executor(Dispatchers.IO.asExecutor()) // 使用协程的调度器
.build()
}.onFailure {
LogCenter.log("KritorClient start failed: ${it.stackTraceToString()}", Level.ERROR)
}
}

fun listen(retryCnt: Int = -1) {
if (::channelJob.isInitialized && channelJob.isActive) {
channelJob.cancel()
}
channelJob = GlobalScope.launch(Dispatchers.IO) {
runCatching {
val stub = ReverseServiceGrpcKt.ReverseServiceCoroutineStub(channel)
registerEvent(EventType.EVENT_TYPE_MESSAGE)
registerEvent(EventType.EVENT_TYPE_CORE_EVENT)
registerEvent(EventType.EVENT_TYPE_REQUEST)
registerEvent(EventType.EVENT_TYPE_NOTICE)
stub.reverseStream(channelFlow {
senderChannel.collect { send(it) }
}).collect {
onReceive(it)
}
}.onFailure {
LogCenter.log("KritorClient listen failed, retry after 15s: ${it.stackTraceToString()}", Level.WARN)
}
delay(15.seconds)
LogCenter.log("KritorClient listen retrying, retryCnt = $retryCnt", Level.WARN)
if (retryCnt != 0) listen(retryCnt - 1)
}
}

fun registerEvent(eventType: EventType) {
GlobalScope.launch(Dispatchers.IO) {
runCatching {
EventServiceGrpcKt.EventServiceCoroutineStub(channel).registerPassiveListener(channelFlow {
when(eventType) {
EventType.EVENT_TYPE_MESSAGE -> GlobalEventTransmitter.onMessageEvent {
send(eventStructure {
this.type = EventType.EVENT_TYPE_MESSAGE
this.message = it.second
})
}
EventType.EVENT_TYPE_CORE_EVENT -> {}
EventType.EVENT_TYPE_NOTICE -> GlobalEventTransmitter.onNoticeEvent {
send(eventStructure {
this.type = EventType.EVENT_TYPE_NOTICE
this.notice = it
})
}
EventType.EVENT_TYPE_REQUEST -> GlobalEventTransmitter.onRequestEvent {
send(eventStructure {
this.type = EventType.EVENT_TYPE_REQUEST
this.request = it
})
}
EventType.UNRECOGNIZED -> {}
}
})
}.onFailure {
LogCenter.log("KritorClient registerEvent failed: ${it.stackTraceToString()}", Level.ERROR)
}
}
}

private suspend fun onReceive(request: Request) = GlobalScope.launch {
//LogCenter.log("KritorClient onReceive: $request")
runCatching {
val rsp = handleGrpc(request.cmd, request.buf.toByteArray())
senderChannel.emit(Response.newBuilder()
.setCmd(request.cmd)
.setCode(ReqCode.SUCCESS)
.setMsg("success")
.setSeq(request.seq)
.setBuf(ByteString.copyFrom(rsp))
.build())
}.onFailure {
senderChannel.emit(Response.newBuilder()
.setCmd(request.cmd)
.setCode(ReqCode.INTERNAL)
.setMsg(it.stackTraceToString())
.setSeq(request.seq)
.setBuf(ByteString.EMPTY)
.build())
}
}

fun isActive(): Boolean {
return !channel.isShutdown
}

fun close() {
channel.shutdown()
}
}
6 changes: 6 additions & 0 deletions xposed/src/main/java/kritor/handlers/GrpcHandlers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package kritor.handlers

internal object GrpcHandlers {


}
2 changes: 2 additions & 0 deletions xposed/src/main/java/kritor/server/KritorServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class KritorServer(
.addService(GroupFileService)
.addService(MessageService)
.addService(EventService)
.addService(ForwardMessageService)
.addService(WebService)
.build()!!

fun start(block: Boolean = false) {
Expand Down
50 changes: 50 additions & 0 deletions xposed/src/main/java/kritor/service/ForwardMessageService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package kritor.service

import com.tencent.qqnt.kernel.nativeinterface.MsgConstant
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.message.Element
import io.kritor.message.ElementType
import io.kritor.message.ForwardMessageRequest
import io.kritor.message.ForwardMessageResponse
import io.kritor.message.ForwardMessageServiceGrpcKt
import io.kritor.message.Scene
import io.kritor.message.element
import io.kritor.message.forwardMessageResponse
import qq.service.contact.longPeer
import qq.service.msg.ForwardMessageHelper
import qq.service.msg.MessageHelper
import qq.service.msg.NtMsgConvertor

internal object ForwardMessageService: ForwardMessageServiceGrpcKt.ForwardMessageServiceCoroutineImplBase() {
@Grpc("ForwardMessageService", "ForwardMessage")
override suspend fun forwardMessage(request: ForwardMessageRequest): ForwardMessageResponse {
val contact = request.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)
}

val forwardMessage = ForwardMessageHelper.uploadMultiMsg(contact.chatType, contact.longPeer().toString(), contact.guildId, request.messagesList).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow()

val uniseq = MessageHelper.generateMsgId(contact.chatType)
return forwardMessageResponse {
this.messageId = MessageHelper.sendMessage(contact, NtMsgConvertor.convertToNtMsgs(contact, uniseq, arrayListOf(element {
this.type = ElementType.FORWARD
this.forward = forwardMessage
})), request.retryCount, uniseq).onFailure {
throw StatusRuntimeException(Status.INTERNAL.withCause(it))
}.getOrThrow()
this.resId = forwardMessage.id
}
}
}
1 change: 1 addition & 0 deletions xposed/src/main/java/kritor/service/GroupFileService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ internal object GroupFileService: GroupFileServiceGrpcKt.GroupFileServiceCorouti
return renameFolderResponse { }
}

@Grpc("GroupFileService", "GetFileSystemInfo")
override suspend fun getFileSystemInfo(request: GetFileSystemInfoRequest): GetFileSystemInfoResponse {
return getGroupFileSystemInfo(request.groupId)
}
Expand Down
3 changes: 1 addition & 2 deletions xposed/src/main/java/kritor/service/GroupService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ import io.kritor.group.prohibitedUserInfo
import io.kritor.group.setGroupAdminResponse
import io.kritor.group.setGroupUniqueTitleResponse
import io.kritor.group.setGroupWholeBanResponse
import moe.fuqiuluo.shamrock.helper.TroopHonorHelper
import moe.fuqiuluo.shamrock.helper.TroopHonorHelper.decodeHonor
import moe.fuqiuluo.shamrock.tools.ifNullOrEmpty
import qq.service.contact.ContactHelper
Expand Down Expand Up @@ -89,7 +88,7 @@ internal object GroupService: GroupServiceGrpcKt.GroupServiceCoroutineImplBase()
}
}

@Grpc("GroupService", "PokeMember")
@Grpc("GroupService", "PokeMember", )
override suspend fun pokeMember(request: PokeMemberRequest): PokeMemberResponse {
GroupHelper.pokeMember(request.groupId, when(request.targetCase!!) {
PokeMemberRequest.TargetCase.TARGET_UIN -> request.targetUin
Expand Down
Loading

0 comments on commit ee5fcc3

Please sign in to comment.